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/openxr/openxr_render_loop.h"
6 
7 #include "device/vr/openxr/openxr_api_wrapper.h"
8 #include "device/vr/openxr/openxr_input_helper.h"
9 #include "device/vr/util/stage_utils.h"
10 #include "device/vr/util/transform_utils.h"
11 #include "ui/gfx/geometry/angle_conversions.h"
12 #include "ui/gfx/transform.h"
13 #include "ui/gfx/transform_util.h"
14 
15 namespace device {
16 
OpenXrRenderLoop(base::RepeatingCallback<void (mojom::VRDisplayInfoPtr)> on_display_info_changed,XrInstance instance,const OpenXrExtensionHelper & extension_helper)17 OpenXrRenderLoop::OpenXrRenderLoop(
18     base::RepeatingCallback<void(mojom::VRDisplayInfoPtr)>
19         on_display_info_changed,
20     XrInstance instance,
21     const OpenXrExtensionHelper& extension_helper)
22     : XRCompositorCommon(),
23       instance_(instance),
24       extension_helper_(extension_helper),
25       on_display_info_changed_(std::move(on_display_info_changed)) {
26   DCHECK(instance_ != XR_NULL_HANDLE);
27 }
28 
~OpenXrRenderLoop()29 OpenXrRenderLoop::~OpenXrRenderLoop() {
30   Stop();
31 }
32 
GetNextFrameData()33 mojom::XRFrameDataPtr OpenXrRenderLoop::GetNextFrameData() {
34   mojom::XRFrameDataPtr frame_data = mojom::XRFrameData::New();
35   frame_data->frame_id = next_frame_id_;
36 
37   Microsoft::WRL::ComPtr<ID3D11Texture2D> texture;
38   if (XR_FAILED(openxr_->BeginFrame(&texture))) {
39     return frame_data;
40   }
41 
42   texture_helper_.SetBackbuffer(texture.Get());
43 
44   frame_data->time_delta =
45       base::TimeDelta::FromNanoseconds(openxr_->GetPredictedDisplayTime());
46 
47   frame_data->input_state =
48       input_helper_->GetInputState(openxr_->GetPredictedDisplayTime());
49 
50   frame_data->pose = mojom::VRPose::New();
51 
52   base::Optional<gfx::Quaternion> orientation;
53   base::Optional<gfx::Point3F> position;
54   if (XR_SUCCEEDED(openxr_->GetHeadPose(
55           &orientation, &position, &frame_data->pose->emulated_position))) {
56     if (orientation.has_value())
57       frame_data->pose->orientation = orientation;
58 
59     if (position.has_value())
60       frame_data->pose->position = position;
61   }
62 
63   UpdateStageParameters();
64 
65   bool updated_eye_parameters = UpdateEyeParameters();
66 
67   if (updated_eye_parameters) {
68     frame_data->left_eye = current_display_info_->left_eye.Clone();
69     frame_data->right_eye = current_display_info_->right_eye.Clone();
70 
71     main_thread_task_runner_->PostTask(
72         FROM_HERE, base::BindOnce(on_display_info_changed_,
73                                   current_display_info_.Clone()));
74   }
75 
76   return frame_data;
77 }
78 
StartRuntime()79 bool OpenXrRenderLoop::StartRuntime() {
80   DCHECK(instance_ != XR_NULL_HANDLE);
81   DCHECK(!openxr_);
82   DCHECK(!input_helper_);
83   DCHECK(!current_display_info_);
84 
85   // The new wrapper object is stored in a temporary variable instead of
86   // openxr_ so that the local unique_ptr cleans up the object if starting
87   // a session fails. openxr_ is set later in this method once we know
88   // starting the session succeeds.
89   std::unique_ptr<OpenXrApiWrapper> openxr =
90       OpenXrApiWrapper::Create(instance_);
91   if (!openxr)
92     return false;
93 
94   texture_helper_.SetUseBGRA(true);
95   LUID luid;
96   if (XR_FAILED(openxr->GetLuid(&luid, extension_helper_)) ||
97       !texture_helper_.SetAdapterLUID(luid) ||
98       !texture_helper_.EnsureInitialized() ||
99       XR_FAILED(openxr->InitSession(texture_helper_.GetDevice(), &input_helper_,
100                                     extension_helper_))) {
101     texture_helper_.Reset();
102     return false;
103   }
104 
105   // Starting session succeeded so we can set the member variable.
106   // Any additional code added below this should never fail.
107   openxr_ = std::move(openxr);
108   texture_helper_.SetDefaultSize(openxr_->GetViewSize());
109 
110   openxr_->RegisterInteractionProfileChangeCallback(
111       base::BindRepeating(&OpenXRInputHelper::OnInteractionProfileChanged,
112                           input_helper_->GetWeakPtr()));
113   openxr_->RegisterVisibilityChangeCallback(base::BindRepeating(
114       &OpenXrRenderLoop::SetVisibilityState, weak_ptr_factory_.GetWeakPtr()));
115   InitializeDisplayInfo();
116 
117   return true;
118 }
119 
StopRuntime()120 void OpenXrRenderLoop::StopRuntime() {
121   // Has to reset input_helper_ before reset openxr_. If we destroy openxr_
122   // first, input_helper_destructor will try to call the actual openxr runtime
123   // rather than the mock in tests.
124   input_helper_.reset();
125   openxr_ = nullptr;
126   current_display_info_ = nullptr;
127   texture_helper_.Reset();
128 }
129 
GetEnvironmentBlendMode(device::mojom::XRSessionMode session_mode)130 device::mojom::XREnvironmentBlendMode OpenXrRenderLoop::GetEnvironmentBlendMode(
131     device::mojom::XRSessionMode session_mode) {
132   return openxr_->PickEnvironmentBlendModeForSession(session_mode);
133 }
134 
GetInteractionMode(device::mojom::XRSessionMode session_mode)135 device::mojom::XRInteractionMode OpenXrRenderLoop::GetInteractionMode(
136     device::mojom::XRSessionMode session_mode) {
137   return device::mojom::XRInteractionMode::kWorldSpace;
138 }
139 
CanEnableAntiAliasing() const140 bool OpenXrRenderLoop::CanEnableAntiAliasing() const {
141   return openxr_->CanEnableAntiAliasing();
142 }
143 
OnSessionStart()144 void OpenXrRenderLoop::OnSessionStart() {
145   LogViewerType(VrViewerType::OPENXR_UNKNOWN);
146 }
147 
PreComposite()148 bool OpenXrRenderLoop::PreComposite() {
149   return true;
150 }
151 
HasSessionEnded()152 bool OpenXrRenderLoop::HasSessionEnded() {
153   return openxr_ && openxr_->UpdateAndGetSessionEnded();
154 }
155 
SubmitCompositedFrame()156 bool OpenXrRenderLoop::SubmitCompositedFrame() {
157   return XR_SUCCEEDED(openxr_->EndFrame());
158 }
159 
ClearPendingFrameInternal()160 void OpenXrRenderLoop::ClearPendingFrameInternal() {
161   // Complete the frame if OpenXR has started one with BeginFrame. This also
162   // releases the swapchain image that was acquired in BeginFrame so that the
163   // next frame can acquire it.
164   if (openxr_->HasPendingFrame() && XR_FAILED(openxr_->EndFrame())) {
165     // The start of the next frame will detect that the session has ended via
166     // HasSessionEnded and will exit presentation.
167     StopRuntime();
168     return;
169   }
170 }
171 
172 // Return true if display info has changed.
InitializeDisplayInfo()173 void OpenXrRenderLoop::InitializeDisplayInfo() {
174   if (!current_display_info_) {
175     current_display_info_ = mojom::VRDisplayInfo::New();
176     current_display_info_->right_eye = mojom::VREyeParameters::New();
177     current_display_info_->left_eye = mojom::VREyeParameters::New();
178   }
179 
180   gfx::Size view_size = openxr_->GetViewSize();
181   current_display_info_->left_eye->render_width = view_size.width();
182   current_display_info_->right_eye->render_width = view_size.width();
183   current_display_info_->left_eye->render_height = view_size.height();
184   current_display_info_->right_eye->render_height = view_size.height();
185 
186   // display info can't be send without fov info because of the mojo definition.
187   current_display_info_->left_eye->field_of_view =
188       mojom::VRFieldOfView::New(45.0f, 45.0f, 45.0f, 45.0f);
189   current_display_info_->right_eye->field_of_view =
190       current_display_info_->left_eye->field_of_view.Clone();
191 
192   main_thread_task_runner_->PostTask(
193       FROM_HERE,
194       base::BindOnce(on_display_info_changed_, current_display_info_.Clone()));
195 }
196 
197 // return true if either left_eye or right_eye updated.
UpdateEyeParameters()198 bool OpenXrRenderLoop::UpdateEyeParameters() {
199   bool changed = false;
200 
201   XrView left;
202   XrView right;
203   openxr_->GetHeadFromEyes(&left, &right);
204   gfx::Size view_size = openxr_->GetViewSize();
205 
206   changed |= UpdateEye(left, view_size, &current_display_info_->left_eye);
207 
208   changed |= UpdateEye(right, view_size, &current_display_info_->right_eye);
209 
210   return changed;
211 }
212 
UpdateEye(const XrView & view_head,const gfx::Size & view_size,mojom::VREyeParametersPtr * eye) const213 bool OpenXrRenderLoop::UpdateEye(const XrView& view_head,
214                                  const gfx::Size& view_size,
215                                  mojom::VREyeParametersPtr* eye) const {
216   bool changed = false;
217 
218   // TODO(crbug.com/999573): Query eye-to-head transform from the device and use
219   // that instead of just building a transformation matrix from the translation
220   // component.
221   gfx::Transform head_from_eye = vr_utils::MakeTranslationTransform(
222       view_head.pose.position.x, view_head.pose.position.y,
223       view_head.pose.position.z);
224 
225   if ((*eye)->head_from_eye != head_from_eye) {
226     (*eye)->head_from_eye = head_from_eye;
227     changed = true;
228   }
229 
230   if ((*eye)->render_width != static_cast<uint32_t>(view_size.width())) {
231     (*eye)->render_width = static_cast<uint32_t>(view_size.width());
232     changed = true;
233   }
234 
235   if ((*eye)->render_height != static_cast<uint32_t>(view_size.height())) {
236     (*eye)->render_height = static_cast<uint32_t>(view_size.height());
237     changed = true;
238   }
239 
240   mojom::VRFieldOfViewPtr fov =
241       mojom::VRFieldOfView::New(gfx::RadToDeg(view_head.fov.angleUp),
242                                 gfx::RadToDeg(-view_head.fov.angleDown),
243                                 gfx::RadToDeg(-view_head.fov.angleLeft),
244                                 gfx::RadToDeg(view_head.fov.angleRight));
245   if (!(*eye)->field_of_view || !fov->Equals(*(*eye)->field_of_view)) {
246     (*eye)->field_of_view = std::move(fov);
247     changed = true;
248   }
249 
250   return changed;
251 }
252 
UpdateStageParameters()253 void OpenXrRenderLoop::UpdateStageParameters() {
254   XrExtent2Df stage_bounds;
255   gfx::Transform local_from_stage;
256   if (openxr_->GetStageParameters(&stage_bounds, &local_from_stage)) {
257     mojom::VRStageParametersPtr stage_parameters =
258         mojom::VRStageParameters::New();
259     // mojo_from_local is identity, as is stage_from_floor, so we can directly
260     // assign local_from_stage and mojo_from_floor.
261     stage_parameters->mojo_from_floor = local_from_stage;
262     stage_parameters->bounds = vr_utils::GetStageBoundsFromSize(
263         stage_bounds.width, stage_bounds.height);
264     SetStageParameters(std::move(stage_parameters));
265   } else {
266     SetStageParameters(nullptr);
267   }
268 }
269 
270 }  // namespace device
271