chromium/device/vr/openxr/android/openxr_scene_understanding_manager_android.cc

// 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.

#ifdef UNSAFE_BUFFERS_BUILD
// TODO(crbug.com/351564777): Remove this and convert code to safer constructs.
#pragma allow_unsafe_buffers
#endif

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

#include <algorithm>
#include <memory>
#include <optional>
#include <set>
#include <utility>
#include <vector>

#include "base/containers/flat_set.h"
#include "base/no_destructor.h"
#include "device/vr/openxr/openxr_extension_helper.h"
#include "device/vr/openxr/openxr_util.h"
#include "device/vr/public/mojom/xr_session.mojom-shared.h"
#include "third_party/openxr/dev/xr_android.h"
#include "third_party/openxr/src/include/openxr/openxr.h"

namespace {
constexpr uint32_t kMaxHitTestResults = 2;
}  // namespace

namespace device {

OpenXRSceneUnderstandingManagerAndroid::
    ~OpenXRSceneUnderstandingManagerAndroid() = default;

OpenXRSceneUnderstandingManagerAndroid::OpenXRSceneUnderstandingManagerAndroid(
    const OpenXrExtensionHelper& extension_helper,
    XrSession session,
    XrSpace mojo_space)
    : extension_helper_(extension_helper),
      session_(session),
      mojo_space_(mojo_space) {}

bool OpenXRSceneUnderstandingManagerAndroid::OnNewHitTestSubscription() {
  if (plane_tracker_ == XR_NULL_HANDLE) {
    XrTrackableTrackerCreateInfoANDROID create_info{
        XR_TYPE_TRACKABLE_TRACKER_CREATE_INFO_ANDROID};
    create_info.trackableType = XR_TRACKABLE_TYPE_PLANE_ANDROID;

    XrResult result =
        extension_helper_->ExtensionMethods().xrCreateTrackableTrackerANDROID(
            session_, &create_info, &plane_tracker_);
    RETURN_VAL_IF_XR_FAILED(result, false);
  }

  return true;
}

void OpenXRSceneUnderstandingManagerAndroid::
    OnAllHitTestSubscriptionsRemoved() {
  if (plane_tracker_ != XR_NULL_HANDLE) {
    // We don't really need to handle if we fail to destroy the plane tracker,
    // but log it for future debuggability.
    XrResult result =
        extension_helper_->ExtensionMethods().xrDestroyTrackableTrackerANDROID(
            plane_tracker_);
    plane_tracker_ = XR_NULL_HANDLE;
    if (XR_FAILED(result)) {
      LOG(ERROR) << __func__ << " Could not destroy a plane tracker";
    }
  }
}

void OpenXRSceneUnderstandingManagerAndroid::OnFrameUpdate(
    XrTime predicted_display_time) {
  predicted_display_time_ = predicted_display_time;
}

std::vector<mojom::XRHitResultPtr>
OpenXRSceneUnderstandingManagerAndroid::RequestHitTest(
    const gfx::Point3F& ray_origin,
    const gfx::Vector3dF& ray_direction) {
  if (predicted_display_time_ == 0 || plane_tracker_ == XR_NULL_HANDLE) {
    DLOG(ERROR)
        << __func__
        << " Missing either predicted display time or plane tracker. Returning";
    return {};
  }

  XrRaycastInfoANDROID raycast_info = {XR_TYPE_RAYCAST_INFO_ANDROID};
  raycast_info.maxResults = kMaxHitTestResults;
  raycast_info.trackerCount = 1;
  raycast_info.trackers = &plane_tracker_;
  raycast_info.origin =
      XrVector3f{ray_origin.x(), ray_origin.y(), ray_origin.z()};
  raycast_info.trajectory =
      XrVector3f{ray_direction.x(), ray_direction.y(), ray_direction.z()};
  raycast_info.space = mojo_space_;
  raycast_info.time = predicted_display_time_;

  XrRaycastHitResultANDROID xr_results_array[kMaxHitTestResults];
  XrRaycastHitResultsANDROID xr_hit_results = {
      XR_TYPE_RAYCAST_HIT_RESULTS_ANDROID};
  xr_hit_results.resultsCapacityInput = kMaxHitTestResults;
  xr_hit_results.results = xr_results_array;

  RETURN_VAL_IF_XR_FAILED(
      extension_helper_->ExtensionMethods().xrRaycastANDROID(
          session_, &raycast_info, &xr_hit_results),
      {});

  // We receive the hit test results back in increasing distance from the item
  // that they hit, with the Y-direction matching the normal of the plane, and
  // in the space that we specified (which is mojo space), this all matches what
  // WebXR expects, so we simply have to convert the XrPosef to a device pose.
  std::vector<mojom::XRHitResultPtr> hit_results;
  hit_results.reserve(xr_hit_results.resultsCountOutput);
  for (uint32_t i = 0; i < xr_hit_results.resultsCountOutput; i++) {
    mojom::XRHitResultPtr result = mojom::XRHitResult::New();
    result->mojo_from_result = XrPoseToDevicePose(xr_results_array[i].pose);
    hit_results.push_back(std::move(result));
  }

  DVLOG(2) << __func__ << ": hit_results->size()=" << hit_results.size();
  return hit_results;
}

OpenXrSceneUnderstandingManagerAndroidFactory::
    OpenXrSceneUnderstandingManagerAndroidFactory() = default;
OpenXrSceneUnderstandingManagerAndroidFactory::
    ~OpenXrSceneUnderstandingManagerAndroidFactory() = default;

const base::flat_set<std::string_view>&
OpenXrSceneUnderstandingManagerAndroidFactory::GetRequestedExtensions() const {
  static base::NoDestructor<base::flat_set<std::string_view>> kExtensions({
      XR_ANDROID_TRACKABLES_EXTENSION_NAME,
      XR_ANDROID_RAYCAST_EXTENSION_NAME,
  });

  return *kExtensions;
}

std::set<device::mojom::XRSessionFeature>
OpenXrSceneUnderstandingManagerAndroidFactory::GetSupportedFeatures(
    const OpenXrExtensionEnumeration* extension_enum) const {
  if (!IsEnabled(extension_enum)) {
    return {};
  }

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

std::unique_ptr<OpenXRSceneUnderstandingManager>
OpenXrSceneUnderstandingManagerAndroidFactory::CreateSceneUnderstandingManager(
    const OpenXrExtensionHelper& extension_helper,
    XrSession session,
    XrSpace mojo_space) const {
  bool is_supported = IsEnabled(extension_helper.ExtensionEnumeration());
  DVLOG(2) << __func__ << " is_supported=" << is_supported;
  if (is_supported) {
    return std::make_unique<OpenXRSceneUnderstandingManagerAndroid>(
        extension_helper, session, mojo_space);
  }

  return nullptr;
}
}  // namespace device