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_api_wrapper.h"
6
7 #include <stdint.h>
8 #include <algorithm>
9 #include <array>
10
11 #include "base/check.h"
12 #include "base/notreached.h"
13 #include "device/vr/openxr/openxr_input_helper.h"
14 #include "device/vr/openxr/openxr_util.h"
15 #include "device/vr/test/test_hook.h"
16 #include "ui/gfx/geometry/point3_f.h"
17 #include "ui/gfx/geometry/quaternion.h"
18 #include "ui/gfx/geometry/size.h"
19 #include "ui/gfx/transform.h"
20 #include "ui/gfx/transform_util.h"
21
22 namespace device {
23
24 namespace {
25 constexpr XrSystemId kInvalidSystem = -1;
26 // Only supported view configuration:
27 constexpr XrViewConfigurationType kSupportedViewConfiguration =
28 XR_VIEW_CONFIGURATION_TYPE_PRIMARY_STEREO;
29 constexpr uint32_t kNumViews = 2;
30
31 // We can get into a state where frames are not requested, such as when the
32 // visibility state is hidden. Since OpenXR events are polled at the beginning
33 // of a frame, polling would not occur in this state. To ensure events are
34 // occasionally polled, a timer loop run every kTimeBetweenPollingEvents to poll
35 // events if significant time has elapsed since the last time events were
36 // polled.
37 constexpr base::TimeDelta kTimeBetweenPollingEvents =
38 base::TimeDelta::FromSecondsD(1);
39
40 } // namespace
41
Create(XrInstance instance)42 std::unique_ptr<OpenXrApiWrapper> OpenXrApiWrapper::Create(
43 XrInstance instance) {
44 std::unique_ptr<OpenXrApiWrapper> openxr =
45 std::make_unique<OpenXrApiWrapper>();
46
47 if (!openxr->Initialize(instance)) {
48 return nullptr;
49 }
50
51 return openxr;
52 }
53
54 OpenXrApiWrapper::OpenXrApiWrapper() = default;
55
~OpenXrApiWrapper()56 OpenXrApiWrapper::~OpenXrApiWrapper() {
57 Uninitialize();
58 }
59
Reset()60 void OpenXrApiWrapper::Reset() {
61 unbounded_space_ = XR_NULL_HANDLE;
62 local_space_ = XR_NULL_HANDLE;
63 stage_space_ = XR_NULL_HANDLE;
64 view_space_ = XR_NULL_HANDLE;
65 color_swapchain_ = XR_NULL_HANDLE;
66 session_ = XR_NULL_HANDLE;
67 blend_mode_ = XR_ENVIRONMENT_BLEND_MODE_MAX_ENUM;
68 stage_bounds_ = {};
69 system_ = kInvalidSystem;
70 instance_ = XR_NULL_HANDLE;
71
72 view_configs_.clear();
73 color_swapchain_images_.clear();
74 frame_state_ = {};
75 origin_from_eye_views_.clear();
76 head_from_eye_views_.clear();
77 layer_projection_views_.clear();
78 }
79
Initialize(XrInstance instance)80 bool OpenXrApiWrapper::Initialize(XrInstance instance) {
81 Reset();
82 session_running_ = false;
83 pending_frame_ = false;
84 // Set to min so that the first call to EnsureEventPolling is guaranteed to
85 // call ProcessEvents, which will update this variable from there on.
86 last_process_events_time_ = base::TimeTicks::Min();
87
88 DCHECK(instance != XR_NULL_HANDLE);
89 instance_ = instance;
90
91 DCHECK(HasInstance());
92
93 if (XR_FAILED(InitializeSystem())) {
94 // When initialization fails, the caller should release this object, so we
95 // don't need to destroy the instance created above as it is destroyed in
96 // the destructor.
97 DCHECK(!IsInitialized());
98 return false;
99 }
100
101 DCHECK(IsInitialized());
102
103 if (test_hook_) {
104 // Allow our mock implementation of OpenXr to be controlled by tests.
105 // The mock implementation of xrCreateInstance returns a pointer to the
106 // service test hook (g_test_helper) as the instance.
107 service_test_hook_ = reinterpret_cast<ServiceTestHook*>(instance_);
108 service_test_hook_->SetTestHook(test_hook_);
109
110 test_hook_->AttachCurrentThread();
111 }
112
113 return true;
114 }
115
IsInitialized() const116 bool OpenXrApiWrapper::IsInitialized() const {
117 return HasInstance() && HasSystem();
118 }
119
Uninitialize()120 void OpenXrApiWrapper::Uninitialize() {
121 // The instance is owned by the OpenXRDevice, so don't destroy it here.
122
123 // Destroying an session in OpenXr also destroys all child objects of that
124 // instance (including the swapchain, and spaces objects),
125 // so they don't need to be manually destroyed.
126 if (HasSession()) {
127 xrDestroySession(session_);
128 }
129
130 if (test_hook_)
131 test_hook_->DetachCurrentThread();
132
133 Reset();
134 session_running_ = false;
135 pending_frame_ = false;
136
137 // Set to max so events are no longer polled in the EnsureEventPolling loop.
138 last_process_events_time_ = base::TimeTicks::Max();
139 }
140
HasInstance() const141 bool OpenXrApiWrapper::HasInstance() const {
142 return instance_ != XR_NULL_HANDLE;
143 }
144
HasSystem() const145 bool OpenXrApiWrapper::HasSystem() const {
146 return system_ != kInvalidSystem && view_configs_.size() == kNumViews;
147 }
148
HasBlendMode() const149 bool OpenXrApiWrapper::HasBlendMode() const {
150 return blend_mode_ != XR_ENVIRONMENT_BLEND_MODE_MAX_ENUM;
151 }
152
HasSession() const153 bool OpenXrApiWrapper::HasSession() const {
154 return session_ != XR_NULL_HANDLE;
155 }
156
HasColorSwapChain() const157 bool OpenXrApiWrapper::HasColorSwapChain() const {
158 return color_swapchain_ != XR_NULL_HANDLE &&
159 color_swapchain_images_.size() > 0;
160 }
161
HasSpace(XrReferenceSpaceType type) const162 bool OpenXrApiWrapper::HasSpace(XrReferenceSpaceType type) const {
163 switch (type) {
164 case XR_REFERENCE_SPACE_TYPE_LOCAL:
165 return local_space_ != XR_NULL_HANDLE;
166 case XR_REFERENCE_SPACE_TYPE_VIEW:
167 return view_space_ != XR_NULL_HANDLE;
168 case XR_REFERENCE_SPACE_TYPE_STAGE:
169 return stage_space_ != XR_NULL_HANDLE;
170 case XR_REFERENCE_SPACE_TYPE_UNBOUNDED_MSFT:
171 return unbounded_space_ != XR_NULL_HANDLE;
172 default:
173 NOTREACHED();
174 return false;
175 }
176 }
177
HasFrameState() const178 bool OpenXrApiWrapper::HasFrameState() const {
179 return frame_state_.type == XR_TYPE_FRAME_STATE;
180 }
181
InitializeSystem()182 XrResult OpenXrApiWrapper::InitializeSystem() {
183 DCHECK(HasInstance());
184 DCHECK(!HasSystem());
185
186 XrSystemId system;
187 RETURN_IF_XR_FAILED(GetSystem(instance_, &system));
188
189 uint32_t view_count;
190 RETURN_IF_XR_FAILED(xrEnumerateViewConfigurationViews(
191 instance_, system, kSupportedViewConfiguration, 0, &view_count, nullptr));
192
193 // It would be an error for an OpenXr runtime to return anything other than 2
194 // views to an app that only requested
195 // XR_VIEW_CONFIGURATION_TYPE_PRIMARY_STEREO.
196 DCHECK(view_count == kNumViews);
197
198 std::vector<XrViewConfigurationView> view_configs(
199 view_count, {XR_TYPE_VIEW_CONFIGURATION_VIEW});
200 RETURN_IF_XR_FAILED(xrEnumerateViewConfigurationViews(
201 instance_, system, kSupportedViewConfiguration, view_count, &view_count,
202 view_configs.data()));
203
204 // Only assign the member variables on success. If any of the above XR calls
205 // fail, the vector cleans up view_configs if necessary. system does not need
206 // to be cleaned up because it is not allocated.
207 system_ = system;
208 view_configs_ = std::move(view_configs);
209
210 return XR_SUCCESS;
211 }
212
GetMojoBlendMode(XrEnvironmentBlendMode xr_blend_mode)213 device::mojom::XREnvironmentBlendMode OpenXrApiWrapper::GetMojoBlendMode(
214 XrEnvironmentBlendMode xr_blend_mode) {
215 switch (xr_blend_mode) {
216 case XR_ENVIRONMENT_BLEND_MODE_OPAQUE:
217 return device::mojom::XREnvironmentBlendMode::kOpaque;
218 case XR_ENVIRONMENT_BLEND_MODE_ADDITIVE:
219 return device::mojom::XREnvironmentBlendMode::kAdditive;
220 case XR_ENVIRONMENT_BLEND_MODE_ALPHA_BLEND:
221 return device::mojom::XREnvironmentBlendMode::kAlphaBlend;
222 case XR_ENVIRONMENT_BLEND_MODE_MAX_ENUM:
223 NOTREACHED();
224 };
225 return device::mojom::XREnvironmentBlendMode::kOpaque;
226 }
227
228 device::mojom::XREnvironmentBlendMode
PickEnvironmentBlendModeForSession(device::mojom::XRSessionMode session_mode)229 OpenXrApiWrapper::PickEnvironmentBlendModeForSession(
230 device::mojom::XRSessionMode session_mode) {
231 DCHECK(HasInstance());
232 std::vector<XrEnvironmentBlendMode> supported_blend_modes =
233 GetSupportedBlendModes(instance_, system_);
234
235 DCHECK(supported_blend_modes.size() > 0);
236
237 blend_mode_ = supported_blend_modes[0];
238
239 switch (session_mode) {
240 case device::mojom::XRSessionMode::kImmersiveVr:
241 if (base::Contains(supported_blend_modes,
242 XR_ENVIRONMENT_BLEND_MODE_OPAQUE))
243 blend_mode_ = XR_ENVIRONMENT_BLEND_MODE_OPAQUE;
244 break;
245 case device::mojom::XRSessionMode::kImmersiveAr:
246 if (base::Contains(supported_blend_modes,
247 XR_ENVIRONMENT_BLEND_MODE_ADDITIVE))
248 blend_mode_ = XR_ENVIRONMENT_BLEND_MODE_ADDITIVE;
249 break;
250 case device::mojom::XRSessionMode::kInline:
251 NOTREACHED();
252 }
253
254 return GetMojoBlendMode(blend_mode_);
255 }
256
UpdateAndGetSessionEnded()257 bool OpenXrApiWrapper::UpdateAndGetSessionEnded() {
258 // Ensure we have the latest state from the OpenXR runtime.
259 if (XR_FAILED(ProcessEvents())) {
260 DCHECK(!session_running_);
261 }
262
263 // This object is initialized at creation and uninitialized when the OpenXR
264 // session has ended. Once uninitialized, this object is never re-initialized.
265 // If a new session is requested by WebXR, a new object is created.
266 return !IsInitialized();
267 }
268
269 // Callers of this function must check the XrResult return value and destroy
270 // this OpenXrApiWrapper object on failure to clean up any intermediate
271 // objects that may have been created before the failure.
InitSession(const Microsoft::WRL::ComPtr<ID3D11Device> & d3d_device,std::unique_ptr<OpenXRInputHelper> * input_helper,const OpenXrExtensionHelper & extension_helper)272 XrResult OpenXrApiWrapper::InitSession(
273 const Microsoft::WRL::ComPtr<ID3D11Device>& d3d_device,
274 std::unique_ptr<OpenXRInputHelper>* input_helper,
275 const OpenXrExtensionHelper& extension_helper) {
276 DCHECK(d3d_device.Get());
277 DCHECK(IsInitialized());
278
279 RETURN_IF_XR_FAILED(CreateSession(d3d_device));
280 RETURN_IF_XR_FAILED(CreateSwapchain());
281 RETURN_IF_XR_FAILED(
282 CreateSpace(XR_REFERENCE_SPACE_TYPE_LOCAL, &local_space_));
283 RETURN_IF_XR_FAILED(CreateSpace(XR_REFERENCE_SPACE_TYPE_VIEW, &view_space_));
284
285 // It's ok if stage_space_ fails since not all OpenXR devices are required to
286 // support this reference space.
287 CreateSpace(XR_REFERENCE_SPACE_TYPE_STAGE, &stage_space_);
288 UpdateStageBounds();
289
290 if (extension_helper.ExtensionEnumeration()->ExtensionSupported(
291 XR_MSFT_UNBOUNDED_REFERENCE_SPACE_EXTENSION_NAME)) {
292 RETURN_IF_XR_FAILED(
293 CreateSpace(XR_REFERENCE_SPACE_TYPE_UNBOUNDED_MSFT, &unbounded_space_));
294 }
295
296 RETURN_IF_XR_FAILED(CreateGamepadHelper(input_helper, extension_helper));
297
298 // Since the objects in these arrays are used on every frame,
299 // we don't want to create and destroy these objects every frame,
300 // so create the number of objects we need and reuse them.
301 origin_from_eye_views_.resize(kNumViews);
302 head_from_eye_views_.resize(kNumViews);
303 layer_projection_views_.resize(kNumViews);
304
305 // Make sure all of the objects we initialized are there.
306 DCHECK(HasSession());
307 DCHECK(HasColorSwapChain());
308 DCHECK(HasSpace(XR_REFERENCE_SPACE_TYPE_LOCAL));
309 DCHECK(HasSpace(XR_REFERENCE_SPACE_TYPE_VIEW));
310 DCHECK(input_helper);
311
312 EnsureEventPolling();
313
314 return XR_SUCCESS;
315 }
316
CreateSession(const Microsoft::WRL::ComPtr<ID3D11Device> & d3d_device)317 XrResult OpenXrApiWrapper::CreateSession(
318 const Microsoft::WRL::ComPtr<ID3D11Device>& d3d_device) {
319 DCHECK(d3d_device.Get());
320 DCHECK(!HasSession());
321 DCHECK(IsInitialized());
322
323 XrGraphicsBindingD3D11KHR d3d11_binding = {
324 XR_TYPE_GRAPHICS_BINDING_D3D11_KHR};
325 d3d11_binding.device = d3d_device.Get();
326
327 XrSessionCreateInfo session_create_info = {XR_TYPE_SESSION_CREATE_INFO};
328 session_create_info.next = &d3d11_binding;
329 session_create_info.systemId = system_;
330
331 return xrCreateSession(instance_, &session_create_info, &session_);
332 }
333
CreateSwapchain()334 XrResult OpenXrApiWrapper::CreateSwapchain() {
335 DCHECK(IsInitialized());
336 DCHECK(HasSession());
337 DCHECK(!HasColorSwapChain());
338
339 gfx::Size view_size = GetViewSize();
340
341 XrSwapchainCreateInfo swapchain_create_info = {XR_TYPE_SWAPCHAIN_CREATE_INFO};
342 swapchain_create_info.arraySize = 1;
343 // OpenXR's swapchain format expects to describe the texture content.
344 // The result of a swapchain image created from OpenXR API always contains a
345 // typeless texture. On the other hand, WebGL API uses CSS color convention
346 // that's sRGB. The RGBA typelss texture from OpenXR swapchain image leads to
347 // a linear format render target view (reference to function
348 // D3D11TextureHelper::EnsureRenderTargetView in d3d11_texture_helper.cc).
349 // Therefore, the content in this openxr swapchain image is in sRGB format.
350 swapchain_create_info.format = DXGI_FORMAT_R8G8B8A8_UNORM_SRGB;
351
352 // WebVR and WebXR textures are double wide, meaning the texture contains
353 // both the left and the right eye, so the width of the swapchain texture
354 // needs to be doubled.
355 swapchain_create_info.width = view_size.width() * 2;
356 swapchain_create_info.height = view_size.height();
357 swapchain_create_info.mipCount = 1;
358 swapchain_create_info.faceCount = 1;
359 swapchain_create_info.sampleCount = GetRecommendedSwapchainSampleCount();
360 swapchain_create_info.usageFlags = XR_SWAPCHAIN_USAGE_COLOR_ATTACHMENT_BIT;
361 XrSwapchain color_swapchain;
362 RETURN_IF_XR_FAILED(
363 xrCreateSwapchain(session_, &swapchain_create_info, &color_swapchain));
364
365 uint32_t chain_length;
366 RETURN_IF_XR_FAILED(
367 xrEnumerateSwapchainImages(color_swapchain, 0, &chain_length, nullptr));
368
369 std::vector<XrSwapchainImageD3D11KHR> color_swapchain_images(chain_length);
370 for (XrSwapchainImageD3D11KHR& image : color_swapchain_images) {
371 image.type = XR_TYPE_SWAPCHAIN_IMAGE_D3D11_KHR;
372 }
373
374 RETURN_IF_XR_FAILED(xrEnumerateSwapchainImages(
375 color_swapchain, color_swapchain_images.size(), &chain_length,
376 reinterpret_cast<XrSwapchainImageBaseHeader*>(
377 color_swapchain_images.data())));
378
379 color_swapchain_ = color_swapchain;
380 color_swapchain_images_ = std::move(color_swapchain_images);
381 return XR_SUCCESS;
382 }
383
CreateSpace(XrReferenceSpaceType type,XrSpace * space)384 XrResult OpenXrApiWrapper::CreateSpace(XrReferenceSpaceType type,
385 XrSpace* space) {
386 DCHECK(HasSession());
387 DCHECK(!HasSpace(type));
388
389 XrReferenceSpaceCreateInfo space_create_info = {
390 XR_TYPE_REFERENCE_SPACE_CREATE_INFO};
391 space_create_info.referenceSpaceType = type;
392 space_create_info.poseInReferenceSpace = PoseIdentity();
393
394 return xrCreateReferenceSpace(session_, &space_create_info, space);
395 }
396
CreateGamepadHelper(std::unique_ptr<OpenXRInputHelper> * input_helper,const OpenXrExtensionHelper & extension_helper)397 XrResult OpenXrApiWrapper::CreateGamepadHelper(
398 std::unique_ptr<OpenXRInputHelper>* input_helper,
399 const OpenXrExtensionHelper& extension_helper) {
400 DCHECK(HasSession());
401 DCHECK(HasSpace(XR_REFERENCE_SPACE_TYPE_LOCAL));
402
403 return OpenXRInputHelper::CreateOpenXRInputHelper(
404 instance_, extension_helper, session_, local_space_, input_helper);
405 }
406
BeginSession()407 XrResult OpenXrApiWrapper::BeginSession() {
408 DCHECK(HasSession());
409
410 XrSessionBeginInfo session_begin_info = {XR_TYPE_SESSION_BEGIN_INFO};
411 session_begin_info.primaryViewConfigurationType = kSupportedViewConfiguration;
412
413 XrResult xr_result = xrBeginSession(session_, &session_begin_info);
414 if (XR_SUCCEEDED(xr_result))
415 session_running_ = true;
416
417 return xr_result;
418 }
419
BeginFrame(Microsoft::WRL::ComPtr<ID3D11Texture2D> * texture)420 XrResult OpenXrApiWrapper::BeginFrame(
421 Microsoft::WRL::ComPtr<ID3D11Texture2D>* texture) {
422 DCHECK(HasSession());
423 DCHECK(HasColorSwapChain());
424
425 if (!session_running_)
426 return XR_ERROR_SESSION_NOT_RUNNING;
427
428 XrFrameWaitInfo wait_frame_info = {XR_TYPE_FRAME_WAIT_INFO};
429 XrFrameState frame_state = {XR_TYPE_FRAME_STATE};
430 RETURN_IF_XR_FAILED(xrWaitFrame(session_, &wait_frame_info, &frame_state));
431 frame_state_ = frame_state;
432
433 XrFrameBeginInfo begin_frame_info = {XR_TYPE_FRAME_BEGIN_INFO};
434 RETURN_IF_XR_FAILED(xrBeginFrame(session_, &begin_frame_info));
435 pending_frame_ = true;
436
437 XrSwapchainImageAcquireInfo acquire_info = {
438 XR_TYPE_SWAPCHAIN_IMAGE_ACQUIRE_INFO};
439 uint32_t color_swapchain_image_index;
440 RETURN_IF_XR_FAILED(xrAcquireSwapchainImage(color_swapchain_, &acquire_info,
441 &color_swapchain_image_index));
442
443 XrSwapchainImageWaitInfo wait_info = {XR_TYPE_SWAPCHAIN_IMAGE_WAIT_INFO};
444 wait_info.timeout = XR_INFINITE_DURATION;
445
446 RETURN_IF_XR_FAILED(xrWaitSwapchainImage(color_swapchain_, &wait_info));
447 RETURN_IF_XR_FAILED(UpdateProjectionLayers());
448
449 *texture = color_swapchain_images_[color_swapchain_image_index].texture;
450
451 return XR_SUCCESS;
452 }
453
EndFrame()454 XrResult OpenXrApiWrapper::EndFrame() {
455 DCHECK(pending_frame_);
456 DCHECK(HasBlendMode());
457 DCHECK(HasSession());
458 DCHECK(HasColorSwapChain());
459 DCHECK(HasSpace(XR_REFERENCE_SPACE_TYPE_LOCAL));
460 DCHECK(HasFrameState());
461
462 XrSwapchainImageReleaseInfo release_info = {
463 XR_TYPE_SWAPCHAIN_IMAGE_RELEASE_INFO};
464 RETURN_IF_XR_FAILED(xrReleaseSwapchainImage(color_swapchain_, &release_info));
465
466 XrCompositionLayerProjection multi_projection_layer = {
467 XR_TYPE_COMPOSITION_LAYER_PROJECTION};
468 XrCompositionLayerProjection* multi_projection_layer_ptr =
469 &multi_projection_layer;
470 multi_projection_layer.space = local_space_;
471 multi_projection_layer.viewCount = origin_from_eye_views_.size();
472 multi_projection_layer.views = layer_projection_views_.data();
473
474 XrFrameEndInfo end_frame_info = {XR_TYPE_FRAME_END_INFO};
475 end_frame_info.environmentBlendMode = blend_mode_;
476 end_frame_info.layerCount = 1;
477 end_frame_info.layers =
478 reinterpret_cast<const XrCompositionLayerBaseHeader* const*>(
479 &multi_projection_layer_ptr);
480 end_frame_info.displayTime = frame_state_.predictedDisplayTime;
481
482 RETURN_IF_XR_FAILED(xrEndFrame(session_, &end_frame_info));
483 pending_frame_ = false;
484
485 return XR_SUCCESS;
486 }
487
HasPendingFrame() const488 bool OpenXrApiWrapper::HasPendingFrame() const {
489 return pending_frame_;
490 }
491
UpdateProjectionLayers()492 XrResult OpenXrApiWrapper::UpdateProjectionLayers() {
493 RETURN_IF_XR_FAILED(
494 LocateViews(XR_REFERENCE_SPACE_TYPE_LOCAL, &origin_from_eye_views_));
495 RETURN_IF_XR_FAILED(
496 LocateViews(XR_REFERENCE_SPACE_TYPE_VIEW, &head_from_eye_views_));
497
498 gfx::Size view_size = GetViewSize();
499 for (uint32_t view_index = 0; view_index < origin_from_eye_views_.size();
500 view_index++) {
501 const XrView& view = origin_from_eye_views_[view_index];
502
503 XrCompositionLayerProjectionView& layer_projection_view =
504 layer_projection_views_[view_index];
505
506 layer_projection_view.type = XR_TYPE_COMPOSITION_LAYER_PROJECTION_VIEW;
507 layer_projection_view.pose = view.pose;
508 layer_projection_view.fov = view.fov;
509 layer_projection_view.subImage.swapchain = color_swapchain_;
510 // Since we're in double wide mode, the texture
511 // array only has one texture and is always index 0.
512 layer_projection_view.subImage.imageArrayIndex = 0;
513 layer_projection_view.subImage.imageRect.extent.width = view_size.width();
514 layer_projection_view.subImage.imageRect.extent.height = view_size.height();
515 // x coordinates is 0 for first view, 0 + i*width for ith view.
516 layer_projection_view.subImage.imageRect.offset.x =
517 view_size.width() * view_index;
518 layer_projection_view.subImage.imageRect.offset.y = 0;
519 }
520
521 return XR_SUCCESS;
522 }
523
LocateViews(XrReferenceSpaceType type,std::vector<XrView> * views) const524 XrResult OpenXrApiWrapper::LocateViews(XrReferenceSpaceType type,
525 std::vector<XrView>* views) const {
526 DCHECK(HasSession());
527
528 XrViewState view_state = {XR_TYPE_VIEW_STATE};
529 XrViewLocateInfo view_locate_info = {XR_TYPE_VIEW_LOCATE_INFO};
530 view_locate_info.viewConfigurationType = kSupportedViewConfiguration;
531 view_locate_info.displayTime = frame_state_.predictedDisplayTime;
532
533 switch (type) {
534 case XR_REFERENCE_SPACE_TYPE_LOCAL:
535 view_locate_info.space = local_space_;
536 break;
537 case XR_REFERENCE_SPACE_TYPE_VIEW:
538 view_locate_info.space = view_space_;
539 break;
540 case XR_REFERENCE_SPACE_TYPE_STAGE:
541 case XR_REFERENCE_SPACE_TYPE_UNBOUNDED_MSFT:
542 case XR_REFERENCE_SPACE_TYPE_MAX_ENUM:
543 NOTREACHED();
544 }
545
546 // Initialize the XrView objects' type field to XR_TYPE_VIEW. xrLocateViews
547 // fails validation if this isn't set.
548 std::vector<XrView> new_views(kNumViews, {XR_TYPE_VIEW});
549 uint32_t view_count;
550 RETURN_IF_XR_FAILED(xrLocateViews(session_, &view_locate_info, &view_state,
551 new_views.size(), &view_count,
552 new_views.data()));
553 DCHECK(view_count == kNumViews);
554
555 // If the position or orientation is not valid, don't update the views so that
556 // the previous valid views are used instead.
557 if ((view_state.viewStateFlags & XR_VIEW_STATE_POSITION_VALID_BIT) &&
558 (view_state.viewStateFlags & XR_VIEW_STATE_ORIENTATION_VALID_BIT)) {
559 *views = std::move(new_views);
560 }
561
562 return XR_SUCCESS;
563 }
564
565 // Returns the next predicted display time in nanoseconds.
GetPredictedDisplayTime() const566 XrTime OpenXrApiWrapper::GetPredictedDisplayTime() const {
567 DCHECK(IsInitialized());
568 DCHECK(HasFrameState());
569
570 return frame_state_.predictedDisplayTime;
571 }
572
GetHeadPose(base::Optional<gfx::Quaternion> * orientation,base::Optional<gfx::Point3F> * position,bool * emulated_position) const573 XrResult OpenXrApiWrapper::GetHeadPose(
574 base::Optional<gfx::Quaternion>* orientation,
575 base::Optional<gfx::Point3F>* position,
576 bool* emulated_position) const {
577 DCHECK(HasSpace(XR_REFERENCE_SPACE_TYPE_LOCAL));
578 DCHECK(HasSpace(XR_REFERENCE_SPACE_TYPE_VIEW));
579
580 XrSpaceLocation view_from_local = {XR_TYPE_SPACE_LOCATION};
581 RETURN_IF_XR_FAILED(xrLocateSpace(view_space_, local_space_,
582 frame_state_.predictedDisplayTime,
583 &view_from_local));
584
585 // emulated_position indicates when there is a fallback from a fully-tracked
586 // (i.e. 6DOF) type case to some form of orientation-only type tracking
587 // (i.e. 3DOF/IMU type sensors)
588 // Thus we have to make sure orientation is tracked.
589 // Valid Bit only indicates it's either tracked or emulated, we have to check
590 // for XR_SPACE_LOCATION_ORIENTATION_TRACKED_BIT to make sure orientation is
591 // tracked.
592 if (view_from_local.locationFlags &
593 XR_SPACE_LOCATION_ORIENTATION_TRACKED_BIT) {
594 *orientation = gfx::Quaternion(
595 view_from_local.pose.orientation.x, view_from_local.pose.orientation.y,
596 view_from_local.pose.orientation.z, view_from_local.pose.orientation.w);
597 } else {
598 *orientation = base::nullopt;
599 }
600
601 if (view_from_local.locationFlags & XR_SPACE_LOCATION_POSITION_VALID_BIT) {
602 *position = gfx::Point3F(view_from_local.pose.position.x,
603 view_from_local.pose.position.y,
604 view_from_local.pose.position.z);
605 } else {
606 *position = base::nullopt;
607 }
608
609 *emulated_position = true;
610 if (view_from_local.locationFlags & XR_SPACE_LOCATION_POSITION_TRACKED_BIT) {
611 *emulated_position = false;
612 }
613
614 return XR_SUCCESS;
615 }
616
GetHeadFromEyes(XrView * left,XrView * right) const617 void OpenXrApiWrapper::GetHeadFromEyes(XrView* left, XrView* right) const {
618 DCHECK(HasSession());
619
620 *left = head_from_eye_views_[0];
621 *right = head_from_eye_views_[1];
622 }
623
GetLuid(LUID * luid,const OpenXrExtensionHelper & extension_helper) const624 XrResult OpenXrApiWrapper::GetLuid(
625 LUID* luid,
626 const OpenXrExtensionHelper& extension_helper) const {
627 DCHECK(IsInitialized());
628
629 if (extension_helper.ExtensionMethods().xrGetD3D11GraphicsRequirementsKHR ==
630 nullptr)
631 return XR_ERROR_FUNCTION_UNSUPPORTED;
632
633 XrGraphicsRequirementsD3D11KHR graphics_requirements = {
634 XR_TYPE_GRAPHICS_REQUIREMENTS_D3D11_KHR};
635 RETURN_IF_XR_FAILED(
636 extension_helper.ExtensionMethods().xrGetD3D11GraphicsRequirementsKHR(
637 instance_, system_, &graphics_requirements));
638
639 luid->LowPart = graphics_requirements.adapterLuid.LowPart;
640 luid->HighPart = graphics_requirements.adapterLuid.HighPart;
641
642 return XR_SUCCESS;
643 }
644
EnsureEventPolling()645 void OpenXrApiWrapper::EnsureEventPolling() {
646 // Events are usually processed at the beginning of a frame. When frames
647 // aren't being requested, this timer loop ensures OpenXR events are
648 // occasionally polled while OpenXR is active.
649 if (IsInitialized()) {
650 if (base::TimeTicks::Now() - last_process_events_time_ >
651 kTimeBetweenPollingEvents) {
652 if (XR_FAILED(ProcessEvents())) {
653 DCHECK(!session_running_);
654 }
655 }
656
657 // Verify that OpenXR is still active after processing events.
658 if (IsInitialized()) {
659 base::ThreadTaskRunnerHandle::Get()->PostDelayedTask(
660 FROM_HERE,
661 base::BindOnce(&OpenXrApiWrapper::EnsureEventPolling,
662 weak_ptr_factory_.GetWeakPtr()),
663 kTimeBetweenPollingEvents);
664 }
665 }
666 }
667
ProcessEvents()668 XrResult OpenXrApiWrapper::ProcessEvents() {
669 XrEventDataBuffer event_data{XR_TYPE_EVENT_DATA_BUFFER};
670 XrResult xr_result = xrPollEvent(instance_, &event_data);
671
672 while (XR_SUCCEEDED(xr_result) && xr_result != XR_EVENT_UNAVAILABLE) {
673 if (event_data.type == XR_TYPE_EVENT_DATA_SESSION_STATE_CHANGED) {
674 XrEventDataSessionStateChanged* session_state_changed =
675 reinterpret_cast<XrEventDataSessionStateChanged*>(&event_data);
676 // We only have will only have one session and we should make sure the
677 // session that is having state_changed event is ours.
678 DCHECK(session_state_changed->session == session_);
679 switch (session_state_changed->state) {
680 case XR_SESSION_STATE_READY:
681 xr_result = BeginSession();
682 break;
683 case XR_SESSION_STATE_STOPPING:
684 session_running_ = false;
685 xr_result = xrEndSession(session_);
686 Uninitialize();
687 return xr_result;
688 case XR_SESSION_STATE_SYNCHRONIZED:
689 visibility_changed_callback_.Run(
690 device::mojom::XRVisibilityState::HIDDEN);
691 break;
692 case XR_SESSION_STATE_VISIBLE:
693 visibility_changed_callback_.Run(
694 device::mojom::XRVisibilityState::VISIBLE_BLURRED);
695 break;
696 case XR_SESSION_STATE_FOCUSED:
697 visibility_changed_callback_.Run(
698 device::mojom::XRVisibilityState::VISIBLE);
699 break;
700 default:
701 break;
702 }
703 } else if (event_data.type == XR_TYPE_EVENT_DATA_INSTANCE_LOSS_PENDING) {
704 DCHECK(session_ != XR_NULL_HANDLE);
705 Uninitialize();
706 return XR_ERROR_INSTANCE_LOST;
707 } else if (event_data.type ==
708 XR_TYPE_EVENT_DATA_REFERENCE_SPACE_CHANGE_PENDING) {
709 XrEventDataReferenceSpaceChangePending* reference_space_change_pending =
710 reinterpret_cast<XrEventDataReferenceSpaceChangePending*>(
711 &event_data);
712 DCHECK(reference_space_change_pending->session == session_);
713 // TODO(crbug.com/1015049)
714 // Currently WMR only throw reference space change event for stage.
715 // Other runtimes may decide to do it differently.
716 if (reference_space_change_pending->referenceSpaceType ==
717 XR_REFERENCE_SPACE_TYPE_STAGE) {
718 UpdateStageBounds();
719 }
720 } else if (event_data.type ==
721 XR_TYPE_EVENT_DATA_INTERACTION_PROFILE_CHANGED) {
722 XrEventDataInteractionProfileChanged* interaction_profile_changed =
723 reinterpret_cast<XrEventDataInteractionProfileChanged*>(&event_data);
724 DCHECK(interaction_profile_changed->session == session_);
725 interaction_profile_changed_callback_.Run(&xr_result);
726 }
727
728 if (XR_FAILED(xr_result)) {
729 Uninitialize();
730 return xr_result;
731 }
732
733 event_data.type = XR_TYPE_EVENT_DATA_BUFFER;
734 xr_result = xrPollEvent(instance_, &event_data);
735 }
736
737 last_process_events_time_ = base::TimeTicks::Now();
738
739 if (XR_FAILED(xr_result))
740 Uninitialize();
741 return xr_result;
742 }
743
GetViewSize() const744 gfx::Size OpenXrApiWrapper::GetViewSize() const {
745 DCHECK(IsInitialized());
746 CHECK(view_configs_.size() == kNumViews);
747
748 return gfx::Size(std::max(view_configs_[0].recommendedImageRectWidth,
749 view_configs_[1].recommendedImageRectWidth),
750 std::max(view_configs_[0].recommendedImageRectHeight,
751 view_configs_[1].recommendedImageRectHeight));
752 }
753
GetRecommendedSwapchainSampleCount() const754 uint32_t OpenXrApiWrapper::GetRecommendedSwapchainSampleCount() const {
755 DCHECK(IsInitialized());
756
757 auto start = view_configs_.begin();
758 auto end = view_configs_.end();
759
760 auto compareSwapchainCounts = [](const XrViewConfigurationView& i,
761 const XrViewConfigurationView& j) {
762 return i.recommendedSwapchainSampleCount <
763 j.recommendedSwapchainSampleCount;
764 };
765
766 return std::min_element(start, end, compareSwapchainCounts)
767 ->recommendedSwapchainSampleCount;
768 }
769
770 // From the OpenXR Spec:
771 // maxSwapchainSampleCount is the maximum number of sub-data element samples
772 // supported for swapchain images that will be rendered into for this view.
773 //
774 // To ease the workload on low end devices, we disable anti-aliasing when the
775 // max sample count is 1.
CanEnableAntiAliasing() const776 bool OpenXrApiWrapper::CanEnableAntiAliasing() const {
777 DCHECK(IsInitialized());
778
779 const auto compareMaxSwapchainSampleCounts =
780 [](const XrViewConfigurationView& i, const XrViewConfigurationView& j) {
781 return (i.maxSwapchainSampleCount < j.maxSwapchainSampleCount);
782 };
783
784 const auto it_min_element =
785 std::min_element(view_configs_.begin(), view_configs_.end(),
786 compareMaxSwapchainSampleCounts);
787 return (it_min_element->maxSwapchainSampleCount > 1);
788 }
789
790 // stage bounds is fixed unless we received event
791 // XrEventDataReferenceSpaceChangePending
UpdateStageBounds()792 XrResult OpenXrApiWrapper::UpdateStageBounds() {
793 DCHECK(HasSession());
794 XrResult xr_result = xrGetReferenceSpaceBoundsRect(
795 session_, XR_REFERENCE_SPACE_TYPE_STAGE, &stage_bounds_);
796 if (XR_FAILED(xr_result)) {
797 stage_bounds_.height = 0;
798 stage_bounds_.width = 0;
799 }
800
801 return xr_result;
802 }
803
GetStageParameters(XrExtent2Df * stage_bounds,gfx::Transform * local_from_stage)804 bool OpenXrApiWrapper::GetStageParameters(XrExtent2Df* stage_bounds,
805 gfx::Transform* local_from_stage) {
806 DCHECK(stage_bounds);
807 DCHECK(local_from_stage);
808 DCHECK(HasSession());
809
810 if (!HasSpace(XR_REFERENCE_SPACE_TYPE_LOCAL))
811 return false;
812
813 if (!HasSpace(XR_REFERENCE_SPACE_TYPE_STAGE))
814 return false;
815
816 *stage_bounds = stage_bounds_;
817
818 XrSpaceLocation local_from_stage_location = {XR_TYPE_SPACE_LOCATION};
819 if (FAILED(xrLocateSpace(stage_space_, local_space_,
820 frame_state_.predictedDisplayTime,
821 &local_from_stage_location)) ||
822 !(local_from_stage_location.locationFlags &
823 XR_SPACE_LOCATION_ORIENTATION_VALID_BIT) ||
824 !(local_from_stage_location.locationFlags &
825 XR_SPACE_LOCATION_POSITION_VALID_BIT)) {
826 return false;
827 }
828
829 // Convert the orientation and translation given by runtime into a
830 // transformation matrix.
831 gfx::DecomposedTransform local_from_stage_decomp;
832 local_from_stage_decomp.quaternion =
833 gfx::Quaternion(local_from_stage_location.pose.orientation.x,
834 local_from_stage_location.pose.orientation.y,
835 local_from_stage_location.pose.orientation.z,
836 local_from_stage_location.pose.orientation.w);
837 local_from_stage_decomp.translate[0] =
838 local_from_stage_location.pose.position.x;
839 local_from_stage_decomp.translate[1] =
840 local_from_stage_location.pose.position.y;
841 local_from_stage_decomp.translate[2] =
842 local_from_stage_location.pose.position.z;
843
844 *local_from_stage = gfx::ComposeTransform(local_from_stage_decomp);
845 return true;
846 }
847
RegisterInteractionProfileChangeCallback(const base::RepeatingCallback<void (XrResult *)> & interaction_profile_callback)848 void OpenXrApiWrapper::RegisterInteractionProfileChangeCallback(
849 const base::RepeatingCallback<void(XrResult*)>&
850 interaction_profile_callback) {
851 interaction_profile_changed_callback_ =
852 std::move(interaction_profile_callback);
853 }
854
RegisterVisibilityChangeCallback(const base::RepeatingCallback<void (mojom::XRVisibilityState)> & visibility_changed_callback)855 void OpenXrApiWrapper::RegisterVisibilityChangeCallback(
856 const base::RepeatingCallback<void(mojom::XRVisibilityState)>&
857 visibility_changed_callback) {
858 visibility_changed_callback_ = std::move(visibility_changed_callback);
859 }
860
861 VRTestHook* OpenXrApiWrapper::test_hook_ = nullptr;
862 ServiceTestHook* OpenXrApiWrapper::service_test_hook_ = nullptr;
SetTestHook(VRTestHook * hook)863 void OpenXrApiWrapper::SetTestHook(VRTestHook* hook) {
864 // This may be called from any thread - tests are responsible for
865 // maintaining thread safety, typically by not changing the test hook
866 // while presenting.
867 test_hook_ = hook;
868 if (service_test_hook_) {
869 service_test_hook_->SetTestHook(test_hook_);
870 }
871 }
872
873 } // namespace device
874