1 // Copyright 2019 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/windows_mixed_reality/mixed_reality_device.h"
6
7 #include <math.h>
8 #include <utility>
9
10 #include "base/macros.h"
11 #include "base/memory/ptr_util.h"
12 #include "base/message_loop/message_pump_type.h"
13 #include "base/numerics/math_constants.h"
14 #include "base/threading/thread.h"
15 #include "build/build_config.h"
16 #include "device/vr/util/transform_utils.h"
17 #include "device/vr/windows_mixed_reality/mixed_reality_renderloop.h"
18 #include "mojo/public/cpp/bindings/pending_remote.h"
19 #include "ui/gfx/geometry/angle_conversions.h"
20
21 namespace device {
22
23 namespace {
24
25 // Windows Mixed Reality doesn't give out display info until you start a
26 // presentation session and "Holographic Cameras" are added to the scene.
27 // However our mojo interface expects display info right away to support WebVR.
28 // We create a fake display info to use, then notify the client that the display
29 // info changed when we get real data.
CreateFakeVRDisplayInfo()30 mojom::VRDisplayInfoPtr CreateFakeVRDisplayInfo() {
31 mojom::VRDisplayInfoPtr display_info = mojom::VRDisplayInfo::New();
32
33 display_info->left_eye = mojom::VREyeParameters::New();
34 display_info->right_eye = mojom::VREyeParameters::New();
35 mojom::VREyeParametersPtr& left_eye = display_info->left_eye;
36 mojom::VREyeParametersPtr& right_eye = display_info->right_eye;
37
38 left_eye->field_of_view = mojom::VRFieldOfView::New(45, 45, 45, 45);
39 right_eye->field_of_view = mojom::VRFieldOfView::New(45, 45, 45, 45);
40
41 left_eye->head_from_eye = vr_utils::DefaultHeadFromLeftEyeTransform();
42 right_eye->head_from_eye = vr_utils::DefaultHeadFromRightEyeTransform();
43
44 constexpr uint32_t width = 1024;
45 constexpr uint32_t height = 1024;
46 left_eye->render_width = width;
47 left_eye->render_height = height;
48 right_eye->render_width = left_eye->render_width;
49 right_eye->render_height = left_eye->render_height;
50
51 return display_info;
52 }
53
54 } // namespace
55
MixedRealityDevice()56 MixedRealityDevice::MixedRealityDevice()
57 : VRDeviceBase(device::mojom::XRDeviceId::WINDOWS_MIXED_REALITY_ID) {
58 SetVRDisplayInfo(CreateFakeVRDisplayInfo());
59 }
60
~MixedRealityDevice()61 MixedRealityDevice::~MixedRealityDevice() {
62 Shutdown();
63 }
64
65 mojo::PendingRemote<mojom::XRCompositorHost>
BindCompositorHost()66 MixedRealityDevice::BindCompositorHost() {
67 return compositor_host_receiver_.BindNewPipeAndPassRemote();
68 }
69
RequestSession(mojom::XRRuntimeSessionOptionsPtr options,mojom::XRRuntime::RequestSessionCallback callback)70 void MixedRealityDevice::RequestSession(
71 mojom::XRRuntimeSessionOptionsPtr options,
72 mojom::XRRuntime::RequestSessionCallback callback) {
73 DCHECK_EQ(options->mode, mojom::XRSessionMode::kImmersiveVr);
74
75 if (!render_loop_)
76 CreateRenderLoop();
77
78 if (!render_loop_->IsRunning()) {
79 // We need to start a UI message loop or we will not receive input events
80 // on 1809 or newer.
81 base::Thread::Options options;
82 options.message_pump_type = base::MessagePumpType::UI;
83 render_loop_->StartWithOptions(options);
84
85 // IsRunning() should be true here unless the thread failed to start (likely
86 // memory exhaustion). If the thread fails to start, then we fail to create
87 // a session.
88 if (!render_loop_->IsRunning()) {
89 std::move(callback).Run(nullptr, mojo::NullRemote());
90 return;
91 }
92
93 if (overlay_receiver_) {
94 render_loop_->task_runner()->PostTask(
95 FROM_HERE, base::BindOnce(&XRCompositorCommon::RequestOverlay,
96 base::Unretained(render_loop_.get()),
97 std::move(overlay_receiver_)));
98 }
99 }
100
101 auto my_callback =
102 base::BindOnce(&MixedRealityDevice::OnRequestSessionResult,
103 weak_ptr_factory_.GetWeakPtr(), std::move(callback));
104
105 auto on_presentation_ended = base::BindOnce(
106 &MixedRealityDevice::OnPresentationEnded, weak_ptr_factory_.GetWeakPtr());
107
108 auto on_visibility_state_changed =
109 base::BindRepeating(&MixedRealityDevice::OnVisibilityStateChanged,
110 weak_ptr_factory_.GetWeakPtr());
111
112 render_loop_->task_runner()->PostTask(
113 FROM_HERE, base::BindOnce(&XRCompositorCommon::RequestSession,
114 base::Unretained(render_loop_.get()),
115 std::move(on_presentation_ended),
116 std::move(on_visibility_state_changed),
117 std::move(options), std::move(my_callback)));
118 }
119
Shutdown()120 void MixedRealityDevice::Shutdown() {
121 // Wait for the render loop to stop before completing destruction.
122 if (render_loop_ && render_loop_->IsRunning())
123 render_loop_->Stop();
124 }
125
CreateRenderLoop()126 void MixedRealityDevice::CreateRenderLoop() {
127 auto on_info_changed = base::BindRepeating(
128 &MixedRealityDevice::SetVRDisplayInfo, weak_ptr_factory_.GetWeakPtr());
129 render_loop_ =
130 std::make_unique<MixedRealityRenderLoop>(std::move(on_info_changed));
131 }
132
OnPresentationEnded()133 void MixedRealityDevice::OnPresentationEnded() {}
134
OnRequestSessionResult(mojom::XRRuntime::RequestSessionCallback callback,bool result,mojom::XRSessionPtr session)135 void MixedRealityDevice::OnRequestSessionResult(
136 mojom::XRRuntime::RequestSessionCallback callback,
137 bool result,
138 mojom::XRSessionPtr session) {
139 if (!result) {
140 OnPresentationEnded();
141 std::move(callback).Run(nullptr, mojo::NullRemote());
142 return;
143 }
144
145 OnStartPresenting();
146
147 session->display_info = display_info_.Clone();
148 std::move(callback).Run(
149 std::move(session),
150 exclusive_controller_receiver_.BindNewPipeAndPassRemote());
151
152 // Use of Unretained is safe because the callback will only occur if the
153 // binding is not destroyed.
154 exclusive_controller_receiver_.set_disconnect_handler(base::BindOnce(
155 &MixedRealityDevice::OnPresentingControllerMojoConnectionError,
156 base::Unretained(this)));
157 }
158
CreateImmersiveOverlay(mojo::PendingReceiver<mojom::ImmersiveOverlay> overlay_receiver)159 void MixedRealityDevice::CreateImmersiveOverlay(
160 mojo::PendingReceiver<mojom::ImmersiveOverlay> overlay_receiver) {
161 if (!render_loop_)
162 CreateRenderLoop();
163 if (render_loop_->IsRunning()) {
164 render_loop_->task_runner()->PostTask(
165 FROM_HERE, base::BindOnce(&XRCompositorCommon::RequestOverlay,
166 base::Unretained(render_loop_.get()),
167 std::move(overlay_receiver)));
168 } else {
169 overlay_receiver_ = std::move(overlay_receiver);
170 }
171 }
172
173 // XRSessionController
SetFrameDataRestricted(bool restricted)174 void MixedRealityDevice::SetFrameDataRestricted(bool restricted) {
175 // Presentation sessions can not currently be restricted.
176 DCHECK(false);
177 }
178
OnPresentingControllerMojoConnectionError()179 void MixedRealityDevice::OnPresentingControllerMojoConnectionError() {
180 if (render_loop_) {
181 render_loop_->task_runner()->PostTask(
182 FROM_HERE, base::BindOnce(&XRCompositorCommon::ExitPresent,
183 base::Unretained(render_loop_.get())));
184 }
185
186 OnExitPresent();
187 exclusive_controller_receiver_.reset();
188 }
189
190 } // namespace device
191