#include "device/vr/openxr/openxr_api_wrapper.h"
#include <stdint.h>
#include <algorithm>
#include <array>
#include <cmath>
#include <type_traits>
#include "base/check.h"
#include "base/containers/contains.h"
#include "base/debug/dump_without_crashing.h"
#include "base/feature_list.h"
#include "base/functional/callback_helpers.h"
#include "base/notreached.h"
#include "base/numerics/angle_conversions.h"
#include "base/task/single_thread_task_runner.h"
#include "base/trace_event/trace_event.h"
#include "base/trace_event/typed_macros.h"
#include "components/viz/common/gpu/context_provider.h"
#include "device/vr/openxr/openxr_extension_helper.h"
#include "device/vr/openxr/openxr_graphics_binding.h"
#include "device/vr/openxr/openxr_input_helper.h"
#include "device/vr/openxr/openxr_layers.h"
#include "device/vr/openxr/openxr_stage_bounds_provider.h"
#include "device/vr/openxr/openxr_util.h"
#include "device/vr/openxr/openxr_view_configuration.h"
#include "device/vr/public/cpp/features.h"
#include "device/vr/public/mojom/xr_session.mojom.h"
#include "device/vr/test/test_hook.h"
#include "gpu/GLES2/gl2extchromium.h"
#include "gpu/command_buffer/client/shared_image_interface.h"
#include "gpu/command_buffer/common/shared_image_usage.h"
#include "third_party/abseil-cpp/absl/container/flat_hash_map.h"
#include "third_party/blink/public/common/features_generated.h"
#include "third_party/openxr/src/include/openxr/openxr.h"
#include "ui/gfx/geometry/point3_f.h"
#include "ui/gfx/geometry/quaternion.h"
#include "ui/gfx/geometry/size.h"
#include "ui/gfx/geometry/transform.h"
#if BUILDFLAG(IS_WIN)
#include <dxgi1_2.h>
#endif
namespace device {
namespace {
constexpr base::TimeDelta kTimeBetweenPollingEvents = base::Seconds(1);
const char* GetXrSessionStateName(XrSessionState state) {
switch (state) {
case XR_SESSION_STATE_UNKNOWN:
return "Unknown";
case XR_SESSION_STATE_IDLE:
return "Idle";
case XR_SESSION_STATE_READY:
return "Ready";
case XR_SESSION_STATE_SYNCHRONIZED:
return "Synchronized";
case XR_SESSION_STATE_VISIBLE:
return "Visible";
case XR_SESSION_STATE_FOCUSED:
return "Focused";
case XR_SESSION_STATE_STOPPING:
return "Stopping";
case XR_SESSION_STATE_LOSS_PENDING:
return "Loss_Pending";
case XR_SESSION_STATE_EXITING:
return "Exiting";
case XR_SESSION_STATE_MAX_ENUM:
return "Max_Enum";
}
NOTREACHED();
}
constexpr float kDefaultHeightEstimate = -1.6;
}
std::unique_ptr<OpenXrApiWrapper> OpenXrApiWrapper::Create(
XrInstance instance,
OpenXrGraphicsBinding* graphics_binding) {
std::unique_ptr<OpenXrApiWrapper> openxr =
std::make_unique<OpenXrApiWrapper>();
if (!openxr->Initialize(instance, graphics_binding)) {
return nullptr;
}
return openxr;
}
XrResult OpenXrApiWrapper::GetSystem(XrInstance instance, XrSystemId* system) {
XrSystemGetInfo system_info = {XR_TYPE_SYSTEM_GET_INFO};
system_info.formFactor = XR_FORM_FACTOR_HEAD_MOUNTED_DISPLAY;
return xrGetSystem(instance, &system_info, system);
}
std::vector<XrEnvironmentBlendMode> OpenXrApiWrapper::GetSupportedBlendModes(
XrInstance instance,
XrSystemId system) {
uint32_t blend_mode_count;
const XrViewConfigurationType kSupportedViewConfiguration =
XR_VIEW_CONFIGURATION_TYPE_PRIMARY_STEREO;
if (XR_FAILED(xrEnumerateEnvironmentBlendModes(instance, system,
kSupportedViewConfiguration, 0,
&blend_mode_count, nullptr))) {
return {};
}
std::vector<XrEnvironmentBlendMode> environment_blend_modes(blend_mode_count);
if (XR_FAILED(xrEnumerateEnvironmentBlendModes(
instance, system, kSupportedViewConfiguration, blend_mode_count,
&blend_mode_count, environment_blend_modes.data()))) {
return {};
}
return environment_blend_modes;
}
bool OpenXrApiWrapper::NeedsSeparateActivity() {
return test_hook_ == nullptr;
}
OpenXrApiWrapper::OpenXrApiWrapper() = default;
OpenXrApiWrapper::~OpenXrApiWrapper() {
Uninitialize();
}
void OpenXrApiWrapper::Reset() {
SetXrSessionState(XR_SESSION_STATE_UNKNOWN);
depth_sensor_.reset();
light_estimator_.reset();
scene_understanding_manager_.reset();
unbounded_space_provider_.reset();
visibility_mask_handler_.reset();
unbounded_space_ = XR_NULL_HANDLE;
local_space_ = XR_NULL_HANDLE;
stage_space_ = XR_NULL_HANDLE;
view_space_ = XR_NULL_HANDLE;
session_ = XR_NULL_HANDLE;
blend_mode_ = XR_ENVIRONMENT_BLEND_MODE_MAX_ENUM;
stage_bounds_ = {};
bounds_provider_.reset();
system_ = XR_NULL_SYSTEM_ID;
instance_ = XR_NULL_HANDLE;
enabled_features_.clear();
graphics_binding_ = nullptr;
primary_view_config_ = OpenXrViewConfiguration();
secondary_view_configs_.clear();
frame_state_ = {};
input_helper_.reset();
session_options_.reset();
on_session_started_callback_.Reset();
on_session_ended_callback_.Reset();
visibility_changed_callback_.Reset();
}
bool OpenXrApiWrapper::Initialize(XrInstance instance,
OpenXrGraphicsBinding* graphics_binding) {
Reset();
if (!graphics_binding) {
return false;
}
graphics_binding_ = graphics_binding;
session_running_ = false;
pending_frame_ = false;
DCHECK(instance != XR_NULL_HANDLE);
instance_ = instance;
DCHECK(HasInstance());
if (XR_FAILED(InitializeSystem())) {
return false;
}
if (!graphics_binding_->Initialize(instance_, system_)) {
return false;
}
DCHECK(IsInitialized());
if (test_hook_) {
service_test_hook_ = reinterpret_cast<ServiceTestHook*>(instance_);
service_test_hook_->SetTestHook(test_hook_);
test_hook_->AttachCurrentThread();
}
return true;
}
bool OpenXrApiWrapper::IsInitialized() const {
return HasInstance() && HasSystem();
}
XrResult OpenXrApiWrapper::ShutdownSession() {
if (!HasSession()) {
return XR_SUCCESS;
}
XrResult result = xrEndSession(session_);
Uninitialize();
return result;
}
void OpenXrApiWrapper::Uninitialize() {
if (HasSession()) {
graphics_binding_->OnSessionDestroyed(
context_provider_ ? context_provider_->SharedImageInterface()
: nullptr);
xrDestroySession(session_);
}
if (test_hook_)
test_hook_->DetachCurrentThread();
if (on_session_ended_callback_) {
on_session_ended_callback_.Run(ExitXrPresentReason::kOpenXrUninitialize);
}
if (on_session_started_callback_) {
std::move(on_session_started_callback_)
.Run(std::move(session_options_), XR_ERROR_INITIALIZATION_FAILED);
}
Reset();
session_running_ = false;
pending_frame_ = false;
}
bool OpenXrApiWrapper::HasInstance() const {
return instance_ != XR_NULL_HANDLE;
}
bool OpenXrApiWrapper::HasSystem() const {
return system_ != XR_NULL_SYSTEM_ID && primary_view_config_.Initialized();
}
bool OpenXrApiWrapper::HasBlendMode() const {
return blend_mode_ != XR_ENVIRONMENT_BLEND_MODE_MAX_ENUM;
}
bool OpenXrApiWrapper::HasSession() const {
return session_ != XR_NULL_HANDLE;
}
bool OpenXrApiWrapper::HasSpace(XrReferenceSpaceType type) const {
if (unbounded_space_provider_ &&
unbounded_space_provider_->GetType() == type) {
return unbounded_space_ != XR_NULL_HANDLE;
}
switch (type) {
case XR_REFERENCE_SPACE_TYPE_LOCAL:
return local_space_ != XR_NULL_HANDLE;
case XR_REFERENCE_SPACE_TYPE_VIEW:
return view_space_ != XR_NULL_HANDLE;
case XR_REFERENCE_SPACE_TYPE_STAGE:
return stage_space_ != XR_NULL_HANDLE;
case XR_REFERENCE_SPACE_TYPE_LOCAL_FLOOR_EXT:
return local_floor_space_ != XR_NULL_HANDLE;
default:
NOTREACHED();
}
}
bool OpenXrApiWrapper::HasFrameState() const {
return frame_state_.type == XR_TYPE_FRAME_STATE;
}
bool OpenXrApiWrapper::IsFeatureEnabled(
device::mojom::XRSessionFeature feature) const {
return base::Contains(enabled_features_, feature);
}
XrResult OpenXrApiWrapper::InitializeViewConfig(
XrViewConfigurationType type,
OpenXrViewConfiguration& view_config) {
std::vector<XrViewConfigurationView> view_properties;
RETURN_IF_XR_FAILED(GetPropertiesForViewConfig(type, view_properties));
view_config.Initialize(type, std::move(view_properties),
graphics_binding_->GetMaxTextureSize());
return XR_SUCCESS;
}
XrResult OpenXrApiWrapper::GetPropertiesForViewConfig(
XrViewConfigurationType type,
std::vector<XrViewConfigurationView>& view_properties) const {
uint32_t view_count;
RETURN_IF_XR_FAILED(xrEnumerateViewConfigurationViews(
instance_, system_, type, 0, &view_count, nullptr));
view_properties.resize(view_count, {XR_TYPE_VIEW_CONFIGURATION_VIEW});
RETURN_IF_XR_FAILED(
xrEnumerateViewConfigurationViews(instance_, system_, type, view_count,
&view_count, view_properties.data()));
return XR_SUCCESS;
}
XrResult OpenXrApiWrapper::InitializeSystem() {
DCHECK(HasInstance());
DCHECK(!HasSystem());
RETURN_IF_XR_FAILED(GetSystem(instance_, &system_));
RETURN_IF_XR_FAILED(
InitializeViewConfig(kPrimaryViewConfiguration, primary_view_config_));
DCHECK_EQ(primary_view_config_.Properties().size(), kNumPrimaryViews);
primary_view_config_.SetActive(true);
uint32_t view_config_count;
RETURN_IF_XR_FAILED(xrEnumerateViewConfigurations(
instance_, system_, 0, &view_config_count, nullptr));
std::vector<XrViewConfigurationType> view_config_types(view_config_count);
RETURN_IF_XR_FAILED(xrEnumerateViewConfigurations(
instance_, system_, view_config_count, &view_config_count,
view_config_types.data()));
for (const auto& view_config_type : kSecondaryViewConfigurations) {
if (base::Contains(view_config_types, view_config_type)) {
OpenXrViewConfiguration view_config;
RETURN_IF_XR_FAILED(InitializeViewConfig(view_config_type, view_config));
secondary_view_configs_.emplace(view_config_type, std::move(view_config));
}
}
return XR_SUCCESS;
}
device::mojom::XREnvironmentBlendMode OpenXrApiWrapper::GetMojoBlendMode(
XrEnvironmentBlendMode xr_blend_mode) {
switch (xr_blend_mode) {
case XR_ENVIRONMENT_BLEND_MODE_OPAQUE:
return device::mojom::XREnvironmentBlendMode::kOpaque;
case XR_ENVIRONMENT_BLEND_MODE_ADDITIVE:
return device::mojom::XREnvironmentBlendMode::kAdditive;
case XR_ENVIRONMENT_BLEND_MODE_ALPHA_BLEND:
return device::mojom::XREnvironmentBlendMode::kAlphaBlend;
case XR_ENVIRONMENT_BLEND_MODE_MAX_ENUM:
NOTREACHED();
};
return device::mojom::XREnvironmentBlendMode::kOpaque;
}
device::mojom::XREnvironmentBlendMode
OpenXrApiWrapper::PickEnvironmentBlendModeForSession(
device::mojom::XRSessionMode session_mode) {
DCHECK(HasInstance());
std::vector<XrEnvironmentBlendMode> supported_blend_modes =
GetSupportedBlendModes(instance_, system_);
DCHECK(supported_blend_modes.size() > 0);
blend_mode_ = supported_blend_modes[0];
switch (session_mode) {
case device::mojom::XRSessionMode::kImmersiveVr:
if (base::Contains(supported_blend_modes,
XR_ENVIRONMENT_BLEND_MODE_OPAQUE))
blend_mode_ = XR_ENVIRONMENT_BLEND_MODE_OPAQUE;
break;
case device::mojom::XRSessionMode::kImmersiveAr:
if (base::Contains(supported_blend_modes,
XR_ENVIRONMENT_BLEND_MODE_ALPHA_BLEND)) {
blend_mode_ = XR_ENVIRONMENT_BLEND_MODE_ALPHA_BLEND;
} else if (base::Contains(supported_blend_modes,
XR_ENVIRONMENT_BLEND_MODE_ADDITIVE)) {
blend_mode_ = XR_ENVIRONMENT_BLEND_MODE_ADDITIVE;
}
break;
case device::mojom::XRSessionMode::kInline:
NOTREACHED();
}
return GetMojoBlendMode(blend_mode_);
}
OpenXrPlaneManager* OpenXrApiWrapper::GetPlaneManager() {
return scene_understanding_manager_ &&
IsFeatureEnabled(mojom::XRSessionFeature::PLANE_DETECTION)
? scene_understanding_manager_->GetPlaneManager()
: nullptr;
}
OpenXrAnchorManager* OpenXrApiWrapper::GetAnchorManager() {
return scene_understanding_manager_
? scene_understanding_manager_->GetAnchorManager()
: nullptr;
}
OpenXrHitTestManager* OpenXrApiWrapper::GetHitTestManager() {
return scene_understanding_manager_
? scene_understanding_manager_->GetHitTestManager()
: nullptr;
}
OpenXrLightEstimator* OpenXrApiWrapper::GetLightEstimator() {
return light_estimator_.get();
}
OpenXrDepthSensor* OpenXrApiWrapper::GetDepthSensor() {
return depth_sensor_.get();
}
XrResult OpenXrApiWrapper::EnableSupportedFeatures(
const OpenXrExtensionHelper& extension_helper) {
CHECK(session_options_);
enabled_features_.clear();
auto mode = session_options_->mode;
auto enable_function = [&extension_helper,
mode](device::mojom::XRSessionFeature feature) {
return IsFeatureSupportedForMode(feature, mode) &&
extension_helper.IsFeatureSupported(feature);
};
if (!std::ranges::all_of(session_options_->required_features,
enable_function)) {
DVLOG(1) << __func__ << ": Not all required features could be supported.";
for (const auto& feature : session_options_->required_features) {
if (!enable_function(feature)) {
DVLOG(1) << __func__ << ": " << feature << " Could not be supported";
}
}
return XR_ERROR_INITIALIZATION_FAILED;
}
std::unordered_set<mojom::XRSessionFeature> requested_features;
requested_features.insert(session_options_->required_features.begin(),
session_options_->required_features.end());
std::ranges::copy_if(
session_options_->optional_features,
std::inserter(requested_features, requested_features.begin()),
enable_function);
for (const auto& feature : requested_features) {
bool is_enabled = false;
bool is_required =
base::Contains(session_options_->required_features, feature);
switch (feature) {
case mojom::XRSessionFeature::REF_SPACE_LOCAL_FLOOR:
if (!HasSpace(XR_REFERENCE_SPACE_TYPE_LOCAL_FLOOR_EXT)) {
CreateSpace(XR_REFERENCE_SPACE_TYPE_LOCAL_FLOOR_EXT,
&local_floor_space_);
} else {
DLOG(ERROR) << __func__ << ": Already had local floor space";
}
is_enabled = HasSpace(XR_REFERENCE_SPACE_TYPE_LOCAL_FLOOR_EXT);
break;
case mojom::XRSessionFeature::REF_SPACE_BOUNDED_FLOOR:
if (!HasSpace(XR_REFERENCE_SPACE_TYPE_STAGE)) {
CreateSpace(XR_REFERENCE_SPACE_TYPE_STAGE, &stage_space_);
}
bounds_provider_ = extension_helper.CreateStageBoundsProvider(session_);
UpdateStageBounds();
is_enabled =
HasSpace(XR_REFERENCE_SPACE_TYPE_STAGE) && bounds_provider_;
break;
case mojom::XRSessionFeature::REF_SPACE_UNBOUNDED:
unbounded_space_provider_ =
extension_helper.CreateUnboundedSpaceProvider();
if (unbounded_space_provider_) {
if (XR_FAILED(unbounded_space_provider_->CreateSpace(
session_, &unbounded_space_))) {
unbounded_space_provider_ = nullptr;
}
}
is_enabled = unbounded_space_ != XR_NULL_HANDLE;
break;
case mojom::XRSessionFeature::PLANE_DETECTION:
if (scene_understanding_manager_ == nullptr) {
scene_understanding_manager_ =
extension_helper.CreateSceneUnderstandingManager(
this, local_space_, session_options_->required_features,
session_options_->optional_features);
}
is_enabled = scene_understanding_manager_ != nullptr &&
scene_understanding_manager_->GetPlaneManager() != nullptr;
break;
case mojom::XRSessionFeature::HIT_TEST:
if (scene_understanding_manager_ == nullptr) {
scene_understanding_manager_ =
extension_helper.CreateSceneUnderstandingManager(
this, local_space_, session_options_->required_features,
session_options_->optional_features);
}
is_enabled =
scene_understanding_manager_ != nullptr &&
scene_understanding_manager_->GetHitTestManager() != nullptr;
break;
case mojom::XRSessionFeature::LIGHT_ESTIMATION:
light_estimator_ =
extension_helper.CreateLightEstimator(session_, local_space_);
is_enabled = light_estimator_ != nullptr;
break;
case mojom::XRSessionFeature::ANCHORS:
if (scene_understanding_manager_ == nullptr) {
scene_understanding_manager_ =
extension_helper.CreateSceneUnderstandingManager(
this, local_space_, session_options_->required_features,
session_options_->optional_features);
}
is_enabled =
scene_understanding_manager_ != nullptr &&
scene_understanding_manager_->GetAnchorManager() != nullptr;
break;
case mojom::XRSessionFeature::DEPTH:
if (session_options_->depth_options) {
depth_sensor_ = extension_helper.CreateDepthSensor(
session_, local_space_, *session_options_->depth_options);
}
is_enabled = depth_sensor_ != nullptr;
break;
case mojom::XRSessionFeature::HAND_INPUT:
is_enabled = input_helper_ && input_helper_->IsHandTrackingEnabled();
break;
case mojom::XRSessionFeature::SECONDARY_VIEWS:
case mojom::XRSessionFeature::WEBGPU:
is_enabled = true;
break;
case mojom::XRSessionFeature::REF_SPACE_VIEWER:
case mojom::XRSessionFeature::REF_SPACE_LOCAL:
is_enabled = true;
break;
case mojom::XRSessionFeature::LAYERS:
is_enabled = graphics_binding_->SupportsLayers();
break;
case mojom::XRSessionFeature::FRONT_FACING:
case mojom::XRSessionFeature::IMAGE_TRACKING:
case mojom::XRSessionFeature::CAMERA_ACCESS:
case mojom::XRSessionFeature::DOM_OVERLAY:
DLOG(ERROR) << __func__
<< " Received request for unsupported feature: " << feature;
break;
}
DVLOG(1) << __func__ << " feature=" << feature
<< " is_enabled=" << is_enabled << " is_required=" << is_required;
if (is_enabled) {
enabled_features_.insert(feature);
} else if (is_required) {
return XR_ERROR_INITIALIZATION_FAILED;
}
}
return XR_SUCCESS;
}
bool OpenXrApiWrapper::UpdateAndGetSessionEnded() {
if (!IsInitialized()) {
return true;
}
if (XR_FAILED(ProcessEvents())) {
DCHECK(!session_running_);
}
return !IsInitialized();
}
XrResult OpenXrApiWrapper::InitSession(
mojom::XRRuntimeSessionOptionsPtr options,
const OpenXrExtensionHelper& extension_helper,
SessionStartedCallback on_session_started_callback,
SessionEndedCallback on_session_ended_callback,
VisibilityChangedCallback visibility_changed_callback) {
DCHECK(IsInitialized());
DCHECK(options);
extension_helper_ = &extension_helper;
session_options_ = std::move(options);
on_session_started_callback_ = std::move(on_session_started_callback);
on_session_ended_callback_ = std::move(on_session_ended_callback);
visibility_changed_callback_ = std::move(visibility_changed_callback);
XrSystemProperties system_properties = {XR_TYPE_SYSTEM_PROPERTIES};
RETURN_IF_XR_FAILED(
xrGetSystemProperties(instance_, system_, &system_properties));
DVLOG(1) << __func__ << " : Vendor Id: " << system_properties.vendorId
<< " : System Name: " << system_properties.systemName;
RETURN_IF_XR_FAILED(CreateSession());
RETURN_IF_XR_FAILED(
CreateSpace(XR_REFERENCE_SPACE_TYPE_LOCAL, &local_space_));
RETURN_IF_XR_FAILED(CreateSpace(XR_REFERENCE_SPACE_TYPE_VIEW, &view_space_));
const bool webgpu_session =
base::Contains(session_options_->required_features,
device::mojom::XRSessionFeature::WEBGPU) ||
base::Contains(session_options_->optional_features,
device::mojom::XRSessionFeature::WEBGPU);
graphics_binding_->OnSessionCreated(local_space_, webgpu_session);
bool swapchain_size_updated = RecomputeSwapchainSizeAndViewports();
DCHECK(swapchain_size_updated);
RETURN_IF_XR_FAILED(CreateSwapchain());
bool enable_hand_tracking =
base::Contains(session_options_->required_features,
device::mojom::XRSessionFeature::HAND_INPUT) ||
base::Contains(session_options_->optional_features,
device::mojom::XRSessionFeature::HAND_INPUT);
RETURN_IF_XR_FAILED(OpenXRInputHelper::CreateOpenXRInputHelper(
instance_, system_properties.systemName, extension_helper, session_,
local_space_, enable_hand_tracking, &input_helper_));
DCHECK(HasSession());
DCHECK(graphics_binding_->HasBaseLayerColorSwapchain());
DCHECK(HasSpace(XR_REFERENCE_SPACE_TYPE_LOCAL));
DCHECK(HasSpace(XR_REFERENCE_SPACE_TYPE_VIEW));
DCHECK(input_helper_);
XrResult result = EnableSupportedFeatures(extension_helper);
if (XR_FAILED(result)) {
std::move(on_session_started_callback_)
.Run(std::move(session_options_), result);
return result;
}
if (OpenXrVisibilityMaskHandler::IsSupported(
*extension_helper.ExtensionEnumeration()) &&
base::FeatureList::IsEnabled(blink::features::kWebXRVisibilityMask)) {
visibility_mask_handler_ = std::make_unique<OpenXrVisibilityMaskHandler>(
extension_helper, session_);
}
EnsureEventPolling();
return XR_SUCCESS;
}
XrResult OpenXrApiWrapper::CreateSession() {
DCHECK(!HasSession());
DCHECK(IsInitialized());
XrSessionCreateInfo session_create_info = {XR_TYPE_SESSION_CREATE_INFO};
session_create_info.next = graphics_binding_->GetSessionCreateInfo();
session_create_info.systemId = system_;
return xrCreateSession(instance_, &session_create_info, &session_);
}
XrResult OpenXrApiWrapper::CreateSwapchain() {
DCHECK(IsInitialized());
DCHECK(HasSession());
RETURN_IF_XR_FAILED(graphics_binding_->CreateBaseLayerSwapchain(
session_, GetRecommendedSwapchainSampleCount()));
CreateSharedMailboxes();
return XR_SUCCESS;
}
bool OpenXrApiWrapper::RecomputeSwapchainSizeAndViewports() {
uint32_t total_width = 0;
uint32_t total_height = 0;
for (const auto& view_properties : primary_view_config_.Properties()) {
total_width += view_properties.Width();
total_height = std::max(total_height, view_properties.Height());
}
primary_view_config_.SetViewport(0, 0, total_width, total_height);
if (IsFeatureEnabled(mojom::XRSessionFeature::SECONDARY_VIEWS)) {
for (auto& secondary_view_config : secondary_view_configs_) {
OpenXrViewConfiguration& view_config = secondary_view_config.second;
if (view_config.Active()) {
uint32_t view_width = 0;
uint32_t view_height = 0;
for (const auto& view_properties : view_config.Properties()) {
view_width += view_properties.Width();
view_height = std::max(view_height, view_properties.Height());
}
view_config.SetViewport(total_width, 0, view_width, view_height);
total_width += view_width;
total_height = std::max(total_height, view_height);
}
}
}
auto swapchain_image_size =
graphics_binding_->GetProjectionLayerSwapchainImageSize();
if (swapchain_image_size.width() != static_cast<int>(total_width) ||
swapchain_image_size.height() != static_cast<int>(total_height)) {
graphics_binding_->SetProjectionLayerSwapchainImageSize(
gfx::Size(total_width, total_height));
return true;
}
return false;
}
XrSpace OpenXrApiWrapper::GetReferenceSpace(
device::mojom::XRReferenceSpaceType type) const {
switch (type) {
case device::mojom::XRReferenceSpaceType::kLocal:
return local_space_;
case device::mojom::XRReferenceSpaceType::kViewer:
return view_space_;
case device::mojom::XRReferenceSpaceType::kBoundedFloor:
return stage_space_;
case device::mojom::XRReferenceSpaceType::kUnbounded:
return unbounded_space_;
case device::mojom::XRReferenceSpaceType::kLocalFloor:
return local_floor_space_;
}
NOTREACHED();
}
std::optional<XrLocation>
OpenXrApiWrapper::GetXrLocationFromNativeOriginInformation(
const mojom::XRNativeOriginInformation& native_origin,
const gfx::Transform& native_origin_from_object) {
switch (native_origin.which()) {
case mojom::XRNativeOriginInformation::Tag::kReferenceSpaceType:
return XrLocation{
GfxTransformToXrPose(native_origin_from_object),
GetReferenceSpace(native_origin.get_reference_space_type())};
case mojom::XRNativeOriginInformation::Tag::kAnchorId:
if (auto* anchor_manager = GetAnchorManager(); anchor_manager) {
return anchor_manager->GetXrLocationFromAnchor(
native_origin.get_anchor_id(), native_origin_from_object);
}
return std::nullopt;
case mojom::XRNativeOriginInformation::Tag::kPlaneId:
if (auto* plane_manager = GetPlaneManager(); plane_manager) {
return plane_manager->GetXrLocationFromPlane(
native_origin.get_plane_id(), native_origin_from_object);
}
return std::nullopt;
case mojom::XRNativeOriginInformation::Tag::kHandJointSpaceInfo:
if (session_state_ != XR_SESSION_STATE_FOCUSED) {
return std::nullopt;
}
return input_helper_->GetXrLocationFromHandJoint(
local_space_, *native_origin.get_hand_joint_space_info(),
native_origin_from_object);
case mojom::XRNativeOriginInformation::Tag::kInputSourceSpaceInfo:
if (session_state_ != XR_SESSION_STATE_FOCUSED) {
return std::nullopt;
}
return input_helper_->GetXrLocationFromInputSource(
*native_origin.get_input_source_space_info(),
native_origin_from_object);
case mojom::XRNativeOriginInformation::Tag::kImageIndex:
NOTREACHED();
}
}
bool OpenXrApiWrapper::ShouldCreateSharedImages() const {
if (!HasSession()) {
return false;
}
#if BUILDFLAG(IS_WIN)
if (!graphics_binding_->IsWebGPUSession()) {
if (CanEnableAntiAliasing()) {
return false;
}
XrViewConfigurationProperties view_configuration_props = {
XR_TYPE_VIEW_CONFIGURATION_PROPERTIES};
if (XR_FAILED(xrGetViewConfigurationProperties(
instance_, system_, primary_view_config_.Type(),
&view_configuration_props)) ||
(view_configuration_props.fovMutable == XR_FALSE)) {
return false;
}
}
#endif
return graphics_binding_->CanUseSharedImages();
}
void OpenXrApiWrapper::OnContextProviderCreated(
scoped_refptr<viz::ContextProvider> context_provider) {
context_provider_ = std::move(context_provider);
CreateSharedMailboxes();
}
void OpenXrApiWrapper::OnContextProviderLost() {
graphics_binding_->OnContextProviderLost();
context_provider_ = nullptr;
}
void OpenXrApiWrapper::CreateSharedMailboxes() {
if (!context_provider_ || !ShouldCreateSharedImages()) {
return;
}
gpu::SharedImageInterface* shared_image_interface =
context_provider_->SharedImageInterface();
graphics_binding_->CreateBaseLayerSharedImages(shared_image_interface);
}
XrResult OpenXrApiWrapper::CreateSpace(XrReferenceSpaceType type,
XrSpace* space) {
DCHECK(HasSession());
DCHECK(!HasSpace(type));
XrReferenceSpaceCreateInfo space_create_info = {
XR_TYPE_REFERENCE_SPACE_CREATE_INFO};
space_create_info.referenceSpaceType = type;
space_create_info.poseInReferenceSpace = PoseIdentity();
XrResult result = xrCreateReferenceSpace(session_, &space_create_info, space);
if (XR_FAILED(result) && type == XR_REFERENCE_SPACE_TYPE_LOCAL_FLOOR_EXT) {
if (!HasSpace(XR_REFERENCE_SPACE_TYPE_LOCAL)) {
return result;
}
result = CreateEmulatedLocalFloorSpace(space);
}
return result;
}
XrResult OpenXrApiWrapper::CreateEmulatedLocalFloorSpace(XrSpace* space) {
CHECK(HasSession());
CHECK(HasSpace(XR_REFERENCE_SPACE_TYPE_LOCAL));
if (!HasSpace(XR_REFERENCE_SPACE_TYPE_STAGE)) {
CreateSpace(XR_REFERENCE_SPACE_TYPE_STAGE, &stage_space_);
}
std::optional<gfx::Transform> maybe_local_from_stage = GetLocalFromStage();
if (!maybe_local_from_stage) {
DVLOG(3) << __func__ << ": GetLocalFromStage failed, estimating height";
maybe_local_from_stage =
gfx::Transform::MakeTranslation(0.0, kDefaultHeightEstimate);
try_recreate_local_floor_ = HasSpace(XR_REFERENCE_SPACE_TYPE_STAGE);
}
XrReferenceSpaceCreateInfo space_create_info = {
XR_TYPE_REFERENCE_SPACE_CREATE_INFO};
space_create_info.referenceSpaceType = XR_REFERENCE_SPACE_TYPE_LOCAL;
space_create_info.poseInReferenceSpace =
GfxTransformToXrPose(*maybe_local_from_stage);
RETURN_IF_XR_FAILED(
xrCreateReferenceSpace(session_, &space_create_info, space));
emulated_local_floor_ = true;
return XR_SUCCESS;
}
XrResult OpenXrApiWrapper::UpdateLocalFloorSpace() {
DVLOG(3) << __func__;
if (!emulated_local_floor_ && !try_recreate_local_floor_) {
return XR_SUCCESS;
}
if (HasSpace(XR_REFERENCE_SPACE_TYPE_LOCAL_FLOOR_EXT)) {
RETURN_IF_XR_FAILED(xrDestroySpace(local_floor_space_));
local_floor_space_ = XR_NULL_HANDLE;
emulated_local_floor_ = false;
}
try_recreate_local_floor_ = false;
return CreateSpace(XR_REFERENCE_SPACE_TYPE_LOCAL_FLOOR_EXT,
&local_floor_space_);
}
XrResult OpenXrApiWrapper::BeginSession() {
DCHECK(HasSession());
DCHECK(on_session_started_callback_);
XrSessionBeginInfo session_begin_info = {XR_TYPE_SESSION_BEGIN_INFO};
session_begin_info.primaryViewConfigurationType = primary_view_config_.Type();
XrSecondaryViewConfigurationSessionBeginInfoMSFT secondary_view_config_info =
{XR_TYPE_SECONDARY_VIEW_CONFIGURATION_SESSION_BEGIN_INFO_MSFT};
std::vector<XrViewConfigurationType> secondary_view_config_types;
if (IsFeatureEnabled(mojom::XRSessionFeature::SECONDARY_VIEWS)) {
secondary_view_config_types.reserve(secondary_view_configs_.size());
for (const auto& secondary_view_config : secondary_view_configs_) {
secondary_view_config_types.emplace_back(secondary_view_config.first);
}
secondary_view_config_info.viewConfigurationCount =
secondary_view_config_types.size();
secondary_view_config_info.enabledViewConfigurationTypes =
secondary_view_config_types.data();
session_begin_info.next = &secondary_view_config_info;
}
XrResult xr_result = xrBeginSession(session_, &session_begin_info);
if (XR_SUCCEEDED(xr_result))
session_running_ = true;
std::move(on_session_started_callback_)
.Run(std::move(session_options_), xr_result);
return xr_result;
}
XrResult OpenXrApiWrapper::BeginFrame() {
TRACE_EVENT0("xr", "BeginFrame");
DCHECK(HasSession());
DCHECK(graphics_binding_->HasBaseLayerColorSwapchain());
if (!session_running_)
return XR_ERROR_SESSION_NOT_RUNNING;
XrFrameWaitInfo wait_frame_info = {XR_TYPE_FRAME_WAIT_INFO};
XrFrameState frame_state = {XR_TYPE_FRAME_STATE};
XrSecondaryViewConfigurationFrameStateMSFT secondary_view_frame_states = {
XR_TYPE_SECONDARY_VIEW_CONFIGURATION_FRAME_STATE_MSFT};
std::vector<XrSecondaryViewConfigurationStateMSFT>
secondary_view_config_states;
if (IsFeatureEnabled(mojom::XRSessionFeature::SECONDARY_VIEWS)) {
secondary_view_config_states.resize(
secondary_view_configs_.size(),
{XR_TYPE_SECONDARY_VIEW_CONFIGURATION_STATE_MSFT});
secondary_view_frame_states.viewConfigurationCount =
secondary_view_config_states.size();
secondary_view_frame_states.viewConfigurationStates =
secondary_view_config_states.data();
frame_state.next = &secondary_view_frame_states;
}
TRACE_EVENT_BEGIN0("xr", "xrWaitFrame");
RETURN_IF_XR_FAILED(xrWaitFrame(session_, &wait_frame_info, &frame_state));
TRACE_EVENT_END0("xr", "xrWaitFrame");
frame_state_ = frame_state;
if (try_recreate_local_floor_) {
UpdateLocalFloorSpace();
}
if (IsFeatureEnabled(mojom::XRSessionFeature::SECONDARY_VIEWS)) {
RETURN_IF_XR_FAILED(
UpdateSecondaryViewConfigStates(secondary_view_config_states));
}
XrFrameBeginInfo begin_frame_info = {XR_TYPE_FRAME_BEGIN_INFO};
RETURN_IF_XR_FAILED(xrBeginFrame(session_, &begin_frame_info));
pending_frame_ = true;
RETURN_IF_XR_FAILED(graphics_binding_->ActivateSwapchainImages(
context_provider_->SharedImageInterface()));
RETURN_IF_XR_FAILED(UpdateViewConfigurations());
return XR_SUCCESS;
}
XrResult OpenXrApiWrapper::UpdateViewConfigurations() {
DCHECK(primary_view_config_.Active());
DCHECK(!primary_view_config_.Viewport().IsEmpty());
RETURN_IF_XR_FAILED(
LocateViews(XR_REFERENCE_SPACE_TYPE_LOCAL, primary_view_config_));
if (IsFeatureEnabled(mojom::XRSessionFeature::SECONDARY_VIEWS)) {
for (auto& view_config : secondary_view_configs_) {
OpenXrViewConfiguration& config = view_config.second;
if (config.Active()) {
RETURN_IF_XR_FAILED(LocateViews(XR_REFERENCE_SPACE_TYPE_LOCAL, config));
}
}
}
return XR_SUCCESS;
}
XrResult OpenXrApiWrapper::UpdateSecondaryViewConfigStates(
const std::vector<XrSecondaryViewConfigurationStateMSFT>& states) {
DCHECK(IsFeatureEnabled(mojom::XRSessionFeature::SECONDARY_VIEWS));
bool state_changed = false;
for (const XrSecondaryViewConfigurationStateMSFT& state : states) {
DCHECK(
base::Contains(secondary_view_configs_, state.viewConfigurationType));
OpenXrViewConfiguration& view_config =
secondary_view_configs_.at(state.viewConfigurationType);
if (view_config.Active() != state.active) {
state_changed = true;
view_config.SetActive(state.active);
if (view_config.Active()) {
std::vector<XrViewConfigurationView> view_properties;
RETURN_IF_XR_FAILED(GetPropertiesForViewConfig(
state.viewConfigurationType, view_properties));
view_config.SetProperties(std::move(view_properties),
graphics_binding_->GetMaxTextureSize());
}
}
}
if (state_changed && RecomputeSwapchainSizeAndViewports()) {
graphics_binding_->DestroyBaseLayerSwapchain(
context_provider_ ? context_provider_->SharedImageInterface()
: nullptr);
RETURN_IF_XR_FAILED(CreateSwapchain());
}
return XR_SUCCESS;
}
XrResult OpenXrApiWrapper::EndFrame() {
DCHECK(pending_frame_);
DCHECK(HasBlendMode());
DCHECK(HasSession());
DCHECK(graphics_binding_->HasBaseLayerColorSwapchain());
DCHECK(HasSpace(XR_REFERENCE_SPACE_TYPE_LOCAL));
DCHECK(HasFrameState());
std::unique_ptr<OpenXrLayers> layers =
graphics_binding_->GetLayersForViewConfig(this, primary_view_config_);
if (IsFeatureEnabled(mojom::XRSessionFeature::SECONDARY_VIEWS)) {
for (const auto& secondary_view_config : secondary_view_configs_) {
const OpenXrViewConfiguration& view_config = secondary_view_config.second;
if (view_config.Active()) {
layers->AddSecondaryLayerForType(
local_space_, view_config.Type(), blend_mode_,
graphics_binding_->GetBaseLayerProjectionViews(view_config),
graphics_binding_->GetFlipLayerLayout());
}
}
}
XrFrameEndInfo end_frame_info = {XR_TYPE_FRAME_END_INFO};
end_frame_info.layerCount = layers->PrimaryLayerCount();
end_frame_info.layers = layers->PrimaryLayerData();
end_frame_info.displayTime = frame_state_.predictedDisplayTime;
end_frame_info.environmentBlendMode = blend_mode_;
XrSecondaryViewConfigurationFrameEndInfoMSFT secondary_view_end_frame_info = {
XR_TYPE_SECONDARY_VIEW_CONFIGURATION_FRAME_END_INFO_MSFT};
if (layers->SecondaryConfigCount() > 0) {
secondary_view_end_frame_info.viewConfigurationCount =
layers->SecondaryConfigCount();
secondary_view_end_frame_info.viewConfigurationLayersInfo =
layers->SecondaryConfigData();
end_frame_info.next = &secondary_view_end_frame_info;
}
RETURN_IF_XR_FAILED(graphics_binding_->ReleaseActiveSwapchainImages());
TRACE_EVENT_BEGIN0("xr", "xrEndFrame");
RETURN_IF_XR_FAILED(xrEndFrame(session_, &end_frame_info));
TRACE_EVENT_END0("xr", "xrEndFrame");
pending_frame_ = false;
return XR_SUCCESS;
}
bool OpenXrApiWrapper::HasPendingFrame() const {
return pending_frame_;
}
XrResult OpenXrApiWrapper::LocateViews(XrReferenceSpaceType space_type,
OpenXrViewConfiguration& view_config) {
DCHECK(HasSession());
XrViewState view_state = {XR_TYPE_VIEW_STATE};
XrViewLocateInfo view_locate_info = {XR_TYPE_VIEW_LOCATE_INFO};
view_locate_info.viewConfigurationType = view_config.Type();
view_locate_info.displayTime = frame_state_.predictedDisplayTime;
switch (space_type) {
case XR_REFERENCE_SPACE_TYPE_LOCAL:
view_locate_info.space = local_space_;
break;
case XR_REFERENCE_SPACE_TYPE_VIEW:
view_locate_info.space = view_space_;
break;
case XR_REFERENCE_SPACE_TYPE_STAGE:
case XR_REFERENCE_SPACE_TYPE_UNBOUNDED_MSFT:
case XR_REFERENCE_SPACE_TYPE_COMBINED_EYE_VARJO:
case XR_REFERENCE_SPACE_TYPE_MAX_ENUM:
case XR_REFERENCE_SPACE_TYPE_LOCAL_FLOOR_EXT:
case XR_REFERENCE_SPACE_TYPE_LOCALIZATION_MAP_ML:
NOTREACHED();
}
std::vector<XrView> new_views(view_config.Views().size(), {XR_TYPE_VIEW});
uint32_t view_count;
RETURN_IF_XR_FAILED(xrLocateViews(session_, &view_locate_info, &view_state,
new_views.size(), &view_count,
new_views.data()));
DCHECK_EQ(view_count, view_config.Views().size());
if ((view_state.viewStateFlags & XR_VIEW_STATE_POSITION_VALID_BIT) &&
(view_state.viewStateFlags & XR_VIEW_STATE_ORIENTATION_VALID_BIT)) {
view_config.SetViews(std::move(new_views));
received_initial_valid_primary_views_ = true;
} else {
DVLOG(3) << __func__ << " Could not locate views";
constexpr uint64_t kMaxInvalidViewFrames = 288;
if (!received_initial_valid_primary_views_ &&
frames_before_initial_valid_primary_views_++ >= kMaxInvalidViewFrames) {
LOG(ERROR) << "No valid views have been received in "
<< kMaxInvalidViewFrames << " frames";
ShutdownSession();
return XR_ERROR_RUNTIME_FAILURE;
}
}
return XR_SUCCESS;
}
XrTime OpenXrApiWrapper::GetPredictedDisplayTime() const {
DCHECK(IsInitialized());
DCHECK(HasFrameState());
return frame_state_.predictedDisplayTime;
}
mojom::XRViewPtr OpenXrApiWrapper::CreateView(
const OpenXrViewConfiguration& view_config,
uint32_t view_index,
mojom::XREye eye,
uint32_t x_offset) const {
const XrView& xr_view = view_config.Views()[view_index];
mojom::XRViewPtr view = mojom::XRView::New();
view->eye = eye;
view->geometry = mojom::XRViewGeometry::New();
view->geometry->mojo_from_view = XrPoseToGfxTransform(xr_view.pose);
view->geometry->field_of_view = XrFovToMojomFov(xr_view.fov);
view->viewport =
gfx::Rect(x_offset, 0, view_config.Properties()[view_index].Width(),
view_config.Properties()[view_index].Height());
view->is_first_person_observer =
view_config.Type() ==
XR_VIEW_CONFIGURATION_TYPE_SECONDARY_MONO_FIRST_PERSON_OBSERVER_MSFT;
if (visibility_mask_handler_) {
visibility_mask_handler_->UpdateVisibilityMaskData(view_config.Type(),
view_index, view);
}
return view;
}
const std::unordered_set<mojom::XRSessionFeature>&
OpenXrApiWrapper::GetEnabledFeatures() const {
return enabled_features_;
}
std::vector<mojom::XRViewPtr> OpenXrApiWrapper::GetViews() const {
uint32_t x_offset = primary_view_config_.Viewport().x();
std::vector<mojom::XRViewPtr> views;
for (size_t i = 0; i < primary_view_config_.Views().size(); i++) {
views.emplace_back(
CreateView(primary_view_config_, i, GetEyeFromIndex(i), x_offset));
x_offset += primary_view_config_.Properties()[i].Width();
}
if (IsFeatureEnabled(mojom::XRSessionFeature::SECONDARY_VIEWS)) {
for (const auto& secondary_view_config : secondary_view_configs_) {
const OpenXrViewConfiguration& view_config = secondary_view_config.second;
if (view_config.Active()) {
x_offset = view_config.Viewport().x();
for (size_t i = 0; i < view_config.Views().size(); i++) {
views.emplace_back(
CreateView(view_config, i, mojom::XREye::kNone, x_offset));
x_offset += view_config.Properties()[i].Width();
}
}
}
}
return views;
}
std::vector<mojom::XRViewPtr> OpenXrApiWrapper::GetDefaultViews() const {
DCHECK(IsInitialized());
const std::vector<OpenXrViewProperties>& view_properties =
primary_view_config_.Properties();
CHECK_EQ(view_properties.size(), kNumPrimaryViews);
std::vector<mojom::XRViewPtr> views(view_properties.size());
uint32_t x_offset = 0;
for (uint32_t i = 0; i < views.size(); i++) {
views[i] = mojom::XRView::New();
mojom::XRView* view = views[i].get();
view->eye = GetEyeFromIndex(i);
view->viewport = gfx::Rect(x_offset, 0, view_properties[i].Width(),
view_properties[i].Height());
view->geometry = mojom::XRViewGeometry::New();
view->geometry->field_of_view =
mojom::VRFieldOfView::New(45.0f, 45.0f, 45.0f, 45.0f);
x_offset += view_properties[i].Width();
}
return views;
}
float OpenXrApiWrapper::RecommendedViewportScale() const {
float recommended_scale = 1.0f;
for (const auto& property : primary_view_config_.Properties()) {
recommended_scale =
std::min(recommended_scale, property.RecommendedViewportScale());
}
return recommended_scale;
}
mojom::VRPosePtr OpenXrApiWrapper::GetViewerPose() const {
TRACE_EVENT0("xr", "GetViewerPose");
XrSpaceLocation local_from_viewer = {XR_TYPE_SPACE_LOCATION};
if (XR_FAILED(xrLocateSpace(view_space_, local_space_,
frame_state_.predictedDisplayTime,
&local_from_viewer))) {
return nullptr;
}
const auto& pose_state = local_from_viewer.locationFlags;
const bool orientation_valid =
pose_state & XR_SPACE_LOCATION_ORIENTATION_VALID_BIT;
const bool orientation_tracked =
pose_state & XR_SPACE_LOCATION_ORIENTATION_TRACKED_BIT;
const bool position_valid = pose_state & XR_SPACE_LOCATION_POSITION_VALID_BIT;
const bool position_tracked =
pose_state & XR_SPACE_LOCATION_POSITION_TRACKED_BIT;
const bool can_send_orientation = orientation_valid && orientation_tracked;
const bool can_send_position = position_valid;
if (!can_send_orientation && !can_send_position) {
return nullptr;
}
mojom::VRPosePtr pose = mojom::VRPose::New();
if (can_send_orientation) {
pose->orientation = gfx::Quaternion(local_from_viewer.pose.orientation.x,
local_from_viewer.pose.orientation.y,
local_from_viewer.pose.orientation.z,
local_from_viewer.pose.orientation.w);
}
if (can_send_position) {
pose->position = gfx::Point3F(local_from_viewer.pose.position.x,
local_from_viewer.pose.position.y,
local_from_viewer.pose.position.z);
}
pose->emulated_position = !position_tracked;
return pose;
}
std::vector<mojom::XRInputSourceStatePtr> OpenXrApiWrapper::GetInputState() {
return input_helper_->GetInputState(GetPredictedDisplayTime());
}
void OpenXrApiWrapper::OnHideInputSources() {
input_helper_->OnHideInputSources();
}
void OpenXrApiWrapper::PollFuture(
XrFutureEXT future,
base::OnceCallback<void(XrFutureEXT)> on_ready_callback) {
pending_futures_.emplace(future, std::move(on_ready_callback));
}
void OpenXrApiWrapper::ProcessPendingFutures() {
if (pending_futures_.empty()) {
return;
}
auto it = pending_futures_.begin();
while (it != pending_futures_.end()) {
XrFuturePollInfoEXT poll_info = {XR_TYPE_FUTURE_POLL_INFO_EXT};
poll_info.future = it->first;
XrFuturePollResultEXT poll_result = {XR_TYPE_FUTURE_POLL_RESULT_EXT};
XrResult result = extension_helper_->ExtensionMethods().xrPollFutureEXT(
instance_, &poll_info, &poll_result);
if (result == XR_SUCCESS &&
poll_result.state == XR_FUTURE_STATE_READY_EXT) {
std::move(it->second).Run(it->first);
pending_futures_.erase(it++);
} else if (XR_FAILED(result)) {
std::move(it->second).Run(XR_NULL_FUTURE_EXT);
pending_futures_.erase(it++);
} else {
it++;
}
}
}
void OpenXrApiWrapper::EnsureEventPolling() {
if (IsInitialized()) {
if (XR_FAILED(ProcessEvents())) {
DCHECK(!session_running_);
}
if (IsInitialized()) {
base::SingleThreadTaskRunner::GetCurrentDefault()->PostDelayedTask(
FROM_HERE,
base::BindOnce(&OpenXrApiWrapper::EnsureEventPolling,
weak_ptr_factory_.GetWeakPtr()),
kTimeBetweenPollingEvents);
}
}
}
XrResult OpenXrApiWrapper::ProcessEvents() {
if (!HasInstance()) {
return XR_ERROR_INSTANCE_LOST;
}
ProcessPendingFutures();
if (input_helper_ && input_helper_->ReceivedExitGesture()) {
return ShutdownSession();
}
XrEventDataBuffer event_data{XR_TYPE_EVENT_DATA_BUFFER};
XrResult xr_result = xrPollEvent(instance_, &event_data);
while (XR_SUCCEEDED(xr_result) && xr_result != XR_EVENT_UNAVAILABLE) {
if (event_data.type == XR_TYPE_EVENT_DATA_SESSION_STATE_CHANGED) {
XrEventDataSessionStateChanged* session_state_changed =
reinterpret_cast<XrEventDataSessionStateChanged*>(&event_data);
DCHECK(session_state_changed->session == session_);
SetXrSessionState(session_state_changed->state);
switch (session_state_changed->state) {
case XR_SESSION_STATE_READY:
xr_result = BeginSession();
break;
case XR_SESSION_STATE_STOPPING:
return ShutdownSession();
case XR_SESSION_STATE_SYNCHRONIZED:
visibility_changed_callback_.Run(
device::mojom::XRVisibilityState::HIDDEN);
break;
case XR_SESSION_STATE_VISIBLE:
visibility_changed_callback_.Run(
device::mojom::XRVisibilityState::VISIBLE_BLURRED);
break;
case XR_SESSION_STATE_FOCUSED:
visibility_changed_callback_.Run(
device::mojom::XRVisibilityState::VISIBLE);
break;
case XR_SESSION_STATE_EXITING:
Uninitialize();
return xr_result;
default:
break;
}
} else if (event_data.type == XR_TYPE_EVENT_DATA_INSTANCE_LOSS_PENDING) {
DCHECK(session_ != XR_NULL_HANDLE);
LOG(ERROR) << "Received Instance Loss Event";
TRACE_EVENT_INSTANT0("xr", "InstanceLossPendingEvent",
TRACE_EVENT_SCOPE_THREAD);
Uninitialize();
return XR_ERROR_INSTANCE_LOST;
} else if (event_data.type ==
XR_TYPE_EVENT_DATA_REFERENCE_SPACE_CHANGE_PENDING) {
XrEventDataReferenceSpaceChangePending* reference_space_change_pending =
reinterpret_cast<XrEventDataReferenceSpaceChangePending*>(
&event_data);
DCHECK(reference_space_change_pending->session == session_);
if (reference_space_change_pending->referenceSpaceType ==
XR_REFERENCE_SPACE_TYPE_STAGE) {
UpdateStageBounds();
UpdateLocalFloorSpace();
} else if (reference_space_change_pending->referenceSpaceType ==
XR_REFERENCE_SPACE_TYPE_LOCAL ||
reference_space_change_pending->referenceSpaceType ==
XR_REFERENCE_SPACE_TYPE_LOCAL_FLOOR_EXT) {
UpdateLocalFloorSpace();
} else if (unbounded_space_provider_ &&
reference_space_change_pending->referenceSpaceType ==
unbounded_space_provider_->GetType()) {
}
} else if (event_data.type ==
XR_TYPE_EVENT_DATA_INTERACTION_PROFILE_CHANGED) {
XrEventDataInteractionProfileChanged* interaction_profile_changed =
reinterpret_cast<XrEventDataInteractionProfileChanged*>(&event_data);
DCHECK_EQ(interaction_profile_changed->session, session_);
xr_result = input_helper_->OnInteractionProfileChanged();
} else if (event_data.type ==
XR_TYPE_EVENT_DATA_SPATIAL_DISCOVERY_RECOMMENDED_EXT) {
if (scene_understanding_manager_) {
scene_understanding_manager_->OnDiscoveryRecommended(
reinterpret_cast<const XrEventDataSpatialDiscoveryRecommendedEXT*>(
&event_data));
}
} else if (event_data.type ==
XR_TYPE_EVENT_DATA_VISIBILITY_MASK_CHANGED_KHR) {
auto* mask_changed_event =
reinterpret_cast<XrEventDataVisibilityMaskChangedKHR*>(&event_data);
if (mask_changed_event->session != session_) {
continue;
}
if (visibility_mask_handler_) {
visibility_mask_handler_->OnVisibilityMaskChanged(*mask_changed_event);
}
} else {
DVLOG(1) << __func__ << " Unhandled event type: " << event_data.type;
TRACE_EVENT_INSTANT1("xr", "UnandledXrEvent", TRACE_EVENT_SCOPE_THREAD,
"type", event_data.type);
}
if (XR_FAILED(xr_result)) {
TRACE_EVENT_INSTANT2("xr", "EventProcessingFailed",
TRACE_EVENT_SCOPE_THREAD, "type", event_data.type,
"xr_result", xr_result);
Uninitialize();
return xr_result;
}
event_data.type = XR_TYPE_EVENT_DATA_BUFFER;
xr_result = xrPollEvent(instance_, &event_data);
}
if (XR_FAILED(xr_result)) {
TRACE_EVENT_INSTANT1("xr", "EventPollingFailed", TRACE_EVENT_SCOPE_THREAD,
"xr_result", xr_result);
Uninitialize();
}
return xr_result;
}
uint32_t OpenXrApiWrapper::GetRecommendedSwapchainSampleCount() const {
DCHECK(IsInitialized());
return std::ranges::min_element(
primary_view_config_.Properties(), {},
[](const OpenXrViewProperties& view) {
return view.RecommendedSwapchainSampleCount();
})
->RecommendedSwapchainSampleCount();
}
bool OpenXrApiWrapper::CanEnableAntiAliasing() const {
return primary_view_config_.CanEnableAntiAliasing();
}
void OpenXrApiWrapper::UpdateStageBounds() {
DCHECK(HasSession());
if (bounds_provider_) {
stage_bounds_ = bounds_provider_->GetStageBounds();
}
}
bool OpenXrApiWrapper::GetStageParameters(
std::vector<gfx::Point3F>& stage_bounds,
gfx::Transform& local_from_stage) {
DCHECK(HasSession());
if (!IsFeatureEnabled(mojom::XRSessionFeature::REF_SPACE_BOUNDED_FLOOR)) {
return false;
}
if (!HasSpace(XR_REFERENCE_SPACE_TYPE_LOCAL) ||
!HasSpace(XR_REFERENCE_SPACE_TYPE_STAGE)) {
return false;
}
std::optional<gfx::Transform> maybe_local_from_stage = GetLocalFromStage();
if (!maybe_local_from_stage) {
return false;
}
stage_bounds = stage_bounds_;
local_from_stage = maybe_local_from_stage.value();
return true;
}
std::optional<gfx::Transform> OpenXrApiWrapper::GetLocalFromFloor() {
if (!IsFeatureEnabled(
device::mojom::XRSessionFeature::REF_SPACE_LOCAL_FLOOR)) {
return std::nullopt;
}
if (!HasSpace(XR_REFERENCE_SPACE_TYPE_LOCAL)) {
return std::nullopt;
}
if (!HasSpace(XR_REFERENCE_SPACE_TYPE_LOCAL_FLOOR_EXT)) {
return std::nullopt;
}
return GetBaseSpaceFromSpace(mojom::XRReferenceSpaceType::kLocal,
mojom::XRReferenceSpaceType::kLocalFloor);
}
std::optional<gfx::Transform> OpenXrApiWrapper::GetLocalFromStage() {
if (!HasSpace(XR_REFERENCE_SPACE_TYPE_LOCAL)) {
return std::nullopt;
}
if (!HasSpace(XR_REFERENCE_SPACE_TYPE_STAGE)) {
return std::nullopt;
}
return GetBaseSpaceFromSpace(mojom::XRReferenceSpaceType::kLocal,
mojom::XRReferenceSpaceType::kBoundedFloor);
}
std::optional<gfx::Transform> OpenXrApiWrapper::GetBaseSpaceFromSpace(
mojom::XRReferenceSpaceType base_space_type,
mojom::XRReferenceSpaceType space_type) {
TRACE_EVENT2("xr", "GetBaseSpaceFromSpace", "base_space", base_space_type,
"space", space_type);
auto base_space = GetReferenceSpace(base_space_type);
auto space = GetReferenceSpace(space_type);
XrSpaceLocation base_space_from_space_location = {XR_TYPE_SPACE_LOCATION};
if (XR_FAILED(xrLocateSpace(space, base_space,
frame_state_.predictedDisplayTime,
&base_space_from_space_location)) ||
!IsPoseValid(base_space_from_space_location.locationFlags)) {
return std::nullopt;
}
gfx::DecomposedTransform base_space_from_space_decomp;
base_space_from_space_decomp.quaternion =
gfx::Quaternion(base_space_from_space_location.pose.orientation.x,
base_space_from_space_location.pose.orientation.y,
base_space_from_space_location.pose.orientation.z,
base_space_from_space_location.pose.orientation.w);
base_space_from_space_decomp.translate[0] =
base_space_from_space_location.pose.position.x;
base_space_from_space_decomp.translate[1] =
base_space_from_space_location.pose.position.y;
base_space_from_space_decomp.translate[2] =
base_space_from_space_location.pose.position.z;
gfx::Transform base_space_from_space =
gfx::Transform::Compose(base_space_from_space_decomp);
std::array<float, 16> transform_data;
base_space_from_space.GetColMajorF(transform_data);
bool contains_nan = std::ranges::any_of(
transform_data, [](const float f) { return std::isnan(f); });
if (contains_nan) {
base::debug::DumpWithoutCrashing();
return std::nullopt;
}
return base_space_from_space;
}
void OpenXrApiWrapper::SetXrSessionState(XrSessionState new_state) {
if (session_state_ == new_state)
return;
const char* old_state_name = GetXrSessionStateName(session_state_);
const char* new_state_name = GetXrSessionStateName(new_state);
DVLOG(1) << __func__ << " Transitioning from: " << old_state_name
<< " to: " << new_state_name;
if (session_state_ != XR_SESSION_STATE_UNKNOWN) {
TRACE_EVENT_NESTABLE_ASYNC_END1("xr", "XRSessionState", this, "state",
old_state_name);
}
if (new_state != XR_SESSION_STATE_UNKNOWN) {
TRACE_EVENT_NESTABLE_ASYNC_BEGIN1("xr", "XRSessionState", this, "state",
new_state_name);
}
session_state_ = new_state;
}
VRTestHook* OpenXrApiWrapper::test_hook_ = nullptr;
ServiceTestHook* OpenXrApiWrapper::service_test_hook_ = nullptr;
void OpenXrApiWrapper::SetTestHook(VRTestHook* hook) {
test_hook_ = hook;
if (service_test_hook_) {
service_test_hook_->SetTestHook(test_hook_);
}
}
}