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