Add OpenXR to xr_browser_tests infrastructure and enable tests
This change runs most of the existing OpenVR/WMR tests with
OpenXR by adding OpenXR to the WEBXR_VR_ALL_RUNTIMES* macros.
Remaining tests to add are addressed in:
crbug.com/986637 (input)
crbug.com/986621 (drawing pixels)
Bug: 976434
Change-Id: I6b0a48d37d1380ba50b97b2d97d5b7accb9c495b
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/1714304
Commit-Queue: Patrick To <patrto@microsoft.com>
Reviewed-by: Brian Sheedy <bsheedy@chromium.org>
Reviewed-by: Alexander Cooper <alcooper@chromium.org>
Cr-Commit-Position: refs/heads/master@{#683403}
diff --git a/chrome/browser/vr/BUILD.gn b/chrome/browser/vr/BUILD.gn
index e5cbac3c..4533f89d 100644
--- a/chrome/browser/vr/BUILD.gn
+++ b/chrome/browser/vr/BUILD.gn
@@ -775,6 +775,11 @@
]
data_deps += [ "//device/vr:openvr_mock" ]
+
+ if (enable_openxr) {
+ deps += [ "//third_party/openxr" ]
+ data_deps += [ "//device/vr:openxr_mock" ]
+ }
}
}
diff --git a/chrome/browser/vr/test/multi_class_browser_test.h b/chrome/browser/vr/test/multi_class_browser_test.h
index 8aeb9c6..05ac882 100644
--- a/chrome/browser/vr/test/multi_class_browser_test.h
+++ b/chrome/browser/vr/test/multi_class_browser_test.h
@@ -6,6 +6,7 @@
#define CHROME_BROWSER_VR_TEST_MULTI_CLASS_BROWSER_TEST_H_
#include "content/public/test/browser_test.h"
+#include "device/vr/buildflags/buildflags.h"
#include "testing/gtest/include/gtest/gtest.h"
// A collection of macros to automatically run a browser test multiple times
@@ -70,6 +71,15 @@
void MULTI_CLASS_RUNNER_NAME_(test_name)::ActuallyRunTestOnMainThread( \
base_class* t)
+#define IN_PROC_MULTI_CLASS_BROWSER_TEST_F3( \
+ test_class1, test_class2, test_class3, base_class, test_name) \
+ DEFINE_RUN_TEST_IMPL_(test_name, base_class) \
+ DEFINE_BROWSER_TEST_(test_class1, test_name) \
+ DEFINE_BROWSER_TEST_(test_class2, test_name) \
+ DEFINE_BROWSER_TEST_(test_class3, test_name) \
+ void MULTI_CLASS_RUNNER_NAME_(test_name)::ActuallyRunTestOnMainThread( \
+ base_class* t)
+
#define IN_PROC_MULTI_CLASS_PLUS_INCOGNITO_BROWSER_TEST_F2( \
test_class1, test_class2, base_class, test_name) \
DEFINE_RUN_TEST_IMPL_(test_name, base_class) \
@@ -80,19 +90,45 @@
void MULTI_CLASS_RUNNER_NAME_(test_name)::ActuallyRunTestOnMainThread( \
base_class* t)
+#define IN_PROC_MULTI_CLASS_PLUS_INCOGNITO_BROWSER_TEST_F3( \
+ test_class1, test_class2, test_class3, base_class, test_name) \
+ DEFINE_RUN_TEST_IMPL_(test_name, base_class) \
+ DEFINE_BROWSER_TEST_(test_class1, test_name) \
+ DEFINE_BROWSER_TEST_(test_class2, test_name) \
+ DEFINE_BROWSER_TEST_(test_class3, test_name) \
+ DEFINE_INCOGNITO_BROWSER_TEST_(test_class1, test_name) \
+ DEFINE_INCOGNITO_BROWSER_TEST_(test_class2, test_name) \
+ DEFINE_INCOGNITO_BROWSER_TEST_(test_class3, test_name) \
+ void MULTI_CLASS_RUNNER_NAME_(test_name)::ActuallyRunTestOnMainThread( \
+ base_class* t)
+
// Helper macro to cut down on duplicate code since most uses of
-// IN_PROC_MULTI_CLASS_BROWSER_TEST_F2 are passed the same OpenVR and WMR
-// classes and the same base class
+// IN_PROC_MULTI_CLASS_BROWSER_TEST_F3 are passed the same OpenVR, WMR, and
+// OpenXR classes and the same base class
+#if BUILDFLAG(ENABLE_OPENXR)
+#define WEBXR_VR_ALL_RUNTIMES_BROWSER_TEST_F(test_name) \
+ IN_PROC_MULTI_CLASS_BROWSER_TEST_F3( \
+ WebXrVrOpenVrBrowserTest, WebXrVrWmrBrowserTest, \
+ WebXrVrOpenXrBrowserTest, WebXrVrBrowserTestBase, test_name)
+#else
#define WEBXR_VR_ALL_RUNTIMES_BROWSER_TEST_F(test_name) \
IN_PROC_MULTI_CLASS_BROWSER_TEST_F2(WebXrVrOpenVrBrowserTest, \
WebXrVrWmrBrowserTest, \
WebXrVrBrowserTestBase, test_name)
+#endif // BUILDFLAG(ENABLE_OPENXR)
// The same as WEBXR_VR_ALL_RUNTIMES_BROWSER_TEST_F, but runs the tests in
// incognito mode as well.
+#if BUILDFLAG(ENABLE_OPENXR)
+#define WEBXR_VR_ALL_RUNTIMES_PLUS_INCOGNITO_BROWSER_TEST_F(test_name) \
+ IN_PROC_MULTI_CLASS_PLUS_INCOGNITO_BROWSER_TEST_F3( \
+ WebXrVrOpenVrBrowserTest, WebXrVrWmrBrowserTest, \
+ WebXrVrOpenXrBrowserTest, WebXrVrBrowserTestBase, test_name)
+#else
#define WEBXR_VR_ALL_RUNTIMES_PLUS_INCOGNITO_BROWSER_TEST_F(test_name) \
IN_PROC_MULTI_CLASS_PLUS_INCOGNITO_BROWSER_TEST_F2( \
WebXrVrOpenVrBrowserTest, WebXrVrWmrBrowserTest, WebXrVrBrowserTestBase, \
test_name)
+#endif // ENABLE_OPENXR
#endif // CHROME_BROWSER_VR_TEST_MULTI_CLASS_BROWSER_TEST_H_
diff --git a/chrome/browser/vr/test/webxr_vr_browser_test.cc b/chrome/browser/vr/test/webxr_vr_browser_test.cc
index 863b159..6da016f 100644
--- a/chrome/browser/vr/test/webxr_vr_browser_test.cc
+++ b/chrome/browser/vr/test/webxr_vr_browser_test.cc
@@ -14,6 +14,10 @@
namespace vr {
+WebXrVrBrowserTestBase::WebXrVrBrowserTestBase() {
+ enable_features_.push_back(features::kWebXr);
+}
+
void WebXrVrBrowserTestBase::EnterSessionWithUserGesture(
content::WebContents* web_contents) {
#if defined(OS_WIN)
@@ -69,7 +73,26 @@
return gfx::Vector3dF();
}
+WebXrVrRuntimelessBrowserTest::WebXrVrRuntimelessBrowserTest() {
+#if BUILDFLAG(ENABLE_WINDOWS_MR)
+ disable_features_.push_back(features::kWindowsMixedReality);
+#endif
+}
+
+WebXrVrRuntimelessBrowserTestSensorless::
+ WebXrVrRuntimelessBrowserTestSensorless() {
+ disable_features_.push_back(device::kWebXrOrientationSensorDevice);
+}
+
#if defined(OS_WIN)
+
+WebXrVrOpenVrBrowserTestBase::WebXrVrOpenVrBrowserTestBase() {
+ enable_features_.push_back(features::kOpenVR);
+#if BUILDFLAG(ENABLE_WINDOWS_MR)
+ disable_features_.push_back(features::kWindowsMixedReality);
+#endif
+}
+
XrBrowserTestBase::RuntimeType WebXrVrOpenVrBrowserTestBase::GetRuntimeType()
const {
return XrBrowserTestBase::RuntimeType::RUNTIME_OPENVR;
@@ -95,6 +118,56 @@
const {
return XrBrowserTestBase::RuntimeType::RUNTIME_WMR;
}
+
+#if BUILDFLAG(ENABLE_OPENXR)
+
+WebXrVrOpenXrBrowserTestBase::WebXrVrOpenXrBrowserTestBase() {
+ enable_features_.push_back(features::kOpenXR);
+#if BUILDFLAG(ENABLE_WINDOWS_MR)
+ disable_features_.push_back(features::kWindowsMixedReality);
+#endif
+}
+
+WebXrVrOpenXrBrowserTestBase::~WebXrVrOpenXrBrowserTestBase() = default;
+
+XrBrowserTestBase::RuntimeType WebXrVrOpenXrBrowserTestBase::GetRuntimeType()
+ const {
+ return XrBrowserTestBase::RuntimeType::RUNTIME_OPENXR;
+}
+#endif // BUILDFLAG(ENABLE_OPENXR)
+
+WebXrVrOpenVrBrowserTest::WebXrVrOpenVrBrowserTest() {
+ // We know at this point that we're going to be running with both OpenVR and
+ // WebXR enabled, so enforce the DirectX 11.1 requirement.
+ runtime_requirements_.push_back(XrTestRequirement::DIRECTX_11_1);
+}
+
+WebXrVrWmrBrowserTest::WebXrVrWmrBrowserTest() {
+ // WMR already enabled by default.
+ runtime_requirements_.push_back(XrTestRequirement::DIRECTX_11_1);
+}
+
+#if BUILDFLAG(ENABLE_OPENXR)
+WebXrVrOpenXrBrowserTest::WebXrVrOpenXrBrowserTest() {
+ runtime_requirements_.push_back(XrTestRequirement::DIRECTX_11_1);
+}
+#endif // BUILDFLAG(ENABLE_OPENXR)
+
+// Test classes with WebXR disabled.
+WebXrVrOpenVrBrowserTestWebXrDisabled::WebXrVrOpenVrBrowserTestWebXrDisabled() {
+ disable_features_.push_back(features::kWebXr);
+}
+
+WebXrVrWmrBrowserTestWebXrDisabled::WebXrVrWmrBrowserTestWebXrDisabled() {
+ disable_features_.push_back(features::kWebXr);
+}
+
+#if BUILDFLAG(ENABLE_OPENXR)
+WebXrVrOpenXrBrowserTestWebXrDisabled::WebXrVrOpenXrBrowserTestWebXrDisabled() {
+ disable_features_.push_back(features::kWebXr);
+}
+#endif // BUIDFLAG(ENABLE_OPENXR)
+
#endif // OS_WIN
} // namespace vr
diff --git a/chrome/browser/vr/test/webxr_vr_browser_test.h b/chrome/browser/vr/test/webxr_vr_browser_test.h
index 71fcc1e..4c37eddd 100644
--- a/chrome/browser/vr/test/webxr_vr_browser_test.h
+++ b/chrome/browser/vr/test/webxr_vr_browser_test.h
@@ -27,7 +27,7 @@
// WebXR for VR-specific test base class without any particular runtime.
class WebXrVrBrowserTestBase : public WebXrBrowserTestBase {
public:
- WebXrVrBrowserTestBase() { enable_features_.push_back(features::kWebXr); }
+ WebXrVrBrowserTestBase();
void EnterSessionWithUserGesture(content::WebContents* web_contents) override;
void EnterSessionWithUserGestureOrFail(
content::WebContents* web_contents) override;
@@ -52,11 +52,7 @@
// Test class with OpenVR disabled.
class WebXrVrRuntimelessBrowserTest : public WebXrVrBrowserTestBase {
public:
- WebXrVrRuntimelessBrowserTest() {
-#if BUILDFLAG(ENABLE_WINDOWS_MR)
- disable_features_.push_back(features::kWindowsMixedReality);
-#endif
- }
+ WebXrVrRuntimelessBrowserTest();
};
// WebXrOrientationSensorDevice is only defined when the enable_vr flag is set.
@@ -64,9 +60,7 @@
class WebXrVrRuntimelessBrowserTestSensorless
: public WebXrVrRuntimelessBrowserTest {
public:
- WebXrVrRuntimelessBrowserTestSensorless() {
- disable_features_.push_back(device::kWebXrOrientationSensorDevice);
- }
+ WebXrVrRuntimelessBrowserTestSensorless();
};
#endif // BUILDFLAG(ENABLE_VR)
@@ -75,12 +69,7 @@
// OpenVR-specific subclass of WebXrVrBrowserTestBase.
class WebXrVrOpenVrBrowserTestBase : public WebXrVrBrowserTestBase {
public:
- WebXrVrOpenVrBrowserTestBase() {
- enable_features_.push_back(features::kOpenVR);
-#if BUILDFLAG(ENABLE_WINDOWS_MR)
- disable_features_.push_back(features::kWindowsMixedReality);
-#endif
- }
+ WebXrVrOpenVrBrowserTestBase();
XrBrowserTestBase::RuntimeType GetRuntimeType() const override;
gfx::Vector3dF GetControllerOffset() const override;
};
@@ -102,39 +91,54 @@
std::unique_ptr<MockXRDeviceHookBase> dummy_hook_;
};
+#if BUILDFLAG(ENABLE_OPENXR)
+// OpenXR-specific subclass of WebXrVrBrowserTestBase.
+class WebXrVrOpenXrBrowserTestBase : public WebXrVrBrowserTestBase {
+ public:
+ WebXrVrOpenXrBrowserTestBase();
+ ~WebXrVrOpenXrBrowserTestBase() override;
+ XrBrowserTestBase::RuntimeType GetRuntimeType() const override;
+};
+#endif // BUILDFLAG(ENABLE_OPENXR)
+
// Test class with standard features enabled: WebXR and OpenVR.
class WebXrVrOpenVrBrowserTest : public WebXrVrOpenVrBrowserTestBase {
public:
- WebXrVrOpenVrBrowserTest() {
- // We know at this point that we're going to be running with both OpenVR and
- // WebXR enabled, so enforce the DirectX 11.1 requirement.
- runtime_requirements_.push_back(XrTestRequirement::DIRECTX_11_1);
- }
+ WebXrVrOpenVrBrowserTest();
};
class WebXrVrWmrBrowserTest : public WebXrVrWmrBrowserTestBase {
public:
- WebXrVrWmrBrowserTest() {
- // WMR already enabled by default.
- runtime_requirements_.push_back(XrTestRequirement::DIRECTX_11_1);
- }
+ WebXrVrWmrBrowserTest();
};
+#if BUILDFLAG(ENABLE_OPENXR)
+class WebXrVrOpenXrBrowserTest : public WebXrVrOpenXrBrowserTestBase {
+ public:
+ WebXrVrOpenXrBrowserTest();
+};
+#endif // BUILDFLAG(ENABLE_OPENXR)
+
// Test classes with WebXR disabled.
class WebXrVrOpenVrBrowserTestWebXrDisabled
: public WebXrVrOpenVrBrowserTestBase {
public:
- WebXrVrOpenVrBrowserTestWebXrDisabled() {
- disable_features_.push_back(features::kWebXr);
- }
+ WebXrVrOpenVrBrowserTestWebXrDisabled();
};
class WebXrVrWmrBrowserTestWebXrDisabled : public WebXrVrWmrBrowserTestBase {
public:
- WebXrVrWmrBrowserTestWebXrDisabled() {
- disable_features_.push_back(features::kWebXr);
- }
+ WebXrVrWmrBrowserTestWebXrDisabled();
};
+
+#if BUILDFLAG(ENABLE_OPENXR)
+class WebXrVrOpenXrBrowserTestWebXrDisabled
+ : public WebXrVrOpenXrBrowserTestBase {
+ public:
+ WebXrVrOpenXrBrowserTestWebXrDisabled();
+};
+#endif // BUIDFLAG(ENABLE_OPENXR)
+
#endif // OS_WIN
} // namespace vr
diff --git a/chrome/browser/vr/test/xr_browser_test.cc b/chrome/browser/vr/test/xr_browser_test.cc
index 2cac9c3..648c624 100644
--- a/chrome/browser/vr/test/xr_browser_test.cc
+++ b/chrome/browser/vr/test/xr_browser_test.cc
@@ -41,6 +41,8 @@
constexpr char XrBrowserTestBase::kVrConfigPathVal[];
constexpr char XrBrowserTestBase::kVrLogPathEnvVar[];
constexpr char XrBrowserTestBase::kVrLogPathVal[];
+constexpr char XrBrowserTestBase::kOpenXrConfigPathEnvVar[];
+constexpr char XrBrowserTestBase::kOpenXrConfigPathVal[];
constexpr char XrBrowserTestBase::kTestFileDir[];
constexpr char XrBrowserTestBase::kSwitchIgnoreRuntimeRequirements[];
const std::vector<std::string> XrBrowserTestBase::kRequiredTestSwitches{
@@ -135,6 +137,16 @@
env_->SetVar(kVrLogPathEnvVar, MakeExecutableRelative(kVrLogPathVal)))
<< "Failed to set OpenVR log location environment variable";
+ // Set the environment variable to use the mock OpenXR client.
+ // If the kOpenXrConfigPathEnvVar environment variable is set, the OpenXR
+ // loader will look for the OpenXR runtime specified in that json file. The
+ // json file contains the path to the runtime, relative to the json file
+ // itself. Otherwise, the OpenXR loader loads the active OpenXR runtime
+ // installed on the system, which is specified by a registry key.
+ ASSERT_TRUE(env_->SetVar(kOpenXrConfigPathEnvVar,
+ MakeExecutableRelative(kOpenXrConfigPathVal)))
+ << "Failed to set OpenXR JSON location environment variable";
+
// Set any command line flags that subclasses have set, e.g. enabling WebVR
// and OpenVR support.
for (const auto& switch_string : append_switches_) {
@@ -164,6 +176,7 @@
case XrBrowserTestBase::RuntimeType::RUNTIME_OPENVR:
return device::XrAxisType::kTrackpad;
case XrBrowserTestBase::RuntimeType::RUNTIME_WMR:
+ case XrBrowserTestBase::RuntimeType::RUNTIME_OPENXR:
return device::XrAxisType::kJoystick;
case XrBrowserTestBase::RuntimeType::RUNTIME_NONE:
return device::XrAxisType::kNone;
@@ -177,6 +190,7 @@
case XrBrowserTestBase::RuntimeType::RUNTIME_OPENVR:
return device::XrAxisType::kJoystick;
case XrBrowserTestBase::RuntimeType::RUNTIME_WMR:
+ case XrBrowserTestBase::RuntimeType::RUNTIME_OPENXR:
return device::XrAxisType::kTrackpad;
case XrBrowserTestBase::RuntimeType::RUNTIME_NONE:
return device::XrAxisType::kNone;
diff --git a/chrome/browser/vr/test/xr_browser_test.h b/chrome/browser/vr/test/xr_browser_test.h
index ad29ac4c..f321f5c 100644
--- a/chrome/browser/vr/test/xr_browser_test.h
+++ b/chrome/browser/vr/test/xr_browser_test.h
@@ -55,6 +55,9 @@
static constexpr char kVrConfigPathVal[] = "./";
static constexpr char kVrLogPathEnvVar[] = "VR_LOG_PATH";
static constexpr char kVrLogPathVal[] = "./";
+ static constexpr char kOpenXrConfigPathEnvVar[] = "XR_RUNTIME_JSON";
+ static constexpr char kOpenXrConfigPathVal[] =
+ "./mock_vr_clients/bin/openxr/openxr.json";
static constexpr char kTestFileDir[] =
"chrome/test/data/xr/e2e_test_files/html/";
static constexpr char kSwitchIgnoreRuntimeRequirements[] =
@@ -71,7 +74,8 @@
enum class RuntimeType {
RUNTIME_NONE = 0,
RUNTIME_OPENVR = 1,
- RUNTIME_WMR = 2
+ RUNTIME_WMR = 2,
+ RUNTIME_OPENXR = 3
};
XrBrowserTestBase();
diff --git a/chrome/browser/vr/webxr_vr_frame_pose_browser_test.cc b/chrome/browser/vr/webxr_vr_frame_pose_browser_test.cc
index dcf6660..75ecdb5 100644
--- a/chrome/browser/vr/webxr_vr_frame_pose_browser_test.cc
+++ b/chrome/browser/vr/webxr_vr_frame_pose_browser_test.cc
@@ -159,6 +159,7 @@
} // namespace
+// TODO(crbug.com/986621) - OpenXR currently hard codes data
// Pixel test for WebXR - start presentation, submit frames, get data back out.
// Validates that submitted frames used expected pose.
WEBXR_VR_ALL_RUNTIMES_BROWSER_TEST_F(TestPresentationPoses) {
diff --git a/chrome/browser/vr/webxr_vr_indicators_browser_test.cc b/chrome/browser/vr/webxr_vr_indicators_browser_test.cc
index 7ba1d1e..435f707 100644
--- a/chrome/browser/vr/webxr_vr_indicators_browser_test.cc
+++ b/chrome/browser/vr/webxr_vr_indicators_browser_test.cc
@@ -160,7 +160,11 @@
UserFriendlyElementName::kWebXrLocationPermissionIndicator, false}});
}
-WEBXR_VR_ALL_RUNTIMES_BROWSER_TEST_F(
+// TODO(crbug.com/986621) - Enable for OpenXR
+IN_PROC_MULTI_CLASS_BROWSER_TEST_F2(
+ WebXrVrOpenVrBrowserTest,
+ WebXrVrWmrBrowserTest,
+ WebXrVrBrowserTestBase,
TestLocationIndicatorWhenUserAskedToPrompt) {
TestForInitialIndicatorForContentType(
t, {{CONTENT_SETTINGS_TYPE_GEOLOCATION, CONTENT_SETTING_ASK,
@@ -182,7 +186,12 @@
});
}
-WEBXR_VR_ALL_RUNTIMES_BROWSER_TEST_F(
+// TODO(crbug.com/986621) - Enable for OpenXR
+IN_PROC_MULTI_CLASS_BROWSER_TEST_F2(
+ WebXrVrOpenVrBrowserTest,
+ WebXrVrWmrBrowserTest,
+ WebXrVrBrowserTestBase,
+
TestMultipleInitialIndicators_OneDeviceAllowed) {
TestForInitialIndicatorForContentType(
t,
diff --git a/chrome/browser/vr/webxr_vr_input_browser_test.cc b/chrome/browser/vr/webxr_vr_input_browser_test.cc
index 38d30db1..b06e14a3 100644
--- a/chrome/browser/vr/webxr_vr_input_browser_test.cc
+++ b/chrome/browser/vr/webxr_vr_input_browser_test.cc
@@ -214,9 +214,13 @@
std::move(callback).Run();
}
+// TODO(crbug.com/986637) - Enable for OpenXR
// Ensure that when an input source's handedness changes, an input source change
// event is fired and a new input source is created.
-WEBXR_VR_ALL_RUNTIMES_BROWSER_TEST_F(TestInputHandednessChange) {
+IN_PROC_MULTI_CLASS_BROWSER_TEST_F2(WebXrVrOpenVrBrowserTest,
+ WebXrVrWmrBrowserTest,
+ WebXrVrBrowserTestBase,
+ TestInputHandednessChange) {
WebXrControllerInputMock my_mock;
unsigned int controller_index =
my_mock.CreateAndConnectMinimalGamepad(t->GetPrimaryAxisType());
@@ -254,12 +258,16 @@
t->EndTest();
}
+// TODO(crbug.com/986637) - Enable for OpenXR
// Test that inputsourceschange events contain only the expected added/removed
// input sources when a mock controller is connected/disconnected.
// Also validates that if an input source changes substantially we get an event
// containing both the removal of the old one and the additon of the new one,
// rather than two events.
-WEBXR_VR_ALL_RUNTIMES_BROWSER_TEST_F(TestInputSourcesChange) {
+IN_PROC_MULTI_CLASS_BROWSER_TEST_F2(WebXrVrOpenVrBrowserTest,
+ WebXrVrWmrBrowserTest,
+ WebXrVrBrowserTestBase,
+ TestInputSourcesChange) {
WebXrControllerInputMock my_mock;
// TODO(crbug.com/963676): Figure out if the race is a product or test bug.
@@ -424,10 +432,14 @@
EndTest();
}
+// TODO(crbug.com/986637) - Enable for OpenXR
// Ensure that if a Gamepad has the minimum required number of axes/buttons to
// be considered an xr-standard Gamepad, that it is exposed as such, and that
// we can check the state of it's priamry axes/button.
-WEBXR_VR_ALL_RUNTIMES_BROWSER_TEST_F(TestGamepadMinimumData) {
+IN_PROC_MULTI_CLASS_BROWSER_TEST_F2(WebXrVrOpenVrBrowserTest,
+ WebXrVrWmrBrowserTest,
+ WebXrVrBrowserTestBase,
+ TestGamepadMinimumData) {
WebXrControllerInputMock my_mock;
unsigned int controller_index =
@@ -467,10 +479,14 @@
t->EndTest();
}
+// TODO(crbug.com/986637) - Enable for OpenXR
// Ensure that if a Gamepad has all of the required and optional buttons as
// specified by the xr-standard mapping, that those buttons are plumbed up
// in their required places.
-WEBXR_VR_ALL_RUNTIMES_BROWSER_TEST_F(TestGamepadCompleteData) {
+IN_PROC_MULTI_CLASS_BROWSER_TEST_F2(WebXrVrOpenVrBrowserTest,
+ WebXrVrWmrBrowserTest,
+ WebXrVrBrowserTestBase,
+ TestGamepadCompleteData) {
WebXrControllerInputMock my_mock;
// Create a controller that supports all reserved buttons.
@@ -682,10 +698,14 @@
EndTest();
}
+// TODO(crbug.com/986637) - Enable for OpenXR
// Test that controller input is registered via WebXR's input method.
// Equivalent to
// WebXrVrInputTest#testControllerClicksRegisteredOnDaydream_WebXr.
-WEBXR_VR_ALL_RUNTIMES_BROWSER_TEST_F(TestControllerInputRegistered) {
+IN_PROC_MULTI_CLASS_BROWSER_TEST_F2(WebXrVrOpenVrBrowserTest,
+ WebXrVrWmrBrowserTest,
+ WebXrVrBrowserTestBase,
+ TestControllerInputRegistered) {
WebXrControllerInputMock my_mock;
unsigned int controller_index =
@@ -768,9 +788,13 @@
return array_string;
}
+// TODO(crbug.com/986637) - Enable for OpenXR
// Test that changes in controller position are properly plumbed through to
// WebXR.
-WEBXR_VR_ALL_RUNTIMES_BROWSER_TEST_F(TestControllerPositionTracking) {
+IN_PROC_MULTI_CLASS_BROWSER_TEST_F2(WebXrVrOpenVrBrowserTest,
+ WebXrVrWmrBrowserTest,
+ WebXrVrBrowserTestBase,
+ TestControllerPositionTracking) {
WebXrControllerInputMock my_mock;
auto controller_data = my_mock.CreateValidController(
diff --git a/chrome/browser/vr/webxr_vr_pixel_browser_test.cc b/chrome/browser/vr/webxr_vr_pixel_browser_test.cc
index 3ee0462d..4ab26731 100644
--- a/chrome/browser/vr/webxr_vr_pixel_browser_test.cc
+++ b/chrome/browser/vr/webxr_vr_pixel_browser_test.cc
@@ -91,6 +91,8 @@
IN_PROC_BROWSER_TEST_F(WebVrOpenVrBrowserTest, TestPresentationPixels) {
TestPresentationPixelsImpl(this, "test_webvr_pixels");
}
+
+// TODO(crbug.com/986621) - OpenXR currently hard codes data
WEBXR_VR_ALL_RUNTIMES_BROWSER_TEST_F(TestPresentationPixels) {
TestPresentationPixelsImpl(t, "test_webxr_pixels");
}
diff --git a/chrome/browser/vr/webxr_vr_transition_browser_test.cc b/chrome/browser/vr/webxr_vr_transition_browser_test.cc
index 3c593c0..4dbdac09 100644
--- a/chrome/browser/vr/webxr_vr_transition_browser_test.cc
+++ b/chrome/browser/vr/webxr_vr_transition_browser_test.cc
@@ -8,6 +8,7 @@
#include "chrome/browser/vr/test/multi_class_browser_test.h"
#include "chrome/browser/vr/test/webxr_vr_browser_test.h"
#include "content/public/test/browser_test_utils.h"
+#include "device/vr/buildflags/buildflags.h"
// Browser test equivalent of
// chrome/android/javatests/src/.../browser/vr/WebXrVrTransitionTest.java.
@@ -53,10 +54,19 @@
TestApiDisabledWithoutFlagSetImpl(this,
"test_webvr_disabled_without_flag_set");
}
+
+#if BUILDFLAG(ENABLE_OPENXR)
+IN_PROC_MULTI_CLASS_BROWSER_TEST_F3(WebXrVrOpenVrBrowserTestWebXrDisabled,
+ WebXrVrWmrBrowserTestWebXrDisabled,
+ WebXrVrOpenXrBrowserTestWebXrDisabled,
+ WebXrVrBrowserTestBase,
+ TestWebXrDisabledWithoutFlagSet) {
+#else
IN_PROC_MULTI_CLASS_BROWSER_TEST_F2(WebXrVrOpenVrBrowserTestWebXrDisabled,
WebXrVrWmrBrowserTestWebXrDisabled,
WebXrVrBrowserTestBase,
TestWebXrDisabledWithoutFlagSet) {
+#endif // BUILDFLAG(ENABLE_OPENXR)
TestApiDisabledWithoutFlagSetImpl(t, "test_webxr_disabled_without_flag_set");
}
diff --git a/chrome/services/isolated_xr_device/BUILD.gn b/chrome/services/isolated_xr_device/BUILD.gn
index 0474d93..33a5b14 100644
--- a/chrome/services/isolated_xr_device/BUILD.gn
+++ b/chrome/services/isolated_xr_device/BUILD.gn
@@ -2,6 +2,8 @@
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
+import("//device/vr/buildflags/buildflags.gni")
+
source_set("lib") {
sources = [
"xr_device_service.cc",
@@ -14,6 +16,10 @@
"xr_test_hook_wrapper.h",
]
+ if (enable_openxr) {
+ configs += [ "//third_party/openxr:config" ]
+ }
+
deps = [
"//base",
"//chrome/common",
diff --git a/chrome/services/isolated_xr_device/xr_service_test_hook.cc b/chrome/services/isolated_xr_device/xr_service_test_hook.cc
index fe5d4f1..a8fcfbd 100644
--- a/chrome/services/isolated_xr_device/xr_service_test_hook.cc
+++ b/chrome/services/isolated_xr_device/xr_service_test_hook.cc
@@ -17,18 +17,27 @@
#include "device/vr/windows_mixed_reality/mixed_reality_statics.h"
#endif // BUILDFLAG(ENABLE_WINDOWS_MR)
+#if BUILDFLAG(ENABLE_OPENXR)
+#include "device/vr/openxr/openxr_api_wrapper.h"
+#endif // BUIDLFLAG(ENABLE_OPENXR)
+
namespace {
void UnsetTestHook(std::unique_ptr<device::XRTestHookWrapper> wrapper) {
+ // Unset the testhook wrapper with the VR runtimes,
+ // so any future calls to them don't use it.
+
#if BUILDFLAG(ENABLE_OPENVR)
- // Unset the testhook wrapper with OpenVR, so any
- // future calls to OpenVR don't use it.
device::OpenVRWrapper::SetTestHook(nullptr);
#endif // BUILDFLAG(ENABLE_OPENVR)
#if BUILDFLAG(ENABLE_WINDOWS_MR)
device::MixedRealityDeviceStatics::SetTestHook(nullptr);
#endif // BUILDFLAG(ENABLE_WINDOWS_MR)
+
+#if BUILDFLAG(ENABLE_OPENXR)
+ device::OpenXrApiWrapper::SetTestHook(nullptr);
+#endif // BUILDFLAG(ENABLE_OPENXR)
}
} // namespace
@@ -43,7 +52,7 @@
hook ? std::make_unique<XRTestHookWrapper>(hook.PassInterface())
: nullptr;
- // Register the wrapper testhook with OpenVR and WMR.
+ // Register the wrapper testhook with the VR runtimes
#if BUILDFLAG(ENABLE_OPENVR)
OpenVRWrapper::SetTestHook(wrapper.get());
#endif // BUILDFLAG(ENABLE_OPENVR)
@@ -52,6 +61,10 @@
MixedRealityDeviceStatics::SetTestHook(wrapper.get());
#endif // BUILDFLAG(ENABLE_WINDOWS_MR)
+#if BUILDFLAG(ENABLE_OPENXR)
+ OpenXrApiWrapper::SetTestHook(wrapper.get());
+#endif // BUILDFLAG(ENABLE_OPENXR)
+
// Store the new wrapper, so we keep it alive.
wrapper_ = std::move(wrapper);
diff --git a/device/vr/BUILD.gn b/device/vr/BUILD.gn
index 946f6bf..34c3dff 100644
--- a/device/vr/BUILD.gn
+++ b/device/vr/BUILD.gn
@@ -332,6 +332,52 @@
}
}
+if (enable_openxr) {
+ # The OpenXR Loader by default looks for the path to the OpenXR Runtime from a
+ # registry key, which typically points to the OpenXR runtime installed on the
+ # system. In test, we want to use the mock OpenXR runtime that is created
+ # below in :openxr_mock. If the XR_RUNTIME_JSON environment variable is set,
+ # the OpenXR loader instead looks for the path to the OpenXR runtime in the
+ # json file instead of the registry key. This json file copied to the output
+ # folder points to our mock OpenXR runtime.
+ copy("json_mock") {
+ sources = [
+ "openxr/test/openxr.json",
+ ]
+ outputs = [
+ "$root_out_dir/mock_vr_clients/bin/openxr/openxr.json",
+ ]
+ }
+
+ shared_library("openxr_mock") {
+ testonly = true
+ output_name = "mock_vr_clients/bin/openxr/openxrruntime"
+
+ sources = [
+ "openxr/openxr_util.cc",
+ "openxr/openxr_util.h",
+ "openxr/test/fake_openxr_impl_api.cc",
+ "openxr/test/openxr_negotiate.h",
+ "openxr/test/openxr_test_helper.cc",
+ "openxr/test/openxr_test_helper.h",
+ "test/test_hook.h",
+ ]
+
+ configs += [ "//third_party/openxr:config" ]
+
+ libs = [
+ "d3d11.lib",
+ "DXGI.lib",
+ ]
+
+ deps = [
+ "//base",
+ "//device/vr:json_mock",
+ "//device/vr/public/mojom:test_mojom",
+ ]
+ }
+}
+
if (enable_gvr_services) {
java_sources_needing_jni =
[ "android/java/src/org/chromium/device/vr/NonPresentingGvrContext.java" ]
diff --git a/device/vr/openxr/openxr_api_wrapper.cc b/device/vr/openxr/openxr_api_wrapper.cc
index 1680c0b..a8ec334 100644
--- a/device/vr/openxr/openxr_api_wrapper.cc
+++ b/device/vr/openxr/openxr_api_wrapper.cc
@@ -12,6 +12,7 @@
#include "base/logging.h"
#include "device/vr/openxr/openxr_gamepad_helper.h"
#include "device/vr/openxr/openxr_util.h"
+#include "device/vr/test/test_hook.h"
#include "ui/gfx/geometry/point3_f.h"
#include "ui/gfx/geometry/quaternion.h"
@@ -130,6 +131,17 @@
}
DCHECK(IsInitialized());
+
+ if (test_hook_) {
+ // Allow our mock implementation of OpenXR to be controlled by tests.
+ // The mock implementation of xrCreateInstance returns a pointer to the
+ // service test hook (g_test_helper) as the instance.
+ service_test_hook_ = reinterpret_cast<ServiceTestHook*>(instance_);
+ service_test_hook_->SetTestHook(test_hook_);
+
+ test_hook_->AttachCurrentThread();
+ }
+
return true;
}
@@ -145,6 +157,9 @@
xrDestroyInstance(instance_);
}
+ if (test_hook_)
+ test_hook_->DetachCurrentThread();
+
Reset();
}
@@ -403,7 +418,7 @@
wait_info.timeout = XR_INFINITE_DURATION;
RETURN_IF_XR_FAILED(xrWaitSwapchainImage(color_swapchain_, &wait_info));
- RETURN_IF_XR_FAILED(UpdateProjectionLayers());
+ RETURN_IF_XR_FAILED(UpdateProjectionLayers(frame_state.predictedDisplayTime));
*texture = color_swapchain_images_[color_swapchain_image_index].texture;
frame_state_ = frame_state;
@@ -445,13 +460,14 @@
return xr_result;
}
-XrResult OpenXrApiWrapper::UpdateProjectionLayers() {
+XrResult OpenXrApiWrapper::UpdateProjectionLayers(
+ XrTime predicted_display_time) {
XrResult xr_result;
XrViewState view_state = {XR_TYPE_VIEW_STATE};
XrViewLocateInfo view_locate_info = {XR_TYPE_VIEW_LOCATE_INFO};
- view_locate_info.displayTime = frame_state_.predictedDisplayTime;
+ view_locate_info.displayTime = predicted_display_time;
view_locate_info.space = local_space_;
uint32_t view_count = 0;
@@ -506,6 +522,9 @@
RETURN_IF_XR_FAILED(xrLocateSpace(
view_space_, local_space_, frame_state_.predictedDisplayTime, &relation));
+ DCHECK(relation.relationFlags & XR_SPACE_RELATION_ORIENTATION_VALID_BIT);
+ DCHECK(relation.relationFlags & XR_SPACE_RELATION_POSITION_VALID_BIT);
+
orientation->set_x(relation.pose.orientation.x);
orientation->set_y(relation.pose.orientation.y);
orientation->set_z(relation.pose.orientation.z);
@@ -559,4 +578,16 @@
->recommendedSwapchainSampleCount;
}
+VRTestHook* OpenXrApiWrapper::test_hook_ = nullptr;
+ServiceTestHook* OpenXrApiWrapper::service_test_hook_ = nullptr;
+void OpenXrApiWrapper::SetTestHook(VRTestHook* hook) {
+ // This may be called from any thread - tests are responsible for
+ // maintaining thread safety, typically by not changing the test hook
+ // while presenting.
+ test_hook_ = hook;
+ if (service_test_hook_) {
+ service_test_hook_->SetTestHook(test_hook_);
+ }
+}
+
} // namespace device
diff --git a/device/vr/openxr/openxr_api_wrapper.h b/device/vr/openxr/openxr_api_wrapper.h
index dcb97426..1fc638dc 100644
--- a/device/vr/openxr/openxr_api_wrapper.h
+++ b/device/vr/openxr/openxr_api_wrapper.h
@@ -12,6 +12,7 @@
#include <vector>
#include "base/macros.h"
+#include "device/vr/vr_export.h"
#include "third_party/openxr/include/openxr/openxr.h"
#include "third_party/openxr/include/openxr/openxr_platform.h"
@@ -23,6 +24,8 @@
namespace device {
class OpenXrGamepadHelper;
+class VRTestHook;
+class ServiceTestHook;
class OpenXrApiWrapper {
public:
@@ -34,6 +37,8 @@
static bool IsHardwareAvailable();
static bool IsApiAvailable();
+ static VRTestHook* GetTestHook();
+
XrResult StartSession(const Microsoft::WRL::ComPtr<ID3D11Device>& d3d_device,
std::unique_ptr<OpenXrGamepadHelper>* gamepad_helper);
@@ -45,6 +50,8 @@
XrTime GetPredictedDisplayTime() const;
+ static void DEVICE_VR_EXPORT SetTestHook(VRTestHook* hook);
+
void GetViewSize(uint32_t* width, uint32_t* height) const;
XrResult GetLuid(LUID* luid) const;
@@ -66,7 +73,7 @@
std::unique_ptr<OpenXrGamepadHelper>* gamepad_helper);
XrResult BeginSession();
- XrResult UpdateProjectionLayers();
+ XrResult UpdateProjectionLayers(XrTime predicted_display_time);
bool HasInstance() const;
bool HasSystem() const;
@@ -78,7 +85,11 @@
uint32_t GetRecommendedSwapchainSampleCount() const;
- // OpenXr objects
+ // Testing objects
+ static VRTestHook* test_hook_;
+ static ServiceTestHook* service_test_hook_;
+
+ // OpenXR objects
// These objects are valid on successful initialization.
XrInstance instance_;
diff --git a/device/vr/openxr/openxr_render_loop.cc b/device/vr/openxr/openxr_render_loop.cc
index 1c656b1..8da8e69 100644
--- a/device/vr/openxr/openxr_render_loop.cc
+++ b/device/vr/openxr/openxr_render_loop.cc
@@ -20,17 +20,16 @@
}
mojom::XRFrameDataPtr OpenXrRenderLoop::GetNextFrameData() {
- Microsoft::WRL::ComPtr<ID3D11Texture2D> texture;
+ mojom::XRFrameDataPtr frame_data = mojom::XRFrameData::New();
+ frame_data->frame_id = next_frame_id_;
+ Microsoft::WRL::ComPtr<ID3D11Texture2D> texture;
if (XR_FAILED(openxr_->BeginFrame(&texture))) {
- return nullptr;
+ return frame_data;
}
texture_helper_.SetBackbuffer(texture.Get());
- mojom::XRFrameDataPtr frame_data = mojom::XRFrameData::New();
- frame_data->frame_id = next_frame_id_;
-
frame_data->time_delta =
base::TimeDelta::FromNanoseconds(openxr_->GetPredictedDisplayTime());
diff --git a/device/vr/openxr/openxr_util.h b/device/vr/openxr/openxr_util.h
index 663fb7d..91969e0 100644
--- a/device/vr/openxr/openxr_util.h
+++ b/device/vr/openxr/openxr_util.h
@@ -30,6 +30,14 @@
return XR_STATE_UNAVAILABLE; \
} while (false)
+#define RETURN_IF_FALSE(condition, error_code, msg) \
+ do { \
+ if (!(condition)) { \
+ LOG(ERROR) << __FUNCTION__ << ": " << msg; \
+ return error_code; \
+ } \
+ } while (false)
+
// Returns the identity pose, where the position is {0, 0, 0} and the
// orientation is {0, 0, 0, 1}.
XrPosef PoseIdentity();
diff --git a/device/vr/openxr/test/DEPS b/device/vr/openxr/test/DEPS
new file mode 100644
index 0000000..11b30de
--- /dev/null
+++ b/device/vr/openxr/test/DEPS
@@ -0,0 +1,3 @@
+include_rules = [
+ "+third_party/openxr/include/openxr",
+]
diff --git a/device/vr/openxr/test/fake_openxr_impl_api.cc b/device/vr/openxr/test/fake_openxr_impl_api.cc
new file mode 100644
index 0000000..4a63bd0
--- /dev/null
+++ b/device/vr/openxr/test/fake_openxr_impl_api.cc
@@ -0,0 +1,488 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <directxmath.h>
+#include <wrl.h>
+
+#include "device/vr/openxr/openxr_util.h"
+#include "device/vr/openxr/test/openxr_negotiate.h"
+#include "device/vr/openxr/test/openxr_test_helper.h"
+
+namespace {
+// Global test helper that communicates with the test and contains the mock
+// OpenXR runtime state/properties. A reference to this is returned as the
+// instance handle through xrCreateInstance.
+OpenXrTestHelper g_test_helper;
+} // namespace
+
+// Mock implementations of openxr runtime.dll APIs.
+// Please add new APIs in alphabetical order.
+
+XrResult xrAcquireSwapchainImage(
+ XrSwapchain swapchain,
+ const XrSwapchainImageAcquireInfo* acquire_info,
+ uint32_t* index) {
+ DLOG(INFO) << __FUNCTION__;
+ XrResult xr_result;
+
+ RETURN_IF_XR_FAILED(g_test_helper.ValidateSwapchain(swapchain));
+ RETURN_IF_FALSE(acquire_info->type == XR_TYPE_SWAPCHAIN_IMAGE_ACQUIRE_INFO,
+ XR_ERROR_VALIDATION_FAILURE,
+ "xrAcquireSwapchainImage type invalid");
+
+ *index = g_test_helper.NextSwapchainImageIndex();
+
+ return XR_SUCCESS;
+}
+
+XrResult xrBeginFrame(XrSession session,
+ const XrFrameBeginInfo* frame_begin_info) {
+ DLOG(INFO) << __FUNCTION__;
+ XrResult xr_result;
+
+ RETURN_IF_XR_FAILED(g_test_helper.ValidateSession(session));
+ RETURN_IF_FALSE(frame_begin_info->type == XR_TYPE_FRAME_BEGIN_INFO,
+ XR_ERROR_VALIDATION_FAILURE, "XrFrameBeginInfo type invalid");
+
+ return XR_SUCCESS;
+}
+
+XrResult xrBeginSession(XrSession session,
+ const XrSessionBeginInfo* begin_info) {
+ DLOG(INFO) << __FUNCTION__;
+ XrResult xr_result;
+
+ RETURN_IF_XR_FAILED(g_test_helper.ValidateSession(session));
+ RETURN_IF_FALSE(begin_info->type == XR_TYPE_SESSION_BEGIN_INFO,
+ XR_ERROR_VALIDATION_FAILURE,
+ "XrSessionBeginInfo type invalid");
+ RETURN_IF_FALSE(begin_info->primaryViewConfigurationType ==
+ XR_VIEW_CONFIGURATION_TYPE_PRIMARY_STEREO,
+ XR_ERROR_VALIDATION_FAILURE,
+ "XrSessionBeginInfo primaryViewConfigurationType invalid");
+
+ RETURN_IF_XR_FAILED(g_test_helper.BeginSession());
+
+ return XR_SUCCESS;
+}
+
+XrResult xrCreateInstance(const XrInstanceCreateInfo* create_info,
+ XrInstance* instance) {
+ DLOG(INFO) << __FUNCTION__;
+
+ RETURN_IF_FALSE(create_info->type == XR_TYPE_INSTANCE_CREATE_INFO,
+ XR_ERROR_VALIDATION_FAILURE,
+ "XrInstanceCreateInfo type invalid");
+
+ RETURN_IF_FALSE(create_info->enabledExtensionCount ==
+ OpenXrTestHelper::NumExtensionsSupported(),
+ XR_ERROR_VALIDATION_FAILURE, "enabledExtensionCount invalid");
+
+ for (uint32_t i = 0; i < create_info->enabledExtensionCount; i++) {
+ bool valid_extension = false;
+ for (size_t j = 0; j < OpenXrTestHelper::NumExtensionsSupported(); j++) {
+ if (strcmp(create_info->enabledExtensionNames[i],
+ OpenXrTestHelper::kExtensions[j]) == 0) {
+ valid_extension = true;
+ break;
+ }
+ }
+
+ RETURN_IF_FALSE(valid_extension, XR_ERROR_VALIDATION_FAILURE,
+ "enabledExtensionNames contains invalid extensions");
+ }
+
+ // Return the test helper object back to the OpenXrAPIWrapper so it can use
+ // it as the TestHookRegistration.
+ *instance = reinterpret_cast<XrInstance>(&g_test_helper);
+
+ return XR_SUCCESS;
+}
+
+XrResult xrCreateReferenceSpace(XrSession session,
+ const XrReferenceSpaceCreateInfo* create_info,
+ XrSpace* space) {
+ DLOG(INFO) << __FUNCTION__;
+ XrResult xr_result;
+
+ RETURN_IF_XR_FAILED(g_test_helper.ValidateSession(session));
+ RETURN_IF_FALSE(create_info->type == XR_TYPE_REFERENCE_SPACE_CREATE_INFO,
+ XR_ERROR_VALIDATION_FAILURE,
+ "XrReferenceSpaceCreateInfo type invalid");
+ RETURN_IF_FALSE(
+ create_info->referenceSpaceType == XR_REFERENCE_SPACE_TYPE_LOCAL ||
+ create_info->referenceSpaceType == XR_REFERENCE_SPACE_TYPE_VIEW,
+ XR_ERROR_VALIDATION_FAILURE,
+ "XrReferenceSpaceCreateInfo referenceSpaceType invalid");
+ RETURN_IF_XR_FAILED(g_test_helper.ValidateXrPosefIsIdentity(
+ create_info->poseInReferenceSpace));
+
+ switch (create_info->referenceSpaceType) {
+ case XR_REFERENCE_SPACE_TYPE_LOCAL:
+ *space = g_test_helper.CreateLocalSpace();
+ break;
+ case XR_REFERENCE_SPACE_TYPE_VIEW:
+ *space = g_test_helper.CreateViewSpace();
+ break;
+ default:
+ RETURN_IF_FALSE(false, XR_ERROR_VALIDATION_FAILURE,
+ "XrReferenceSpaceCreateInfo referenceSpaceType invalid");
+ }
+
+ return XR_SUCCESS;
+}
+
+XrResult xrCreateSession(XrInstance instance,
+ const XrSessionCreateInfo* create_info,
+ XrSession* session) {
+ DLOG(INFO) << __FUNCTION__;
+ XrResult xr_result;
+
+ RETURN_IF_XR_FAILED(g_test_helper.ValidateInstance(instance));
+ RETURN_IF_FALSE(create_info->type == XR_TYPE_SESSION_CREATE_INFO,
+ XR_ERROR_VALIDATION_FAILURE,
+ "XrSessionCreateInfo type invalid");
+ RETURN_IF_XR_FAILED(g_test_helper.ValidateSystemId(create_info->systemId));
+
+ const XrGraphicsBindingD3D11KHR* binding =
+ static_cast<const XrGraphicsBindingD3D11KHR*>(create_info->next);
+ RETURN_IF_FALSE(binding->type == XR_TYPE_GRAPHICS_BINDING_D3D11_KHR,
+ XR_ERROR_VALIDATION_FAILURE,
+ "XrGraphicsBindingD3D11KHR type invalid");
+ RETURN_IF_FALSE(binding->device != nullptr, XR_ERROR_VALIDATION_FAILURE,
+ "D3D11Device is null");
+
+ g_test_helper.SetD3DDevice(binding->device);
+ *session = g_test_helper.GetSession();
+
+ return XR_SUCCESS;
+}
+
+XrResult xrCreateSwapchain(XrSession session,
+ const XrSwapchainCreateInfo* create_info,
+ XrSwapchain* swapchain) {
+ DLOG(INFO) << __FUNCTION__;
+ XrResult xr_result;
+
+ RETURN_IF_XR_FAILED(g_test_helper.ValidateSession(session));
+ RETURN_IF_FALSE(create_info->type == XR_TYPE_SWAPCHAIN_CREATE_INFO,
+ XR_ERROR_VALIDATION_FAILURE,
+ "XrSwapchainCreateInfo type invalid");
+ RETURN_IF_FALSE(create_info->arraySize == 1, XR_ERROR_VALIDATION_FAILURE,
+ "XrSwapchainCreateInfo arraySize invalid");
+ RETURN_IF_FALSE(create_info->format == DXGI_FORMAT_R8G8B8A8_UNORM,
+ XR_ERROR_SWAPCHAIN_FORMAT_UNSUPPORTED,
+ "XrSwapchainCreateInfo format unsupported");
+ RETURN_IF_FALSE(create_info->width == OpenXrTestHelper::kDimension * 2,
+ XR_ERROR_VALIDATION_FAILURE,
+ "XrSwapchainCreateInfo width is not dimension * 2");
+ RETURN_IF_FALSE(create_info->height == OpenXrTestHelper::kDimension,
+ XR_ERROR_VALIDATION_FAILURE,
+ "XrSwapchainCreateInfo height is not dimension");
+ RETURN_IF_FALSE(create_info->mipCount == 1, XR_ERROR_VALIDATION_FAILURE,
+ "XrSwapchainCreateInfo mipCount is not 1");
+ RETURN_IF_FALSE(create_info->faceCount == 1, XR_ERROR_VALIDATION_FAILURE,
+ "XrSwapchainCreateInfo faceCount is not 1");
+ RETURN_IF_FALSE(create_info->sampleCount == OpenXrTestHelper::kSwapCount,
+ XR_ERROR_VALIDATION_FAILURE,
+ "XrSwapchainCreateInfo sampleCount invalid");
+ RETURN_IF_FALSE(
+ create_info->usageFlags == XR_SWAPCHAIN_USAGE_COLOR_ATTACHMENT_BIT,
+ XR_ERROR_VALIDATION_FAILURE,
+ "XrSwapchainCreateInfo usageFlags is not "
+ "XR_SWAPCHAIN_USAGE_COLOR_ATTACHMENT_BIT");
+
+ *swapchain = g_test_helper.GetSwapchain();
+
+ return XR_SUCCESS;
+}
+
+XrResult xrDestroyInstance(XrInstance instance) {
+ DLOG(INFO) << __FUNCTION__;
+ XrResult xr_result;
+
+ RETURN_IF_XR_FAILED(g_test_helper.ValidateInstance(instance));
+
+ return XR_SUCCESS;
+}
+
+XrResult xrEndFrame(XrSession session, const XrFrameEndInfo* frame_end_info) {
+ DLOG(INFO) << __FUNCTION__;
+ XrResult xr_result;
+
+ RETURN_IF_XR_FAILED(g_test_helper.ValidateSession(session));
+ RETURN_IF_FALSE(frame_end_info->type == XR_TYPE_FRAME_END_INFO,
+ XR_ERROR_VALIDATION_FAILURE, "frame_end_info type invalid");
+ RETURN_IF_FALSE(frame_end_info->environmentBlendMode ==
+ OpenXrTestHelper::kEnvironmentBlendMode,
+ XR_ERROR_VALIDATION_FAILURE,
+ "frame_end_info environmentBlendMode invalid");
+ RETURN_IF_FALSE(frame_end_info->layerCount == 1, XR_ERROR_VALIDATION_FAILURE,
+ "frame_end_info layerCount invalid");
+ RETURN_IF_XR_FAILED(
+ g_test_helper.ValidatePredictedDisplayTime(frame_end_info->displayTime));
+
+ g_test_helper.OnPresentedFrame();
+
+ return XR_SUCCESS;
+}
+
+XrResult xrEndSession(XrSession session) {
+ DLOG(INFO) << __FUNCTION__;
+ XrResult xr_result;
+
+ RETURN_IF_XR_FAILED(g_test_helper.ValidateSession(session));
+ RETURN_IF_XR_FAILED(g_test_helper.EndSession());
+
+ return XR_SUCCESS;
+}
+
+XrResult xrEnumerateEnvironmentBlendModes(
+ XrInstance instance,
+ XrSystemId system_id,
+ uint32_t environmentBlendModeCapacityInput,
+ uint32_t* environmentBlendModeCountOutput,
+ XrEnvironmentBlendMode* environmentBlendModes) {
+ DLOG(INFO) << __FUNCTION__;
+ XrResult xr_result;
+
+ RETURN_IF_XR_FAILED(g_test_helper.ValidateInstance(instance));
+ RETURN_IF_XR_FAILED(g_test_helper.ValidateSystemId(system_id));
+
+ *environmentBlendModeCountOutput = 1;
+
+ if (environmentBlendModeCapacityInput != 0) {
+ *environmentBlendModes = OpenXrTestHelper::kEnvironmentBlendMode;
+ }
+
+ return XR_SUCCESS;
+}
+
+XrResult xrEnumerateInstanceExtensionProperties(
+ const char* layer_name,
+ uint32_t property_capacity_input,
+ uint32_t* property_count_output,
+ XrExtensionProperties* properties) {
+ DLOG(INFO) << __FUNCTION__;
+
+ RETURN_IF_FALSE(
+ property_capacity_input >= OpenXrTestHelper::NumExtensionsSupported() ||
+ property_capacity_input == 0,
+ XR_ERROR_SIZE_INSUFFICIENT, "XrExtensionProperties array is too small");
+
+ *property_count_output = OpenXrTestHelper::NumExtensionsSupported();
+
+ if (property_capacity_input != 0) {
+ for (uint32_t i = 0; i < OpenXrTestHelper::NumExtensionsSupported(); i++) {
+ errno_t error = strcpy_s(properties[i].extensionName,
+ OpenXrTestHelper::kExtensions[i]);
+ DCHECK(error == 0);
+ properties[i].specVersion = 1;
+ }
+ }
+
+ return XR_SUCCESS;
+}
+
+XrResult xrEnumerateViewConfigurationViews(
+ XrInstance instance,
+ XrSystemId system_id,
+ XrViewConfigurationType view_configuration_type,
+ uint32_t view_capacity_input,
+ uint32_t* view_count_output,
+ XrViewConfigurationView* views) {
+ DLOG(INFO) << __FUNCTION__;
+ XrResult xr_result;
+
+ RETURN_IF_XR_FAILED(g_test_helper.ValidateInstance(instance));
+ RETURN_IF_XR_FAILED(g_test_helper.ValidateSystemId(system_id));
+ RETURN_IF_FALSE(
+ view_configuration_type == XR_VIEW_CONFIGURATION_TYPE_PRIMARY_STEREO,
+ XR_ERROR_VALIDATION_FAILURE,
+ "xrEnumerateViewConfigurationViews viewConfigurationType invalid");
+
+ *view_count_output = OpenXrTestHelper::NumViews();
+
+ if (view_capacity_input != 0) {
+ views[0] = OpenXrTestHelper::kViewConfigurationViews[0];
+ views[1] = OpenXrTestHelper::kViewConfigurationViews[1];
+ }
+
+ return XR_SUCCESS;
+}
+
+XrResult xrEnumerateSwapchainImages(XrSwapchain swapchain,
+ uint32_t image_capacity_input,
+ uint32_t* image_count_output,
+ XrSwapchainImageBaseHeader* images) {
+ DLOG(INFO) << __FUNCTION__;
+ XrResult xr_result;
+
+ RETURN_IF_XR_FAILED(g_test_helper.ValidateSwapchain(swapchain));
+ RETURN_IF_FALSE(
+ image_capacity_input == OpenXrTestHelper::kMinSwapchainBuffering ||
+ image_capacity_input == 0,
+ XR_ERROR_SIZE_INSUFFICIENT,
+ "xrEnumerateSwapchainImages does not equal length returned by "
+ "xrCreateSwapchain");
+
+ *image_count_output = OpenXrTestHelper::kMinSwapchainBuffering;
+
+ if (image_capacity_input != 0) {
+ const std::vector<Microsoft::WRL::ComPtr<ID3D11Texture2D>>& textures =
+ g_test_helper.GetSwapchainTextures();
+ DCHECK(textures.size() == image_capacity_input);
+
+ for (uint32_t i = 0; i < image_capacity_input; i++) {
+ XrSwapchainImageD3D11KHR& image =
+ reinterpret_cast<XrSwapchainImageD3D11KHR*>(images)[i];
+
+ RETURN_IF_FALSE(image.type == XR_TYPE_SWAPCHAIN_IMAGE_D3D11_KHR,
+ XR_ERROR_VALIDATION_FAILURE,
+ "XrSwapchainImageD3D11KHR type invalid");
+
+ image.texture = textures[i].Get();
+ }
+ }
+
+ return XR_SUCCESS;
+}
+
+XrResult xrGetD3D11GraphicsRequirementsKHR(
+ XrInstance instance,
+ XrSystemId system_id,
+ XrGraphicsRequirementsD3D11KHR* graphics_requirements) {
+ DLOG(INFO) << __FUNCTION__;
+ XrResult xr_result;
+
+ RETURN_IF_XR_FAILED(g_test_helper.ValidateInstance(instance));
+ RETURN_IF_XR_FAILED(g_test_helper.ValidateSystemId(system_id));
+ RETURN_IF_FALSE(
+ graphics_requirements->type == XR_TYPE_GRAPHICS_REQUIREMENTS_D3D11_KHR,
+ XR_ERROR_VALIDATION_FAILURE,
+ "XrGraphicsRequirementsD3D11KHR type invalid");
+
+ Microsoft::WRL::ComPtr<IDXGIFactory1> dxgi_factory;
+ Microsoft::WRL::ComPtr<IDXGIAdapter> adapter;
+ HRESULT hr = CreateDXGIFactory1(IID_PPV_ARGS(&dxgi_factory));
+ DCHECK(SUCCEEDED(hr));
+ for (int i = 0; SUCCEEDED(dxgi_factory->EnumAdapters(i, &adapter)); i++) {
+ DXGI_ADAPTER_DESC desc;
+ adapter->GetDesc(&desc);
+ graphics_requirements->adapterLuid = desc.AdapterLuid;
+
+ // Require D3D11.1 to support shared NT handles.
+ graphics_requirements->minFeatureLevel = D3D_FEATURE_LEVEL_11_1;
+
+ return XR_SUCCESS;
+ }
+
+ RETURN_IF_FALSE(false, XR_ERROR_VALIDATION_FAILURE,
+ "Unable to create query DXGI Adapter");
+}
+
+XrResult xrGetSystem(XrInstance instance,
+ const XrSystemGetInfo* get_info,
+ XrSystemId* system_id) {
+ DLOG(INFO) << __FUNCTION__;
+ XrResult xr_result;
+
+ RETURN_IF_XR_FAILED(g_test_helper.ValidateInstance(instance));
+ RETURN_IF_FALSE(get_info->type == XR_TYPE_SYSTEM_GET_INFO,
+ XR_ERROR_VALIDATION_FAILURE, "XrSystemGetInfo type invalid");
+ RETURN_IF_FALSE(get_info->formFactor == XR_FORM_FACTOR_HEAD_MOUNTED_DISPLAY,
+ XR_ERROR_VALIDATION_FAILURE,
+ "XrSystemGetInfo formFactor invalid");
+
+ *system_id = g_test_helper.GetSystemId();
+
+ return XR_SUCCESS;
+}
+
+XrResult xrLocateSpace(XrSpace space,
+ XrSpace baseSpace,
+ XrTime time,
+ XrSpaceRelation* relation) {
+ DLOG(INFO) << __FUNCTION__;
+ XrResult xr_result;
+
+ RETURN_IF_XR_FAILED(g_test_helper.ValidateSpace(space));
+ RETURN_IF_XR_FAILED(g_test_helper.ValidateSpace(baseSpace));
+ RETURN_IF_XR_FAILED(g_test_helper.ValidatePredictedDisplayTime(time));
+
+ g_test_helper.GetPose(&(relation->pose));
+
+ relation->relationFlags = XR_SPACE_RELATION_ORIENTATION_VALID_BIT |
+ XR_SPACE_RELATION_POSITION_VALID_BIT;
+
+ return XR_SUCCESS;
+}
+
+XrResult xrLocateViews(XrSession session,
+ const XrViewLocateInfo* view_locate_info,
+ XrViewState* view_state,
+ uint32_t view_capacity_input,
+ uint32_t* view_count_output,
+ XrView* views) {
+ DLOG(INFO) << __FUNCTION__;
+ XrResult xr_result;
+
+ RETURN_IF_XR_FAILED(g_test_helper.ValidateSession(session));
+ RETURN_IF_XR_FAILED(g_test_helper.ValidatePredictedDisplayTime(
+ view_locate_info->displayTime));
+ RETURN_IF_FALSE(view_locate_info->type == XR_TYPE_VIEW_LOCATE_INFO,
+ XR_ERROR_VALIDATION_FAILURE,
+ "xrLocateViews view_locate_info type invalid");
+ RETURN_IF_XR_FAILED(g_test_helper.ValidateSpace(view_locate_info->space));
+
+ return XR_SUCCESS;
+}
+
+XrResult xrReleaseSwapchainImage(
+ XrSwapchain swapchain,
+ const XrSwapchainImageReleaseInfo* release_info) {
+ DLOG(INFO) << __FUNCTION__;
+ XrResult xr_result;
+
+ RETURN_IF_XR_FAILED(g_test_helper.ValidateSwapchain(swapchain));
+ RETURN_IF_FALSE(release_info->type == XR_TYPE_SWAPCHAIN_IMAGE_RELEASE_INFO,
+ XR_ERROR_VALIDATION_FAILURE,
+ "xrReleaseSwapchainImage type invalid");
+
+ return XR_SUCCESS;
+}
+
+XrResult xrWaitFrame(XrSession session,
+ const XrFrameWaitInfo* frame_wait_info,
+ XrFrameState* frame_state) {
+ DLOG(INFO) << __FUNCTION__;
+ XrResult xr_result;
+
+ RETURN_IF_XR_FAILED(g_test_helper.ValidateSession(session));
+ RETURN_IF_FALSE(frame_wait_info->type == XR_TYPE_FRAME_WAIT_INFO,
+ XR_ERROR_VALIDATION_FAILURE, "frame_wait_info type invalid");
+ RETURN_IF_FALSE(frame_state->type == XR_TYPE_FRAME_STATE,
+ XR_ERROR_VALIDATION_FAILURE,
+ "XR_TYPE_FRAME_STATE type invalid");
+
+ frame_state->predictedDisplayTime = g_test_helper.NextPredictedDisplayTime();
+
+ return XR_SUCCESS;
+}
+
+XrResult xrWaitSwapchainImage(XrSwapchain swapchain,
+ const XrSwapchainImageWaitInfo* wait_info) {
+ DLOG(INFO) << __FUNCTION__;
+ XrResult xr_result;
+
+ RETURN_IF_XR_FAILED(g_test_helper.ValidateSwapchain(swapchain));
+ RETURN_IF_FALSE(wait_info->type == XR_TYPE_SWAPCHAIN_IMAGE_WAIT_INFO,
+ XR_ERROR_VALIDATION_FAILURE,
+ "xrWaitSwapchainImage type invalid");
+ RETURN_IF_FALSE(wait_info->timeout == XR_INFINITE_DURATION,
+ XR_ERROR_VALIDATION_FAILURE,
+ "xrWaitSwapchainImage timeout not XR_INFINITE_DURATION");
+
+ return XR_SUCCESS;
+}
diff --git a/device/vr/openxr/test/openxr.json b/device/vr/openxr/test/openxr.json
new file mode 100644
index 0000000..af26106b
--- /dev/null
+++ b/device/vr/openxr/test/openxr.json
@@ -0,0 +1,6 @@
+{
+ "file_format_version": "1.0.0",
+ "runtime": {
+ "library_path": ".\\OpenXrRuntime.dll"
+ }
+}
diff --git a/device/vr/openxr/test/openxr_negotiate.h b/device/vr/openxr/test/openxr_negotiate.h
new file mode 100644
index 0000000..fd5dfdb9
--- /dev/null
+++ b/device/vr/openxr/test/openxr_negotiate.h
@@ -0,0 +1,93 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// 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_NEGOTIATE_H_
+#define DEVICE_VR_OPENXR_TEST_OPENXR_NEGOTIATE_H_
+
+#include <d3d11.h>
+#include <unknwn.h>
+
+#include "third_party/openxr/include/openxr/loader_interfaces.h"
+#include "third_party/openxr/include/openxr/openxr.h"
+#include "third_party/openxr/include/openxr/openxr_platform.h"
+
+// This file contains functions that are used by the openxr_loader.dll to call
+// into the fake OpenXR Runtime. Used for testing purposes only, so this should
+// only be used to call the fake OpenXR APIs defined in
+// fake_openxr_impl_api.cc.
+
+// Please add new OpenXR APIs below in alphabetical order.
+XrResult GetInstanceProcAddress(XrInstance instance,
+ const char* name,
+ PFN_xrVoidFunction* function) {
+ if (strcmp(name, "xrAcquireSwapchainImage") == 0) {
+ *function = reinterpret_cast<PFN_xrVoidFunction>(xrAcquireSwapchainImage);
+ } else if (strcmp(name, "xrBeginFrame") == 0) {
+ *function = reinterpret_cast<PFN_xrVoidFunction>(xrBeginFrame);
+ } else if (strcmp(name, "xrBeginSession") == 0) {
+ *function = reinterpret_cast<PFN_xrVoidFunction>(xrBeginSession);
+ } else if (strcmp(name, "xrCreateInstance") == 0) {
+ *function = reinterpret_cast<PFN_xrVoidFunction>(xrCreateInstance);
+ } else if (strcmp(name, "xrCreateReferenceSpace") == 0) {
+ *function = reinterpret_cast<PFN_xrVoidFunction>(xrCreateReferenceSpace);
+ } else if (strcmp(name, "xrCreateSession") == 0) {
+ *function = reinterpret_cast<PFN_xrVoidFunction>(xrCreateSession);
+ } else if (strcmp(name, "xrCreateSwapchain") == 0) {
+ *function = reinterpret_cast<PFN_xrVoidFunction>(xrCreateSwapchain);
+ } else if (strcmp(name, "xrDestroyInstance") == 0) {
+ *function = reinterpret_cast<PFN_xrVoidFunction>(xrDestroyInstance);
+ } else if (strcmp(name, "xrEndFrame") == 0) {
+ *function = reinterpret_cast<PFN_xrVoidFunction>(xrEndFrame);
+ } else if (strcmp(name, "xrEndSession") == 0) {
+ *function = reinterpret_cast<PFN_xrVoidFunction>(xrEndSession);
+ } else if (strcmp(name, "xrEnumerateEnvironmentBlendModes") == 0) {
+ *function =
+ reinterpret_cast<PFN_xrVoidFunction>(xrEnumerateEnvironmentBlendModes);
+ } else if (strcmp(name, "xrEnumerateInstanceExtensionProperties") == 0) {
+ *function = reinterpret_cast<PFN_xrVoidFunction>(
+ xrEnumerateInstanceExtensionProperties);
+ } else if (strcmp(name, "xrEnumerateSwapchainImages") == 0) {
+ *function =
+ reinterpret_cast<PFN_xrVoidFunction>(xrEnumerateSwapchainImages);
+ } else if (strcmp(name, "xrEnumerateViewConfigurationViews") == 0) {
+ *function =
+ reinterpret_cast<PFN_xrVoidFunction>(xrEnumerateViewConfigurationViews);
+ } else if (strcmp(name, "xrGetD3D11GraphicsRequirementsKHR") == 0) {
+ *function =
+ reinterpret_cast<PFN_xrVoidFunction>(xrGetD3D11GraphicsRequirementsKHR);
+ } else if (strcmp(name, "xrGetSystem") == 0) {
+ *function = reinterpret_cast<PFN_xrVoidFunction>(xrGetSystem);
+ } else if (strcmp(name, "xrLocateSpace") == 0) {
+ *function = reinterpret_cast<PFN_xrVoidFunction>(xrLocateSpace);
+ } else if (strcmp(name, "xrLocateViews") == 0) {
+ *function = reinterpret_cast<PFN_xrVoidFunction>(xrLocateViews);
+ } else if (strcmp(name, "xrReleaseSwapchainImage") == 0) {
+ *function = reinterpret_cast<PFN_xrVoidFunction>(xrReleaseSwapchainImage);
+ } else if (strcmp(name, "xrWaitFrame") == 0) {
+ *function = reinterpret_cast<PFN_xrVoidFunction>(xrWaitFrame);
+ } else if (strcmp(name, "xrWaitSwapchainImage") == 0) {
+ *function = reinterpret_cast<PFN_xrVoidFunction>(xrWaitSwapchainImage);
+ } else {
+ return XR_ERROR_FUNCTION_UNSUPPORTED;
+ }
+
+ return XR_SUCCESS;
+}
+
+// The single exported function in fake OpenXR Runtime DLL which the OpenXR
+// loader calls for negotiation. GetInstanceProcAddress is returned to the
+// loader, which is then used by the loader to call OpenXR APIs.
+// extern "C" is needed because the OpenXR Loader expects the name of this
+// function to be unmangled. The real OpenXR runtime does this as well.
+extern "C" __declspec(dllexport) XrResult xrNegotiateLoaderRuntimeInterface(
+ const XrNegotiateLoaderInfo* loaderInfo,
+ XrNegotiateRuntimeRequest* runtimeRequest) {
+ runtimeRequest->runtimeInterfaceVersion = 1;
+ runtimeRequest->runtimeXrVersion = XR_MAKE_VERSION(0, 1, 0);
+ runtimeRequest->getInstanceProcAddr = GetInstanceProcAddress;
+
+ return XR_SUCCESS;
+}
+
+#endif // DEVICE_VR_OPENXR_TEST_OPENXR_NEGOTIATE_H_
diff --git a/device/vr/openxr/test/openxr_test_helper.cc b/device/vr/openxr/test/openxr_test_helper.cc
new file mode 100644
index 0000000..6a1dad9
--- /dev/null
+++ b/device/vr/openxr/test/openxr_test_helper.cc
@@ -0,0 +1,280 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "device/vr/openxr/test/openxr_test_helper.h"
+
+#include <cmath>
+#include <limits>
+
+#include "device/vr/openxr/openxr_util.h"
+#include "third_party/openxr/include/openxr/openxr_platform.h"
+#include "ui/gfx/transform.h"
+#include "ui/gfx/transform_util.h"
+
+// Initialize static variables in OpenXrTestHelper.
+const char* OpenXrTestHelper::kExtensions[] = {
+ XR_KHR_D3D11_ENABLE_EXTENSION_NAME};
+const uint32_t OpenXrTestHelper::kDimension = 128;
+const uint32_t OpenXrTestHelper::kSwapCount = 1;
+const uint32_t OpenXrTestHelper::kMinSwapchainBuffering = 3;
+const uint32_t OpenXrTestHelper::kMaxViewCount = 2;
+const XrViewConfigurationView OpenXrTestHelper::kViewConfigView = {
+ XR_TYPE_VIEW_CONFIGURATION_VIEW, nullptr,
+ OpenXrTestHelper::kDimension, OpenXrTestHelper::kDimension,
+ OpenXrTestHelper::kDimension, OpenXrTestHelper::kDimension,
+ OpenXrTestHelper::kSwapCount, OpenXrTestHelper::kSwapCount};
+XrViewConfigurationView OpenXrTestHelper::kViewConfigurationViews[] = {
+ OpenXrTestHelper::kViewConfigView, OpenXrTestHelper::kViewConfigView};
+const XrEnvironmentBlendMode OpenXrTestHelper::kEnvironmentBlendMode =
+ XR_ENVIRONMENT_BLEND_MODE_OPAQUE;
+
+uint32_t OpenXrTestHelper::NumExtensionsSupported() {
+ return sizeof(kExtensions) / sizeof(kExtensions[0]);
+}
+
+uint32_t OpenXrTestHelper::NumViews() {
+ return sizeof(kViewConfigurationViews) / sizeof(kViewConfigurationViews[0]);
+}
+
+OpenXrTestHelper::OpenXrTestHelper()
+ : system_id_(0),
+ session_(XR_NULL_HANDLE),
+ swapchain_(XR_NULL_HANDLE),
+ local_space_(XR_NULL_HANDLE),
+ view_space_(XR_NULL_HANDLE),
+ session_running_(false),
+ acquired_swapchain_texture_(0),
+ next_action_space_(0),
+ next_predicted_display_time_(0) {}
+
+OpenXrTestHelper::~OpenXrTestHelper() = default;
+
+void OpenXrTestHelper::TestFailure() {
+ NOTREACHED();
+}
+
+void OpenXrTestHelper::SetTestHook(device::VRTestHook* hook) {
+ base::AutoLock auto_lock(lock_);
+ test_hook_ = hook;
+}
+
+void OpenXrTestHelper::OnPresentedFrame() {
+ static uint32_t frame_id = 1;
+
+ base::AutoLock auto_lock(lock_);
+ if (!test_hook_)
+ return;
+
+ // TODO(https://crbug.com/986621): The frame color is currently hard-coded to
+ // what the pixel tests expects. We should instead store the actual WebGL
+ // texture and read from it, which will also verify the correct swapchain
+ // texture was used.
+
+ device::DeviceConfig device_config = test_hook_->WaitGetDeviceConfig();
+ device::SubmittedFrameData frame_data = {};
+
+ if (std::abs(device_config.interpupillary_distance - 0.2f) <
+ std::numeric_limits<float>::epsilon()) {
+ // TestPresentationPoses sets the ipd to 0.2f, whereas tests by default have
+ // an ipd of 0.1f. This test has specific formulas to determine the colors,
+ // specified in test_webxr_poses.html.
+ frame_data.color = {
+ frame_id % 256, ((frame_id - frame_id % 256) / 256) % 256,
+ ((frame_id - frame_id % (256 * 256)) / (256 * 256)) % 256, 255};
+ } else {
+ // The WebXR tests by default clears to blue. TestPresentationPixels
+ // verifies this color.
+ frame_data.color = {0, 0, 255, 255};
+ }
+
+ frame_data.left_eye = true;
+ test_hook_->OnFrameSubmitted(frame_data);
+
+ frame_data.left_eye = false;
+ test_hook_->OnFrameSubmitted(frame_data);
+
+ frame_id++;
+}
+
+XrSystemId OpenXrTestHelper::GetSystemId() {
+ system_id_ = 1;
+ return system_id_;
+}
+
+XrSession OpenXrTestHelper::GetSession() {
+ // reinterpret_cast needed because XrSession is a pointer type.
+ session_ = reinterpret_cast<XrSession>(2);
+ return session_;
+}
+
+XrSwapchain OpenXrTestHelper::GetSwapchain() {
+ // reinterpret_cast needed because XrSwapchain is a pointer type.
+ swapchain_ = reinterpret_cast<XrSwapchain>(3);
+ return swapchain_;
+}
+
+XrSpace OpenXrTestHelper::CreateLocalSpace() {
+ // reinterpret_cast needed because XrSpace is a pointer type.
+ local_space_ = reinterpret_cast<XrSpace>(++next_action_space_);
+ return local_space_;
+}
+
+XrSpace OpenXrTestHelper::CreateViewSpace() {
+ // reinterpret_cast needed because XrSpace is a pointer type.
+ view_space_ = reinterpret_cast<XrSpace>(++next_action_space_);
+ return view_space_;
+}
+
+XrResult OpenXrTestHelper::BeginSession() {
+ RETURN_IF_FALSE(!session_running_, XR_ERROR_SESSION_RUNNING,
+ "Session is already running");
+
+ session_running_ = true;
+ return XR_SUCCESS;
+}
+
+XrResult OpenXrTestHelper::EndSession() {
+ RETURN_IF_FALSE(session_running_, XR_ERROR_SESSION_NOT_RUNNING,
+ "Session is not currently running");
+
+ session_running_ = false;
+ return XR_SUCCESS;
+}
+
+void OpenXrTestHelper::SetD3DDevice(ID3D11Device* d3d_device) {
+ DCHECK(d3d_device_ == nullptr);
+ DCHECK(d3d_device != nullptr);
+ d3d_device_ = d3d_device;
+
+ D3D11_TEXTURE2D_DESC desc{};
+ desc.Width = kDimension * 2; // Using a double wide texture
+ desc.Height = kDimension;
+ desc.MipLevels = 1;
+ desc.ArraySize = 1;
+ desc.Format = DXGI_FORMAT_R8G8B8A8_UNORM;
+ desc.SampleDesc.Count = 1;
+ desc.Usage = D3D11_USAGE_DEFAULT;
+ desc.BindFlags = D3D11_BIND_RENDER_TARGET;
+
+ for (uint32_t i = 0; i < kMinSwapchainBuffering; i++) {
+ Microsoft::WRL::ComPtr<ID3D11Texture2D> texture;
+ HRESULT hr = d3d_device_->CreateTexture2D(&desc, nullptr, &texture);
+ DCHECK(hr == S_OK);
+
+ textures_arr_.push_back(texture);
+ }
+}
+
+const std::vector<Microsoft::WRL::ComPtr<ID3D11Texture2D>>&
+OpenXrTestHelper::GetSwapchainTextures() const {
+ return textures_arr_;
+}
+
+uint32_t OpenXrTestHelper::NextSwapchainImageIndex() {
+ acquired_swapchain_texture_ =
+ (acquired_swapchain_texture_ + 1) % textures_arr_.size();
+ return acquired_swapchain_texture_;
+}
+
+XrTime OpenXrTestHelper::NextPredictedDisplayTime() {
+ return ++next_predicted_display_time_;
+}
+
+void OpenXrTestHelper::GetPose(XrPosef* pose) {
+ *pose = device::PoseIdentity();
+
+ base::AutoLock lock(lock_);
+ if (test_hook_) {
+ device::PoseFrameData pose_data = test_hook_->WaitGetPresentingPose();
+ if (pose_data.is_valid) {
+ gfx::Transform transform = PoseFrameDataToTransform(pose_data);
+
+ gfx::DecomposedTransform decomposed_transform;
+ bool decomposable =
+ gfx::DecomposeTransform(&decomposed_transform, transform);
+ DCHECK(decomposable);
+
+ pose->orientation.x = decomposed_transform.quaternion.x();
+ pose->orientation.y = decomposed_transform.quaternion.y();
+ pose->orientation.z = decomposed_transform.quaternion.z();
+ pose->orientation.w = decomposed_transform.quaternion.w();
+
+ pose->position.x = decomposed_transform.translate[0];
+ pose->position.y = decomposed_transform.translate[1];
+ pose->position.z = decomposed_transform.translate[2];
+ }
+ }
+}
+
+XrResult OpenXrTestHelper::ValidateInstance(XrInstance instance) const {
+ // The Fake OpenXR Runtime returns this global OpenXrTestHelper object as the
+ // instance value on xrCreateInstance.
+ RETURN_IF_FALSE(reinterpret_cast<OpenXrTestHelper*>(instance) == this,
+ XR_ERROR_VALIDATION_FAILURE, "XrInstance invalid");
+
+ return XR_SUCCESS;
+}
+
+XrResult OpenXrTestHelper::ValidateSystemId(XrSystemId system_id) const {
+ RETURN_IF_FALSE(system_id_ != 0, XR_ERROR_SYSTEM_INVALID,
+ "XrSystemId has not been queried");
+ RETURN_IF_FALSE(system_id == system_id_, XR_ERROR_SYSTEM_INVALID,
+ "XrSystemId invalid");
+
+ return XR_SUCCESS;
+}
+
+XrResult OpenXrTestHelper::ValidateSession(XrSession session) const {
+ RETURN_IF_FALSE(session_ != XR_NULL_HANDLE, XR_ERROR_VALIDATION_FAILURE,
+ "XrSession has not been queried");
+ RETURN_IF_FALSE(session == session_, XR_ERROR_VALIDATION_FAILURE,
+ "XrSession invalid");
+
+ return XR_SUCCESS;
+}
+
+XrResult OpenXrTestHelper::ValidateSwapchain(XrSwapchain swapchain) const {
+ RETURN_IF_FALSE(swapchain_ != XR_NULL_HANDLE, XR_ERROR_VALIDATION_FAILURE,
+ "XrSwapchain has not been queried");
+ RETURN_IF_FALSE(swapchain == swapchain_, XR_ERROR_VALIDATION_FAILURE,
+ "XrSwapchain invalid");
+
+ return XR_SUCCESS;
+}
+
+XrResult OpenXrTestHelper::ValidateSpace(XrSpace space) const {
+ RETURN_IF_FALSE(space != XR_NULL_HANDLE, XR_ERROR_HANDLE_INVALID,
+ "XrSpace has not been queried");
+ RETURN_IF_FALSE(reinterpret_cast<uint32_t>(space) <= next_action_space_,
+ XR_ERROR_HANDLE_INVALID, "XrSpace invalid");
+
+ return XR_SUCCESS;
+}
+
+XrResult OpenXrTestHelper::ValidatePredictedDisplayTime(XrTime time) const {
+ RETURN_IF_FALSE(time != 0, XR_ERROR_VALIDATION_FAILURE,
+ "XrTime has not been queried");
+ RETURN_IF_FALSE(time <= next_predicted_display_time_,
+ XR_ERROR_VALIDATION_FAILURE,
+ "XrTime predicted display time invalid");
+
+ return XR_SUCCESS;
+}
+
+XrResult OpenXrTestHelper::ValidateXrPosefIsIdentity(
+ const XrPosef& pose) const {
+ XrPosef identity = device::PoseIdentity();
+ bool is_identity = true;
+ is_identity &= pose.orientation.x == identity.orientation.x;
+ is_identity &= pose.orientation.y == identity.orientation.y;
+ is_identity &= pose.orientation.z == identity.orientation.z;
+ is_identity &= pose.orientation.w == identity.orientation.w;
+ is_identity &= pose.position.x == identity.position.x;
+ is_identity &= pose.position.y == identity.position.y;
+ is_identity &= pose.position.z == identity.position.z;
+ RETURN_IF_FALSE(is_identity, XR_ERROR_VALIDATION_FAILURE,
+ "XrPosef is not an identity");
+
+ return XR_SUCCESS;
+}
diff --git a/device/vr/openxr/test/openxr_test_helper.h b/device/vr/openxr/test/openxr_test_helper.h
new file mode 100644
index 0000000..38e960f4
--- /dev/null
+++ b/device/vr/openxr/test/openxr_test_helper.h
@@ -0,0 +1,98 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// 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 <d3d11.h>
+#include <unknwn.h>
+#include <wrl.h>
+#include <vector>
+
+#include "base/synchronization/lock.h"
+#include "device/vr/test/test_hook.h"
+#include "third_party/openxr/include/openxr/openxr.h"
+
+class OpenXrTestHelper : public device::ServiceTestHook {
+ public:
+ OpenXrTestHelper();
+ ~OpenXrTestHelper();
+
+ 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();
+ XrSession GetSession();
+ XrSwapchain GetSwapchain();
+ XrSpace CreateLocalSpace();
+ XrSpace CreateViewSpace();
+
+ XrResult BeginSession();
+ XrResult EndSession();
+
+ void SetD3DDevice(ID3D11Device* d3d_device);
+ const std::vector<Microsoft::WRL::ComPtr<ID3D11Texture2D>>&
+ GetSwapchainTextures() const;
+ uint32_t NextSwapchainImageIndex();
+ XrTime NextPredictedDisplayTime();
+
+ void GetPose(XrPosef* pose);
+
+ // Methods that validate the parameter with the current state of the runtime.
+ 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 ValidatePredictedDisplayTime(XrTime time) const;
+ XrResult ValidateXrPosefIsIdentity(const XrPosef& pose) const;
+
+ // Properties of the mock OpenXR runtime that does not change are created
+ // as static variables.
+ static uint32_t NumExtensionsSupported();
+ static uint32_t NumViews();
+ static const char* kExtensions[];
+ static const uint32_t kDimension;
+ static const uint32_t kSwapCount;
+ static const uint32_t kMinSwapchainBuffering;
+ static const uint32_t kMaxViewCount;
+ static const XrViewConfigurationView kViewConfigView;
+ static XrViewConfigurationView kViewConfigurationViews[];
+ static const XrEnvironmentBlendMode kEnvironmentBlendMode;
+
+ private:
+ // 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_;
+ XrSpace local_space_;
+ XrSpace view_space_;
+
+ // Properties that changes depending on the state of the runtime.
+ bool session_running_;
+ Microsoft::WRL::ComPtr<ID3D11Device> d3d_device_;
+ std::vector<Microsoft::WRL::ComPtr<ID3D11Texture2D>> textures_arr_;
+ uint32_t acquired_swapchain_texture_;
+ uint32_t next_action_space_;
+ XrTime next_predicted_display_time_;
+
+ device::VRTestHook* test_hook_ GUARDED_BY(lock_) = nullptr;
+ base::Lock lock_;
+};
+
+#endif // DEVICE_VR_OPENXR_TEST_OPENXR_TEST_HELPER_H_