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, ¤t_display_info_->left_eye);
207
208 changed |= UpdateEye(right, view_size, ¤t_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