910e62b5创建于 1月15日历史提交
// Copyright 2024 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "device/vr/openxr/android/openxr_depth_sensor_android.h"

#include <array>
#include <concepts>
#include <memory>
#include <set>

#include "base/containers/contains.h"
#include "base/containers/fixed_flat_map.h"
#include "base/containers/flat_set.h"
#include "base/feature_list.h"
#include "base/no_destructor.h"
#include "base/numerics/safe_conversions.h"
#include "base/time/time.h"
#include "base/trace_event/trace_event.h"
#include "device/vr/openxr/openxr_extension_helper.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/vr_service.mojom.h"
#include "device/vr/public/mojom/xr_session.mojom.h"
#include "third_party/openxr/dev/xr_android.h"
#include "third_party/openxr/src/include/openxr/openxr.h"

namespace device {

namespace {
// The spec essentially requires that the depth views line up with the
// XR_VIEW_CONFIGURATION_TYPE_PRIMARY_STEREO type, off of which we define these
// constants. This provides an extra layer of security in case we migrate types
// or anything else that this class is kept up-to-date.
static_assert(kNumPrimaryViews == 2);
static_assert(kLeftView == 0);
static_assert(kRightView == 1);

// Returns the index in the |XrDepthAcquireResultANDROID| |views| member for the
// requested eye per the specification.
size_t GetDepthViewIndex(const mojom::XREye& eye) {
  CHECK(eye == mojom::XREye::kLeft || eye == mojom::XREye::kRight);
  if (eye == mojom::XREye::kLeft) {
    return kLeftView;
  }

  return kRightView;
}

// The depthImage from OpenXR consists of two sets of pixels one after the
// other. The first num_pixels floats in depthImage are the left eye, and the
// pixels after that are the right eye.
size_t GetDepthImageOffset(const mojom::XREye& eye, size_t pixels_per_image) {
  CHECK(eye == mojom::XREye::kLeft || eye == mojom::XREye::kRight);
  if (eye == mojom::XREye::kRight) {
    return pixels_per_image;
  }

  return 0;
}

// A map of the resolutions that we support to a gfx::Size, since we ultimately
// need to send that across mojom, and allows us to properly handle whichever
// size we get back from the API.
constexpr auto kResolutionSizeMap =
    base::MakeFixedFlatMap<XrDepthCameraResolutionANDROID, gfx::Size>(
        {{XR_DEPTH_CAMERA_RESOLUTION_80x80_ANDROID, {80, 80}},
         {XR_DEPTH_CAMERA_RESOLUTION_160x160_ANDROID, {160, 160}},
         {XR_DEPTH_CAMERA_RESOLUTION_320x320_ANDROID, {320, 320}}});

constexpr std::array<XrDepthCameraResolutionANDROID, 3> kResolutionPreferences{
    XR_DEPTH_CAMERA_RESOLUTION_320x320_ANDROID,
    XR_DEPTH_CAMERA_RESOLUTION_160x160_ANDROID,
    XR_DEPTH_CAMERA_RESOLUTION_80x80_ANDROID};
static_assert(kResolutionSizeMap.size() == kResolutionPreferences.size(),
              "Need to have a corresponding resolution size for every "
              "preferred resolution that we can support");

constexpr size_t GetByteSize(const mojom::XRDepthDataFormat& format) {
  switch (format) {
    case mojom::XRDepthDataFormat::kLuminanceAlpha:
    case mojom::XRDepthDataFormat::kUnsignedShort:
      return sizeof(uint16_t);
    case mojom::XRDepthDataFormat::kFloat32:
      return sizeof(float);
  }
}
static_assert(sizeof(uint16_t) ==
              GetByteSize(mojom::XRDepthDataFormat::kLuminanceAlpha));
static_assert(sizeof(uint16_t) ==
              GetByteSize(mojom::XRDepthDataFormat::kUnsignedShort));

// Essentially this returns the projection matrix for a given camera. Screen
// coordinates appear to need to be in clip space, e.g. [-1,1]. "Camera Space",
// conforms to space expectations compatible with other transforms used
// throughout the runtime and references a space with the camera location as the
// origin.
gfx::Transform GetScreenFromCamera(const mojom::VRFieldOfViewPtr& fov) {
  constexpr float near_depth = 0.0001;
  constexpr float far_depth = 10000;
  constexpr double kDegToRad = M_PI / 180.0;

  float up_rad = fov->up_degrees * kDegToRad;
  float down_rad = fov->down_degrees * kDegToRad;
  float left_rad = fov->left_degrees * kDegToRad;
  float right_rad = fov->right_degrees * kDegToRad;

  float up_tan = tanf(up_rad);
  float down_tan = tanf(down_rad);
  float left_tan = tanf(left_rad);
  float right_tan = tanf(right_rad);
  float x_scale = 2.0f / (left_tan + right_tan);
  float y_scale = 2.0f / (up_tan + down_tan);
  float inv_nf = 1.0f / (near_depth - far_depth);

  return gfx::Transform::ColMajor(
      x_scale, 0.0f, 0.0f, 0.0f, 0.0f, y_scale, 0.0f, 0.0f,
      -((left_tan - right_tan) * x_scale * 0.5),
      ((up_tan - down_tan) * y_scale * 0.5), (near_depth + far_depth) * inv_nf,
      -1.0f, 0.0f, 0.0f, (2.0f * far_depth * near_depth) * inv_nf, 0.0f);
}

// Converts an array coordinate value [0,size) to a texture coordinate [0, 1].
inline float ToTexCoord(float val, float size) {
  return (val + 0.5f) / size;
}

// Converts a texture coordinate [0,1] to "clip space" [-1, 1]. This is a
// necessary conversion when transforming a point through a projection matrix
// (screen_from_foo or foo_from_screen) in our normal terminology.
inline float ToClipSpace(float val) {
  return 2.0f * val - 1.0f;
}

// Converts from "clip space" [-1, 1] to texture coordinate space [0,1]. This is
// a necessary conversion to map a point transformed through a projection matrix
// back to something that can be used to sample a texture.
inline float FromClipSpace(float val) {
  return (val + 1.0f) / 2.0f;
}

inline size_t buffer_location(size_t col, size_t row, size_t row_size) {
  return row * row_size + col;
}

template <typename T>
inline void WriteToSpanStart(base::span<uint8_t> output, T val) {
  base::span<const uint8_t> val_span;
  if constexpr (std::floating_point<T>) {
    // Floating point types do not have unique object representations, but this
    // code is just serializing them, so allow it.
    val_span = base::byte_span_from_ref(base::allow_nonunique_obj, val);
  } else {
    val_span = base::byte_span_from_ref(val);
  }
  output.first<sizeof(T)>().copy_from(val_span);
}

// Helper function to copy depth data on the CPU. This expects to receive the
// raw array of data received from the OpenXr API and will convert it to an
// array of the same size. This function is responsible for mapping a point from
// the "pixel" it would occupy in the output buffer to sample the corresponding
// point in the depth buffer by applying all required transforms. After the
// float value is sampled, it will apply |conversion_fn| to map from float to
// |T| to assign it to the output array.
template <typename T, typename FunctionType>
void CopyDepthData(base::span<const float> input,
                   base::span<uint8_t> output,
                   gfx::Size image_size,
                   XrDepthViewANDROID depth_view,
                   const mojom::XRViewPtr& view,
                   bool reproject_depth_view,
                   FunctionType&& conversion_fn) {
  TRACE_EVENT0("xr", "CopyDepthData");
  // We should've handled an invalid image_size before getting to this point.
  size_t num_pixels;
  CHECK(image_size.GetCheckedArea().AssignIfValid(&num_pixels));
  CHECK_EQ(input.size(), num_pixels);
  CHECK_EQ(output.size_bytes(), num_pixels * sizeof(T));

  // If we don't need to reproject, we just need to copy and convert each
  // element in the order their currently in.
  if (!reproject_depth_view) {
    for (const auto& val : input) {
      WriteToSpanStart(output, conversion_fn(val));
      output = output.subspan(sizeof(T));
    }
    return;
  }

  // Otherwise, we need to reproject the depth data to align with the XRView.
  // Extract width/height for readability (and to use size_t).
  const size_t width = image_size.width();
  const size_t height = image_size.height();
  const gfx::Transform view_from_eye_screen =
      GetScreenFromCamera(view->geometry->field_of_view).GetCheckedInverse();
  const gfx::Transform depth_screen_from_depth =
      GetScreenFromCamera(XrFovToMojomFov(depth_view.fov));

  // Depth pose is initially local_from_depth (based on passing local space
  // into the object upon creation).
  // TOOD(crbug.com/40684534): Create local_from_mojom transformations.
  const gfx::Transform local_from_mojom;
  const auto depth_from_mojom =
      XrPoseToGfxTransform(depth_view.pose).GetCheckedInverse() *
      local_from_mojom;
  const auto& mojom_from_view = view->geometry->mojo_from_view;
  const gfx::Transform depth_screen_from_eye_screen =
      depth_screen_from_depth * depth_from_mojom * mojom_from_view *
      view_from_eye_screen;
  for (size_t y = 0; y < height; y++) {
    for (size_t x = 0; x < width; x++) {
      // Assign a z value of 1 to convert from cartesian (screen) coordinates to
      // a homogeneous Euclidean (2D) coordinate space.
      // Add a negative to the y coordinate because y=0 corresponds to the top
      // of the image, i.e. 1 in clip space.
      const gfx::Point3F eye_screen_clip_coord{
          ToClipSpace(ToTexCoord(x, width)),
          -ToClipSpace(ToTexCoord(y, height)), 1};
      const gfx::Point3F depth_screen_clip_coord =
          depth_screen_from_eye_screen.MapPoint(eye_screen_clip_coord);

      // Revert the -y to sample into the OpenXR depth texture.
      const gfx::PointF depth_screen_texture_coord(
          FromClipSpace(depth_screen_clip_coord.x()),
          FromClipSpace(-depth_screen_clip_coord.y()));

      // If x or y is less than 0 it's out of bounds and we should ignore it.
      // We'll convert back to whole buffer coordinates before checking the
      // width and height.
      if (depth_screen_texture_coord.x() < 0 ||
          depth_screen_texture_coord.y() < 0) {
        // We need to ensure that the whole span gets initialized.
        WriteToSpanStart(output, T());
        // Advance the span so that the start is the next uninitialized spot.
        output = output.subspan(sizeof(T));
        continue;
      }

      const gfx::PointF depth_screen_buffer_coord =
          gfx::ScalePoint(depth_screen_texture_coord, width, height);

      // We've already verified that these values can't be negative, so we can
      // safely convert to size_t now.
      // Anything from N.0 to N.999... should be treated as belonging to the
      // pixel originating at N. The previous addition of 0.5 helped to ensure
      // accuracy by forcing us to sample the value that the middle of the pixel
      // should be, as such it would be inappropriate to subtract the 0.5 again
      // as that might force us to sample a different pixel than where our
      // centerpoint should be. This static_cast from float to size_t
      // essentially is equivalent to truncation to leave us with N.
      const size_t depth_y = static_cast<size_t>(depth_screen_buffer_coord.y());
      const size_t depth_x = static_cast<size_t>(depth_screen_buffer_coord.x());

      // If the new point is out of bounds, ignore it.
      // Note that we do this part of the bounds check after the conversion from
      // float to size_t to ensure accuracy of the conversion.
      if (depth_x >= width || depth_y >= height) {
        // We need to ensure that the whole span gets initialized.
        WriteToSpanStart(output, T());
        // Advance the span so that the start is the next uninitialized spot.
        output = output.subspan(sizeof(T));
        continue;
      }

      float depth_value = input[buffer_location(depth_x, depth_y, width)];

      // The continuous `subspan` calls will essentially keep advancing output
      // through the underlying data structure for the span so that the first
      // sizeof(T) bytes are also the next unwritten bytes and correspond to
      // our current x/y "spot".
      WriteToSpanStart(output, conversion_fn(depth_value));

      // Advance the span so that the start is the next uninitialized spot.
      output = output.subspan(sizeof(T));
    }
  }

  // Since we've been advancing the span the whole time and already verified
  // that the originally passed in output span is the same size as the input, we
  // should now be at the end of the span we received, which means that output
  // should be empty.
  CHECK(output.empty());
}
}  // namespace

OpenXrDepthSensorAndroid::OpenXrDepthSensorAndroid(
    const OpenXrExtensionHelper& extension_helper,
    XrSession session,
    XrSpace mojo_space,
    const mojom::XRDepthOptions& depth_options)
    : extension_helper_(extension_helper),
      session_(session),
      mojo_space_(mojo_space) {
  DVLOG(1) << __func__;
  // We can only support CPU optimized depth, so we can only support depth if
  // either no preferences were specified or if cpu-optimized was specified.
  const auto& usage_preferences = depth_options.usage_preferences;
  const bool can_support_depth =
      usage_preferences.empty() ||
      base::Contains(usage_preferences, mojom::XRDepthUsage::kCPUOptimized);

  if (can_support_depth) {
    depth_config_ = mojom::XRDepthConfig::New();
    depth_config_->depth_usage = mojom::XRDepthUsage::kCPUOptimized;

    // We can support all of the current data formats, so just grab the first if
    // they were specified, and if none were, use float32 (our native type).
    static_assert(static_cast<int>(mojom::XRDepthDataFormat::kMaxValue) == 3);
    if (!depth_options.data_format_preferences.empty()) {
      depth_config_->depth_data_format =
          depth_options.data_format_preferences[0];
    } else {
      depth_config_->depth_data_format = mojom::XRDepthDataFormat::kFloat32;
    }

    // We can support all of the current depth types, so just grab the first if
    // they were specified. If none were, use `raw` unless overridden by the
    // feature flag. Note that this also allows us to simply use if/else when
    // parsing the depth_type in this file.
    static_assert(static_cast<int>(mojom::XRDepthType::kMaxValue) == 2);
    if (!depth_options.depth_type_request.empty()) {
      depth_config_->depth_type = depth_options.depth_type_request[0];
    } else {
      depth_config_->depth_type =
          base::FeatureList::IsEnabled(features::kOpenXrAndroidSmoothDepth)
              ? mojom::XRDepthType::kSmooth
              : mojom::XRDepthType::kRaw;
    }

    match_depth_view_ = depth_options.match_depth_view;
  } else {
    DVLOG(1) << __func__ << " Cannot support depth";
  }
}

OpenXrDepthSensorAndroid::~OpenXrDepthSensorAndroid() {
  DVLOG(1) << __func__;
  if (HasSwapchain()) {
    // In the (likely) event that the session has been destroyed before us, this
    // will fail. So just ignore the result returned here.
    DestroySwapchain();
  }
}

bool OpenXrDepthSensorAndroid::HasSwapchain() const {
  return swapchain_ != XR_NULL_HANDLE;
}

void OpenXrDepthSensorAndroid::SetDepthActive(bool depth_active) {
  DVLOG(1) << __func__ << "depth_should_be_active_=" << depth_should_be_active_
           << " depth_active=" << depth_active
           << " HasSwapchain()=" << HasSwapchain();
  depth_should_be_active_ = depth_active;

  // Whether or not we have a swapchain is the actual measure of if we are
  // active or not.
  if (depth_should_be_active_ == HasSwapchain()) {
    return;
  }

  if (depth_should_be_active_) {
    CreateSwapchain();
  } else {
    DestroySwapchain();
  }

  if (depth_should_be_active_ != HasSwapchain()) {
    DLOG(WARNING) << __func__ << " failed.";
  }
}

XrResult OpenXrDepthSensorAndroid::CreateSwapchain() {
  DVLOG(1) << __func__;
  CHECK(!HasSwapchain());
  TRACE_EVENT0("xr", "CreateSwapchain");

  if (!initialized_) {
    return XR_ERROR_FEATURE_UNSUPPORTED;
  }

  XrDepthSwapchainCreateInfoANDROID swapchain_create_info{
      XR_TYPE_DEPTH_SWAPCHAIN_CREATE_INFO_ANDROID};
  swapchain_create_info.resolution = depth_camera_resolution_;
  if (depth_config_->depth_type == mojom::XRDepthType::kSmooth) {
    swapchain_create_info.createFlags =
        XR_DEPTH_SWAPCHAIN_CREATE_SMOOTH_DEPTH_IMAGE_BIT_ANDROID;
  } else {
    swapchain_create_info.createFlags =
        XR_DEPTH_SWAPCHAIN_CREATE_RAW_DEPTH_IMAGE_BIT_ANDROID;
  }
  RETURN_IF_XR_FAILED(
      extension_helper_->ExtensionMethods().xrCreateDepthSwapchainANDROID(
          session_, &swapchain_create_info, &swapchain_));

  uint32_t image_count_output = 0;
  RETURN_IF_XR_FAILED(extension_helper_->ExtensionMethods()
                          .xrEnumerateDepthSwapchainImagesANDROID(
                              swapchain_, 0, &image_count_output, nullptr));

  depth_images_.resize(image_count_output);
  for (auto& image : depth_images_) {
    image.type = XR_TYPE_DEPTH_SWAPCHAIN_IMAGE_ANDROID;
  }

  RETURN_IF_XR_FAILED(extension_helper_->ExtensionMethods()
                          .xrEnumerateDepthSwapchainImagesANDROID(
                              swapchain_, depth_images_.size(),
                              &image_count_output, depth_images_.data()));

  // Realistically this should never happen, but since it theoretically can,
  // it shouldn't be a CHECK.
  if (image_count_output != depth_images_.size()) {
    LOG(ERROR) << __func__ << " Swapchain size changed during creation";
    return XR_ERROR_INITIALIZATION_FAILED;
  }

  return XR_SUCCESS;
}

void OpenXrDepthSensorAndroid::DestroySwapchain() {
  DVLOG(1) << __func__;
  CHECK(HasSwapchain());
  TRACE_EVENT0("xr", "DestroySwapchain");
  extension_helper_->ExtensionMethods().xrDestroyDepthSwapchainANDROID(
      swapchain_);

  swapchain_ = XR_NULL_HANDLE;
  depth_images_.clear();
}

XrResult OpenXrDepthSensorAndroid::Initialize() {
  DVLOG(1) << __func__;
  if (initialized_) {
    return XR_SUCCESS;
  }

  if (!depth_config_) {
    return XR_ERROR_FEATURE_UNSUPPORTED;
  }

  uint32_t supported_resolutions_count;
  RETURN_IF_XR_FAILED(
      extension_helper_->ExtensionMethods().xrEnumerateDepthResolutionsANDROID(
          session_, 0, &supported_resolutions_count, nullptr));

  std::vector<XrDepthCameraResolutionANDROID> supported_resolutions(
      supported_resolutions_count, XR_DEPTH_CAMERA_RESOLUTION_MAX_ENUM_ANDROID);
  RETURN_IF_XR_FAILED(
      extension_helper_->ExtensionMethods().xrEnumerateDepthResolutionsANDROID(
          session_, supported_resolutions_count, &supported_resolutions_count,
          supported_resolutions.data()));

  // Realistically this should never happen, but since it theoretically can,
  // it shouldn't be a CHECK.
  if (supported_resolutions_count != supported_resolutions.size()) {
    LOG(ERROR) << __func__
               << " Supported resolution size changed during creation";
    return XR_ERROR_INITIALIZATION_FAILED;
  }

  auto it = std::ranges::find_if(
      kResolutionPreferences.begin(), kResolutionPreferences.end(),
      [&supported_resolutions](
          const XrDepthCameraResolutionANDROID& resolution) {
        return base::Contains(supported_resolutions, resolution);
      });

  if (it == kResolutionPreferences.end()) {
    DLOG(ERROR) << __func__ << " No Supported Depth Resolution";
    return XR_ERROR_INITIALIZATION_FAILED;
  }

  depth_camera_resolution_ = *it;

  // We will try to create the swapchain as needed when querying depth data,
  // so call ourselves initialized regardless of the success or failure of
  // creating it.
  initialized_ = true;
  return CreateSwapchain();
}

mojom::XRDepthConfigPtr OpenXrDepthSensorAndroid::GetDepthConfig() {
  return depth_config_ ? depth_config_.Clone() : nullptr;
}

void OpenXrDepthSensorAndroid::PopulateDepthData(
    XrTime frame_time,
    const std::vector<mojom::XRViewPtr>& views) {
  DVLOG(3) << __func__;
  // We could fail to be initialized if depth isn't actually supported.
  if (!initialized_) {
    DVLOG(3) << __func__ << " Not initialized.";
    return;
  }

  if (!HasSwapchain()) {
    // If we don't have a swapchain, and we shouldn't, then just no-op. We're
    // inactive.
    if (!depth_should_be_active_) {
      return;
    }

    // We should be active, but for some reason aren't. Maybe creating the
    // swapchain failed, try to create it again. This should be rare.
    DLOG(WARNING) << __func__ << " did not have swapchain, when expected to.";
    if (XR_FAILED(CreateSwapchain())) {
      DLOG(WARNING) << __func__ << " failed to create swapchain";
      return;
    }
  }

  // By this time, we should've already exited if we don't have a swapchain.
  CHECK(HasSwapchain());

  if (views.size() < kNumPrimaryViews ||
      views[kLeftView]->eye != mojom::XREye::kLeft ||
      views[kRightView]->eye != mojom::XREye::kRight) {
    DLOG(ERROR) << __func__ << " Incorrect eye configuration";
    return;
  }
  TRACE_EVENT0("xr", "PopulateDepthData");

  XrDepthAcquireInfoANDROID acquire_info = {XR_TYPE_DEPTH_ACQUIRE_INFO_ANDROID};
  acquire_info.space = mojo_space_;
  acquire_info.displayTime = frame_time;

  XrDepthAcquireResultANDROID acquire_result = {
      XR_TYPE_DEPTH_ACQUIRE_RESULT_ANDROID};
  XrResult result = extension_helper_->ExtensionMethods()
                        .xrAcquireDepthSwapchainImagesANDROID(
                            swapchain_, &acquire_info, &acquire_result);
  if (XR_FAILED(result)) {
    DLOG(ERROR) << __func__
                << " Failed to acquire depth swapchain images: " << result;
    return;
  }

  if (acquire_result.acquiredIndex >= depth_images_.size()) {
    DLOG(ERROR) << __func__ << " Acquired Index was out of bounds: "
                << acquire_result.acquiredIndex << " vs "
                << depth_images_.size();
    return;
  }

  for (size_t i = 0; i < kNumPrimaryViews; i++) {
    views[i]->depth_data = GetDepthDataForEye(acquire_result, views[i]);
  }
}

mojom::XRDepthDataPtr OpenXrDepthSensorAndroid::GetDepthDataForEye(
    const XrDepthAcquireResultANDROID& acquire_result,
    const mojom::XRViewPtr& view) {
  const auto& eye = view->eye;
  DVLOG(3) << __func__ << " eye: " << eye;
  CHECK(eye == mojom::XREye::kLeft || eye == mojom::XREye::kRight);
  auto& depth_image = depth_images_[acquire_result.acquiredIndex];

  const auto& image_size = kResolutionSizeMap.at(depth_camera_resolution_);
  size_t num_pixels;
  if (!image_size.GetCheckedArea().AssignIfValid(&num_pixels)) {
    DLOG(ERROR) << __func__ << " Image size overflowed";
    return nullptr;
  }

  const auto& data_format = depth_config_->depth_data_format;
  size_t buffer_size;
  if (!base::CheckMul<size_t>(GetByteSize(data_format), num_pixels)
           .AssignIfValid(&buffer_size)) {
    DLOG(ERROR) << __func__ << " Buffer size overflowed";
    return nullptr;
  }

  auto depth_views = base::span(acquire_result.views);
  XrDepthViewANDROID depth_view = depth_views[GetDepthViewIndex(eye)];

  size_t pixel_offset = GetDepthImageOffset(eye, num_pixels);
  auto* depth_image_ptr =
      depth_config_->depth_type == mojom::XRDepthType::kSmooth
          ? depth_image.smoothDepthImage
          : depth_image.rawDepthImage;

  // SAFETY: `num_pixels` is calculated above using checked multiplication
  // based on the resolution that the Depth API was created with
  // (`depth_camera_resolution_`). Per specification, the depth API returns a
  // single array of two images (one for each eye), starting at the pointer for
  // that data.
  UNSAFE_BUFFERS(base::span<const float> full_depth_image_span =
                     base::span(depth_image_ptr, 2 * num_pixels));

  base::span<const float> depth_image_span =
      full_depth_image_span.subspan(pixel_offset, num_pixels);
  mojom::XRDepthDataUpdatedPtr result = mojom::XRDepthDataUpdated::New();
  result->size = image_size;

  // If we don't have to match our depth view, then we need to send up the
  // information about the depth camera's geometry.
  if (!match_depth_view_) {
    result->view_geometry = mojom::XRViewGeometry::New();
    auto& geometry = result->view_geometry;
    geometry->field_of_view = XrFovToMojomFov(depth_view.fov);

    // TOOD(crbug.com/40684534): Define mojo space.
    gfx::Transform mojo_from_local;
    // |depth_view.pose| is local_from_view
    geometry->mojo_from_view =
        mojo_from_local * XrPoseToGfxTransform(depth_view.pose);
  }

  switch (depth_config_->depth_data_format) {
    case mojom::XRDepthDataFormat::kFloat32:
      CHECK(GetByteSize(data_format) == sizeof(float));
      // Results are already in meters.
      result->raw_value_to_meters = 1;

      // SPECIAL CASE: If we don't need to reproject use big_buffer's "copy"
      // constructor, since we already have the data in the format we need.
      // Otherwise, allocate a BigBuffer of the appropriate size and perform the
      // reprojection and copy.
      if (!match_depth_view_) {
        // Floating point types do not have unique object representations, but
        // we're using the byte span for serialization, which is allowed.
        result->pixel_data = mojo_base::BigBuffer(
            base::as_byte_span(base::allow_nonunique_obj, depth_image_span));
      } else {
        result->pixel_data = mojo_base::BigBuffer(buffer_size);
        CopyDepthData<float>(depth_image_span, result->pixel_data, image_size,
                             depth_view, view, match_depth_view_,
                             [](float val) { return val; });
      }
      break;
    case mojom::XRDepthDataFormat::kLuminanceAlpha:
    case mojom::XRDepthDataFormat::kUnsignedShort:
      CHECK(GetByteSize(data_format) == sizeof(uint16_t));
      // We'll be converting to millimeters.
      result->raw_value_to_meters = 1 / 1000.0f;
      result->pixel_data = mojo_base::BigBuffer(buffer_size);

      CopyDepthData<uint16_t>(
          depth_image_span, result->pixel_data, image_size, depth_view, view,
          match_depth_view_, [](float val) {
            // val is in meters, so convert to mm to avoid losing precision.
            return base::saturated_cast<uint16_t>(std::nearbyint(val * 1000));
          });
      break;
  }

  return mojom::XRDepthData::NewUpdatedDepthData(std::move(result));
}

OpenXrDepthSensorAndroidFactory::OpenXrDepthSensorAndroidFactory() = default;
OpenXrDepthSensorAndroidFactory::~OpenXrDepthSensorAndroidFactory() = default;

const base::flat_set<std::string_view>&
OpenXrDepthSensorAndroidFactory::GetRequestedExtensions() const {
  static base::NoDestructor<base::flat_set<std::string_view>> kExtensions(
      {XR_ANDROID_DEPTH_TEXTURE_EXTENSION_NAME});
  return *kExtensions;
}

std::set<device::mojom::XRSessionFeature>
OpenXrDepthSensorAndroidFactory::GetSupportedFeatures() const {
  if (!IsEnabled()) {
    return {};
  }

  return {device::mojom::XRSessionFeature::DEPTH};
}

void OpenXrDepthSensorAndroidFactory::CheckAndUpdateEnabledState(
    const OpenXrExtensionEnumeration* extension_enum,
    XrInstance instance,
    XrSystemId system) {
  if (!AreAllRequestedExtensionsSupported(extension_enum)) {
    SetEnabled(false);
    return;
  }

  XrSystemDepthTrackingPropertiesANDROID depth_properties{
      XR_TYPE_SYSTEM_DEPTH_TRACKING_PROPERTIES_ANDROID};

  XrSystemProperties system_properties{XR_TYPE_SYSTEM_PROPERTIES};
  system_properties.next = &depth_properties;

  bool depth_supported = false;
  XrResult result = xrGetSystemProperties(instance, system, &system_properties);
  if (XR_SUCCEEDED(result)) {
    depth_supported = depth_properties.supportsDepthTracking;
  }

  SetEnabled(depth_supported);
}

std::unique_ptr<OpenXrDepthSensor>
OpenXrDepthSensorAndroidFactory::CreateDepthSensor(
    const OpenXrExtensionHelper& extension_helper,
    XrSession session,
    XrSpace mojo_space,
    const mojom::XRDepthOptions& depth_options) const {
  bool is_supported = IsEnabled();
  DVLOG(2) << __func__ << " is_supported=" << is_supported;
  if (is_supported) {
    return std::make_unique<OpenXrDepthSensorAndroid>(
        extension_helper, session, mojo_space, depth_options);
  }

  return nullptr;
}

}  // namespace device