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