chromium/device/vr/openxr/test/openxr_test_helper.h

// Copyright 2019 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#ifndef DEVICE_VR_OPENXR_TEST_OPENXR_TEST_HELPER_H_
#define DEVICE_VR_OPENXR_TEST_OPENXR_TEST_HELPER_H_

#include <array>
#include <optional>
#include <queue>
#include <unordered_map>
#include <unordered_set>
#include <vector>

#include "base/memory/raw_ptr_exclusion.h"
#include "base/synchronization/lock.h"
#include "device/vr/openxr/openxr_platform.h"
#include "device/vr/openxr/openxr_view_configuration.h"
#include "device/vr/test/test_hook.h"
#include "third_party/openxr/src/include/openxr/openxr.h"

#if BUILDFLAG(IS_WIN)
#include <wrl.h>
#endif

namespace gfx {
class Transform;
}  // namespace gfx

class OpenXrTestHelper : public device::ServiceTestHook {
 public:
  OpenXrTestHelper();
  ~OpenXrTestHelper();

  // Because the test helper isn't intended to be recreated, even if an instance
  // is destroyed, this should be called whenever a session is/would have been
  // terminated regardless of the path it took to be terminated; otherwise, it
  // may not be possible to request a new session.
  void Reset();
  void TestFailure();

  // TestHookRegistration
  void SetTestHook(device::VRTestHook* hook) final;

  // Helper methods called by the mock OpenXR runtime. These methods will
  // call back into the test hook, thus communicating with the test object
  // on the browser process side.
  void OnPresentedFrame();

  // Helper methods called by the mock OpenXR runtime to query or set the
  // state of the runtime.

  XrSystemId GetSystemId();
  XrSystemProperties GetSystemProperties();

  XrSwapchain CreateSwapchain();
  XrResult DestroySwapchain(XrSwapchain);
  XrInstance CreateInstance();
  XrResult DestroyInstance(XrInstance instance);
  XrResult CreateSession(XrSession* session);
  XrResult DestroySession(XrSession session);
  XrResult GetActionStateFloat(XrAction action, XrActionStateFloat* data) const;
  XrResult GetActionStateBoolean(XrAction action,
                                 XrActionStateBoolean* data) const;
  XrResult GetActionStateVector2f(XrAction action,
                                  XrActionStateVector2f* data) const;
  XrResult GetActionStatePose(XrAction action, XrActionStatePose* data) const;
  XrResult CreateActionSpace(
      const XrActionSpaceCreateInfo& action_space_create_info,
      XrSpace* space);
  XrSpace CreateReferenceSpace(XrReferenceSpaceType type);
  XrResult DestroySpace(XrSpace space);
  XrResult CreateAction(XrActionSet action_set,
                        const XrActionCreateInfo& create_info,
                        XrAction* action);
  XrActionSet CreateActionSet(const XrActionSetCreateInfo& create_info);
  XrResult DestroyActionSet(XrActionSet action_set);
  XrPath GetPath(std::string path_string);
  XrPath GetCurrentInteractionProfile();
  XrHandTrackerEXT CreateHandTracker(XrHandEXT hand);
  XrResult DestroyHandTracker(XrHandTrackerEXT hand_tracker);

  device::OpenXrViewConfiguration& GetViewConfigInfo(
      XrViewConfigurationType view_config);
  std::vector<XrViewConfigurationType> SupportedViewConfigs() const;
  XrResult GetSecondaryConfigStates(
      uint32_t count,
      XrSecondaryViewConfigurationStateMSFT* states) const;
  XrViewConfigurationType PrimaryViewConfig() const;

  XrResult BeginSession(
      const std::vector<XrViewConfigurationType>& view_configs);
  XrResult EndSession();
  XrResult BeginFrame();
  XrResult EndFrame();
  XrSession session() { return session_; }

  XrResult BindActionAndPath(XrPath interaction_profile_path,
                             XrActionSuggestedBinding binding);

  void SetD3DDevice(ID3D11Device* d3d_device);
  XrResult AttachActionSets(const XrSessionActionSetsAttachInfo& attach_info);
  uint32_t AttachedActionSetsSize() const;
  XrResult SyncActionData(XrActionSet action_set);
  const std::vector<Microsoft::WRL::ComPtr<ID3D11Texture2D>>&
  GetSwapchainTextures() const;
  void LocateSpace(XrSpace space, XrPosef* pose);
  std::string PathToString(XrPath path) const;
  bool UpdateData();
  bool UpdateViews(XrViewConfigurationType view_config_type,
                   XrView views[],
                   uint32_t size);

