1 // Copyright (c) 2017 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/openvr/openvr_device.h"
6 
7 #include <math.h>
8 
9 #include <utility>
10 
11 #include "base/bind.h"
12 #include "base/bind_helpers.h"
13 #include "base/macros.h"
14 #include "base/memory/ptr_util.h"
15 #include "base/numerics/math_constants.h"
16 #include "build/build_config.h"
17 #include "device/vr/openvr/openvr_render_loop.h"
18 #include "device/vr/openvr/openvr_type_converters.h"
19 #include "device/vr/util/stage_utils.h"
20 #include "mojo/public/cpp/bindings/pending_remote.h"
21 #include "third_party/openvr/src/headers/openvr.h"
22 #include "ui/gfx/geometry/angle_conversions.h"
23 
24 namespace device {
25 
26 namespace {
27 
28 constexpr base::TimeDelta kPollingInterval =
29     base::TimeDelta::FromSecondsD(0.25);
30 
OpenVRFovToWebVRFov(vr::IVRSystem * vr_system,vr::Hmd_Eye eye)31 mojom::VRFieldOfViewPtr OpenVRFovToWebVRFov(vr::IVRSystem* vr_system,
32                                             vr::Hmd_Eye eye) {
33   auto out = mojom::VRFieldOfView::New();
34   float up_tan, down_tan, left_tan, right_tan;
35   vr_system->GetProjectionRaw(eye, &left_tan, &right_tan, &up_tan, &down_tan);
36 
37   // TODO(billorr): Plumb the expected projection matrix over mojo instead of
38   // using angles. Up and down are intentionally swapped to account for
39   // differences in expected projection matrix format for GVR and OpenVR.
40   out->up_degrees = gfx::RadToDeg(atanf(down_tan));
41   out->down_degrees = -gfx::RadToDeg(atanf(up_tan));
42   out->left_degrees = -gfx::RadToDeg(atanf(left_tan));
43   out->right_degrees = gfx::RadToDeg(atanf(right_tan));
44   return out;
45 }
46 
HmdMatrix34ToTransform(const vr::HmdMatrix34_t & mat)47 gfx::Transform HmdMatrix34ToTransform(const vr::HmdMatrix34_t& mat) {
48   // Disable formatting so that the 4x4 matrix is more readable
49   // clang-format off
50   return gfx::Transform(
51      mat.m[0][0], mat.m[0][1], mat.m[0][2], mat.m[0][3],
52      mat.m[1][0], mat.m[1][1], mat.m[1][2], mat.m[1][3],
53      mat.m[2][0], mat.m[2][1], mat.m[2][2], mat.m[2][3],
54      0.0f,        0.0f,        0.0f,        1.0f);
55   // clang-format on
56 }
57 
58 // OpenVR uses A_to_B convention for naming transformation matrices, but we pass
59 // matrices through mojo using the B_from_A naming convention since that what
60 // blink uses.
HeadFromEyeTransform(vr::IVRSystem * vr_system,vr::Hmd_Eye eye)61 gfx::Transform HeadFromEyeTransform(vr::IVRSystem* vr_system, vr::Hmd_Eye eye) {
62   return HmdMatrix34ToTransform(vr_system->GetEyeToHeadTransform(eye));
63 }
64 
CreateVRDisplayInfo(vr::IVRSystem * vr_system,device::mojom::XRDeviceId id)65 mojom::VRDisplayInfoPtr CreateVRDisplayInfo(vr::IVRSystem* vr_system,
66                                             device::mojom::XRDeviceId id) {
67   mojom::VRDisplayInfoPtr display_info = mojom::VRDisplayInfo::New();
68   display_info->id = id;
69 
70   display_info->left_eye = mojom::VREyeParameters::New();
71   display_info->right_eye = mojom::VREyeParameters::New();
72   mojom::VREyeParametersPtr& left_eye = display_info->left_eye;
73   mojom::VREyeParametersPtr& right_eye = display_info->right_eye;
74 
75   left_eye->field_of_view = OpenVRFovToWebVRFov(vr_system, vr::Eye_Left);
76   right_eye->field_of_view = OpenVRFovToWebVRFov(vr_system, vr::Eye_Right);
77 
78   left_eye->head_from_eye = HeadFromEyeTransform(vr_system, vr::Eye_Left);
79   right_eye->head_from_eye = HeadFromEyeTransform(vr_system, vr::Eye_Right);
80 
81   uint32_t width, height;
82   vr_system->GetRecommendedRenderTargetSize(&width, &height);
83   left_eye->render_width = width;
84   left_eye->render_height = height;
85   right_eye->render_width = left_eye->render_width;
86   right_eye->render_height = left_eye->render_height;
87 
88   display_info->stage_parameters = mojom::VRStageParameters::New();
89   vr::HmdMatrix34_t mat =
90       vr_system->GetSeatedZeroPoseToStandingAbsoluteTrackingPose();
91   display_info->stage_parameters->standing_transform =
92       HmdMatrix34ToTransform(mat);
93 
94   vr::IVRChaperone* chaperone = vr::VRChaperone();
95   if (chaperone) {
96     float size_x = 0;
97     float size_z = 0;
98     chaperone->GetPlayAreaSize(&size_x, &size_z);
99     display_info->stage_parameters->bounds =
100         vr_utils::GetStageBoundsFromSize(size_x, size_z);
101   }
102   return display_info;
103 }
104 
105 
106 }  // namespace
107 
OpenVRDevice()108 OpenVRDevice::OpenVRDevice()
109     : VRDeviceBase(device::mojom::XRDeviceId::OPENVR_DEVICE_ID),
110       main_thread_task_runner_(base::ThreadTaskRunnerHandle::Get()) {
111   render_loop_ = std::make_unique<OpenVRRenderLoop>();
112 
113   OnPollingEvents();
114 }
115 
IsHwAvailable()116 bool OpenVRDevice::IsHwAvailable() {
117   return vr::VR_IsHmdPresent();
118 }
119 
IsApiAvailable()120 bool OpenVRDevice::IsApiAvailable() {
121   return vr::VR_IsRuntimeInstalled();
122 }
123 
124 mojo::PendingRemote<mojom::XRCompositorHost>
BindCompositorHost()125 OpenVRDevice::BindCompositorHost() {
126   return compositor_host_receiver_.BindNewPipeAndPassRemote();
127 }
128 
~OpenVRDevice()129 OpenVRDevice::~OpenVRDevice() {
130   Shutdown();
131 }
132 
Shutdown()133 void OpenVRDevice::Shutdown() {
134   // Wait for the render loop to stop before completing destruction. This will
135   // ensure that the IVRSystem doesn't get shutdown until the render loop is no
136   // longer referencing it.
137   if (render_loop_ && render_loop_->IsRunning())
138     render_loop_->Stop();
139 }
140 
RequestSession(mojom::XRRuntimeSessionOptionsPtr options,mojom::XRRuntime::RequestSessionCallback callback)141 void OpenVRDevice::RequestSession(
142     mojom::XRRuntimeSessionOptionsPtr options,
143     mojom::XRRuntime::RequestSessionCallback callback) {
144   if (!EnsureValidDisplayInfo()) {
145     std::move(callback).Run(nullptr, mojo::NullRemote());
146     return;
147   }
148 
149   DCHECK_EQ(options->mode, mojom::XRSessionMode::kImmersiveVr);
150 
151   if (!render_loop_->IsRunning()) {
152     render_loop_->Start();
153 
154     if (!render_loop_->IsRunning()) {
155       std::move(callback).Run(nullptr, mojo::NullRemote());
156       return;
157     }
158 
159     if (overlay_receiver_) {
160       render_loop_->task_runner()->PostTask(
161           FROM_HERE, base::BindOnce(&XRCompositorCommon::RequestOverlay,
162                                     base::Unretained(render_loop_.get()),
163                                     std::move(overlay_receiver_)));
164     }
165   }
166 
167   // We are done using OpenVR until the presentation session ends.
168   openvr_ = nullptr;
169 
170   auto my_callback =
171       base::BindOnce(&OpenVRDevice::OnRequestSessionResult,
172                      weak_ptr_factory_.GetWeakPtr(), std::move(callback));
173 
174   auto on_presentation_ended = base::BindOnce(
175       &OpenVRDevice::OnPresentationEnded, weak_ptr_factory_.GetWeakPtr());
176 
177   render_loop_->task_runner()->PostTask(
178       FROM_HERE,
179       base::BindOnce(&XRCompositorCommon::RequestSession,
180                      base::Unretained(render_loop_.get()),
181                      std::move(on_presentation_ended),
182                      base::DoNothing::Repeatedly<mojom::XRVisibilityState>(),
183                      std::move(options), std::move(my_callback)));
184   outstanding_session_requests_count_++;
185 }
186 
EnsureValidDisplayInfo()187 bool OpenVRDevice::EnsureValidDisplayInfo() {
188   // Ensure we have had a valid display_info set at least once.
189   if (!have_real_display_info_) {
190     DCHECK(!openvr_);
191     // Initialize OpenVR.
192     openvr_ = std::make_unique<OpenVRWrapper>(false /* presenting */);
193     if (!openvr_->IsInitialized()) {
194       openvr_ = nullptr;
195       return false;
196     }
197 
198     SetVRDisplayInfo(CreateVRDisplayInfo(openvr_->GetSystem(), GetId()));
199     have_real_display_info_ = true;
200   }
201   return have_real_display_info_;
202 }
203 
OnPresentationEnded()204 void OpenVRDevice::OnPresentationEnded() {
205   if (!openvr_ && outstanding_session_requests_count_ == 0) {
206     openvr_ = std::make_unique<OpenVRWrapper>(false /* presenting */);
207     if (!openvr_->IsInitialized()) {
208       openvr_ = nullptr;
209       return;
210     }
211   }
212 }
213 
OnRequestSessionResult(mojom::XRRuntime::RequestSessionCallback callback,bool result,mojom::XRSessionPtr session)214 void OpenVRDevice::OnRequestSessionResult(
215     mojom::XRRuntime::RequestSessionCallback callback,
216     bool result,
217     mojom::XRSessionPtr session) {
218   outstanding_session_requests_count_--;
219   if (!result) {
220     OnPresentationEnded();
221     std::move(callback).Run(nullptr, mojo::NullRemote());
222     return;
223   }
224 
225   OnStartPresenting();
226 
227   session->display_info = display_info_.Clone();
228 
229   std::move(callback).Run(
230       std::move(session),
231       exclusive_controller_receiver_.BindNewPipeAndPassRemote());
232 
233   // Use of Unretained is safe because the callback will only occur if the
234   // binding is not destroyed.
235   exclusive_controller_receiver_.set_disconnect_handler(
236       base::BindOnce(&OpenVRDevice::OnPresentingControllerMojoConnectionError,
237                      base::Unretained(this)));
238 }
239 
IsAvailable()240 bool OpenVRDevice::IsAvailable() {
241   return vr::VR_IsRuntimeInstalled() && vr::VR_IsHmdPresent();
242 }
243 
CreateImmersiveOverlay(mojo::PendingReceiver<mojom::ImmersiveOverlay> overlay_receiver)244 void OpenVRDevice::CreateImmersiveOverlay(
245     mojo::PendingReceiver<mojom::ImmersiveOverlay> overlay_receiver) {
246   if (render_loop_->IsRunning()) {
247     render_loop_->task_runner()->PostTask(
248         FROM_HERE, base::BindOnce(&XRCompositorCommon::RequestOverlay,
249                                   base::Unretained(render_loop_.get()),
250                                   std::move(overlay_receiver)));
251   } else {
252     overlay_receiver_ = std::move(overlay_receiver);
253   }
254 }
255 
256 // XRSessionController
SetFrameDataRestricted(bool restricted)257 void OpenVRDevice::SetFrameDataRestricted(bool restricted) {
258   // Presentation sessions can not currently be restricted.
259   DCHECK(false);
260 }
261 
OnPresentingControllerMojoConnectionError()262 void OpenVRDevice::OnPresentingControllerMojoConnectionError() {
263   render_loop_->task_runner()->PostTask(
264       FROM_HERE, base::BindOnce(&XRCompositorCommon::ExitPresent,
265                                 base::Unretained(render_loop_.get())));
266   OnExitPresent();
267   exclusive_controller_receiver_.reset();
268 }
269 
270 // Only deal with events that will cause displayInfo changes for now.
OnPollingEvents()271 void OpenVRDevice::OnPollingEvents() {
272   main_thread_task_runner_->PostDelayedTask(
273       FROM_HERE,
274       base::BindOnce(&OpenVRDevice::OnPollingEvents,
275                      weak_ptr_factory_.GetWeakPtr()),
276       kPollingInterval);
277 
278   if (!openvr_)
279     return;
280 
281   vr::VREvent_t event;
282   bool is_changed = false;
283   while (openvr_->GetSystem()->PollNextEvent(&event, sizeof(event))) {
284     if (event.trackedDeviceIndex != vr::k_unTrackedDeviceIndex_Hmd &&
285         event.trackedDeviceIndex != vr::k_unTrackedDeviceIndexInvalid) {
286       continue;
287     }
288 
289     switch (event.eventType) {
290       case vr::VREvent_TrackedDeviceUpdated:
291       case vr::VREvent_IpdChanged:
292       case vr::VREvent_ChaperoneDataHasChanged:
293       case vr::VREvent_ChaperoneSettingsHaveChanged:
294       case vr::VREvent_ChaperoneUniverseHasChanged:
295         is_changed = true;
296         break;
297 
298       default:
299         break;
300     }
301   }
302 
303   if (is_changed)
304     SetVRDisplayInfo(CreateVRDisplayInfo(openvr_->GetSystem(), GetId()));
305 }
306 
307 }  // namespace device
308