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