  uint32_t NextSwapchainImageIndex();
  XrTime NextPredictedDisplayTime();

  void UpdateEventQueue();
  XrResult PollEvent(XrEventDataBuffer* event_data);

  // Methods that validate the parameter with the current state of the runtime.
  XrResult ValidateAction(XrAction action) const;
  XrResult ValidateActionCreateInfo(
      const XrActionCreateInfo& create_info) const;
  XrResult ValidateActionSet(XrActionSet action_set) const;
  XrResult ValidateActionSetCreateInfo(
      const XrActionSetCreateInfo& create_info) const;
  XrResult ValidateActionSetNotAttached(XrActionSet action_set) const;
  XrResult ValidateActionSpaceCreateInfo(
      const XrActionSpaceCreateInfo& create_info) const;
  XrResult ValidateHandTracker(XrHandTrackerEXT hand_tracker) const;
  XrResult ValidateInstance(XrInstance instance) const;
  XrResult ValidateSystemId(XrSystemId system_id) const;
  XrResult ValidateSession(XrSession session) const;
  XrResult ValidateSwapchain(XrSwapchain swapchain) const;
  XrResult ValidateSpace(XrSpace space) const;
  XrResult ValidatePath(XrPath path) const;
  XrResult ValidatePredictedDisplayTime(XrTime time) const;
  XrResult ValidateXrCompositionLayerProjection(
      XrViewConfigurationType view_config,
      const XrCompositionLayerProjection& projection_layer);
  XrResult ValidateXrPosefIsIdentity(const XrPosef& pose) const;
  XrResult ValidateViews(uint32_t view_capacity_input, XrView* views) const;
  XrResult ValidateViewConfigType(XrViewConfigurationType view_config) const;

  // Properties of the mock OpenXR runtime that do not change are created
  static constexpr const char* const kExtensions[] = {
      XR_KHR_D3D11_ENABLE_EXTENSION_NAME,
      XR_EXT_WIN32_APPCONTAINER_COMPATIBLE_EXTENSION_NAME,
      XR_EXT_SAMSUNG_ODYSSEY_CONTROLLER_EXTENSION_NAME,
      XR_EXT_HP_MIXED_REALITY_CONTROLLER_EXTENSION_NAME,
      XR_MSFT_HAND_INTERACTION_EXTENSION_NAME,
      XR_EXT_HAND_INTERACTION_EXTENSION_NAME,
      XR_HTC_VIVE_COSMOS_CONTROLLER_INTERACTION_EXTENSION_NAME,
      XR_MSFT_SECONDARY_VIEW_CONFIGURATION_EXTENSION_NAME,
      XR_EXT_HAND_TRACKING_EXTENSION_NAME,
  };

  static constexpr uint32_t kPrimaryViewDimension = 128;
  static constexpr uint32_t kSecondaryViewDimension = 64;

  static constexpr uint32_t kSwapCount = 1;
  static constexpr uint32_t kMinSwapchainBuffering = 3;

  static constexpr XrEnvironmentBlendMode kEnvironmentBlendMode =
      XR_ENVIRONMENT_BLEND_MODE_OPAQUE;
  static constexpr const char* kLocalReferenceSpacePath =
      "/reference_space/local";
  static constexpr const char* kStageReferenceSpacePath =
      "/reference_space/stage";
  static constexpr const char* kViewReferenceSpacePath =
      "/reference_space/view";
  static constexpr const char* kUnboundedReferenceSpacePath =
      "/reference_space/unbounded";
  static constexpr XrSystemProperties kSystemProperties = {
      XR_TYPE_SYSTEM_PROPERTIES, nullptr,           0, 0xBADFACE, "Test System",
      {2048, 2048, 1},           {XR_TRUE, XR_TRUE}};

  static constexpr uint32_t kNumExtensionsSupported = std::size(kExtensions);

 private:
  struct ActionProperties {
    std::unordered_map<XrPath, XrPath> profile_binding_map;
    XrActionType type;
    ActionProperties();
    ~ActionProperties();
    ActionProperties(const ActionProperties& other);
  };

