// Copyright 2022 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#define _USE_MATH_DEFINES // For VC++ to get M_PI. This has to be first.
#include "device/vr/openxr/openxr_view_configuration.h"
#include <cmath>
#include "base/check_op.h"
#include "base/ranges/algorithm.h"
#include "build/build_config.h"
#include "device/vr/public/mojom/vr_service.mojom.h"
#include "third_party/openxr/src/include/openxr/openxr.h"
namespace device {
namespace {
// This default isn't necessarily suitable for rendering, but it avoids a rare
// situation where if we cannot locate views on the first frame, xrEndFrame will
// return XR_ERROR_POSE_INVALID which will esesntially terminate the session.
constexpr float kDefaultFov = M_PI / 2.0f;
constexpr XrView kDefaultView{
XR_TYPE_VIEW,
/*next=*/nullptr,
/*pose=*/{{0, 0, 0, 1}, {0, 0, 0}},
/*fov=*/{kDefaultFov, kDefaultFov, kDefaultFov, kDefaultFov}};
} // namespace
mojom::XREye GetEyeFromIndex(int i) {
if (i == kLeftView) {
return mojom::XREye::kLeft;
} else if (i == kRightView) {
return mojom::XREye::kRight;
} else {
return mojom::XREye::kNone;
}
}
OpenXrViewProperties::OpenXrViewProperties(
XrViewConfigurationView xr_properties,
uint32_t view_count)
: xr_properties_(xr_properties), view_count_(view_count) {}
OpenXrViewProperties::~OpenXrViewProperties() = default;
uint32_t OpenXrViewProperties::Width() const {
if constexpr (BUILDFLAG(IS_ANDROID)) {
// TODO(crbug.com/40948737): Devise a more robust way of calculating
// the max size and per view width. (e.g. (viewWidth/totalWidth) *
// maxWidth).
constexpr uint32_t kMaxImageWidth = 4096;
return std::min(xr_properties_.recommendedImageRectWidth,
kMaxImageWidth / view_count_);
}
return xr_properties_.recommendedImageRectWidth;
}
uint32_t OpenXrViewProperties::Height() const {
return xr_properties_.recommendedImageRectHeight;
}
uint32_t OpenXrViewProperties::RecommendedSwapchainSampleCount() const {
return xr_properties_.recommendedSwapchainSampleCount;
}
uint32_t OpenXrViewProperties::MaxSwapchainSampleCount() const {
return xr_properties_.maxSwapchainSampleCount;
}
OpenXrViewConfiguration::OpenXrViewConfiguration() = default;
OpenXrViewConfiguration::OpenXrViewConfiguration(OpenXrViewConfiguration&&) =
default;
OpenXrViewConfiguration::OpenXrViewConfiguration(
const OpenXrViewConfiguration&) = default;
OpenXrViewConfiguration& OpenXrViewConfiguration::operator=(
const OpenXrViewConfiguration&) = default;
OpenXrViewConfiguration::~OpenXrViewConfiguration() = default;
// Used only for testing - initializes an OpenXR view configuration that the
// mock OpenXR runtime supports.
OpenXrViewConfiguration::OpenXrViewConfiguration(XrViewConfigurationType type,
bool active,
uint32_t num_views,
uint32_t dimension,
uint32_t swap_count) {
const XrViewConfigurationView kViewConfigurationView = {
XR_TYPE_VIEW_CONFIGURATION_VIEW,
nullptr,
dimension,
dimension,
dimension,
dimension,
swap_count,
swap_count};
std::vector<XrViewConfigurationView> view_properties(num_views);
for (uint32_t i = 0; i < num_views; i++) {
view_properties[i] = kViewConfigurationView;
}
Initialize(type, std::move(view_properties));
SetActive(active);
}
void OpenXrViewConfiguration::Initialize(
XrViewConfigurationType type,
std::vector<XrViewConfigurationView> properties) {
DCHECK(!initialized_);
DCHECK(!properties.empty());
type_ = type;
active_ = false;
viewport_ = gfx::Rect();
SetProperties(std::move(properties));
local_from_view_.resize(properties_.size(), kDefaultView);
projection_views_.resize(properties_.size());
initialized_ = true;
}
bool OpenXrViewConfiguration::Initialized() const {
return initialized_;
}
XrViewConfigurationType OpenXrViewConfiguration::Type() const {
return type_;
}
void OpenXrViewConfiguration::SetActive(bool active) {
active_ = active;
if (!active_) {
viewport_ = gfx::Rect();
}
}
bool OpenXrViewConfiguration::Active() const {
return active_;
}
const gfx::Rect& OpenXrViewConfiguration::Viewport() const {
return viewport_;
}
void OpenXrViewConfiguration::SetViewport(uint32_t x,
uint32_t y,
uint32_t width,
uint32_t height) {
viewport_ = gfx::Rect(x, y, width, height);
}
const std::vector<OpenXrViewProperties>& OpenXrViewConfiguration::Properties()
const {
return properties_;
}
void OpenXrViewConfiguration::SetProperties(
std::vector<XrViewConfigurationView> properties) {
// The number of views in a view configuration should not change throughout
// the lifetime of the OpenXR instance.
CHECK(properties_.empty() || properties.size() == properties_.size());
uint32_t size = properties.size();
properties_.clear();
properties_.reserve(size);
base::ranges::transform(properties, std::back_inserter(properties_),
[size](const XrViewConfigurationView& view) {
return OpenXrViewProperties(view, size);
});
}
const std::vector<XrView>& OpenXrViewConfiguration::Views() const {
return local_from_view_;
}
void OpenXrViewConfiguration::SetViews(std::vector<XrView> views) {
DCHECK_EQ(views.size(), local_from_view_.size());
local_from_view_ = std::move(views);
}
const std::vector<XrCompositionLayerProjectionView>&
OpenXrViewConfiguration::ProjectionViews() const {
return projection_views_;
}
XrCompositionLayerProjectionView& OpenXrViewConfiguration::GetProjectionView(
uint32_t view_index) {
DCHECK_LT(view_index, projection_views_.size());
return projection_views_[view_index];
}
bool OpenXrViewConfiguration::CanEnableAntiAliasing() const {
// From the OpenXR Spec:
// maxSwapchainSampleCount is the maximum number of sub-data element samples
// supported for swapchain images that will be rendered into for this view.
//
// To ease the workload on low end devices, we disable anti-aliasing when the
// max sample count is 1.
return base::ranges::all_of(properties_,
[](const OpenXrViewProperties& view) {
return view.MaxSwapchainSampleCount() > 1;
});
}
OpenXrLayers::OpenXrLayers(XrSpace space,
XrEnvironmentBlendMode blend_mode,
const std::vector<XrCompositionLayerProjectionView>&
primary_projection_views)
: space_(space), blend_mode_(blend_mode) {
InitializeLayer(primary_projection_views, primary_projection_layer_);
}
OpenXrLayers::~OpenXrLayers() = default;
void OpenXrLayers::AddSecondaryLayerForType(
XrViewConfigurationType type,
const std::vector<XrCompositionLayerProjectionView>& projection_views) {
secondary_projection_layers_.emplace_back();
InitializeLayer(projection_views, secondary_projection_layers_.back());
secondary_composition_layers_.push_back(
reinterpret_cast<XrCompositionLayerBaseHeader*>(
&secondary_projection_layers_.back()));
secondary_layer_info_.emplace_back();
XrSecondaryViewConfigurationLayerInfoMSFT& layer_info =
secondary_layer_info_.back();
layer_info.type = XR_TYPE_SECONDARY_VIEW_CONFIGURATION_LAYER_INFO_MSFT;
layer_info.viewConfigurationType = type;
layer_info.environmentBlendMode = blend_mode_;
layer_info.layerCount = 1;
layer_info.layers = &secondary_composition_layers_.back();
}
void OpenXrLayers::InitializeLayer(
const std::vector<XrCompositionLayerProjectionView>& projection_views,
XrCompositionLayerProjection& layer) {
layer.type = XR_TYPE_COMPOSITION_LAYER_PROJECTION;
layer.next = nullptr;
layer.layerFlags = 0;
layer.space = space_;
layer.viewCount = projection_views.size();
layer.views = projection_views.data();
if (blend_mode_ == XR_ENVIRONMENT_BLEND_MODE_ALPHA_BLEND) {
layer.layerFlags |= XR_COMPOSITION_LAYER_BLEND_TEXTURE_SOURCE_ALPHA_BIT;
}
}
} // namespace device