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