  void CopyTextureDataIntoFrameData(uint32_t x_start, device::ViewData& data);
  void ReinitializeTextures();
  void CreateTextures(uint32_t width, uint32_t height);
  void AddDimensions(const device::OpenXrViewConfiguration& view_config,
                     uint32_t& width,
                     uint32_t& height) const;
  XrResult UpdateAction(XrAction action);
  void SetSessionState(XrSessionState state);
  std::optional<gfx::Transform> GetPose();
  std::optional<device::DeviceConfig> GetDeviceConfig();
  device::ControllerFrameData GetControllerDataFromPath(
      std::string path_string) const;
  void UpdateInteractionProfile(
      device::mojom::OpenXrInteractionProfileType type);
  bool IsSessionRunning() const;
  XrResult ValidateXrCompositionLayerProjectionView(
      const XrCompositionLayerProjectionView& projection_view,
      uint32_t view_count,
      uint32_t index);
  bool GetCanCreateSession();

  // Properties of the mock OpenXR runtime that doesn't change throughout the
  // lifetime of the instance. However, these aren't static because they are
  // initialized to an invalid value and set to their actual value in their
  // respective Get*/Create* functions. This allows these variables to be used
  // to validate that they were queried before being used.
  XrSystemId system_id_;
  XrSession session_;
  XrSwapchain swapchain_;
  XrHandTrackerEXT left_hand_;
  XrHandTrackerEXT right_hand_;

  // Properties that changes depending on the state of the runtime.
  uint32_t frame_count_ = 0;
  XrSessionState session_state_;
  bool frame_begin_;
  Microsoft::WRL::ComPtr<ID3D11Device> d3d_device_;
  std::vector<Microsoft::WRL::ComPtr<ID3D11Texture2D>> textures_arr_;
  uint32_t acquired_swapchain_texture_;
  uint32_t next_handle_;
  XrTime next_predicted_display_time_;
  std::string interaction_profile_;

  // paths_ is used to keep tracked of strings that already has a corresponding
  // path.
  std::vector<std::string> paths_;
  // ActionProperties has an
  // index_in_action_state_arr member which can help index into the following
  // *_action_states_ vector and retrieve data.
  std::unordered_map<XrAction, ActionProperties> actions_;
  std::unordered_map<XrSpace, XrAction> action_spaces_;
  std::unordered_map<XrSpace, std::string> reference_spaces_;
  std::unordered_map<XrActionSet, std::vector<XrAction>> action_sets_;
  std::unordered_map<XrActionSet, std::vector<XrAction>> attached_action_sets_;
  std::unordered_map<XrAction, XrActionStateFloat> float_action_states_;
  std::unordered_map<XrAction, XrActionStateBoolean> boolean_action_states_;
  std::unordered_map<XrAction, XrActionStateVector2f> v2f_action_states_;
  std::unordered_map<XrAction, XrActionStatePose> pose_action_state_;

  // action_names_, action_localized_names_, action_set_names_,
  // action_set_localized_names_ are used to make sure that there won't be any
  // duplicate which is specified in the spec. They are all independent.
  std::unordered_set<std::string> action_names_;
  std::unordered_set<std::string> action_localized_names_;
  std::unordered_set<std::string> action_set_names_;
  std::unordered_set<std::string> action_set_localized_names_;

  // View configurations that were requested by the client when the session
  // begins. There should only be one supported primary view configurations and
  // any number of secondary view configurations.
  std::vector<XrViewConfigurationType> view_configs_enabled_;

  // All view configurations that we currently support, including ones not
  // requested by the client.
  std::unordered_map<XrViewConfigurationType, device::OpenXrViewConfiguration>
      primary_configs_supported_;
  std::unordered_map<XrViewConfigurationType, device::OpenXrViewConfiguration>
      secondary_configs_supported_;

  std::array<device::ControllerFrameData, device::kMaxTrackedDevices> data_arr_;

  std::queue<XrEventDataBuffer> event_queue_;

  // This field is not a raw_ptr<> because it was filtered by the rewriter for:
  // #global-scope
  RAW_PTR_EXCLUSION device::VRTestHook* test_hook_ GUARDED_BY(lock_) = nullptr;
  base::Lock lock_;
};

#endif  // DEVICE_VR_OPENXR_TEST_OPENXR_TEST_HELPER_H_