1 // Copyright 2016 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include "device/vr/android/gvr/gvr_device.h"
6
7 #include <math.h>
8 #include <algorithm>
9 #include <string>
10 #include <utility>
11
12 #include "base/android/android_hardware_buffer_compat.h"
13 #include "base/bind.h"
14 #include "base/memory/ptr_util.h"
15 #include "base/time/time.h"
16 #include "base/trace_event/trace_event.h"
17 #include "device/vr/android/gvr/gvr_delegate.h"
18 #include "device/vr/android/gvr/gvr_delegate_provider.h"
19 #include "device/vr/android/gvr/gvr_delegate_provider_factory.h"
20 #include "device/vr/android/gvr/gvr_device_provider.h"
21 #include "device/vr/android/gvr/gvr_utils.h"
22 #include "device/vr/jni_headers/NonPresentingGvrContext_jni.h"
23 #include "mojo/public/cpp/bindings/pending_remote.h"
24 #include "third_party/gvr-android-sdk/src/libraries/headers/vr/gvr/capi/include/gvr.h"
25 #include "ui/gfx/geometry/rect_f.h"
26 #include "ui/gfx/transform.h"
27 #include "ui/gfx/transform_util.h"
28
29 using base::android::JavaRef;
30
31 namespace device {
32
33 namespace {
34
35 // Default downscale factor for computing the recommended WebXR
36 // render_width/render_height from the 1:1 pixel mapped size. Using a rather
37 // aggressive downscale due to the high overhead of copying pixels
38 // twice before handing off to GVR. For comparison, the polyfill
39 // uses approximately 0.55 on a Pixel XL.
40 static constexpr float kWebXrRecommendedResolutionScale = 0.7;
41
42 // The scale factor for WebXR on devices that don't have shared buffer
43 // support. (Android N and earlier.)
44 static constexpr float kWebXrNoSharedBufferResolutionScale = 0.5;
45
GetMaximumWebVrSize(gvr::GvrApi * gvr_api)46 gfx::Size GetMaximumWebVrSize(gvr::GvrApi* gvr_api) {
47 // Get the default, unscaled size for the WebVR transfer surface
48 // based on the optimal 1:1 render resolution. A scalar will be applied to
49 // this value in the renderer to reduce the render load. This size will also
50 // be reported to the client via CreateVRDisplayInfo as the
51 // client-recommended render_width/render_height and for the GVR
52 // framebuffer. If the client chooses a different size or resizes it
53 // while presenting, we'll resize the transfer surface and GVR
54 // framebuffer to match.
55 gvr::Sizei render_target_size =
56 gvr_api->GetMaximumEffectiveRenderTargetSize();
57
58 gfx::Size webvr_size(render_target_size.width, render_target_size.height);
59
60 // Ensure that the width is an even number so that the eyes each
61 // get the same size, the recommended render_width is per eye
62 // and the client will use the sum of the left and right width.
63 //
64 // TODO(klausw,crbug.com/699350): should we round the recommended
65 // size to a multiple of 2^N pixels to be friendlier to the GPU? The
66 // exact size doesn't matter, and it might be more efficient.
67 webvr_size.set_width(webvr_size.width() & ~1);
68 return webvr_size;
69 }
70
CreateEyeParamater(gvr::GvrApi * gvr_api,gvr::Eye eye,const gvr::BufferViewportList & buffers,const gfx::Size & maximum_size)71 mojom::VREyeParametersPtr CreateEyeParamater(
72 gvr::GvrApi* gvr_api,
73 gvr::Eye eye,
74 const gvr::BufferViewportList& buffers,
75 const gfx::Size& maximum_size) {
76 mojom::VREyeParametersPtr eye_params = mojom::VREyeParameters::New();
77 eye_params->field_of_view = mojom::VRFieldOfView::New();
78 eye_params->render_width = maximum_size.width() / 2;
79 eye_params->render_height = maximum_size.height();
80
81 gvr::BufferViewport eye_viewport = gvr_api->CreateBufferViewport();
82 buffers.GetBufferViewport(eye, &eye_viewport);
83 gvr::Rectf eye_fov = eye_viewport.GetSourceFov();
84 eye_params->field_of_view->up_degrees = eye_fov.top;
85 eye_params->field_of_view->down_degrees = eye_fov.bottom;
86 eye_params->field_of_view->left_degrees = eye_fov.left;
87 eye_params->field_of_view->right_degrees = eye_fov.right;
88
89 gvr::Mat4f eye_mat = gvr_api->GetEyeFromHeadMatrix(eye);
90 gfx::Transform eye_from_head;
91 gvr_utils::GvrMatToTransform(eye_mat, &eye_from_head);
92 DCHECK(eye_from_head.IsInvertible());
93 gfx::Transform head_from_eye;
94 if (eye_from_head.GetInverse(&head_from_eye)) {
95 eye_params->head_from_eye = head_from_eye;
96 }
97
98 return eye_params;
99 }
100
CreateVRDisplayInfo(gvr::GvrApi * gvr_api,mojom::XRDeviceId device_id)101 mojom::VRDisplayInfoPtr CreateVRDisplayInfo(gvr::GvrApi* gvr_api,
102 mojom::XRDeviceId device_id) {
103 TRACE_EVENT0("input", "GvrDelegate::CreateVRDisplayInfo");
104
105 mojom::VRDisplayInfoPtr device = mojom::VRDisplayInfo::New();
106
107 device->id = device_id;
108
109 gvr::BufferViewportList gvr_buffer_viewports =
110 gvr_api->CreateEmptyBufferViewportList();
111 gvr_buffer_viewports.SetToRecommendedBufferViewports();
112
113 gfx::Size maximum_size = GetMaximumWebVrSize(gvr_api);
114 device->left_eye = CreateEyeParamater(gvr_api, GVR_LEFT_EYE,
115 gvr_buffer_viewports, maximum_size);
116 device->right_eye = CreateEyeParamater(gvr_api, GVR_RIGHT_EYE,
117 gvr_buffer_viewports, maximum_size);
118
119 // This scalar will be applied in the renderer to the recommended render
120 // target sizes. For WebVR it will always be applied, for WebXR it can be
121 // overridden.
122 if (base::AndroidHardwareBufferCompat::IsSupportAvailable()) {
123 device->webxr_default_framebuffer_scale = kWebXrRecommendedResolutionScale;
124 } else {
125 device->webxr_default_framebuffer_scale =
126 kWebXrNoSharedBufferResolutionScale;
127 }
128
129 return device;
130 }
131
132 } // namespace
133
GvrDevice()134 GvrDevice::GvrDevice() : VRDeviceBase(mojom::XRDeviceId::GVR_DEVICE_ID) {
135 GvrDelegateProviderFactory::SetDevice(this);
136 }
137
~GvrDevice()138 GvrDevice::~GvrDevice() {
139 if (HasExclusiveSession()) {
140 // We potentially could be destroyed during a navigation before processing
141 // the exclusive session connection error handler. In this case, the
142 // delegate thinks we are still presenting.
143 StopPresenting();
144 }
145
146 if (pending_request_session_callback_) {
147 std::move(pending_request_session_callback_)
148 .Run(nullptr, mojo::NullRemote());
149 }
150
151 GvrDelegateProviderFactory::SetDevice(nullptr);
152 if (!non_presenting_context_.obj())
153 return;
154 JNIEnv* env = base::android::AttachCurrentThread();
155 Java_NonPresentingGvrContext_shutdown(env, non_presenting_context_);
156 }
157
RequestSession(mojom::XRRuntimeSessionOptionsPtr options,mojom::XRRuntime::RequestSessionCallback callback)158 void GvrDevice::RequestSession(
159 mojom::XRRuntimeSessionOptionsPtr options,
160 mojom::XRRuntime::RequestSessionCallback callback) {
161 // We can only process one request at a time.
162 if (pending_request_session_callback_) {
163 std::move(callback).Run(nullptr, mojo::NullRemote());
164 return;
165 }
166 pending_request_session_callback_ = std::move(callback);
167
168 if (!gvr_api_) {
169 Init(base::BindOnce(&GvrDevice::OnInitRequestSessionFinished,
170 base::Unretained(this), std::move(options)));
171 return;
172 }
173 OnInitRequestSessionFinished(std::move(options), true);
174 }
175
OnStartPresentResult(mojom::XRSessionPtr session)176 void GvrDevice::OnStartPresentResult(
177 mojom::XRSessionPtr session) {
178 DCHECK(pending_request_session_callback_);
179
180 if (!session) {
181 std::move(pending_request_session_callback_)
182 .Run(nullptr, mojo::NullRemote());
183 return;
184 }
185
186 OnStartPresenting();
187
188 // Close the binding to ensure any previous sessions were closed.
189 // TODO(billorr): Only do this in OnPresentingControllerMojoConnectionError.
190 exclusive_controller_receiver_.reset();
191
192 std::move(pending_request_session_callback_)
193 .Run(std::move(session),
194 exclusive_controller_receiver_.BindNewPipeAndPassRemote());
195
196 // Unretained is safe because the error handler won't be called after the
197 // binding has been destroyed.
198 exclusive_controller_receiver_.set_disconnect_handler(
199 base::BindOnce(&GvrDevice::OnPresentingControllerMojoConnectionError,
200 base::Unretained(this)));
201 }
202
203 // XRSessionController
SetFrameDataRestricted(bool restricted)204 void GvrDevice::SetFrameDataRestricted(bool restricted) {
205 // Presentation sessions can not currently be restricted.
206 DCHECK(false);
207 }
208
OnPresentingControllerMojoConnectionError()209 void GvrDevice::OnPresentingControllerMojoConnectionError() {
210 StopPresenting();
211 }
212
ShutdownSession(mojom::XRRuntime::ShutdownSessionCallback on_completed)213 void GvrDevice::ShutdownSession(
214 mojom::XRRuntime::ShutdownSessionCallback on_completed) {
215 DVLOG(2) << __func__;
216 StopPresenting();
217
218 // At this point, the main thread session shutdown is complete, but the GL
219 // thread may still be in the process of finishing shutdown or transitioning
220 // to VR Browser mode. Java VrShell::setWebVrModeEnable calls native
221 // VrShell::setWebVrMode which calls BrowserRenderer::SetWebXrMode on the GL
222 // thread, and that triggers the VRB transition via ui_->SetWebVrMode.
223 //
224 // Since tasks posted to the GL thread are handled in sequence, any calls
225 // related to a new session will be processed after the GL thread transition
226 // is complete.
227 //
228 // TODO(https://crbug.com/998307): It would be cleaner to delay the shutdown
229 // until the GL thread transition is complete, but this would need a fair
230 // amount of additional plumbing to ensure that the callback is consistently
231 // called. See also WebXrTestFramework.enterSessionWithUserGesture(), but
232 // it's unclear if changing this would be sufficient to avoid the need for
233 // workarounds there.
234 std::move(on_completed).Run();
235 }
236
StopPresenting()237 void GvrDevice::StopPresenting() {
238 DVLOG(2) << __func__;
239 GvrDelegateProvider* delegate_provider = GetGvrDelegateProvider();
240 if (delegate_provider)
241 delegate_provider->ExitWebVRPresent();
242 OnExitPresent();
243 exclusive_controller_receiver_.reset();
244 }
245
PauseTracking()246 void GvrDevice::PauseTracking() {
247 paused_ = true;
248 if (gvr_api_ && non_presenting_context_.obj()) {
249 gvr_api_->PauseTracking();
250 JNIEnv* env = base::android::AttachCurrentThread();
251 Java_NonPresentingGvrContext_pause(env, non_presenting_context_);
252 }
253 }
254
ResumeTracking()255 void GvrDevice::ResumeTracking() {
256 paused_ = false;
257 if (gvr_api_ && non_presenting_context_.obj()) {
258 gvr_api_->ResumeTracking();
259 JNIEnv* env = base::android::AttachCurrentThread();
260 Java_NonPresentingGvrContext_resume(env, non_presenting_context_);
261 }
262 }
263
GetGvrDelegateProvider()264 GvrDelegateProvider* GvrDevice::GetGvrDelegateProvider() {
265 // GvrDelegateProviderFactory::Create() may return a different
266 // pointer each time. Do not cache it.
267 return GvrDelegateProviderFactory::Create();
268 }
269
OnDisplayConfigurationChanged(JNIEnv * env,const JavaRef<jobject> & obj)270 void GvrDevice::OnDisplayConfigurationChanged(JNIEnv* env,
271 const JavaRef<jobject>& obj) {
272 DCHECK(gvr_api_);
273 SetVRDisplayInfo(CreateVRDisplayInfo(gvr_api_.get(), GetId()));
274 }
275
Init(base::OnceCallback<void (bool)> on_finished)276 void GvrDevice::Init(base::OnceCallback<void(bool)> on_finished) {
277 GvrDelegateProvider* delegate_provider = GetGvrDelegateProvider();
278 if (!delegate_provider || delegate_provider->ShouldDisableGvrDevice()) {
279 std::move(on_finished).Run(false);
280 return;
281 }
282 CreateNonPresentingContext();
283 std::move(on_finished).Run(non_presenting_context_.obj() != nullptr);
284 }
285
CreateNonPresentingContext()286 void GvrDevice::CreateNonPresentingContext() {
287 if (non_presenting_context_.obj())
288 return;
289 JNIEnv* env = base::android::AttachCurrentThread();
290 non_presenting_context_.Reset(
291 Java_NonPresentingGvrContext_create(env, reinterpret_cast<jlong>(this)));
292 if (!non_presenting_context_.obj())
293 return;
294 jlong context = Java_NonPresentingGvrContext_getNativeGvrContext(
295 env, non_presenting_context_);
296 gvr_api_ = gvr::GvrApi::WrapNonOwned(reinterpret_cast<gvr_context*>(context));
297 SetVRDisplayInfo(CreateVRDisplayInfo(gvr_api_.get(), GetId()));
298
299 if (paused_) {
300 PauseTracking();
301 } else {
302 ResumeTracking();
303 }
304 }
305
OnInitRequestSessionFinished(mojom::XRRuntimeSessionOptionsPtr options,bool success)306 void GvrDevice::OnInitRequestSessionFinished(
307 mojom::XRRuntimeSessionOptionsPtr options,
308 bool success) {
309 DCHECK(pending_request_session_callback_);
310
311 if (!success) {
312 std::move(pending_request_session_callback_)
313 .Run(nullptr, mojo::NullRemote());
314 return;
315 }
316
317 GvrDelegateProvider* delegate_provider = GetGvrDelegateProvider();
318 if (!delegate_provider) {
319 std::move(pending_request_session_callback_)
320 .Run(nullptr, mojo::NullRemote());
321 return;
322 }
323
324 DCHECK_EQ(options->mode, mojom::XRSessionMode::kImmersiveVr);
325
326 // StartWebXRPresentation is async as we may trigger a DON (Device ON) flow
327 // that pauses Chrome.
328 delegate_provider->StartWebXRPresentation(
329 GetVRDisplayInfo(), std::move(options),
330 base::BindOnce(&GvrDevice::OnStartPresentResult,
331 weak_ptr_factory_.GetWeakPtr()));
332 }
333
334 } // namespace device
335