From 4fa1925a7c2f35a875c7d0af06f3731ef232fbff Mon Sep 17 00:00:00 2001 From: Ryan Pavlik Date: Mon, 18 Sep 2023 16:16:53 -0500 Subject: [PATCH] OpenXR CTS 1.0.29.0 (2023-09-07) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Conformance Tests - Fix: Use actual acquired image index in swapchain rendering test. (internal MR 2746) - Fix: Do not use Catch2 assertion macros in graphics plugin methods that may be called before the first test case execution begins. (internal MR 2756, internal issue 1387) - Fix: spelling. (internal MR 2766) - Fix: Fix element contents in Android manifest. (internal MR 2840, internal issue 2053) - Fix: Allow building CTS with mingw compiler. (internal MR 2850) - Fix: Do not create an XrInstance during XML writing process, to prevent possible crash if one already exists. (internal MR 2927) - Improvement: Refactor utilities that do not depend on Catch2 into a separate internal library. (internal MR 2669) - Improvement: Refactor and standardize creation of swapchain image format tables, fixing some Vulkan invalid usage. (internal MR 2685, internal issue 1978) - Improvement: Make composition test help/example world locked but based on initial view, for more natural reading. (internal MR 2689) - Improvement: Cleanup and code quality work. (internal MR 2704, internal MR 2717, internal MR 2784, internal MR 2785, internal MR 2808, internal MR 2809) - Improvement: Add separate license file for gradlew and gradlew.bat (internal MR 2725) - Improvement: Optionally poll xrGetSystem before running test cases. (internal MR 2735, OpenXR-CTS issue 53, internal issue 1947) - Improvement: Select the first enumerated environment blend mode by default, rather than always using “opaque” (internal MR 2736, internal issue 1950) - Improvement: Migrate more tests to use the SKIP macro when appropriate. (internal MR 2737, internal issue 1932) - Improvement: Change background color based on selected blend mode: black for additive and transparent for alpha blend. (internal MR 2883, internal issue 1949) - Improvement: Add extra information to errors in case of CTS timeouts. (internal MR 2889) - Improvement: Remove conditional XR_KHR_headless support as the extension is not part of OpenXR 1.0. (internal MR 2901) - Improvement: Remove empty XR_EXT_performance_settings test that was never implemented (internal MR 2902) - Improvement: Fix names of tests to not have spaces, and adjust tags so that the instructions in the README will cause all tests to be executed. (internal MR 2924) - New test: Verify two-call idiom behavior of XR_MSFT_controller_model as well as handling of invalid model keys. (internal MR 2387, internal MR 2858) - New test: Added XR_EXT_plane_detection extension. (internal MR 2510, internal MR 2751, internal MR 2676) - New test: Add non-interactive test for XR_EXT_palm_pose vendor extension. (internal MR 2672) - New test: Add joint query to non-interactive test for XR_EXT_hand_tracking. (internal MR 2729, internal MR 2795, internal MR 2858, internal MR 2916) - New test: Add test for calling xrAcquireSwapchainImage multiple times without calling xrEndFrame. (internal MR 2730) - New test: Add additional tests for XR_EXT_debug_utils based on the test app loader_test. (internal MR 2775) - New test: Add checks for palm position and palm and wrist orientation to XR_EXT_hand_tracking interactive tests. (internal MR 2798) - New test: Add unbound action set to action sets test. (internal MR 2862, internal issue 2043) - New test: Add conformance test for calling xrDestroyInstance from a different thread to xrCreateInstance, and xrDestroySession on a different thread to xrCreateSession. (internal MR 2863) - New test: Add interactive conformance test for infrequently updated swapchains. (internal MR 2873) - New test: Add conformance tests for xrCreateSession failing, then passing (internal MR 2884) - New test: Test xrSyncActions with no active action sets. (internal MR 2903) - New test: Test calling xrLocateSpace with timestamps up to 1s old. (internal MR 2904) GitOrigin-RevId: 2b2967dda2f7cc444561b8547fedf70a5613c6a1 --- .reuse/dep5 | 4 +- CHANGELOG.CTS.md | 104 +++++++++++++++ changes/conformance/mr.2387.gl.md | 4 - changes/conformance/mr.2510.gl.md | 5 - changes/conformance/mr.2669.gl.md | 1 - changes/conformance/mr.2672.gl.md | 1 - changes/conformance/mr.2685.gl.md | 4 - changes/conformance/mr.2689.gl.md | 1 - changes/conformance/mr.2704.gl.md | 7 - changes/conformance/mr.2717.gl.md | 1 - changes/conformance/mr.2725.gl.md | 1 - changes/conformance/mr.2729.gl.md | 5 - changes/conformance/mr.2730.gl.md | 1 - changes/conformance/mr.2735.gl.md | 5 - changes/conformance/mr.2736.gl.md | 4 - changes/conformance/mr.2737.gl.md | 4 - changes/conformance/mr.2746.gl.md | 1 - changes/conformance/mr.2756.gl.md | 4 - changes/conformance/mr.2766.gl.md | 1 - changes/conformance/mr.2775.gl.md | 1 - changes/conformance/mr.2840.gl.md | 4 - changes/conformance/mr.2850.gl.md | 1 - src/conformance/build.gradle | 34 ++--- .../composition_examples/stale_swapchain.png | Bin 0 -> 60387 bytes .../stale_swapchain.png.license | 3 + .../conformance_test/test_FrameSubmission.cpp | 4 +- .../conformance_test/test_HapticInterrupt.cpp | 2 +- .../test_InteractiveThrow.cpp | 2 +- .../test_LayerComposition.cpp | 60 +++++++++ .../conformance_test/test_Swapchains.cpp | 9 +- .../test_XR_EXT_debug_utils.cpp | 14 +- .../test_XR_EXT_eye_gaze_interaction.cpp | 4 +- .../test_XR_EXT_hand_tracking.cpp | 95 +++++++++++--- .../test_XR_EXT_local_floor.cpp | 9 +- .../test_XR_EXT_palm_pose.cpp | 7 +- .../test_XR_EXT_performance_settings.cpp | 28 ---- .../test_XR_KHR_D3D11_enable.cpp | 65 +++++++++- .../test_XR_KHR_D3D12_enable.cpp | 66 +++++++++- .../test_XR_KHR_OpenGL_ES_enable.cpp | 62 ++++++++- .../test_XR_KHR_OpenGL_enable.cpp | 71 ++++++++-- .../test_XR_KHR_composition_layer_cube.cpp | 10 +- ...test_XR_KHR_composition_layer_cylinder.cpp | 11 +- .../test_XR_KHR_composition_layer_depth.cpp | 10 +- ...test_XR_KHR_composition_layer_equirect.cpp | 10 +- .../test_XR_KHR_convert_timespec_time.cpp | 7 +- .../conformance_test/test_XR_KHR_headless.cpp | 72 ----------- .../test_XR_KHR_visibility_mask.cpp | 11 +- .../test_XR_KHR_vulkan_enable.cpp | 64 ++++++++- .../test_XR_KHR_vulkan_enable2.cpp | 64 ++++++++- ...win32_convert_performance_counter_time.cpp | 7 +- .../test_XR_META_performance_metrics.cpp | 9 +- .../conformance_test/test_actions.cpp | 65 ++++++++-- .../conformance_test/test_multithreading.cpp | 7 +- .../test_xrCreateInstance.cpp | 14 ++ .../conformance_test/test_xrCreateSession.cpp | 30 ++++- .../conformance_test/test_xrLocateSpace.cpp | 39 ++++-- .../conformance_test/test_xrLocateViews.cpp | 12 +- src/conformance/framework/composition_utils.h | 2 + .../framework/conformance_framework.cpp | 20 ++- .../framework/conformance_framework.h | 6 +- .../framework/conformance_utils.cpp | 121 +++++++++--------- src/conformance/framework/conformance_utils.h | 24 ++-- src/conformance/framework/graphics_plugin.h | 12 +- .../framework/graphics_plugin_d3d11.cpp | 7 +- .../framework/graphics_plugin_d3d12.cpp | 7 +- .../framework/graphics_plugin_opengl.cpp | 7 +- .../framework/graphics_plugin_opengles.cpp | 7 +- .../framework/graphics_plugin_vulkan.cpp | 13 +- .../framework/mesh_projection_layer.cpp | 2 +- .../framework/xml_test_environment.cpp | 1 - src/conformance/gradle.properties | 10 ++ .../gradle/wrapper/gradle-wrapper.jar | Bin 60756 -> 61608 bytes .../gradle/wrapper/gradle-wrapper.properties | 1 + src/conformance/gradlew | 12 +- src/conformance/gradlew.bat | 1 + .../platform_specific/AndroidManifest.xml | 9 +- src/conformance/settings.gradle | 17 +++ src/external/d3dx12/LICENSE | 34 ++--- src/external/d3dx12/README.md | 59 --------- src/external/d3dx12/d3dx12.h | 79 ++++++++---- 80 files changed, 1012 insertions(+), 580 deletions(-) delete mode 100644 changes/conformance/mr.2387.gl.md delete mode 100644 changes/conformance/mr.2510.gl.md delete mode 100644 changes/conformance/mr.2669.gl.md delete mode 100644 changes/conformance/mr.2672.gl.md delete mode 100644 changes/conformance/mr.2685.gl.md delete mode 100644 changes/conformance/mr.2689.gl.md delete mode 100644 changes/conformance/mr.2704.gl.md delete mode 100644 changes/conformance/mr.2717.gl.md delete mode 100644 changes/conformance/mr.2725.gl.md delete mode 100644 changes/conformance/mr.2729.gl.md delete mode 100644 changes/conformance/mr.2730.gl.md delete mode 100644 changes/conformance/mr.2735.gl.md delete mode 100644 changes/conformance/mr.2736.gl.md delete mode 100644 changes/conformance/mr.2737.gl.md delete mode 100644 changes/conformance/mr.2746.gl.md delete mode 100644 changes/conformance/mr.2756.gl.md delete mode 100644 changes/conformance/mr.2766.gl.md delete mode 100644 changes/conformance/mr.2775.gl.md delete mode 100644 changes/conformance/mr.2840.gl.md delete mode 100644 changes/conformance/mr.2850.gl.md create mode 100644 src/conformance/conformance_test/composition_examples/stale_swapchain.png create mode 100644 src/conformance/conformance_test/composition_examples/stale_swapchain.png.license delete mode 100644 src/conformance/conformance_test/test_XR_EXT_performance_settings.cpp delete mode 100644 src/conformance/conformance_test/test_XR_KHR_headless.cpp create mode 100644 src/conformance/gradle.properties delete mode 100644 src/external/d3dx12/README.md diff --git a/.reuse/dep5 b/.reuse/dep5 index 3df6553a..cedb22dd 100644 --- a/.reuse/dep5 +++ b/.reuse/dep5 @@ -54,8 +54,8 @@ Comment: Unmodified, vendored copy of a subset of the tiny-gltf repo v2.8.9 Files: src/external/d3dx12/* Copyright: Copyright (c) Microsoft Corporation. License: MIT -Comment: Unmodified, vendored copy of DirectX-Headers commit da7aedb - of https://github.com/microsoft/DirectX-Headers filtered to just d3dx12 headers +Comment: Unmodified, vendored copy of d3dx12.h from directx-vs-templates commit 86b9c45 + This repo includes ifdefs to improve compatibility with older Windows SDK verions. Files: src/external/mikktspace/* Copyright: 2011 by Morten S. Mikkelsen diff --git a/CHANGELOG.CTS.md b/CHANGELOG.CTS.md index dd89ef9f..e09d4125 100644 --- a/CHANGELOG.CTS.md +++ b/CHANGELOG.CTS.md @@ -17,6 +17,110 @@ particular, since it is primarily software, pull requests may be integrated as they are accepted even between periodic updates. However, versions that are not signed tags on the `approved` branch are not valid for conformance submission. +## OpenXR CTS 1.0.29.0 (2023-09-07) + +- Conformance Tests + - Fix: Use actual acquired image index in swapchain rendering test. + ([internal MR 2746](https://gitlab.khronos.org/openxr/openxr/merge_requests/2746)) + - Fix: Do not use Catch2 assertion macros in graphics plugin methods that may be + called before the first test case execution begins. + ([internal MR 2756](https://gitlab.khronos.org/openxr/openxr/merge_requests/2756), + [internal issue 1387](https://gitlab.khronos.org/openxr/openxr/issues/1387)) + - Fix: spelling. + ([internal MR 2766](https://gitlab.khronos.org/openxr/openxr/merge_requests/2766)) + - Fix: Fix `` element contents in Android manifest. + ([internal MR 2840](https://gitlab.khronos.org/openxr/openxr/merge_requests/2840), + [internal issue 2053](https://gitlab.khronos.org/openxr/openxr/issues/2053)) + - Fix: Allow building CTS with mingw compiler. + ([internal MR 2850](https://gitlab.khronos.org/openxr/openxr/merge_requests/2850)) + - Fix: Do not create an `XrInstance` during XML writing process, to prevent + possible crash if one already exists. + ([internal MR 2927](https://gitlab.khronos.org/openxr/openxr/merge_requests/2927)) + - Improvement: Refactor utilities that do not depend on Catch2 into a separate + internal library. + ([internal MR 2669](https://gitlab.khronos.org/openxr/openxr/merge_requests/2669)) + - Improvement: Refactor and standardize creation of swapchain image format + tables, fixing some Vulkan invalid usage. + ([internal MR 2685](https://gitlab.khronos.org/openxr/openxr/merge_requests/2685), + [internal issue 1978](https://gitlab.khronos.org/openxr/openxr/issues/1978)) + - Improvement: Make composition test help/example world locked but based on + initial view, for more natural reading. + ([internal MR 2689](https://gitlab.khronos.org/openxr/openxr/merge_requests/2689)) + - Improvement: Cleanup and code quality work. + ([internal MR 2704](https://gitlab.khronos.org/openxr/openxr/merge_requests/2704), + [internal MR 2717](https://gitlab.khronos.org/openxr/openxr/merge_requests/2717), + [internal MR 2784](https://gitlab.khronos.org/openxr/openxr/merge_requests/2784), + [internal MR 2785](https://gitlab.khronos.org/openxr/openxr/merge_requests/2785), + [internal MR 2808](https://gitlab.khronos.org/openxr/openxr/merge_requests/2808), + [internal MR 2809](https://gitlab.khronos.org/openxr/openxr/merge_requests/2809)) + - Improvement: Add separate license file for gradlew and gradlew.bat + ([internal MR 2725](https://gitlab.khronos.org/openxr/openxr/merge_requests/2725)) + - Improvement: Optionally poll `xrGetSystem` before running test cases. + ([internal MR 2735](https://gitlab.khronos.org/openxr/openxr/merge_requests/2735), + [OpenXR-CTS issue 53](https://github.com/KhronosGroup/OpenXR-CTS/issues/53), + [internal issue 1947](https://gitlab.khronos.org/openxr/openxr/issues/1947)) + - Improvement: Select the first enumerated environment blend mode by default, + rather than always using "opaque" + ([internal MR 2736](https://gitlab.khronos.org/openxr/openxr/merge_requests/2736), + [internal issue 1950](https://gitlab.khronos.org/openxr/openxr/issues/1950)) + - Improvement: Migrate more tests to use the `SKIP` macro when appropriate. + ([internal MR 2737](https://gitlab.khronos.org/openxr/openxr/merge_requests/2737), + [internal issue 1932](https://gitlab.khronos.org/openxr/openxr/issues/1932)) + - Improvement: Change background color based on selected blend mode: black for + additive and transparent for alpha blend. + ([internal MR 2883](https://gitlab.khronos.org/openxr/openxr/merge_requests/2883), + [internal issue 1949](https://gitlab.khronos.org/openxr/openxr/issues/1949)) + - Improvement: Add extra information to errors in case of CTS timeouts. + ([internal MR 2889](https://gitlab.khronos.org/openxr/openxr/merge_requests/2889)) + - Improvement: Remove conditional `XR_KHR_headless` support as the extension is + not part of OpenXR 1.0. + ([internal MR 2901](https://gitlab.khronos.org/openxr/openxr/merge_requests/2901)) + - Improvement: Remove empty `XR_EXT_performance_settings` test that was never + implemented + ([internal MR 2902](https://gitlab.khronos.org/openxr/openxr/merge_requests/2902)) + - Improvement: Fix names of tests to not have spaces, and adjust tags so that the + instructions in the README will cause all tests to be executed. + ([internal MR 2924](https://gitlab.khronos.org/openxr/openxr/merge_requests/2924)) + - New test: Verify two-call idiom behavior of `XR_MSFT_controller_model` as well + as handling of invalid model keys. + ([internal MR 2387](https://gitlab.khronos.org/openxr/openxr/merge_requests/2387), + [internal MR 2858](https://gitlab.khronos.org/openxr/openxr/merge_requests/2858)) + - New test: Added `XR_EXT_plane_detection` extension. + ([internal MR 2510](https://gitlab.khronos.org/openxr/openxr/merge_requests/2510), + [internal MR 2751](https://gitlab.khronos.org/openxr/openxr/merge_requests/2751), + [internal MR 2676](https://gitlab.khronos.org/openxr/openxr/merge_requests/2676)) + - New test: Add non-interactive test for `XR_EXT_palm_pose` vendor extension. + ([internal MR 2672](https://gitlab.khronos.org/openxr/openxr/merge_requests/2672)) + - New test: Add joint query to non-interactive test for `XR_EXT_hand_tracking`. + ([internal MR 2729](https://gitlab.khronos.org/openxr/openxr/merge_requests/2729), + [internal MR 2795](https://gitlab.khronos.org/openxr/openxr/merge_requests/2795), + [internal MR 2858](https://gitlab.khronos.org/openxr/openxr/merge_requests/2858), + [internal MR 2916](https://gitlab.khronos.org/openxr/openxr/merge_requests/2916)) + - New test: Add test for calling `xrAcquireSwapchainImage` multiple times without + calling `xrEndFrame`. + ([internal MR 2730](https://gitlab.khronos.org/openxr/openxr/merge_requests/2730)) + - New test: Add additional tests for `XR_EXT_debug_utils` based on the test app + `loader_test`. + ([internal MR 2775](https://gitlab.khronos.org/openxr/openxr/merge_requests/2775)) + - New test: Add checks for palm position and palm and wrist orientation to + `XR_EXT_hand_tracking` interactive tests. + ([internal MR 2798](https://gitlab.khronos.org/openxr/openxr/merge_requests/2798)) + - New test: Add unbound action set to action sets test. + ([internal MR 2862](https://gitlab.khronos.org/openxr/openxr/merge_requests/2862), + [internal issue 2043](https://gitlab.khronos.org/openxr/openxr/issues/2043)) + - New test: Add conformance test for calling `xrDestroyInstance` from a different + thread to `xrCreateInstance`, and `xrDestroySession` on a different thread to + `xrCreateSession`. + ([internal MR 2863](https://gitlab.khronos.org/openxr/openxr/merge_requests/2863)) + - New test: Add interactive conformance test for infrequently updated swapchains. + ([internal MR 2873](https://gitlab.khronos.org/openxr/openxr/merge_requests/2873)) + - New test: Add conformance tests for `xrCreateSession` failing, then passing + ([internal MR 2884](https://gitlab.khronos.org/openxr/openxr/merge_requests/2884)) + - New test: Test `xrSyncActions` with no active action sets. + ([internal MR 2903](https://gitlab.khronos.org/openxr/openxr/merge_requests/2903)) + - New test: Test calling `xrLocateSpace` with timestamps up to 1s old. + ([internal MR 2904](https://gitlab.khronos.org/openxr/openxr/merge_requests/2904)) + ## OpenXR CTS 1.0.27.0 (2023-05-10) This release contains a large number of new or improved tests. It is expected diff --git a/changes/conformance/mr.2387.gl.md b/changes/conformance/mr.2387.gl.md deleted file mode 100644 index 988c6921..00000000 --- a/changes/conformance/mr.2387.gl.md +++ /dev/null @@ -1,4 +0,0 @@ ---- -- mr.2858.gl ---- -New test: Verify two-call idiom behavior of `XR_MSFT_controller_model` as well as handling of invalid model keys. diff --git a/changes/conformance/mr.2510.gl.md b/changes/conformance/mr.2510.gl.md deleted file mode 100644 index bd57edb1..00000000 --- a/changes/conformance/mr.2510.gl.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -- mr.2751.gl -- mr.2676.gl ---- -New test: Added `XR_EXT_plane_detection` extension. diff --git a/changes/conformance/mr.2669.gl.md b/changes/conformance/mr.2669.gl.md deleted file mode 100644 index abcfc994..00000000 --- a/changes/conformance/mr.2669.gl.md +++ /dev/null @@ -1 +0,0 @@ -Improvement: Refactor utilities that do not depend on Catch2 into a separate internal library. diff --git a/changes/conformance/mr.2672.gl.md b/changes/conformance/mr.2672.gl.md deleted file mode 100644 index a703b406..00000000 --- a/changes/conformance/mr.2672.gl.md +++ /dev/null @@ -1 +0,0 @@ -Improvement: Add non-interactive test for XR_EXT_palm_pose vendor extension. diff --git a/changes/conformance/mr.2685.gl.md b/changes/conformance/mr.2685.gl.md deleted file mode 100644 index c77f7c89..00000000 --- a/changes/conformance/mr.2685.gl.md +++ /dev/null @@ -1,4 +0,0 @@ ---- -- issue.1978.gl ---- -Improvement: Refactor and standardize creation of swapchain image format tables, fixing some Vulkan invalid usage. diff --git a/changes/conformance/mr.2689.gl.md b/changes/conformance/mr.2689.gl.md deleted file mode 100644 index 07b3e8bd..00000000 --- a/changes/conformance/mr.2689.gl.md +++ /dev/null @@ -1 +0,0 @@ -Improvement: Make composition test help/example world locked but based on initial view, for more natural reading. diff --git a/changes/conformance/mr.2704.gl.md b/changes/conformance/mr.2704.gl.md deleted file mode 100644 index a792439d..00000000 --- a/changes/conformance/mr.2704.gl.md +++ /dev/null @@ -1,7 +0,0 @@ ---- -- mr.2784.gl -- mr.2785.gl -- mr.2808.gl -- mr.2809.gl ---- -Improvement: Cleanup and code quality work. diff --git a/changes/conformance/mr.2717.gl.md b/changes/conformance/mr.2717.gl.md deleted file mode 100644 index cb4f77b3..00000000 --- a/changes/conformance/mr.2717.gl.md +++ /dev/null @@ -1 +0,0 @@ -Improvement: Fix clang warnings `-Wundef` and `-Wmissing-braces`. diff --git a/changes/conformance/mr.2725.gl.md b/changes/conformance/mr.2725.gl.md deleted file mode 100644 index e33f542f..00000000 --- a/changes/conformance/mr.2725.gl.md +++ /dev/null @@ -1 +0,0 @@ -gradle: Add license for gradlew and gradlew.bat diff --git a/changes/conformance/mr.2729.gl.md b/changes/conformance/mr.2729.gl.md deleted file mode 100644 index 7e44ad1a..00000000 --- a/changes/conformance/mr.2729.gl.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -- mr.2795.gl -- mr.2858.gl ---- -Improvement: Add joint query to non-interactive test for `XR_EXT_hand_tracking`. diff --git a/changes/conformance/mr.2730.gl.md b/changes/conformance/mr.2730.gl.md deleted file mode 100644 index a7720525..00000000 --- a/changes/conformance/mr.2730.gl.md +++ /dev/null @@ -1 +0,0 @@ -Improvement: Add test for calling `xrAcquireSwapchainImage` multiple times without calling `xrEndFrame`. diff --git a/changes/conformance/mr.2735.gl.md b/changes/conformance/mr.2735.gl.md deleted file mode 100644 index d7f49116..00000000 --- a/changes/conformance/mr.2735.gl.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -- issue.53.gh.OpenXR-CTS -- issue.1947.gl ---- -Improvement: Optionally poll `xrGetSystem` before running test cases. diff --git a/changes/conformance/mr.2736.gl.md b/changes/conformance/mr.2736.gl.md deleted file mode 100644 index 29a8c04a..00000000 --- a/changes/conformance/mr.2736.gl.md +++ /dev/null @@ -1,4 +0,0 @@ ---- -- issue.1950.gl ---- -Improvement: Select the first enumerated environment blend mode by default, rather than always using "opaque" diff --git a/changes/conformance/mr.2737.gl.md b/changes/conformance/mr.2737.gl.md deleted file mode 100644 index 1f289d4d..00000000 --- a/changes/conformance/mr.2737.gl.md +++ /dev/null @@ -1,4 +0,0 @@ ---- -- issue.1932.gl ---- -Improvement: Migrate more tests to use the `SKIP` macro when appropriate. diff --git a/changes/conformance/mr.2746.gl.md b/changes/conformance/mr.2746.gl.md deleted file mode 100644 index 34bc4818..00000000 --- a/changes/conformance/mr.2746.gl.md +++ /dev/null @@ -1 +0,0 @@ -Fix: Use actual acquired image index in swapchain rendering test. diff --git a/changes/conformance/mr.2756.gl.md b/changes/conformance/mr.2756.gl.md deleted file mode 100644 index ddfb77dc..00000000 --- a/changes/conformance/mr.2756.gl.md +++ /dev/null @@ -1,4 +0,0 @@ ---- -- issue.1387.gl ---- -Fix: Do not use Catch2 assertion macros in graphics plugin methods that may be called before the first test case execution begins. diff --git a/changes/conformance/mr.2766.gl.md b/changes/conformance/mr.2766.gl.md deleted file mode 100644 index 944b119d..00000000 --- a/changes/conformance/mr.2766.gl.md +++ /dev/null @@ -1 +0,0 @@ -Fix: spelling. diff --git a/changes/conformance/mr.2775.gl.md b/changes/conformance/mr.2775.gl.md deleted file mode 100644 index c2164b35..00000000 --- a/changes/conformance/mr.2775.gl.md +++ /dev/null @@ -1 +0,0 @@ -Improvement: Add additional tests for XR_EXT_debug_utils based on the test app loader_test. diff --git a/changes/conformance/mr.2840.gl.md b/changes/conformance/mr.2840.gl.md deleted file mode 100644 index 9860ffb9..00000000 --- a/changes/conformance/mr.2840.gl.md +++ /dev/null @@ -1,4 +0,0 @@ ---- -- issue.2053.gl ---- -- Fix: Fix `` element contents in Android manifest. diff --git a/changes/conformance/mr.2850.gl.md b/changes/conformance/mr.2850.gl.md deleted file mode 100644 index 85701ee8..00000000 --- a/changes/conformance/mr.2850.gl.md +++ /dev/null @@ -1 +0,0 @@ -Fix building CTS with mingw compiler. diff --git a/src/conformance/build.gradle b/src/conformance/build.gradle index 8276908d..4a4c7060 100644 --- a/src/conformance/build.gradle +++ b/src/conformance/build.gradle @@ -4,22 +4,11 @@ // Open this directory in Android Studio, or build with Gradle, // to build the conformance suite. -buildscript { - repositories { - google() - mavenCentral() - } - dependencies { - classpath 'com.android.tools.build:gradle:7.4.2' - } -} -repositories { - google() - mavenCentral() +plugins { + id 'com.android.application' version '7.4.2' } -apply plugin: 'com.android.application' // These next few lines are just to make the version match the OpenXR release. project.ext.repoRoot = file('../../') @@ -27,33 +16,30 @@ apply from: file('../version.gradle') def assetDest = layout.buildDir.dir('intermediates/assets') -task copyFont(type: Copy) { +task copyAssets(type: Copy) { + // fonts from('conformance_test') { include '*.otf' } - into assetDest -} -task copyAssets(type: Copy) { + // sample images from('conformance_test/composition_examples') { include '*.png' } - into assetDest - dependsOn copyFont -} -task copyLayerInfo(type: Copy) { + // layer manifests from('conformance_test/android_assets') { include '**/*.json' } + into assetDest - dependsOn copyFont } android { compileSdk 29 ndkVersion "21.4.7075529" buildToolsVersion = "30.0.3" + namespace 'org.khronos.openxr.cts' defaultConfig { applicationId "org.khronos.openxr.cts" @@ -65,8 +51,7 @@ android { externalNativeBuild { cmake { - arguments '-DANDROID_STL=c++_shared', - '-DBUILD_TESTS=OFF', + arguments '-DBUILD_TESTS=OFF', '-DBUILD_LOADER=ON', '-DBUILD_CONFORMANCE_TESTS=ON', '-DBUILD_ALL_EXTENSIONS=ON' @@ -74,7 +59,6 @@ android { } } preBuild.dependsOn(copyAssets) - preBuild.dependsOn(copyLayerInfo) } sourceSets { main { diff --git a/src/conformance/conformance_test/composition_examples/stale_swapchain.png b/src/conformance/conformance_test/composition_examples/stale_swapchain.png new file mode 100644 index 0000000000000000000000000000000000000000..91afdfb844a5abe8d530b47a547f488654902fba GIT binary patch literal 60387 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 literal 0 HcmV?d00001 diff --git a/src/conformance/conformance_test/composition_examples/stale_swapchain.png.license b/src/conformance/conformance_test/composition_examples/stale_swapchain.png.license new file mode 100644 index 00000000..6d36aba0 --- /dev/null +++ b/src/conformance/conformance_test/composition_examples/stale_swapchain.png.license @@ -0,0 +1,3 @@ +SPDX-FileCopyrightText: 2020-2023, The Khronos Group Inc. + +SPDX-License-Identifier: Apache-2.0 diff --git a/src/conformance/conformance_test/test_FrameSubmission.cpp b/src/conformance/conformance_test/test_FrameSubmission.cpp index 1a343852..77662b8d 100644 --- a/src/conformance/conformance_test/test_FrameSubmission.cpp +++ b/src/conformance/conformance_test/test_FrameSubmission.cpp @@ -31,6 +31,7 @@ #define ENUM_LIST(name, val) name, constexpr XrEnvironmentBlendMode SupportedBlendModes[] = {XR_LIST_ENUM_XrEnvironmentBlendMode(ENUM_LIST)}; +#undef ENUM_LIST namespace Conformance { @@ -204,7 +205,8 @@ namespace Conformance CHECK(XR_SUCCESS == xrRequestExitSession(session)); - REQUIRE(FrameIterator::RunResult::Success == FrameIterator(&session).RunToSessionState(XR_SESSION_STATE_STOPPING, 5s)); + FrameIterator frameIterator(&session); + frameIterator.RunToSessionState(XR_SESSION_STATE_STOPPING); CHECK(XR_SUCCESS == xrEndSession(session)); diff --git a/src/conformance/conformance_test/test_HapticInterrupt.cpp b/src/conformance/conformance_test/test_HapticInterrupt.cpp index 89422738..c5ad5d63 100644 --- a/src/conformance/conformance_test/test_HapticInterrupt.cpp +++ b/src/conformance/conformance_test/test_HapticInterrupt.cpp @@ -39,7 +39,7 @@ namespace Conformance { constexpr XrVector3f Up{0, 1, 0}; - TEST_CASE("Haptic Interrupt", "[scenario][interactive][no_auto]") + TEST_CASE("HapticInterrupt", "[scenario][interactive][no_auto]") { const char* instructions = "Press the select button on either hand to begin a 2 second haptic output. " diff --git a/src/conformance/conformance_test/test_InteractiveThrow.cpp b/src/conformance/conformance_test/test_InteractiveThrow.cpp index c3fdd4f8..b70c86b2 100644 --- a/src/conformance/conformance_test/test_InteractiveThrow.cpp +++ b/src/conformance/conformance_test/test_InteractiveThrow.cpp @@ -37,7 +37,7 @@ namespace Conformance // Purpose: Verify behavior of action timing and action space linear/angular velocity through throwing // 1. Use action state changed timestamp to query velocities // 2. Use action space velocities at various rigid offsets to verify "lever arm" effect is computed by runtime. - TEST_CASE("Interactive Throw", "[scenario][interactive][no_auto]") + TEST_CASE("InteractiveThrow", "[scenario][interactive][no_auto]") { const char* instructions = "Press and hold 'select' to spawn three rigidly-attached cubes to that controller. " diff --git a/src/conformance/conformance_test/test_LayerComposition.cpp b/src/conformance/conformance_test/test_LayerComposition.cpp index d3cf88c8..049d9340 100644 --- a/src/conformance/conformance_test/test_LayerComposition.cpp +++ b/src/conformance/conformance_test/test_LayerComposition.cpp @@ -653,4 +653,64 @@ namespace Conformance RenderLoop(compositionHelper.GetSession(), updateLayers).Loop(); } + + TEST_CASE("StaleSwapchain", "[composition][interactive][no_auto]") + { + CompositionHelper compositionHelper("Stale swapchain"); + InteractiveLayerManager interactiveLayerManager(compositionHelper, "stale_swapchain.png", + "Updates swapchain of each square at 1Hz. " + "Square on left should be constantly green, and square on right " + "should switch between green and blue every second. " + "If there is any flicker on the green square, " + "likely at the same time as the other square changes color, " + "that is a failure."); + compositionHelper.GetInteractionManager().AttachActionSets(); + compositionHelper.BeginSession(); + + const XrSpace viewSpace = compositionHelper.CreateReferenceSpace(XR_REFERENCE_SPACE_TYPE_VIEW, XrPosef{Quat::Identity, {0, 0, -1}}); + + constexpr int ImageSize = 1; + + // Create an array swapchain + auto swapchainCreateInfo = + compositionHelper.DefaultColorSwapchainCreateInfo(ImageSize, ImageSize, 0, GetGlobalData().graphicsPlugin->GetSRGBA8Format()); + swapchainCreateInfo.usageFlags |= XR_SWAPCHAIN_USAGE_TRANSFER_DST_BIT; + const XrSwapchain constantColorSwapchain = compositionHelper.CreateSwapchain(swapchainCreateInfo); + const XrSwapchain alternatingColorSwapchain = compositionHelper.CreateSwapchain(swapchainCreateInfo); + + RGBAImage images[2] = {RGBAImage(ImageSize, ImageSize), RGBAImage(ImageSize, ImageSize)}; + images[0].DrawRect(0, 0, ImageSize, ImageSize, Colors::Green); + images[1].DrawRect(0, 0, ImageSize, ImageSize, Colors::Blue); + images[0].ConvertToSRGB(); + images[1].ConvertToSRGB(); + + XrCompositionLayerQuad* const constantQuad = + compositionHelper.CreateQuadLayer(constantColorSwapchain, viewSpace, 0.02f, XrPosef{Quat::Identity, {-0.1f, 0, -1}}); + interactiveLayerManager.AddLayer(constantQuad); + + XrCompositionLayerQuad* const alternatingQuad = + compositionHelper.CreateQuadLayer(alternatingColorSwapchain, viewSpace, 0.02f, XrPosef{Quat::Identity, {0.1f, 0, -1}}); + interactiveLayerManager.AddLayer(alternatingQuad); + + XrTime lastUpdate = 0; + bool alternatingIndex = false; + RenderLoop(compositionHelper.GetSession(), [&](const XrFrameState& frameState) { + // Failing this test may create a flashing image. 1Hz is well outside the + // documented normal range for photosensitive epilepsy (rarely as low as 3Hz). + // Regardless, failures may e.g. create a black flash every second, so we use a + // small square to minimise any effects of the failure condition. + if (lastUpdate == 0 || (frameState.predictedDisplayTime - lastUpdate) >= 1_xrSeconds) { + lastUpdate = frameState.predictedDisplayTime; + compositionHelper.AcquireWaitReleaseImage(constantColorSwapchain, [&](const XrSwapchainImageBaseHeader* swapchainImage) { + GetGlobalData().graphicsPlugin->CopyRGBAImage(swapchainImage, 0, images[0]); + }); + compositionHelper.AcquireWaitReleaseImage(alternatingColorSwapchain, [&](const XrSwapchainImageBaseHeader* swapchainImage) { + GetGlobalData().graphicsPlugin->CopyRGBAImage(swapchainImage, 0, images[(uint32_t)alternatingIndex]); + alternatingIndex = !alternatingIndex; + }); + } + return interactiveLayerManager.EndFrame(frameState); + }).Loop(); + } + } // namespace Conformance diff --git a/src/conformance/conformance_test/test_Swapchains.cpp b/src/conformance/conformance_test/test_Swapchains.cpp index db0b5a93..42eddf4e 100644 --- a/src/conformance/conformance_test/test_Swapchains.cpp +++ b/src/conformance/conformance_test/test_Swapchains.cpp @@ -628,13 +628,8 @@ namespace Conformance auto graphicsPlugin = globalData.GetGraphicsPlugin(); - // how long the test should wait for the app to get focus: 10 seconds in release, infinite in debug builds. - auto timeout = (GetGlobalData().options.debugMode ? 3600s : 10s); - CAPTURE(timeout); - FrameIterator frameIterator(&session); - FrameIterator::RunResult runResult = frameIterator.RunToSessionState(XR_SESSION_STATE_FOCUSED, timeout); - REQUIRE(runResult == FrameIterator::RunResult::Success); + frameIterator.RunToSessionState(XR_SESSION_STATE_FOCUSED); XrSwapchain swapchain{XR_NULL_HANDLE}; XrExtent2Di widthHeight{0, 0}; // 0,0 means Use defaults. @@ -673,7 +668,7 @@ namespace Conformance } if ((j % 2) == 0) { - runResult = frameIterator.SubmitFrame(); + FrameIterator::RunResult runResult = frameIterator.SubmitFrame(); REQUIRE(runResult == FrameIterator::RunResult::Success); } } diff --git a/src/conformance/conformance_test/test_XR_EXT_debug_utils.cpp b/src/conformance/conformance_test/test_XR_EXT_debug_utils.cpp index 2b6fda21..751defe6 100644 --- a/src/conformance/conformance_test/test_XR_EXT_debug_utils.cpp +++ b/src/conformance/conformance_test/test_XR_EXT_debug_utils.cpp @@ -796,9 +796,6 @@ namespace Conformance AutoBasicSession::createSession | AutoBasicSession::createSpaces | AutoBasicSession::createSwapchains, instance); FrameIterator frameIterator(&session); - auto timeout = (globalData.options.debugMode ? 3600s : 10s); - CAPTURE(timeout); - // Create a label struct for initial testing XrDebugUtilsLabelEXT first_label = {XR_TYPE_DEBUG_UTILS_LABEL_EXT}; first_label.labelName = first_individual_label_name; @@ -862,8 +859,7 @@ namespace Conformance // Begin the session now. { - FrameIterator::RunResult runResult = frameIterator.RunToSessionState(XR_SESSION_STATE_READY, timeout); - REQUIRE(runResult == FrameIterator::RunResult::Success); + frameIterator.RunToSessionState(XR_SESSION_STATE_READY); XrSessionBeginInfo session_begin_info = {XR_TYPE_SESSION_BEGIN_INFO}; session_begin_info.primaryViewConfigurationType = GetGlobalData().GetOptions().viewConfigurationValue; @@ -942,8 +938,7 @@ namespace Conformance { REQUIRE_RESULT(XR_SUCCESS, xrRequestExitSession(session)); - FrameIterator::RunResult runResult = frameIterator.RunToSessionState(XR_SESSION_STATE_STOPPING, timeout); - REQUIRE(runResult == FrameIterator::RunResult::Success); + frameIterator.RunToSessionState(XR_SESSION_STATE_STOPPING); REQUIRE_RESULT(XR_SUCCESS, xrEndSession(session)); } @@ -1062,11 +1057,8 @@ namespace Conformance AutoBasicInstance instance({XR_EXT_DEBUG_UTILS_EXTENSION_NAME}); AutoBasicSession session(AutoBasicSession::createSession, instance); - // Wait until the runtime is ready for us to begin a session - auto timeout = (GetGlobalData().options.debugMode ? 3600s : 10s); FrameIterator frameIterator(&session); - FrameIterator::RunResult runResult = frameIterator.RunToSessionState(XR_SESSION_STATE_READY, timeout); - REQUIRE(runResult == FrameIterator::RunResult::Success); + frameIterator.RunToSessionState(XR_SESSION_STATE_READY); // Must call extension functions through a function pointer: diff --git a/src/conformance/conformance_test/test_XR_EXT_eye_gaze_interaction.cpp b/src/conformance/conformance_test/test_XR_EXT_eye_gaze_interaction.cpp index e62710a1..19b20bbf 100644 --- a/src/conformance/conformance_test/test_XR_EXT_eye_gaze_interaction.cpp +++ b/src/conformance/conformance_test/test_XR_EXT_eye_gaze_interaction.cpp @@ -43,7 +43,7 @@ namespace Conformance MakeSystemPropertiesBoolChecker(XrSystemEyeGazeInteractionPropertiesEXT{XR_TYPE_SYSTEM_EYE_GAZE_INTERACTION_PROPERTIES_EXT}, &XrSystemEyeGazeInteractionPropertiesEXT::supportsEyeGazeInteraction); - TEST_CASE("XR_EXT_eye_gaze_interaction", "[XR_EXT_eye_gaze_interaction][interactive][no_auto]") + TEST_CASE("XR_EXT_eye_gaze_interaction", "[scenario][interactive][no_auto][XR_EXT_eye_gaze_interaction]") { GlobalData& globalData = GetGlobalData(); if (!globalData.IsInstanceExtensionSupported(XR_EXT_EYE_GAZE_INTERACTION_EXTENSION_NAME)) { @@ -373,7 +373,7 @@ namespace Conformance } } - TEST_CASE("XR_EXT_eye_gaze_interaction-interactive_gaze_only", "[XR_EXT_eye_gaze_interaction][scenario][interactive][no_auto]") + TEST_CASE("XR_EXT_eye_gaze_interaction-interactive_gaze_only", "[scenario][interactive][no_auto][XR_EXT_eye_gaze_interaction]") { GlobalData& globalData = GetGlobalData(); diff --git a/src/conformance/conformance_test/test_XR_EXT_hand_tracking.cpp b/src/conformance/conformance_test/test_XR_EXT_hand_tracking.cpp index e37130fa..adea1a48 100644 --- a/src/conformance/conformance_test/test_XR_EXT_hand_tracking.cpp +++ b/src/conformance/conformance_test/test_XR_EXT_hand_tracking.cpp @@ -20,6 +20,7 @@ #include "utilities/system_properties_helper.h" #include "utilities/utils.h" #include +#include #include #include "common/xr_linear.h" @@ -137,10 +138,8 @@ namespace Conformance REQUIRE_RESULT(xrCreateReferenceSpace(session, &localSpaceCreateInfo, &localSpace), XR_SUCCESS); // Wait until the runtime is ready for us to begin a session - auto timeout = (GetGlobalData().options.debugMode ? 3600s : 10s); FrameIterator frameIterator(&session); - FrameIterator::RunResult runResult = frameIterator.RunToSessionState(XR_SESSION_STATE_READY, timeout); - REQUIRE(runResult == FrameIterator::RunResult::Success); + frameIterator.RunToSessionState(XR_SESSION_STATE_READY); for (auto hand : {LEFT_HAND, RIGHT_HAND}) { std::array, HAND_COUNT> jointLocations; @@ -172,18 +171,12 @@ namespace Conformance // the returned radius must be a positive value. REQUIRE(jointLocations[hand][i].radius > 0); - // If an XrHandJointVelocitiesEXT structure is chained to XrHandJointLocationsEXT::next, - // the returned XrHandJointLocationsEXT::isActive is true, and the velocity is observed - // or can be calculated by the runtime, the runtime must fill in the linear velocity of - // each hand joint within the reference frame of baseSpace and set the - // XR_SPACE_VELOCITY_LINEAR_VALID_BIT. - // Similarly, if an XrHandJointVelocitiesEXT structure is chained to - // XrHandJointLocationsEXT::next, the returned XrHandJointLocationsEXT::isActive is true, - // and the angular velocity is observed or can be calculated by the runtime, the runtime - // must fill in the angular velocity of each joint within the reference frame of baseSpace - // and set the XR_SPACE_VELOCITY_ANGULAR_VALID_BIT. - REQUIRE((jointVelocities[hand][i].velocityFlags & XR_SPACE_VELOCITY_LINEAR_VALID_BIT) != 0); - REQUIRE((jointVelocities[hand][i].velocityFlags & XR_SPACE_VELOCITY_ANGULAR_VALID_BIT) != 0); + // From https://registry.khronos.org/OpenXR/specs/1.0/html/xrspec.html#_locate_hand_joints + // the velocity is observed or can be calculated by the runtime, the runtime must + // fill in the linear velocity of each hand joint within the reference frame of baseSpace + // and set the XR_SPACE_VELOCITY_LINEAR_VALID_BIT. + // So we cannot validate the jointVelocities flags XR_SPACE_VELOCITY_LINEAR_VALID_BIT + // or XR_SPACE_VELOCITY_ANGULAR_VALID_BIT. } else { // If the returned isActive is false, it indicates the hand tracker did not detect the hand @@ -207,10 +200,8 @@ namespace Conformance REQUIRE_RESULT(xrCreateReferenceSpace(session, &localSpaceCreateInfo, &localSpace), XR_SUCCESS); // Wait until the runtime is ready for us to begin a session - auto timeout = (GetGlobalData().options.debugMode ? 3600s : 10s); FrameIterator frameIterator(&session); - FrameIterator::RunResult runResult = frameIterator.RunToSessionState(XR_SESSION_STATE_READY, timeout); - REQUIRE(runResult == FrameIterator::RunResult::Success); + frameIterator.RunToSessionState(XR_SESSION_STATE_READY); // The application must input jointCount as described by the XrHandJointSetEXT when creating the XrHandTrackerEXT. // Otherwise, the runtime must return XR_ERROR_VALIDATION_FAILURE. @@ -328,6 +319,74 @@ namespace Conformance locateInfo.baseSpace = localSpace; locateInfo.time = frameState.predictedDisplayTime; REQUIRE(XR_SUCCESS == xrLocateHandJointsEXT(handTracker[hand], &locateInfo, &locations)); + + if (locations.isActive) { + const auto& wrist = jointLocations[hand][XR_HAND_JOINT_WRIST_EXT]; + const auto& palm = jointLocations[hand][XR_HAND_JOINT_PALM_EXT]; + const auto& middleMetacarpal = jointLocations[hand][XR_HAND_JOINT_MIDDLE_METACARPAL_EXT]; + const auto& middleProximal = jointLocations[hand][XR_HAND_JOINT_MIDDLE_PROXIMAL_EXT]; + + // Check if the palm joint is located correctly for each hand. + if ((palm.locationFlags & XR_SPACE_LOCATION_POSITION_VALID_BIT) != 0 && + (middleMetacarpal.locationFlags & XR_SPACE_LOCATION_POSITION_VALID_BIT) != 0 && + (middleProximal.locationFlags & XR_SPACE_LOCATION_POSITION_VALID_BIT) != 0) { + + // The palm joint is located at the center of the middle finger’s metacarpal bone. + XrPosef expectedPalmPose; + XrVector3f_Lerp(&expectedPalmPose.position, &middleMetacarpal.pose.position, &middleProximal.pose.position, 0.5f); + REQUIRE_THAT(palm.pose.position.x, Catch::Matchers::WithinRel(expectedPalmPose.position.x)); + REQUIRE_THAT(palm.pose.position.y, Catch::Matchers::WithinRel(expectedPalmPose.position.y)); + REQUIRE_THAT(palm.pose.position.z, Catch::Matchers::WithinRel(expectedPalmPose.position.z)); + } + + // Check the palm orientation for each hand if we have valid orientation. + if ((palm.locationFlags & XR_SPACE_LOCATION_ORIENTATION_VALID_BIT) != 0 && + (middleMetacarpal.locationFlags & XR_SPACE_LOCATION_ORIENTATION_VALID_BIT) != 0) { + + // The backward (+Z) direction is parallel to the middle finger’s metacarpal bone, and points away from the + // fingertips. + XrVector3f zAxis{0, 0, 1.0f}; + XrVector3f palmZAxis, middleMetacarpalZAxis; + XrQuaternionf_RotateVector3f(&palmZAxis, &palm.pose.orientation, &zAxis); + XrQuaternionf_RotateVector3f(&middleMetacarpalZAxis, &middleMetacarpal.pose.orientation, &zAxis); + REQUIRE_THAT(XrVector3f_Dot(&palmZAxis, &middleMetacarpalZAxis), Catch::Matchers::WithinRel(1.0f)); + + // The up (+Y) direction is perpendicular to palm surface and pointing towards the back of the hand. + // We can compare this to the +Y axis of the middle metacarpal bone to check gross direction. + XrVector3f yAxis{0, 1.0f, 0}; + XrVector3f palmYAxis, middleMetacarpalYAxis; + XrQuaternionf_RotateVector3f(&palmYAxis, &palm.pose.orientation, &yAxis); + XrQuaternionf_RotateVector3f(&middleMetacarpalYAxis, &middleMetacarpal.pose.orientation, &yAxis); + REQUIRE_THAT(XrVector3f_Dot(&palmYAxis, &middleMetacarpalYAxis), Catch::Matchers::WithinRel(1.0f, 0.1f)); + + // The X direction is perpendicular to the Y and Z directions and follows the right hand rule. + XrVector3f xAxis{0, 1.0f, 0}; + XrVector3f palmXAxis, middleMetacarpalXAxis; + XrQuaternionf_RotateVector3f(&palmXAxis, &palm.pose.orientation, &xAxis); + XrQuaternionf_RotateVector3f(&middleMetacarpalXAxis, &middleMetacarpal.pose.orientation, &xAxis); + REQUIRE_THAT(XrVector3f_Dot(&palmXAxis, &middleMetacarpalXAxis), Catch::Matchers::WithinRel(1.0f, 0.1f)); + } + + // Check the orientation of the wrist pose is correct for each hand, we can only reliably test the +Z direction + // programmatically. + if ((wrist.locationFlags & XR_SPACE_LOCATION_ORIENTATION_VALID_BIT) != 0 && + (middleMetacarpal.locationFlags & XR_SPACE_LOCATION_ORIENTATION_VALID_BIT) != 0) { + + // The (wrist) backward (+Z) direction is parallel to the line from wrist joint to middle finger metacarpal joint, + // and points away from the fingertips. + XrVector3f zAxis{0, 0, 1.0f}; + XrVector3f wristZAxis, fromMiddleMetacarpalToWrist; + XrQuaternionf_RotateVector3f(&wristZAxis, &wrist.pose.orientation, &zAxis); + XrVector3f_Sub(&fromMiddleMetacarpalToWrist, &wrist.pose.position, &middleMetacarpal.pose.position); + XrVector3f_Normalize(&fromMiddleMetacarpalToWrist); + // 0.1 here represents 26 degrees variance between these orientations; which is more than can reasonable be + // explained by numerical inaccuracy... + REQUIRE_THAT(XrVector3f_Dot(&wristZAxis, &fromMiddleMetacarpalToWrist), Catch::Matchers::WithinRel(1.0f, 0.1f)); + if (XrVector3f_Dot(&wristZAxis, &fromMiddleMetacarpalToWrist) > 0.03) { + WARN("Variance between wrist z axis orientation and metacarpal greater than 14 degrees!"); + } + } + } } // Check if user has requested to complete the test. diff --git a/src/conformance/conformance_test/test_XR_EXT_local_floor.cpp b/src/conformance/conformance_test/test_XR_EXT_local_floor.cpp index 0207a3cb..3c80ca86 100644 --- a/src/conformance/conformance_test/test_XR_EXT_local_floor.cpp +++ b/src/conformance/conformance_test/test_XR_EXT_local_floor.cpp @@ -70,17 +70,12 @@ namespace Conformance AutoBasicSession::createSwapchains | AutoBasicSession::createSpaces, instance); - // how long the test should wait for the app to get focus: 10 seconds in release, infinite in debug builds. - auto timeout = (GetGlobalData().options.debugMode ? 3600s : 10s); - CAPTURE(timeout); - // Get frames iterating to the point of app focused state. This will draw frames along the way. FrameIterator frameIterator(&session); - FrameIterator::RunResult runResult = frameIterator.RunToSessionState(XR_SESSION_STATE_FOCUSED, timeout); - REQUIRE(runResult == FrameIterator::RunResult::Success); + frameIterator.RunToSessionState(XR_SESSION_STATE_FOCUSED); // Render one frame to get a predicted display time for the xrLocateSpace calls. - runResult = frameIterator.SubmitFrame(); + FrameIterator::RunResult runResult = frameIterator.SubmitFrame(); REQUIRE(runResult == FrameIterator::RunResult::Success); // If stage space is defined, then LOCAL_FLOOR height off the floor must match STAGE diff --git a/src/conformance/conformance_test/test_XR_EXT_palm_pose.cpp b/src/conformance/conformance_test/test_XR_EXT_palm_pose.cpp index 3d2cb6c5..7955250a 100644 --- a/src/conformance/conformance_test/test_XR_EXT_palm_pose.cpp +++ b/src/conformance/conformance_test/test_XR_EXT_palm_pose.cpp @@ -472,14 +472,9 @@ namespace Conformance AutoBasicSession::createSwapchains, instance); - // how long the test should wait for the app to get focus: 10 seconds in release, infinite in debug builds. - auto timeout = (GetGlobalData().options.debugMode ? 3600s : 10s); - CAPTURE(timeout); - // Get frames iterating to the point of app focused state. This will draw frames along the way. FrameIterator frameIterator(&session); - FrameIterator::RunResult runResult = frameIterator.RunToSessionState(XR_SESSION_STATE_FOCUSED, timeout); - REQUIRE(runResult == FrameIterator::RunResult::Success); + frameIterator.RunToSessionState(XR_SESSION_STATE_FOCUSED); XrSessionActionSetsAttachInfo attachInfo{XR_TYPE_SESSION_ACTION_SETS_ATTACH_INFO}; attachInfo.actionSets = &actionSet; diff --git a/src/conformance/conformance_test/test_XR_EXT_performance_settings.cpp b/src/conformance/conformance_test/test_XR_EXT_performance_settings.cpp deleted file mode 100644 index ee44c0dc..00000000 --- a/src/conformance/conformance_test/test_XR_EXT_performance_settings.cpp +++ /dev/null @@ -1,28 +0,0 @@ -// Copyright (c) 2019-2023, The Khronos Group Inc. -// -// SPDX-License-Identifier: Apache-2.0 -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#include - -namespace Conformance -{ - - TEST_CASE("XR_EXT_performance_settings", "") - { - // XrResult xrPerfSettingsSetPerformanceLevelEXT(XrSession session, XrPerfSettingsDomainEXT domain, - // XrPerfSettingsLevelEXT level); - } - -} // namespace Conformance diff --git a/src/conformance/conformance_test/test_XR_KHR_D3D11_enable.cpp b/src/conformance/conformance_test/test_XR_KHR_D3D11_enable.cpp index bff5dd31..afe4f16e 100644 --- a/src/conformance/conformance_test/test_XR_KHR_D3D11_enable.cpp +++ b/src/conformance/conformance_test/test_XR_KHR_D3D11_enable.cpp @@ -43,8 +43,10 @@ namespace Conformance SKIP(XR_KHR_D3D11_ENABLE_EXTENSION_NAME " not enabled"); } - AutoBasicInstance instance{AutoBasicInstance::createSystemId}; - XrSystemId systemId = instance.systemId; + AutoBasicInstance instance; + + XrSystemId systemId{XR_NULL_SYSTEM_ID}; + REQUIRE(XR_SUCCESS == FindBasicSystem(instance.GetInstance(), &systemId)); // Create the graphics plugin we'll need to exercise session create functionality below. std::shared_ptr graphicsPlugin; @@ -57,12 +59,15 @@ namespace Conformance // We'll use this XrSession and XrSessionCreateInfo for testing below. XrSession session = XR_NULL_HANDLE_CPP; - XrSessionCreateInfo sessionCreateInfo{XR_TYPE_SESSION_CREATE_INFO, nullptr, 0, systemId}; + + XrSessionCreateInfo sessionCreateInfo{XR_TYPE_SESSION_CREATE_INFO}; + sessionCreateInfo.systemId = systemId; + CleanupSessionOnScopeExit cleanup(session); SECTION("No graphics binding") { - graphicsPlugin->InitializeDevice(instance, systemId, true); + REQUIRE(graphicsPlugin->InitializeDevice(instance, systemId, true)); sessionCreateInfo.next = nullptr; CHECK(xrCreateSession(instance, &sessionCreateInfo, &session) == XR_ERROR_GRAPHICS_DEVICE_INVALID); cleanup.Destroy(); @@ -71,7 +76,7 @@ namespace Conformance SECTION("NULL D3D11 device") { - graphicsPlugin->InitializeDevice(instance, systemId, true); + REQUIRE(graphicsPlugin->InitializeDevice(instance, systemId, true)); XrGraphicsBindingD3D11KHR graphicsBinding = *reinterpret_cast(graphicsPlugin->GetGraphicsBinding()); graphicsBinding.device = nullptr; @@ -81,10 +86,39 @@ namespace Conformance graphicsPlugin->ShutdownDevice(); } + SECTION("Valid session after bad session") + { + // Pass invalid binding the first time + { + REQUIRE(graphicsPlugin->InitializeDevice(instance, systemId, true)); + XrGraphicsBindingD3D11KHR graphicsBinding = + *reinterpret_cast(graphicsPlugin->GetGraphicsBinding()); + graphicsBinding.device = nullptr; + sessionCreateInfo.next = reinterpret_cast(&graphicsBinding); + CHECK(xrCreateSession(instance, &sessionCreateInfo, &session) == XR_ERROR_GRAPHICS_DEVICE_INVALID); + cleanup.Destroy(); + graphicsPlugin->ShutdownDevice(); + } + + // Using the same instance pass valid binding the second time + { + REQUIRE(XR_SUCCESS == FindBasicSystem(instance.GetInstance(), &systemId)); + sessionCreateInfo.systemId = systemId; + + REQUIRE(graphicsPlugin->InitializeDevice(instance, systemId, true)); + XrGraphicsBindingD3D11KHR graphicsBinding = + *reinterpret_cast(graphicsPlugin->GetGraphicsBinding()); + sessionCreateInfo.next = reinterpret_cast(&graphicsBinding); + CHECK(xrCreateSession(instance, &sessionCreateInfo, &session) == XR_SUCCESS); + cleanup.Destroy(); + graphicsPlugin->ShutdownDevice(); + } + } + SECTION("Singlethreaded D3D11 device") { // Verify that the runtime supports D3D11_CREATE_DEVICE_SINGLETHREADED. - graphicsPlugin->InitializeDevice(instance, systemId, true, D3D11_CREATE_DEVICE_SINGLETHREADED); + REQUIRE(graphicsPlugin->InitializeDevice(instance, systemId, true, D3D11_CREATE_DEVICE_SINGLETHREADED)); XrGraphicsBindingD3D11KHR graphicsBinding = *reinterpret_cast(graphicsPlugin->GetGraphicsBinding()); sessionCreateInfo.next = reinterpret_cast(&graphicsBinding); @@ -108,11 +142,28 @@ namespace Conformance } }; - graphicsPlugin->InitializeDevice(instance, systemId, true); + auto xrGetD3D11GraphicsRequirementsKHR = + GetInstanceExtensionFunction(instance, "xrGetD3D11GraphicsRequirementsKHR"); + + XrGraphicsRequirementsD3D11KHR referenceGraphicsRequirements{XR_TYPE_GRAPHICS_REQUIREMENTS_D3D11_KHR}; + REQUIRE(xrGetD3D11GraphicsRequirementsKHR(instance, systemId, &referenceGraphicsRequirements) == XR_SUCCESS); + + REQUIRE(graphicsPlugin->InitializeDevice(instance, systemId, true)); XrGraphicsBindingD3D11KHR graphicsBinding = *reinterpret_cast(graphicsPlugin->GetGraphicsBinding()); sessionCreateInfo.next = reinterpret_cast(&graphicsBinding); for (int i = 0; i < 3; ++i) { + REQUIRE(XR_SUCCESS == FindBasicSystem(instance.GetInstance(), &systemId)); + sessionCreateInfo.systemId = systemId; + + XrGraphicsRequirementsD3D11KHR graphicsRequirements{XR_TYPE_GRAPHICS_REQUIREMENTS_D3D11_KHR}; + REQUIRE(xrGetD3D11GraphicsRequirementsKHR(instance, systemId, &graphicsRequirements) == XR_SUCCESS); + + // We expect that the graphics requirements don't change... + REQUIRE(referenceGraphicsRequirements.adapterLuid.HighPart == graphicsRequirements.adapterLuid.HighPart); + REQUIRE(referenceGraphicsRequirements.adapterLuid.LowPart == graphicsRequirements.adapterLuid.LowPart); + REQUIRE(referenceGraphicsRequirements.minFeatureLevel == graphicsRequirements.minFeatureLevel); + CHECK(xrCreateSession(instance, &sessionCreateInfo, &session) == XR_SUCCESS); createSwapchains(graphicsPlugin, session); CHECK(xrDestroySession(session) == XR_SUCCESS); diff --git a/src/conformance/conformance_test/test_XR_KHR_D3D12_enable.cpp b/src/conformance/conformance_test/test_XR_KHR_D3D12_enable.cpp index 7bd3e69a..b815305c 100644 --- a/src/conformance/conformance_test/test_XR_KHR_D3D12_enable.cpp +++ b/src/conformance/conformance_test/test_XR_KHR_D3D12_enable.cpp @@ -42,8 +42,10 @@ namespace Conformance SKIP(XR_KHR_D3D12_ENABLE_EXTENSION_NAME " not enabled"); } - AutoBasicInstance instance{AutoBasicInstance::createSystemId}; - XrSystemId systemId = instance.systemId; + AutoBasicInstance instance; + + XrSystemId systemId{XR_NULL_SYSTEM_ID}; + REQUIRE(XR_SUCCESS == FindBasicSystem(instance.GetInstance(), &systemId)); // Create the graphics plugin we'll need to exercise session create functionality below. std::shared_ptr graphicsPlugin; @@ -56,12 +58,15 @@ namespace Conformance // We'll use this XrSession and XrSessionCreateInfo for testing below. XrSession session = XR_NULL_HANDLE_CPP; - XrSessionCreateInfo sessionCreateInfo{XR_TYPE_SESSION_CREATE_INFO, nullptr, 0, systemId}; + + XrSessionCreateInfo sessionCreateInfo{XR_TYPE_SESSION_CREATE_INFO}; + sessionCreateInfo.systemId = systemId; + CleanupSessionOnScopeExit cleanup(session); SECTION("No graphics binding") { - graphicsPlugin->InitializeDevice(instance, systemId, true); + REQUIRE(graphicsPlugin->InitializeDevice(instance, systemId, true)); sessionCreateInfo.next = nullptr; CHECK(xrCreateSession(instance, &sessionCreateInfo, &session) == XR_ERROR_GRAPHICS_DEVICE_INVALID); cleanup.Destroy(); @@ -70,8 +75,9 @@ namespace Conformance SECTION("NULL D3D12 device") { - graphicsPlugin->InitializeDevice(instance, systemId, true); - XrGraphicsBindingD3D12KHR graphicsBinding = {XR_TYPE_GRAPHICS_BINDING_D3D12_KHR, nullptr}; + REQUIRE(graphicsPlugin->InitializeDevice(instance, systemId, true)); + XrGraphicsBindingD3D12KHR graphicsBinding = + *reinterpret_cast(graphicsPlugin->GetGraphicsBinding()); graphicsBinding.device = nullptr; sessionCreateInfo.next = reinterpret_cast(&graphicsBinding); @@ -81,6 +87,35 @@ namespace Conformance graphicsPlugin->ShutdownDevice(); } + SECTION("Valid session after bad session") + { + // Pass invalid binding the first time + { + REQUIRE(graphicsPlugin->InitializeDevice(instance, systemId, true)); + XrGraphicsBindingD3D12KHR graphicsBinding = + *reinterpret_cast(graphicsPlugin->GetGraphicsBinding()); + graphicsBinding.device = nullptr; + sessionCreateInfo.next = reinterpret_cast(&graphicsBinding); + CHECK(xrCreateSession(instance, &sessionCreateInfo, &session) == XR_ERROR_GRAPHICS_DEVICE_INVALID); + cleanup.Destroy(); + graphicsPlugin->ShutdownDevice(); + } + + // Using the same instance pass valid binding the second time + { + REQUIRE(XR_SUCCESS == FindBasicSystem(instance.GetInstance(), &systemId)); + sessionCreateInfo.systemId = systemId; + + REQUIRE(graphicsPlugin->InitializeDevice(instance, systemId, true)); + XrGraphicsBindingD3D12KHR graphicsBinding = + *reinterpret_cast(graphicsPlugin->GetGraphicsBinding()); + sessionCreateInfo.next = reinterpret_cast(&graphicsBinding); + CHECK(xrCreateSession(instance, &sessionCreateInfo, &session) == XR_SUCCESS); + cleanup.Destroy(); + graphicsPlugin->ShutdownDevice(); + } + } + SECTION("Multiple session with same device") { auto createSwapchains = [](std::shared_ptr graphicsPlugin, XrSession session) { @@ -96,11 +131,28 @@ namespace Conformance } }; - graphicsPlugin->InitializeDevice(instance, systemId, true); + auto xrGetD3D12GraphicsRequirementsKHR = + GetInstanceExtensionFunction(instance, "xrGetD3D12GraphicsRequirementsKHR"); + + XrGraphicsRequirementsD3D12KHR referenceGraphicsRequirements{XR_TYPE_GRAPHICS_REQUIREMENTS_D3D12_KHR}; + REQUIRE(xrGetD3D12GraphicsRequirementsKHR(instance, systemId, &referenceGraphicsRequirements) == XR_SUCCESS); + + REQUIRE(graphicsPlugin->InitializeDevice(instance, systemId, true)); XrGraphicsBindingD3D12KHR graphicsBinding = *reinterpret_cast(graphicsPlugin->GetGraphicsBinding()); sessionCreateInfo.next = reinterpret_cast(&graphicsBinding); for (int i = 0; i < 3; ++i) { + REQUIRE(XR_SUCCESS == FindBasicSystem(instance.GetInstance(), &systemId)); + sessionCreateInfo.systemId = systemId; + + XrGraphicsRequirementsD3D12KHR graphicsRequirements{XR_TYPE_GRAPHICS_REQUIREMENTS_D3D12_KHR}; + REQUIRE(xrGetD3D12GraphicsRequirementsKHR(instance, systemId, &graphicsRequirements) == XR_SUCCESS); + + // We expect that the graphics requirements don't change... + REQUIRE(referenceGraphicsRequirements.adapterLuid.HighPart == graphicsRequirements.adapterLuid.HighPart); + REQUIRE(referenceGraphicsRequirements.adapterLuid.LowPart == graphicsRequirements.adapterLuid.LowPart); + REQUIRE(referenceGraphicsRequirements.minFeatureLevel == graphicsRequirements.minFeatureLevel); + CHECK(xrCreateSession(instance, &sessionCreateInfo, &session) == XR_SUCCESS); createSwapchains(graphicsPlugin, session); CHECK(xrDestroySession(session) == XR_SUCCESS); diff --git a/src/conformance/conformance_test/test_XR_KHR_OpenGL_ES_enable.cpp b/src/conformance/conformance_test/test_XR_KHR_OpenGL_ES_enable.cpp index 429e460c..2d31abe0 100644 --- a/src/conformance/conformance_test/test_XR_KHR_OpenGL_ES_enable.cpp +++ b/src/conformance/conformance_test/test_XR_KHR_OpenGL_ES_enable.cpp @@ -39,8 +39,10 @@ namespace Conformance SKIP(XR_KHR_OPENGL_ES_ENABLE_EXTENSION_NAME " not enabled"); } - AutoBasicInstance instance{AutoBasicInstance::createSystemId}; - XrSystemId systemId = instance.systemId; + AutoBasicInstance instance; + + XrSystemId systemId{XR_NULL_SYSTEM_ID}; + REQUIRE(XR_SUCCESS == FindBasicSystem(instance.GetInstance(), &systemId)); // Create the graphics plugin we'll need to exercise session create functionality below. std::shared_ptr graphicsPlugin; @@ -53,12 +55,15 @@ namespace Conformance // We'll use this XrSession and XrSessionCreateInfo for testing below. XrSession session = XR_NULL_HANDLE_CPP; - XrSessionCreateInfo sessionCreateInfo{XR_TYPE_SESSION_CREATE_INFO, nullptr, 0, systemId}; + + XrSessionCreateInfo sessionCreateInfo{XR_TYPE_SESSION_CREATE_INFO}; + sessionCreateInfo.systemId = systemId; + CleanupSessionOnScopeExit cleanup(session); SECTION("No graphics binding") { - graphicsPlugin->InitializeDevice(instance, systemId, true); + REQUIRE(graphicsPlugin->InitializeDevice(instance, systemId, true)); sessionCreateInfo.next = nullptr; CHECK(xrCreateSession(instance, &sessionCreateInfo, &session) == XR_ERROR_GRAPHICS_DEVICE_INVALID); cleanup.Destroy(); @@ -69,7 +74,7 @@ namespace Conformance // tests related to the graphics binding are OS specific SECTION("NULL context: context is NULL") { - graphicsPlugin->InitializeDevice(instance, systemId, true); + REQUIRE(graphicsPlugin->InitializeDevice(instance, systemId, true)); XrGraphicsBindingOpenGLESAndroidKHR graphicsBinding = *reinterpret_cast(graphicsPlugin->GetGraphicsBinding()); graphicsBinding.context = EGL_NO_CONTEXT; @@ -78,6 +83,35 @@ namespace Conformance cleanup.Destroy(); graphicsPlugin->ShutdownDevice(); } + + SECTION("Valid session after bad session") + { + // Pass invalid binding the first time + { + REQUIRE(graphicsPlugin->InitializeDevice(instance, systemId, true)); + XrGraphicsBindingOpenGLESAndroidKHR graphicsBinding = + *reinterpret_cast(graphicsPlugin->GetGraphicsBinding()); + graphicsBinding.context = EGL_NO_CONTEXT; + sessionCreateInfo.next = reinterpret_cast(&graphicsBinding); + CHECK(xrCreateSession(instance, &sessionCreateInfo, &session) == XR_ERROR_GRAPHICS_DEVICE_INVALID); + cleanup.Destroy(); + graphicsPlugin->ShutdownDevice(); + } + + // Using the same instance pass valid binding the second time + { + REQUIRE(XR_SUCCESS == FindBasicSystem(instance.GetInstance(), &systemId)); + sessionCreateInfo.systemId = systemId; + + REQUIRE(graphicsPlugin->InitializeDevice(instance, systemId, true)); + XrGraphicsBindingOpenGLESAndroidKHR graphicsBinding = + *reinterpret_cast(graphicsPlugin->GetGraphicsBinding()); + sessionCreateInfo.next = reinterpret_cast(&graphicsBinding); + CHECK(xrCreateSession(instance, &sessionCreateInfo, &session) == XR_SUCCESS); + cleanup.Destroy(); + } + } + SECTION("Multiple session with same device") { auto createSwapchains = [](std::shared_ptr graphicsPlugin, XrSession session) { @@ -93,11 +127,27 @@ namespace Conformance } }; - graphicsPlugin->InitializeDevice(instance, systemId, true); + auto xrGetOpenGLESGraphicsRequirementsKHR = + GetInstanceExtensionFunction(instance, "xrGetOpenGLESGraphicsRequirementsKHR"); + + XrGraphicsRequirementsOpenGLESKHR referenceGraphicsRequirements{XR_TYPE_GRAPHICS_REQUIREMENTS_OPENGL_ES_KHR}; + REQUIRE(xrGetOpenGLESGraphicsRequirementsKHR(instance, systemId, &referenceGraphicsRequirements) == XR_SUCCESS); + + REQUIRE(graphicsPlugin->InitializeDevice(instance, systemId, true)); XrGraphicsBindingOpenGLESAndroidKHR graphicsBinding = *reinterpret_cast(graphicsPlugin->GetGraphicsBinding()); sessionCreateInfo.next = reinterpret_cast(&graphicsBinding); for (int i = 0; i < 3; ++i) { + REQUIRE(XR_SUCCESS == FindBasicSystem(instance.GetInstance(), &systemId)); + sessionCreateInfo.systemId = systemId; + + XrGraphicsRequirementsOpenGLESKHR graphicsRequirements{XR_TYPE_GRAPHICS_REQUIREMENTS_OPENGL_ES_KHR}; + REQUIRE(xrGetOpenGLESGraphicsRequirementsKHR(instance, systemId, &graphicsRequirements) == XR_SUCCESS); + + // We expect that the graphics requirements don't change... + REQUIRE(referenceGraphicsRequirements.maxApiVersionSupported == graphicsRequirements.maxApiVersionSupported); + REQUIRE(referenceGraphicsRequirements.minApiVersionSupported == graphicsRequirements.minApiVersionSupported); + CHECK(xrCreateSession(instance, &sessionCreateInfo, &session) == XR_SUCCESS); createSwapchains(graphicsPlugin, session); CHECK(xrDestroySession(session) == XR_SUCCESS); diff --git a/src/conformance/conformance_test/test_XR_KHR_OpenGL_enable.cpp b/src/conformance/conformance_test/test_XR_KHR_OpenGL_enable.cpp index 490500d6..bad989d0 100644 --- a/src/conformance/conformance_test/test_XR_KHR_OpenGL_enable.cpp +++ b/src/conformance/conformance_test/test_XR_KHR_OpenGL_enable.cpp @@ -40,8 +40,10 @@ namespace Conformance SKIP(XR_KHR_OPENGL_ENABLE_EXTENSION_NAME " not enabled"); } - AutoBasicInstance instance{AutoBasicInstance::createSystemId}; - XrSystemId systemId = instance.systemId; + AutoBasicInstance instance; + + XrSystemId systemId{XR_NULL_SYSTEM_ID}; + REQUIRE(XR_SUCCESS == FindBasicSystem(instance.GetInstance(), &systemId)); // Create the graphics plugin we'll need to exercise session create functionality below. std::shared_ptr graphicsPlugin; @@ -54,12 +56,15 @@ namespace Conformance // We'll use this XrSession and XrSessionCreateInfo for testing below. XrSession session = XR_NULL_HANDLE_CPP; - XrSessionCreateInfo sessionCreateInfo{XR_TYPE_SESSION_CREATE_INFO, nullptr, 0, systemId}; + + XrSessionCreateInfo sessionCreateInfo{XR_TYPE_SESSION_CREATE_INFO}; + sessionCreateInfo.systemId = systemId; + CleanupSessionOnScopeExit cleanup(session); SECTION("No graphics binding") { - graphicsPlugin->InitializeDevice(instance, systemId, true); + REQUIRE(graphicsPlugin->InitializeDevice(instance, systemId, true)); sessionCreateInfo.next = nullptr; CHECK(xrCreateSession(instance, &sessionCreateInfo, &session) == XR_ERROR_GRAPHICS_DEVICE_INVALID); cleanup.Destroy(); @@ -70,7 +75,7 @@ namespace Conformance // tests related to the graphics binding are OS specific SECTION("NULL context: both are NULL") { - graphicsPlugin->InitializeDevice(instance, systemId, true); + REQUIRE(graphicsPlugin->InitializeDevice(instance, systemId, true)); XrGraphicsBindingOpenGLWin32KHR graphicsBinding = *reinterpret_cast(graphicsPlugin->GetGraphicsBinding()); graphicsBinding.hDC = nullptr; @@ -80,9 +85,10 @@ namespace Conformance cleanup.Destroy(); graphicsPlugin->ShutdownDevice(); } + SECTION("NULL context: DC is NULL") { - graphicsPlugin->InitializeDevice(instance, systemId, true); + REQUIRE(graphicsPlugin->InitializeDevice(instance, systemId, true)); XrGraphicsBindingOpenGLWin32KHR graphicsBinding = *reinterpret_cast(graphicsPlugin->GetGraphicsBinding()); graphicsBinding.hDC = nullptr; @@ -91,9 +97,10 @@ namespace Conformance cleanup.Destroy(); graphicsPlugin->ShutdownDevice(); } + SECTION("NULL context: GLRC is NULL") { - graphicsPlugin->InitializeDevice(instance, systemId, true); + REQUIRE(graphicsPlugin->InitializeDevice(instance, systemId, true)); XrGraphicsBindingOpenGLWin32KHR graphicsBinding = *reinterpret_cast(graphicsPlugin->GetGraphicsBinding()); graphicsBinding.hGLRC = nullptr; @@ -102,11 +109,41 @@ namespace Conformance cleanup.Destroy(); graphicsPlugin->ShutdownDevice(); } + + SECTION("Valid session after bad session") + { + // Pass invalid binding the first time + { + REQUIRE(graphicsPlugin->InitializeDevice(instance, systemId, true)); + XrGraphicsBindingOpenGLWin32KHR graphicsBinding = + *reinterpret_cast(graphicsPlugin->GetGraphicsBinding()); + graphicsBinding.hDC = nullptr; + sessionCreateInfo.next = reinterpret_cast(&graphicsBinding); + CHECK(xrCreateSession(instance, &sessionCreateInfo, &session) == XR_ERROR_GRAPHICS_DEVICE_INVALID); + cleanup.Destroy(); + graphicsPlugin->ShutdownDevice(); + } + + // Using the same instance pass valid binding the second time + { + REQUIRE(XR_SUCCESS == FindBasicSystem(instance.GetInstance(), &systemId)); + sessionCreateInfo.systemId = systemId; + + REQUIRE(graphicsPlugin->InitializeDevice(instance, systemId, true)); + XrGraphicsBindingOpenGLWin32KHR graphicsBinding = + *reinterpret_cast(graphicsPlugin->GetGraphicsBinding()); + sessionCreateInfo.next = reinterpret_cast(&graphicsBinding); + CHECK(xrCreateSession(instance, &sessionCreateInfo, &session) == XR_SUCCESS); + cleanup.Destroy(); + graphicsPlugin->ShutdownDevice(); + } + } + // This test dies in the tear-down wglMakeCurrent in ksGpuContext_Destroy, turn it off for now. #if 0 SECTION("Context for runtime is not current") { - graphicsPlugin->InitializeDevice(instance, systemId, true); + REQUIRE(graphicsPlugin->InitializeDevice(instance, systemId, true)); sessionCreateInfo.next = graphicsPlugin->GetGraphicsBinding(); // Exercise presence of unrecognized extensions, which the runtime should ignore. @@ -151,11 +188,27 @@ namespace Conformance } }; - graphicsPlugin->InitializeDevice(instance, systemId, true); + auto xrGetOpenGLGraphicsRequirementsKHR = + GetInstanceExtensionFunction(instance, "xrGetOpenGLGraphicsRequirementsKHR"); + + XrGraphicsRequirementsOpenGLKHR referenceGraphicsRequirements{XR_TYPE_GRAPHICS_REQUIREMENTS_OPENGL_KHR}; + REQUIRE(xrGetOpenGLGraphicsRequirementsKHR(instance, systemId, &referenceGraphicsRequirements) == XR_SUCCESS); + + REQUIRE(graphicsPlugin->InitializeDevice(instance, systemId, true)); XrGraphicsBindingOpenGLWin32KHR graphicsBinding = *reinterpret_cast(graphicsPlugin->GetGraphicsBinding()); sessionCreateInfo.next = reinterpret_cast(&graphicsBinding); for (int i = 0; i < 3; ++i) { + REQUIRE(XR_SUCCESS == FindBasicSystem(instance.GetInstance(), &systemId)); + sessionCreateInfo.systemId = systemId; + + XrGraphicsRequirementsOpenGLKHR graphicsRequirements{XR_TYPE_GRAPHICS_REQUIREMENTS_OPENGL_KHR}; + REQUIRE(xrGetOpenGLGraphicsRequirementsKHR(instance, systemId, &graphicsRequirements) == XR_SUCCESS); + + // We expect that the graphics requirements don't change... + REQUIRE(referenceGraphicsRequirements.maxApiVersionSupported == graphicsRequirements.maxApiVersionSupported); + REQUIRE(referenceGraphicsRequirements.minApiVersionSupported == graphicsRequirements.minApiVersionSupported); + CHECK(xrCreateSession(instance, &sessionCreateInfo, &session) == XR_SUCCESS); createSwapchains(graphicsPlugin, session); CHECK(xrDestroySession(session) == XR_SUCCESS); diff --git a/src/conformance/conformance_test/test_XR_KHR_composition_layer_cube.cpp b/src/conformance/conformance_test/test_XR_KHR_composition_layer_cube.cpp index a9ba932f..5cf450ae 100644 --- a/src/conformance/conformance_test/test_XR_KHR_composition_layer_cube.cpp +++ b/src/conformance/conformance_test/test_XR_KHR_composition_layer_cube.cpp @@ -46,8 +46,6 @@ namespace Conformance } auto graphicsPlugin = globalData.GetGraphicsPlugin(); - auto timeout = (GetGlobalData().options.debugMode ? 3600s : 10s); - CAPTURE(timeout); AutoBasicInstance instance({XR_KHR_COMPOSITION_LAYER_CUBE_EXTENSION_NAME}); AutoBasicSession session(AutoBasicSession::createSession | AutoBasicSession::beginSession | AutoBasicSession::createSwapchains | @@ -55,8 +53,7 @@ namespace Conformance instance); FrameIterator frameIterator(&session); - FrameIterator::RunResult runResult = frameIterator.RunToSessionState(XR_SESSION_STATE_FOCUSED, timeout); - REQUIRE(runResult == FrameIterator::RunResult::Success); + frameIterator.RunToSessionState(XR_SESSION_STATE_FOCUSED); // At this point we have a session ready for us to generate custom frames for. // The current XrSessionState is XR_SESSION_STATE_FOCUSED. @@ -104,7 +101,7 @@ namespace Conformance // } XrCompositionLayerCubeKHR; for (const XrQuaternionf& orientation : orientationTestArray) { - runResult = frameIterator.PrepareSubmitFrame(); + FrameIterator::RunResult runResult = frameIterator.PrepareSubmitFrame(); REQUIRE(runResult == FrameIterator::RunResult::Success); // Set up our cubemap layer. We always make two, and some of the time we @@ -150,7 +147,6 @@ namespace Conformance result = xrRequestExitSession(session.GetSession()); CHECK(result == XR_SUCCESS); - runResult = frameIterator.RunToSessionState(XR_SESSION_STATE_STOPPING, timeout); - CHECK(runResult == FrameIterator::RunResult::Success); + frameIterator.RunToSessionState(XR_SESSION_STATE_STOPPING); } } // namespace Conformance diff --git a/src/conformance/conformance_test/test_XR_KHR_composition_layer_cylinder.cpp b/src/conformance/conformance_test/test_XR_KHR_composition_layer_cylinder.cpp index 624d320c..4013bde9 100644 --- a/src/conformance/conformance_test/test_XR_KHR_composition_layer_cylinder.cpp +++ b/src/conformance/conformance_test/test_XR_KHR_composition_layer_cylinder.cpp @@ -43,17 +43,13 @@ namespace Conformance SKIP("Test run not using graphics plugin"); } - auto timeout = (GetGlobalData().options.debugMode ? 3600s : 10s); - CAPTURE(timeout); - AutoBasicInstance instance({XR_KHR_COMPOSITION_LAYER_CYLINDER_EXTENSION_NAME}); AutoBasicSession session(AutoBasicSession::createSession | AutoBasicSession::beginSession | AutoBasicSession::createSwapchains | AutoBasicSession::createSpaces, instance); FrameIterator frameIterator(&session); - FrameIterator::RunResult runResult = frameIterator.RunToSessionState(XR_SESSION_STATE_FOCUSED, timeout); - REQUIRE(runResult == FrameIterator::RunResult::Success); + frameIterator.RunToSessionState(XR_SESSION_STATE_FOCUSED); // At this point we have a session ready for us to generate custom frames for. // The current XrSessionState is XR_SESSION_STATE_FOCUSED. @@ -71,7 +67,7 @@ namespace Conformance std::array radiusTestArray{0, 1.f, INFINITY}; // Spec explicitly supports radius 0 and +infinity for (float radius : radiusTestArray) { - runResult = frameIterator.PrepareSubmitFrame(); + FrameIterator::RunResult runResult = frameIterator.PrepareSubmitFrame(); REQUIRE(runResult == FrameIterator::RunResult::Success); // Set up our cylinder layer. We always make two, and some of the time we @@ -119,7 +115,6 @@ namespace Conformance result = xrRequestExitSession(session.GetSession()); CHECK(result == XR_SUCCESS); - runResult = frameIterator.RunToSessionState(XR_SESSION_STATE_STOPPING, timeout); - CHECK(runResult == FrameIterator::RunResult::Success); + frameIterator.RunToSessionState(XR_SESSION_STATE_STOPPING); } } // namespace Conformance diff --git a/src/conformance/conformance_test/test_XR_KHR_composition_layer_depth.cpp b/src/conformance/conformance_test/test_XR_KHR_composition_layer_depth.cpp index 0b1d18ed..445f70b6 100644 --- a/src/conformance/conformance_test/test_XR_KHR_composition_layer_depth.cpp +++ b/src/conformance/conformance_test/test_XR_KHR_composition_layer_depth.cpp @@ -42,8 +42,6 @@ namespace Conformance auto graphicsPlugin = globalData.GetGraphicsPlugin(); - auto timeout = (globalData.options.debugMode ? 3600s : 10s); - CAPTURE(timeout); AutoBasicInstance instance({XR_KHR_COMPOSITION_LAYER_DEPTH_EXTENSION_NAME}); AutoBasicSession session(AutoBasicSession::createSession | AutoBasicSession::beginSession | AutoBasicSession::createSwapchains | AutoBasicSession::createSpaces, @@ -51,8 +49,7 @@ namespace Conformance REQUIRE(session.IsValidHandle()); FrameIterator frameIterator(&session); - FrameIterator::RunResult runResult = frameIterator.RunToSessionState(XR_SESSION_STATE_FOCUSED, timeout); - REQUIRE(runResult == FrameIterator::RunResult::Success); + frameIterator.RunToSessionState(XR_SESSION_STATE_FOCUSED); // At this point we have a session ready for us to generate custom frames for. // The current XrSessionState is XR_SESSION_STATE_FOCUSED. @@ -98,7 +95,7 @@ namespace Conformance DepthVaryingInfo{0.0f, 1.0f, std::numeric_limits::max(), minimum_useful_z}}; for (const DepthVaryingInfo& varyingInfo : varyingInfoTestArray) { - runResult = frameIterator.PrepareSubmitFrame(); + FrameIterator::RunResult runResult = frameIterator.PrepareSubmitFrame(); REQUIRE(runResult == FrameIterator::RunResult::Success); { @@ -148,8 +145,7 @@ namespace Conformance CHECK(result == XR_SUCCESS); } - runResult = frameIterator.RunToSessionState(XR_SESSION_STATE_STOPPING, timeout); - CHECK(runResult == FrameIterator::RunResult::Success); + frameIterator.RunToSessionState(XR_SESSION_STATE_STOPPING); for (const XrSwapchain& swapchain : depthSwapchains) { XrResult result = xrDestroySwapchain(swapchain); diff --git a/src/conformance/conformance_test/test_XR_KHR_composition_layer_equirect.cpp b/src/conformance/conformance_test/test_XR_KHR_composition_layer_equirect.cpp index 64deccc5..3c30ee33 100644 --- a/src/conformance/conformance_test/test_XR_KHR_composition_layer_equirect.cpp +++ b/src/conformance/conformance_test/test_XR_KHR_composition_layer_equirect.cpp @@ -46,8 +46,6 @@ namespace Conformance } auto graphicsPlugin = globalData.GetGraphicsPlugin(); - auto timeout = (GetGlobalData().options.debugMode ? 3600s : 10s); - CAPTURE(timeout); AutoBasicInstance instance({XR_KHR_COMPOSITION_LAYER_EQUIRECT_EXTENSION_NAME}); AutoBasicSession session(AutoBasicSession::createSession | AutoBasicSession::beginSession | AutoBasicSession::createSwapchains | @@ -55,8 +53,7 @@ namespace Conformance instance); FrameIterator frameIterator(&session); - FrameIterator::RunResult runResult = frameIterator.RunToSessionState(XR_SESSION_STATE_FOCUSED, timeout); - REQUIRE(runResult == FrameIterator::RunResult::Success); + frameIterator.RunToSessionState(XR_SESSION_STATE_FOCUSED); // At this point we have a session ready for us to generate custom frames for. // The current XrSessionState is XR_SESSION_STATE_FOCUSED. @@ -109,7 +106,7 @@ namespace Conformance }; for (const XrQuaternionf& orientation : orientationTestArray) { - runResult = frameIterator.PrepareSubmitFrame(); + FrameIterator::RunResult runResult = frameIterator.PrepareSubmitFrame(); REQUIRE(runResult == FrameIterator::RunResult::Success); // Set up our equirect layer. We always make two, and some of the time we @@ -173,7 +170,6 @@ namespace Conformance result = xrRequestExitSession(session.GetSession()); CHECK(result == XR_SUCCESS); - runResult = frameIterator.RunToSessionState(XR_SESSION_STATE_STOPPING, timeout); - CHECK(runResult == FrameIterator::RunResult::Success); + frameIterator.RunToSessionState(XR_SESSION_STATE_STOPPING); } } // namespace Conformance diff --git a/src/conformance/conformance_test/test_XR_KHR_convert_timespec_time.cpp b/src/conformance/conformance_test/test_XR_KHR_convert_timespec_time.cpp index fa8718c9..b7a2d4de 100644 --- a/src/conformance/conformance_test/test_XR_KHR_convert_timespec_time.cpp +++ b/src/conformance/conformance_test/test_XR_KHR_convert_timespec_time.cpp @@ -134,13 +134,12 @@ namespace Conformance CAPTURE(timespecBefore); // Wait until the runtime is ready for us to begin a session - auto timeout = (GetGlobalData().options.debugMode ? 3600s : 10s); FrameIterator frameIterator(&session); - FrameIterator::RunResult runResult = frameIterator.RunToSessionState(XR_SESSION_STATE_FOCUSED, timeout); - REQUIRE(runResult == FrameIterator::RunResult::Success); + frameIterator.RunToSessionState(XR_SESSION_STATE_FOCUSED); // Submit a frame and query the time for the next frame - frameIterator.SubmitFrame(); + FrameIterator::RunResult runResult = frameIterator.SubmitFrame(); + REQUIRE(runResult == FrameIterator::RunResult::Success); XrTime nextFrameTime = frameIterator.frameState.predictedDisplayTime; // predicted display time is required to be a time in the future so it is fair to assume it is after now. diff --git a/src/conformance/conformance_test/test_XR_KHR_headless.cpp b/src/conformance/conformance_test/test_XR_KHR_headless.cpp deleted file mode 100644 index 2c266c43..00000000 --- a/src/conformance/conformance_test/test_XR_KHR_headless.cpp +++ /dev/null @@ -1,72 +0,0 @@ -// Copyright (c) 2019-2023, The Khronos Group Inc. -// -// SPDX-License-Identifier: Apache-2.0 -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#include "utilities/utils.h" -#include "conformance_utils.h" -#include "conformance_framework.h" -#include -#include -#include -#include -#include -#include -#include - -namespace Conformance -{ - TEST_CASE("XR_KHR_headless", "") - { -#ifdef XR_KHR_headless - GlobalData& globalData = GetGlobalData(); - - // When this extension is enabled, the behavior of existing functions that interact with the - // graphics subsystem is altered. When calling the function xrCreateSession with no graphics - // binding structure, the session will be created as headless. - // - // When operating with a headless session, the function xrEnumerateSwapchainFormats must return - // an empty list of formats. Calls to functions xrCreateSwapchain, xrDestroySwapchain, - // xrAcquireSwapchainImage, xrWaitFrame are invalid. All other functions, including those - // related to tracking, input and haptics, are unaffected. - if (!globalData.IsInstanceExtensionEnabled("XR_KHR_headless")) { - return; - } - - AutoBasicSession session(AutoBasicSession::createSession | AutoBasicSession::skipGraphics); - - SECTION("xrEnumerateSwapchainFormats should return XR_SUCCESS but zero formats.") - { - uint32_t countOutput = UINT32_MAX; - REQUIRE(xrEnumerateSwapchainFormats(session, 0, &countOutput, nullptr) == XR_SUCCESS); - REQUIRE(countOutput == 0); - - int64_t formats[100]; - countOutput = UINT32_MAX; - REQUIRE(xrEnumerateSwapchainFormats(session, sizeof(formats) / sizeof(formats[0]), &countOutput, formats) == XR_SUCCESS); - REQUIRE(countOutput == 0); - } - - // Calls to functions xrCreateSwapchain, xrDestroySwapchain, xrAcquireSwapchainImage, - // xrWaitFrame are invalid, but there isn't a specification for what happens when called. - - // We begin a session and call valid session functions. - XrSessionBeginInfo sessionBeginInfo{XR_TYPE_SESSION_BEGIN_INFO, nullptr, globalData.options.viewConfigurationValue}; - REQUIRE_RESULT_UNQUALIFIED_SUCCESS(xrBeginSession(session, &sessionBeginInfo)); - - // To do: call input and tracking functions here. - REQUIRE_RESULT_UNQUALIFIED_SUCCESS(xrEndSession(session)); -#endif // XR_KHR_headless - } -} // namespace Conformance diff --git a/src/conformance/conformance_test/test_XR_KHR_visibility_mask.cpp b/src/conformance/conformance_test/test_XR_KHR_visibility_mask.cpp index 5a2fe5d5..2fd3a31e 100644 --- a/src/conformance/conformance_test/test_XR_KHR_visibility_mask.cpp +++ b/src/conformance/conformance_test/test_XR_KHR_visibility_mask.cpp @@ -278,8 +278,11 @@ namespace Conformance { MeshHandle mesh; bool meshCoversHiddenArea = maskType == XR_VISIBILITY_MASK_TYPE_HIDDEN_TRIANGLE_MESH_KHR; - XrColor4f color = meshCoversHiddenArea ? BrightRed : DarkSlateGrey; - XrColor4f bgColor = meshCoversHiddenArea ? DarkSlateGrey : BrightRed; + + XrColor4f normalBgColor = GetGlobalData().GetClearColorForBackground(); + + XrColor4f color = meshCoversHiddenArea ? BrightRed : normalBgColor; + XrColor4f bgColor = meshCoversHiddenArea ? normalBgColor : BrightRed; XrVisibilityMaskKHR visibilityMask{XR_TYPE_VISIBILITY_MASK_KHR}; @@ -327,7 +330,7 @@ namespace Conformance return {mesh, bgColor}; } - TEST_CASE("XR_KHR_visibility_mask-interactive", "[XR_KHR_visibility_mask][interactive][no_auto]") + TEST_CASE("XR_KHR_visibility_mask-interactive", "[scenario][interactive][no_auto][XR_KHR_visibility_mask]") { // successcodes="XR_SUCCESS,XR_SESSION_LOSS_PENDING" // errorcodes="XR_ERROR_HANDLE_INVALID,XR_ERROR_INSTANCE_LOST,XR_ERROR_RUNTIME_FAILURE,XR_ERROR_VALIDATION_FAILURE, @@ -379,7 +382,7 @@ namespace Conformance SKIP("No vertices returned, so no visibility mask available in this system."); } - std::vector bgColors{meshProjectionLayerHelper.GetViewCount(), DarkSlateGrey}; + std::vector bgColors{meshProjectionLayerHelper.GetViewCount(), Colors::Green}; // should be overwritten before render // const auto maskTypes = {XR_VISIBILITY_MASK_TYPE_HIDDEN_TRIANGLE_MESH_KHR, XR_VISIBILITY_MASK_TYPE_VISIBLE_TRIANGLE_MESH_KHR, // XR_VISIBILITY_MASK_TYPE_LINE_LOOP_KHR}; diff --git a/src/conformance/conformance_test/test_XR_KHR_vulkan_enable.cpp b/src/conformance/conformance_test/test_XR_KHR_vulkan_enable.cpp index fb12e8cc..3a6cc0f8 100644 --- a/src/conformance/conformance_test/test_XR_KHR_vulkan_enable.cpp +++ b/src/conformance/conformance_test/test_XR_KHR_vulkan_enable.cpp @@ -40,8 +40,10 @@ namespace Conformance SKIP(XR_KHR_VULKAN_ENABLE_EXTENSION_NAME " not enabled"); } - AutoBasicInstance instance{AutoBasicInstance::createSystemId}; - XrSystemId systemId = instance.systemId; + AutoBasicInstance instance; + + XrSystemId systemId{XR_NULL_SYSTEM_ID}; + REQUIRE(XR_SUCCESS == FindBasicSystem(instance.GetInstance(), &systemId)); // Create the graphics plugin we'll need to exercise session create functionality below. std::shared_ptr graphicsPlugin; @@ -54,12 +56,15 @@ namespace Conformance // We'll use this XrSession and XrSessionCreateInfo for testing below. XrSession session = XR_NULL_HANDLE_CPP; - XrSessionCreateInfo sessionCreateInfo{XR_TYPE_SESSION_CREATE_INFO, nullptr, 0, systemId}; + + XrSessionCreateInfo sessionCreateInfo{XR_TYPE_SESSION_CREATE_INFO}; + sessionCreateInfo.systemId = systemId; + CleanupSessionOnScopeExit cleanup(session); SECTION("No graphics binding") { - graphicsPlugin->InitializeDevice(instance, systemId, true); + REQUIRE(graphicsPlugin->InitializeDevice(instance, systemId, true)); sessionCreateInfo.next = nullptr; CHECK(xrCreateSession(instance, &sessionCreateInfo, &session) == XR_ERROR_GRAPHICS_DEVICE_INVALID); cleanup.Destroy(); @@ -68,7 +73,7 @@ namespace Conformance SECTION("Valid vulkan device") { - graphicsPlugin->InitializeDevice(instance, systemId, true); + REQUIRE(graphicsPlugin->InitializeDevice(instance, systemId, true)); XrGraphicsBindingVulkanKHR graphicsBinding = *reinterpret_cast(graphicsPlugin->GetGraphicsBinding()); sessionCreateInfo.next = reinterpret_cast(&graphicsBinding); @@ -79,7 +84,7 @@ namespace Conformance SECTION("NULL vulkan device") { - graphicsPlugin->InitializeDevice(instance, systemId, true); + REQUIRE(graphicsPlugin->InitializeDevice(instance, systemId, true)); XrGraphicsBindingVulkanKHR graphicsBinding = *reinterpret_cast(graphicsPlugin->GetGraphicsBinding()); graphicsBinding.device = VK_NULL_HANDLE; @@ -89,6 +94,35 @@ namespace Conformance graphicsPlugin->ShutdownDevice(); } + SECTION("Valid session after bad session") + { + // Pass invalid binding the first time + { + REQUIRE(graphicsPlugin->InitializeDevice(instance, systemId, true)); + XrGraphicsBindingVulkanKHR graphicsBinding = + *reinterpret_cast(graphicsPlugin->GetGraphicsBinding()); + graphicsBinding.device = VK_NULL_HANDLE; + sessionCreateInfo.next = reinterpret_cast(&graphicsBinding); + CHECK(xrCreateSession(instance, &sessionCreateInfo, &session) == XR_ERROR_GRAPHICS_DEVICE_INVALID); + cleanup.Destroy(); + graphicsPlugin->ShutdownDevice(); + } + + // Using the same instance pass valid binding the second time + { + REQUIRE(XR_SUCCESS == FindBasicSystem(instance.GetInstance(), &systemId)); + sessionCreateInfo.systemId = systemId; + + REQUIRE(graphicsPlugin->InitializeDevice(instance, systemId, true)); + XrGraphicsBindingVulkanKHR graphicsBinding = + *reinterpret_cast(graphicsPlugin->GetGraphicsBinding()); + sessionCreateInfo.next = reinterpret_cast(&graphicsBinding); + CHECK(xrCreateSession(instance, &sessionCreateInfo, &session) == XR_SUCCESS); + cleanup.Destroy(); + graphicsPlugin->ShutdownDevice(); + } + } + SECTION("Multiple session with same device") { auto createSwapchains = [](std::shared_ptr graphicsPlugin, XrSession session) { @@ -104,11 +138,27 @@ namespace Conformance } }; - graphicsPlugin->InitializeDevice(instance, systemId, true); + auto xrGetVulkanGraphicsRequirementsKHR = + GetInstanceExtensionFunction(instance, "xrGetVulkanGraphicsRequirementsKHR"); + + XrGraphicsRequirementsVulkanKHR referenceGraphicsRequirements{XR_TYPE_GRAPHICS_REQUIREMENTS_VULKAN_KHR}; + REQUIRE(xrGetVulkanGraphicsRequirementsKHR(instance, systemId, &referenceGraphicsRequirements) == XR_SUCCESS); + + REQUIRE(graphicsPlugin->InitializeDevice(instance, systemId, true)); XrGraphicsBindingVulkanKHR graphicsBinding = *reinterpret_cast(graphicsPlugin->GetGraphicsBinding()); sessionCreateInfo.next = reinterpret_cast(&graphicsBinding); for (int i = 0; i < 3; ++i) { + REQUIRE(XR_SUCCESS == FindBasicSystem(instance.GetInstance(), &systemId)); + sessionCreateInfo.systemId = systemId; + + XrGraphicsRequirementsVulkanKHR graphicsRequirements{XR_TYPE_GRAPHICS_REQUIREMENTS_VULKAN_KHR}; + REQUIRE(xrGetVulkanGraphicsRequirementsKHR(instance, systemId, &graphicsRequirements) == XR_SUCCESS); + + // We expect that the graphics requirements don't change... + REQUIRE(referenceGraphicsRequirements.maxApiVersionSupported == graphicsRequirements.maxApiVersionSupported); + REQUIRE(referenceGraphicsRequirements.minApiVersionSupported == graphicsRequirements.minApiVersionSupported); + CHECK(xrCreateSession(instance, &sessionCreateInfo, &session) == XR_SUCCESS); createSwapchains(graphicsPlugin, session); CHECK(xrDestroySession(session) == XR_SUCCESS); diff --git a/src/conformance/conformance_test/test_XR_KHR_vulkan_enable2.cpp b/src/conformance/conformance_test/test_XR_KHR_vulkan_enable2.cpp index 7c458dd4..6f2b1f62 100644 --- a/src/conformance/conformance_test/test_XR_KHR_vulkan_enable2.cpp +++ b/src/conformance/conformance_test/test_XR_KHR_vulkan_enable2.cpp @@ -40,8 +40,10 @@ namespace Conformance SKIP(XR_KHR_VULKAN_ENABLE2_EXTENSION_NAME " not enabled"); } - AutoBasicInstance instance{AutoBasicInstance::createSystemId}; - XrSystemId systemId = instance.systemId; + AutoBasicInstance instance; + + XrSystemId systemId{XR_NULL_SYSTEM_ID}; + REQUIRE(XR_SUCCESS == FindBasicSystem(instance.GetInstance(), &systemId)); // Create the graphics plugin we'll need to exercise session create functionality below. std::shared_ptr graphicsPlugin; @@ -54,12 +56,15 @@ namespace Conformance // We'll use this XrSession and XrSessionCreateInfo for testing below. XrSession session = XR_NULL_HANDLE_CPP; - XrSessionCreateInfo sessionCreateInfo{XR_TYPE_SESSION_CREATE_INFO, nullptr, 0, systemId}; + + XrSessionCreateInfo sessionCreateInfo{XR_TYPE_SESSION_CREATE_INFO}; + sessionCreateInfo.systemId = systemId; + CleanupSessionOnScopeExit cleanup(session); SECTION("No graphics binding") { - graphicsPlugin->InitializeDevice(instance, systemId, true); + REQUIRE(graphicsPlugin->InitializeDevice(instance, systemId, true)); sessionCreateInfo.next = nullptr; CHECK(xrCreateSession(instance, &sessionCreateInfo, &session) == XR_ERROR_GRAPHICS_DEVICE_INVALID); cleanup.Destroy(); @@ -68,7 +73,7 @@ namespace Conformance SECTION("Valid vulkan device") { - graphicsPlugin->InitializeDevice(instance, systemId, true); + REQUIRE(graphicsPlugin->InitializeDevice(instance, systemId, true)); XrGraphicsBindingVulkan2KHR graphicsBinding = *reinterpret_cast(graphicsPlugin->GetGraphicsBinding()); sessionCreateInfo.next = reinterpret_cast(&graphicsBinding); @@ -79,7 +84,7 @@ namespace Conformance SECTION("NULL vulkan device") { - graphicsPlugin->InitializeDevice(instance, systemId, true); + REQUIRE(graphicsPlugin->InitializeDevice(instance, systemId, true)); XrGraphicsBindingVulkan2KHR graphicsBinding = *reinterpret_cast(graphicsPlugin->GetGraphicsBinding()); graphicsBinding.device = nullptr; @@ -89,6 +94,35 @@ namespace Conformance graphicsPlugin->ShutdownDevice(); } + SECTION("Valid session after bad session") + { + // Pass invalid binding the first time + { + REQUIRE(graphicsPlugin->InitializeDevice(instance, systemId, true)); + XrGraphicsBindingVulkan2KHR graphicsBinding = + *reinterpret_cast(graphicsPlugin->GetGraphicsBinding()); + graphicsBinding.device = nullptr; + sessionCreateInfo.next = reinterpret_cast(&graphicsBinding); + CHECK(xrCreateSession(instance, &sessionCreateInfo, &session) == XR_ERROR_GRAPHICS_DEVICE_INVALID); + cleanup.Destroy(); + graphicsPlugin->ShutdownDevice(); + } + + // Using the same instance pass valid binding the second time + { + REQUIRE(XR_SUCCESS == FindBasicSystem(instance.GetInstance(), &systemId)); + sessionCreateInfo.systemId = systemId; + + REQUIRE(graphicsPlugin->InitializeDevice(instance, systemId, true)); + XrGraphicsBindingVulkan2KHR graphicsBinding = + *reinterpret_cast(graphicsPlugin->GetGraphicsBinding()); + sessionCreateInfo.next = reinterpret_cast(&graphicsBinding); + CHECK(xrCreateSession(instance, &sessionCreateInfo, &session) == XR_SUCCESS); + cleanup.Destroy(); + graphicsPlugin->ShutdownDevice(); + } + } + SECTION("Multiple session with same device") { auto createSwapchains = [](std::shared_ptr graphicsPlugin, XrSession session) { @@ -104,11 +138,27 @@ namespace Conformance } }; - graphicsPlugin->InitializeDevice(instance, systemId, true); + auto xrGetVulkanGraphicsRequirements2KHR = + GetInstanceExtensionFunction(instance, "xrGetVulkanGraphicsRequirements2KHR"); + + XrGraphicsRequirementsVulkan2KHR referenceGraphicsRequirements{XR_TYPE_GRAPHICS_REQUIREMENTS_VULKAN2_KHR}; + REQUIRE(xrGetVulkanGraphicsRequirements2KHR(instance, systemId, &referenceGraphicsRequirements) == XR_SUCCESS); + + REQUIRE(graphicsPlugin->InitializeDevice(instance, systemId, true)); XrGraphicsBindingVulkan2KHR graphicsBinding = *reinterpret_cast(graphicsPlugin->GetGraphicsBinding()); sessionCreateInfo.next = reinterpret_cast(&graphicsBinding); for (int i = 0; i < 3; ++i) { + REQUIRE(XR_SUCCESS == FindBasicSystem(instance.GetInstance(), &systemId)); + sessionCreateInfo.systemId = systemId; + + XrGraphicsRequirementsVulkan2KHR graphicsRequirements{XR_TYPE_GRAPHICS_REQUIREMENTS_VULKAN2_KHR}; + REQUIRE(xrGetVulkanGraphicsRequirements2KHR(instance, systemId, &graphicsRequirements) == XR_SUCCESS); + + // We expect that the graphics requirements don't change... + REQUIRE(referenceGraphicsRequirements.maxApiVersionSupported == graphicsRequirements.maxApiVersionSupported); + REQUIRE(referenceGraphicsRequirements.minApiVersionSupported == graphicsRequirements.minApiVersionSupported); + CHECK(xrCreateSession(instance, &sessionCreateInfo, &session) == XR_SUCCESS); createSwapchains(graphicsPlugin, session); CHECK(xrDestroySession(session) == XR_SUCCESS); diff --git a/src/conformance/conformance_test/test_XR_KHR_win32_convert_performance_counter_time.cpp b/src/conformance/conformance_test/test_XR_KHR_win32_convert_performance_counter_time.cpp index 78edae70..fb43b0a2 100644 --- a/src/conformance/conformance_test/test_XR_KHR_win32_convert_performance_counter_time.cpp +++ b/src/conformance/conformance_test/test_XR_KHR_win32_convert_performance_counter_time.cpp @@ -121,13 +121,12 @@ namespace Conformance CAPTURE(qpcBefore); // Wait until the runtime is ready for us to begin a session - auto timeout = (GetGlobalData().options.debugMode ? 3600s : 10s); FrameIterator frameIterator(&session); - FrameIterator::RunResult runResult = frameIterator.RunToSessionState(XR_SESSION_STATE_FOCUSED, timeout); - REQUIRE(runResult == FrameIterator::RunResult::Success); + frameIterator.RunToSessionState(XR_SESSION_STATE_FOCUSED); // Submit a frame and query the time for the next frame - frameIterator.SubmitFrame(); + FrameIterator::RunResult runResult = frameIterator.SubmitFrame(); + REQUIRE(runResult == FrameIterator::RunResult::Success); XrTime nextFrameTime = frameIterator.frameState.predictedDisplayTime; // predicted display time is required to be a time in the future so it is fair to assume it is after now. diff --git a/src/conformance/conformance_test/test_XR_META_performance_metrics.cpp b/src/conformance/conformance_test/test_XR_META_performance_metrics.cpp index 25b63a78..585b95ff 100644 --- a/src/conformance/conformance_test/test_XR_META_performance_metrics.cpp +++ b/src/conformance/conformance_test/test_XR_META_performance_metrics.cpp @@ -85,10 +85,6 @@ namespace Conformance SECTION("Query metrics after xrEndFrame") { - // how long the test should wait for the app to get focus: 10 seconds in release, 1hr in debug builds. - auto timeout = (globalData.options.debugMode ? 3600s : 10s); - CAPTURE(timeout); - // Get a session started. AutoBasicSession session(AutoBasicSession::createInstance | AutoBasicSession::createSession | AutoBasicSession::beginSession | AutoBasicSession::createSwapchains | AutoBasicSession::createSpaces, @@ -101,11 +97,10 @@ namespace Conformance // Get frames iterating to the point of app focused state. This will draw frames along the way. FrameIterator frameIterator(&session); - FrameIterator::RunResult runResult = frameIterator.RunToSessionState(XR_SESSION_STATE_FOCUSED, timeout); - REQUIRE(runResult == FrameIterator::RunResult::Success); + frameIterator.RunToSessionState(XR_SESSION_STATE_FOCUSED); // Render one frame to some frame stats. - runResult = frameIterator.SubmitFrame(); + FrameIterator::RunResult runResult = frameIterator.SubmitFrame(); REQUIRE(runResult == FrameIterator::RunResult::Success); for (const XrPath& path : paths) { diff --git a/src/conformance/conformance_test/test_actions.cpp b/src/conformance/conformance_test/test_actions.cpp index ce37068f..fdb1132b 100644 --- a/src/conformance/conformance_test/test_actions.cpp +++ b/src/conformance/conformance_test/test_actions.cpp @@ -478,10 +478,7 @@ namespace Conformance CAPTURE(bindingPath); const XrActionType& actionType = inputSourcePathData.Type; -#define ENUM_NAME(e, val) {(XrActionType)val, #e}, - std::map actionTypeToString = {XR_LIST_ENUM_XrActionType(ENUM_NAME)}; -#undef ENUM_NAME - std::string actionTypeStr = actionTypeToString.at(actionType); + std::string actionTypeStr = enum_to_string(actionType); CAPTURE(actionTypeStr); XrAction* actionRef; @@ -1181,18 +1178,41 @@ namespace Conformance compositionHelper.BeginSession(); SECTION("No Focus") { - compositionHelper.GetInteractionManager().AddActionSet(actionSet); - compositionHelper.GetInteractionManager().AttachActionSets(); + SECTION("No active action sets") + { + XrActionsSyncInfo syncInfo{XR_TYPE_ACTIONS_SYNC_INFO}; + syncInfo.activeActionSets = nullptr; + syncInfo.countActiveActionSets = 0; - XrActionsSyncInfo syncInfo{XR_TYPE_ACTIONS_SYNC_INFO}; - XrActiveActionSet activeActionSet{actionSet}; - syncInfo.activeActionSets = &activeActionSet; - syncInfo.countActiveActionSets = 1; + { + INFO("No action sets attached"); + + REQUIRE_RESULT_SUCCEEDED(xrSyncActions(compositionHelper.GetSession(), &syncInfo)); + } + { + INFO("With action sets attached"); + + compositionHelper.GetInteractionManager().AddActionSet(actionSet); + compositionHelper.GetInteractionManager().AttachActionSets(); + + REQUIRE_RESULT_SUCCEEDED(xrSyncActions(compositionHelper.GetSession(), &syncInfo)); + } + } + SECTION("Active action sets") + { + compositionHelper.GetInteractionManager().AddActionSet(actionSet); + compositionHelper.GetInteractionManager().AttachActionSets(); - REQUIRE_RESULT(xrSyncActions(compositionHelper.GetSession(), &syncInfo), XR_SESSION_NOT_FOCUSED); + XrActionsSyncInfo syncInfo{XR_TYPE_ACTIONS_SYNC_INFO}; + XrActiveActionSet activeActionSet{actionSet}; + syncInfo.activeActionSets = &activeActionSet; + syncInfo.countActiveActionSets = 1; - REQUIRE_RESULT(xrGetActionStateBoolean(compositionHelper.GetSession(), &getInfo, &actionStateBoolean), XR_SUCCESS); - REQUIRE_FALSE(actionStateBoolean.isActive); + REQUIRE_RESULT(xrSyncActions(compositionHelper.GetSession(), &syncInfo), XR_SESSION_NOT_FOCUSED); + + REQUIRE_RESULT(xrGetActionStateBoolean(compositionHelper.GetSession(), &getInfo, &actionStateBoolean), XR_SUCCESS); + REQUIRE_FALSE(actionStateBoolean.isActive); + } } SECTION("Focus") { @@ -1631,6 +1651,12 @@ namespace Conformance REQUIRE_RESULT(xrCreateActionSet(compositionHelper.GetInstance(), &actionSetCreateInfo, &subactionPathFreeActionSet), XR_SUCCESS); + XrActionSet unboundActionActionSet{XR_NULL_HANDLE}; + strcpy(actionSetCreateInfo.localizedActionSetName, "test action set localized name 5"); + strcpy(actionSetCreateInfo.actionSetName, "test_action_set_name_5"); + REQUIRE_RESULT(xrCreateActionSet(compositionHelper.GetInstance(), &actionSetCreateInfo, &unboundActionActionSet), + XR_SUCCESS); + XrAction leftHandAction{XR_NULL_HANDLE}; actionCreateInfo.actionType = XR_ACTION_TYPE_BOOLEAN_INPUT; strcpy(actionCreateInfo.localizedActionName, "test select action"); @@ -1645,12 +1671,19 @@ namespace Conformance actionCreateInfo.subactionPaths = &rightHandPath; REQUIRE_RESULT(xrCreateAction(actionSet, &actionCreateInfo, &rightHandAction), XR_SUCCESS); + XrAction unboundAction{XR_NULL_HANDLE}; + strcpy(actionCreateInfo.localizedActionName, "test select action 4"); + strcpy(actionCreateInfo.actionName, "test_select_action_4"); + actionCreateInfo.subactionPaths = &defaultDevicePath; + REQUIRE_RESULT(xrCreateAction(unboundActionActionSet, &actionCreateInfo, &unboundAction), XR_SUCCESS); + compositionHelper.GetInteractionManager().AddActionBindings( simpleControllerInteractionProfile, {{leftHandAction, StringToPath(compositionHelper.GetInstance(), "/user/hand/left/input/select/click")}, {rightHandAction, StringToPath(compositionHelper.GetInstance(), "/user/hand/right/input/select/click")}}); compositionHelper.GetInteractionManager().AddActionSet(actionSet); compositionHelper.GetInteractionManager().AddActionSet(subactionPathFreeActionSet); + compositionHelper.GetInteractionManager().AddActionSet(unboundActionActionSet); compositionHelper.GetInteractionManager().AttachActionSets(); if (globalData.leftHandUnderTest) { @@ -1663,6 +1696,7 @@ namespace Conformance XrActionsSyncInfo syncInfo{XR_TYPE_ACTIONS_SYNC_INFO}; XrActiveActionSet activeActionSet{actionSet}; XrActiveActionSet subactionPathFreeActiveActionSet{subactionPathFreeActionSet}; + XrActiveActionSet unboundActionActiveActionSet{unboundActionActionSet}; syncInfo.activeActionSets = &activeActionSet; syncInfo.countActiveActionSets = 1; @@ -1754,6 +1788,11 @@ namespace Conformance REQUIRE_RESULT(xrCreateActionSet(compositionHelper.GetInstance(), &actionSetCreateInfo, &unattachedActionSet), XR_SUCCESS); + INFO("Unbound action"); + syncInfo.activeActionSets = &unboundActionActiveActionSet; + unboundActionActiveActionSet.subactionPath = defaultDevicePath; + actionLayerManager.SyncActionsUntilFocusWithMessage(syncInfo); + INFO("unattached action set"); XrActiveActionSet activeActionSet2 = {unattachedActionSet}; syncInfo.countActiveActionSets = 1; diff --git a/src/conformance/conformance_test/test_multithreading.cpp b/src/conformance/conformance_test/test_multithreading.cpp index 75559484..77632e72 100644 --- a/src/conformance/conformance_test/test_multithreading.cpp +++ b/src/conformance/conformance_test/test_multithreading.cpp @@ -328,10 +328,6 @@ namespace Conformance // Exercise session multithreading. { - // how long the test should wait for the app to get focus: 10 seconds in release, infinite in debug builds. - auto timeout = (GetGlobalData().options.debugMode ? 3600s : 10s); - CAPTURE(timeout); - ThreadTestEnvironment env(invocationCount); env.GetAutoBasicSession().Init(AutoBasicSession::beginSession | AutoBasicSession::createActions | AutoBasicSession::createSpaces | AutoBasicSession::createSwapchains); @@ -386,8 +382,7 @@ namespace Conformance // Get frames iterating to the point of app focused state. This will draw frames along the way. FrameIterator frameIterator(&env.GetAutoBasicSession()); - FrameIterator::RunResult runResult = frameIterator.RunToSessionState(XR_SESSION_STATE_FOCUSED, timeout); - REQUIRE(runResult == FrameIterator::RunResult::Success); + frameIterator.RunToSessionState(XR_SESSION_STATE_FOCUSED); env.lastFrameTime = frameIterator.frameState.predictedDisplayTime; diff --git a/src/conformance/conformance_test/test_xrCreateInstance.cpp b/src/conformance/conformance_test/test_xrCreateInstance.cpp index e7ab8232..6af99a31 100644 --- a/src/conformance/conformance_test/test_xrCreateInstance.cpp +++ b/src/conformance/conformance_test/test_xrCreateInstance.cpp @@ -24,6 +24,7 @@ #include #include +#include namespace Conformance { @@ -254,6 +255,7 @@ namespace Conformance // To do: Enable any layers and extensions available. } } + TEST_CASE("xrDestroyInstance", "") { SECTION("null handle") @@ -263,6 +265,18 @@ namespace Conformance CHECK(xrDestroyInstance(XR_NULL_HANDLE_CPP) == XR_ERROR_HANDLE_INVALID); } + SECTION("destroy on a different thread to create") + { + for (int i = 0; i < 2; ++i) { + CAPTURE(i); + AutoBasicInstance instance; + XrResult destroyResult = XR_ERROR_RUNTIME_FAILURE; + std::thread t([&destroyResult, &instance] { destroyResult = xrDestroyInstance(instance); }); + t.join(); + REQUIRE(destroyResult == XR_SUCCESS); + } + } + OPTIONAL_INVALID_HANDLE_VALIDATION_SECTION { GlobalData& globalData = GetGlobalData(); diff --git a/src/conformance/conformance_test/test_xrCreateSession.cpp b/src/conformance/conformance_test/test_xrCreateSession.cpp index a29d4612..cb6f1314 100644 --- a/src/conformance/conformance_test/test_xrCreateSession.cpp +++ b/src/conformance/conformance_test/test_xrCreateSession.cpp @@ -27,6 +27,7 @@ #include #include #include +#include namespace Conformance { @@ -81,7 +82,7 @@ namespace Conformance // Happens if the application tries to create the session but hasn't queried the graphics requirements (e.g. // xrGetD3D12GraphicsRequirementsKHR). This spec states that applications must call this, but // how we enforce it in conformance testing is problematic because a specific return code isn't specified. - graphicsPlugin->InitializeDevice(instance, systemId, false /* checkGraphicsRequirements */); + REQUIRE(graphicsPlugin->InitializeDevice(instance, systemId, false /* checkGraphicsRequirements */)); sessionCreateInfo.next = graphicsPlugin->GetGraphicsBinding(); XrResult sessionResult = xrCreateSession(instance, &sessionCreateInfo, &session); CHECK_THAT(sessionResult, In({XR_ERROR_VALIDATION_FAILURE, XR_ERROR_GRAPHICS_REQUIREMENTS_CALL_MISSING})); @@ -127,4 +128,31 @@ namespace Conformance } } } + + TEST_CASE("xrDestroySession", "") + { + SECTION("null handle") + { + CHECK(xrDestroySession(XR_NULL_HANDLE_CPP) == XR_ERROR_HANDLE_INVALID); + } + + SECTION("destroy on a different thread to create") + { + for (int i = 0; i < 2; ++i) { + CAPTURE(i); + + AutoBasicInstance instance; + AutoBasicSession session(AutoBasicSession::createSession, instance); + XrResult destroySessionResult = XR_ERROR_RUNTIME_FAILURE; + XrResult destroyInstanceResult = XR_ERROR_RUNTIME_FAILURE; + std::thread t([&destroySessionResult, &destroyInstanceResult, &session, &instance] { + destroySessionResult = xrDestroySession(session); + destroyInstanceResult = xrDestroyInstance(instance); + }); + t.join(); + REQUIRE(destroySessionResult == XR_SUCCESS); + REQUIRE(destroyInstanceResult == XR_SUCCESS); + } + } + } } // namespace Conformance diff --git a/src/conformance/conformance_test/test_xrLocateSpace.cpp b/src/conformance/conformance_test/test_xrLocateSpace.cpp index 99c2c6bc..96b54ffa 100644 --- a/src/conformance/conformance_test/test_xrLocateSpace.cpp +++ b/src/conformance/conformance_test/test_xrLocateSpace.cpp @@ -22,31 +22,28 @@ #include #include +#include #include #include #include #include +#include #include namespace Conformance { TEST_CASE("xrLocateSpace", "") { - // how long the test should wait for the app to get focus: 10 seconds in release, infinite in debug builds. - auto timeout = (GetGlobalData().options.debugMode ? 3600s : 10s); - CAPTURE(timeout); - // Get a session started. AutoBasicSession session(AutoBasicSession::createInstance | AutoBasicSession::createSession | AutoBasicSession::beginSession | AutoBasicSession::createSwapchains | AutoBasicSession::createSpaces); // Get frames iterating to the point of app focused state. This will draw frames along the way. FrameIterator frameIterator(&session); - FrameIterator::RunResult runResult = frameIterator.RunToSessionState(XR_SESSION_STATE_FOCUSED, timeout); - REQUIRE(runResult == FrameIterator::RunResult::Success); + frameIterator.RunToSessionState(XR_SESSION_STATE_FOCUSED); // Render one frame to get a predicted display time for the xrLocateSpace calls. - runResult = frameIterator.SubmitFrame(); + FrameIterator::RunResult runResult = frameIterator.SubmitFrame(); REQUIRE(runResult == FrameIterator::RunResult::Success); XrResult result; @@ -88,6 +85,31 @@ namespace Conformance XrTime time = frameIterator.frameState.predictedDisplayTime; CHECK(time != 0); + SECTION("valid inputs") + { + // two identical spaces: + result = xrCreateReferenceSpace(session, &spaceCreateInfo, &spaceA); + CHECK(result == XR_SUCCESS); + result = xrCreateReferenceSpace(session, &spaceCreateInfo, &spaceB); + CHECK(result == XR_SUCCESS); + + // Exercise the predicted display time + result = xrLocateSpace(spaceA, spaceB, time, &location); + CHECK(result == XR_SUCCESS); + + // Exercise 40ms ago (or the first valid time, whichever is later) + result = xrLocateSpace(spaceA, spaceB, std::max(time - 40_xrMilliseconds, (XrTime)1), &location); + CHECK(result == XR_SUCCESS); + + // Exercise 1s ago (or the first valid time, whichever is later) + result = xrLocateSpace(spaceA, spaceB, std::max(time - 1_xrSeconds, (XrTime)1), &location); + CHECK(result == XR_SUCCESS); + + // cleanup + CHECK(XR_SUCCESS == xrDestroySpace(spaceA)); + CHECK(XR_SUCCESS == xrDestroySpace(spaceB)); + } + SECTION("wrong inputs") { // two identical spaces: @@ -233,7 +255,6 @@ namespace Conformance result = xrRequestExitSession(session); CHECK(result == XR_SUCCESS); - runResult = frameIterator.RunToSessionState(XR_SESSION_STATE_STOPPING, timeout); - CHECK(runResult == FrameIterator::RunResult::Success); + frameIterator.RunToSessionState(XR_SESSION_STATE_STOPPING); } } // namespace Conformance diff --git a/src/conformance/conformance_test/test_xrLocateViews.cpp b/src/conformance/conformance_test/test_xrLocateViews.cpp index f1917d62..583dc66e 100644 --- a/src/conformance/conformance_test/test_xrLocateViews.cpp +++ b/src/conformance/conformance_test/test_xrLocateViews.cpp @@ -30,21 +30,16 @@ namespace Conformance { GlobalData& globalData = GetGlobalData(); - // how long the test should wait for the app to get focus: 10 seconds in release, 1hr in debug builds. - auto timeout = (globalData.options.debugMode ? 3600s : 10s); - CAPTURE(timeout); - // Get a session started. AutoBasicSession session(AutoBasicSession::createInstance | AutoBasicSession::createSession | AutoBasicSession::beginSession | AutoBasicSession::createSwapchains | AutoBasicSession::createSpaces); // Get frames iterating to the point of app focused state. This will draw frames along the way. FrameIterator frameIterator(&session); - FrameIterator::RunResult runResult = frameIterator.RunToSessionState(XR_SESSION_STATE_FOCUSED, timeout); - REQUIRE(runResult == FrameIterator::RunResult::Success); + frameIterator.RunToSessionState(XR_SESSION_STATE_FOCUSED); // Render one frame to get a predicted display time for the xrLocateViews calls. - runResult = frameIterator.SubmitFrame(); + FrameIterator::RunResult runResult = frameIterator.SubmitFrame(); REQUIRE(runResult == FrameIterator::RunResult::Success); XrResult result; @@ -170,7 +165,6 @@ namespace Conformance result = xrRequestExitSession(session); CHECK(result == XR_SUCCESS); - runResult = frameIterator.RunToSessionState(XR_SESSION_STATE_STOPPING, timeout); - CHECK(runResult == FrameIterator::RunResult::Success); + frameIterator.RunToSessionState(XR_SESSION_STATE_STOPPING); } } // namespace Conformance diff --git a/src/conformance/framework/composition_utils.h b/src/conformance/framework/composition_utils.h index f20cdff5..21f835de 100644 --- a/src/conformance/framework/composition_utils.h +++ b/src/conformance/framework/composition_utils.h @@ -348,7 +348,9 @@ namespace Conformance constexpr XrColor4f Blue = {0, 0, 1, 1}; constexpr XrColor4f Yellow = {1, 1, 0, 1}; constexpr XrColor4f Orange = {1, 0.65f, 0, 1}; + constexpr XrColor4f Magenta = {1, 0, 1, 1}; constexpr XrColor4f Transparent = {0, 0, 0, 0}; + constexpr XrColor4f Black = {0, 0, 0, 1}; /// A list of unique colors, not including red which is a "failure color". constexpr std::array UniqueColors{Green, Blue, Yellow, Orange}; diff --git a/src/conformance/framework/conformance_framework.cpp b/src/conformance/framework/conformance_framework.cpp index a4dcb037..9a2fa3c7 100644 --- a/src/conformance/framework/conformance_framework.cpp +++ b/src/conformance/framework/conformance_framework.cpp @@ -16,6 +16,7 @@ #include "conformance_framework.h" +#include "composition_utils.h" // for Colors #include "graphics_plugin.h" #include "platform_plugin.h" #include "report.h" @@ -479,11 +480,6 @@ namespace Conformance if (IsInstanceExtensionEnabled(XR_MND_HEADLESS_EXTENSION_NAME)) { return false; } -#ifdef XR_KHR_headless - if (IsInstanceExtensionEnabled(XR_KHR_HEADLESS_EXTENSION_NAME)) { - return false; - } -#endif return true; } @@ -497,4 +493,18 @@ namespace Conformance std::unique_lock lock(dataMutex); conformanceReport.swapchainFormats.emplace_back(format, name); } + + XrColor4f GlobalData::GetClearColorForBackground() const + { + switch (options.environmentBlendModeValue) { + case XR_ENVIRONMENT_BLEND_MODE_OPAQUE: + return DarkSlateGrey; + case XR_ENVIRONMENT_BLEND_MODE_ADDITIVE: + return Colors::Black; + case XR_ENVIRONMENT_BLEND_MODE_ALPHA_BLEND: + return Colors::Transparent; + default: + XRC_THROW("Encountered unrecognized environment blend mode value while determining background color."); + } + } } // namespace Conformance diff --git a/src/conformance/framework/conformance_framework.h b/src/conformance/framework/conformance_framework.h index 20bc7f7e..7cb29a8b 100644 --- a/src/conformance/framework/conformance_framework.h +++ b/src/conformance/framework/conformance_framework.h @@ -355,7 +355,7 @@ namespace Conformance std::shared_ptr GetGraphicsPlugin(); /// Returns true if under the current test environment we require a graphics plugin. This may - /// be false, for example, if the XR_KHR_headless is enabled. + /// be false, for example, if the XR_MND_headless extension is enabled. bool IsGraphicsPluginRequired() const; /// Returns true if a graphics plugin was supplied, or if IsGraphicsPluginRequired() is true. @@ -364,6 +364,9 @@ namespace Conformance /// Record a swapchain format as being supported and tested. void PushSwapchainFormat(int64_t format, const std::string& name); + /// Calculate the clear color to use for the background based on the XrEnvironmentBlendMode in use. + XrColor4f GetClearColorForBackground() const; + public: /// Guards all member data. mutable std::recursive_mutex dataMutex; @@ -605,6 +608,7 @@ MAKE_ENUM_TO_STRING_FUNC(XrViewConfigurationType); MAKE_ENUM_TO_STRING_FUNC(XrVisibilityMaskTypeKHR); MAKE_ENUM_TO_STRING_FUNC(XrFormFactor); MAKE_ENUM_TO_STRING_FUNC(XrEnvironmentBlendMode); +MAKE_ENUM_TO_STRING_FUNC(XrActionType); namespace Catch { diff --git a/src/conformance/framework/conformance_utils.cpp b/src/conformance/framework/conformance_utils.cpp index 3a90faff..d796083b 100644 --- a/src/conformance/framework/conformance_utils.cpp +++ b/src/conformance/framework/conformance_utils.cpp @@ -392,7 +392,7 @@ namespace Conformance // Normally the testing requires a graphics plugin. However, there's currently one case in // which that's not true: when a headless extension is enabled. In that case the - // runtime supports creating a session without a graphics system. See XR_MND_headless and/or XR_KHR_headless doc. + // runtime supports creating a session without a graphics system. See XR_MND_headless doc. if (graphicsPlugin && enableGraphicsSystem) { // If the following fails then this app has a bug, not the runtime. assert(graphicsPlugin->IsInitialized()); @@ -481,8 +481,8 @@ namespace Conformance // that the session is ready. // timeout in case the runtime will never transition to READY: 10s in release, no practical limit in debug - std::chrono::nanoseconds timeout = (GetGlobalData().options.debugMode ? 3600s : 10s); - CountdownTimer countdownTimer(timeout); + auto timeoutToTransitionToSessionState = (GetGlobalData().options.debugMode ? 3600s : 10s); + CountdownTimer countdownTimer(timeoutToTransitionToSessionState); while ((sessionState != XR_SESSION_STATE_READY) && (!countdownTimer.IsTimeUp())) { XrEventDataBuffer eventData{XR_TYPE_EVENT_DATA_BUFFER}; @@ -503,7 +503,24 @@ namespace Conformance } if (sessionState != XR_SESSION_STATE_READY) { - XRC_THROW("Time out waiting for XR_SESSION_STATE_READY session state change"); + // We have failed this check with the timeout. This is a pretty common place to fail + // so we will offer helpful hints for the most common errors - as well as a generic + // message. + + // https://registry.khronos.org/OpenXR/specs/1.0/html/xrspec.html#sessionstatechanged-description + // If the system supports a user engagement sensor and runtime is in XR_SESSION_STATE_IDLE state, + // the runtime should not transition to the XR_SESSION_STATE_READY state until the user starts + // engaging with the device. + + std::string extraInfo; + if (sessionState == XR_SESSION_STATE_IDLE) { + extraInfo = + " If this system supports a user engagement sensor, the runtime may not transition to XR_SESSION_STATE_READY state until the user starts engaging with the device."; + } + + CAPTURE(timeoutToTransitionToSessionState); + CAPTURE(sessionState); + FAIL("Time out waiting for XR_SESSION_STATE_READY session state change after creating a new session." << extraInfo); } XrSessionBeginInfo sessionBeginInfo{XR_TYPE_SESSION_BEGIN_INFO, @@ -627,17 +644,13 @@ namespace Conformance //////////////////////////////////////////////////////////////////////////////////////////////// FrameIterator::FrameIterator(AutoBasicSession* autoBasicSession_) - : autoBasicSession(autoBasicSession_) - , sessionState(autoBasicSession->GetSessionState()) - , countdownTimer() - , frameState() - , viewVector() - { - } - - void FrameIterator::SetAutoBasicSession(AutoBasicSession* autoBasicSession_) + : autoBasicSession(autoBasicSession_), sessionState(autoBasicSession->GetSessionState()), frameState(), viewVector() { - autoBasicSession = autoBasicSession_; + XRC_CHECK_THROW(autoBasicSession); + XRC_CHECK_THROW(autoBasicSession->instance); + XRC_CHECK_THROW(autoBasicSession->session); + XRC_CHECK_THROW(!autoBasicSession->viewConfigurationTypeVector.empty()); + XRC_CHECK_THROW(!autoBasicSession->environmentBlendModeVector.empty()); } XrSessionState FrameIterator::GetCurrentSessionState() const @@ -647,12 +660,6 @@ namespace Conformance FrameIterator::TickResult FrameIterator::PollEvent() { - // App must have called SetAutoBasicSession and set flags enabling these. - if (!autoBasicSession) - return TickResult::Error; - if (!autoBasicSession->instance) - return TickResult::Error; - XrEventDataBuffer eventData{XR_TYPE_EVENT_DATA_BUFFER}; XrResult result = xrPollEvent(autoBasicSession->instance, &eventData); @@ -693,11 +700,10 @@ namespace Conformance if (!GetGlobalData().IsUsingGraphicsPlugin()) return RunResult::Success; - // App must have called SetAutoBasicSession and set flags enabling these. - if (!autoBasicSession) - return RunResult::Error; - if (autoBasicSession->swapchainVector.empty()) + if (autoBasicSession->swapchainVector.empty()) { + // AutoBasicSession must be created with flags including AutoBasicSession::createSwapchains return RunResult::Error; + } // Call the helper function for this. const XrDuration twoSeconds = 2_xrSeconds; @@ -715,15 +721,10 @@ namespace Conformance FrameIterator::RunResult FrameIterator::WaitAndBeginFrame() { - // App must have called SetAutoBasicSession and set flags enabling these. - if (!autoBasicSession) - return RunResult::Error; - if (!autoBasicSession->session) - return RunResult::Error; - if (autoBasicSession->spaceVector.empty()) - return RunResult::Error; - if (autoBasicSession->viewConfigurationTypeVector.empty()) + if (autoBasicSession->spaceVector.empty()) { + // AutoBasicSession must be created with flags including AutoBasicSession::createSpaces return RunResult::Error; + } XrResult result; @@ -756,13 +757,10 @@ namespace Conformance FrameIterator::RunResult FrameIterator::PrepareFrameEndInfo() { - // App must have called SetAutoBasicSession and set flags enabling these. - if (!autoBasicSession) - return RunResult::Error; - if (autoBasicSession->spaceVector.empty()) - return RunResult::Error; - if (autoBasicSession->environmentBlendModeVector.empty()) + if (autoBasicSession->spaceVector.empty()) { + // AutoBasicSession must be created with flags including AutoBasicSession::createSpaces return RunResult::Error; + } if (GetGlobalData().IsUsingGraphicsPlugin() && autoBasicSession->swapchainVector.empty()) return RunResult::Error; @@ -839,27 +837,28 @@ namespace Conformance // Runs until the given XrSessionState is achieved or timesout before so. // targetSessionState may be any XrSessionState, but some session states may require // special handling in order to get to, such as XR_SESSION_STATE_LOSS_PENDING. - FrameIterator::RunResult FrameIterator::RunToSessionState(XrSessionState targetSessionState, std::chrono::nanoseconds timeout) + void FrameIterator::RunToSessionState(XrSessionState targetSessionState) { - if (!autoBasicSession) // App must have called SetAutoBasicSession. - return RunResult::Error; + auto initialSessionState = sessionState; - countdownTimer.Restart(timeout); + auto timeoutToTransitionToSessionState = (GetGlobalData().options.debugMode ? 3600s : 10s); + CAPTURE(timeoutToTransitionToSessionState); + CountdownTimer countdownTimer(timeoutToTransitionToSessionState); while (!countdownTimer.IsTimeUp()) { TickResult tickResult = PollEvent(); + REQUIRE(tickResult != TickResult::Error); - if (tickResult == TickResult::Error) - return RunResult::Error; - - if (sessionState == targetSessionState) - return RunResult::Success; - - if ((sessionState == XR_SESSION_STATE_LOSS_PENDING) || (sessionState == XR_SESSION_STATE_EXITING) || - (sessionState == XR_SESSION_STATE_STOPPING)) { - return RunResult::Timeout; + if (sessionState == targetSessionState) { + // calling SUCCEED here to flush the CAPTURE / INFO messages from this function + SUCCEED(); + return; } + REQUIRE(sessionState != XR_SESSION_STATE_LOSS_PENDING); + REQUIRE(sessionState != XR_SESSION_STATE_EXITING); + REQUIRE(sessionState != XR_SESSION_STATE_STOPPING); + // At this point sessionState is one of XR_SESSION_STATE_UNKNOWN, // XR_SESSION_STATE_IDLE, XR_SESSION_STATE_READY, XR_SESSION_STATE_SYNCHRONIZED, // XR_SESSION_STATE_VISIBLE, XR_SESSION_STATE_FOCUSED. We proceed based on the @@ -882,9 +881,7 @@ namespace Conformance XrSessionBeginInfo sessionBeginInfo{ XR_TYPE_SESSION_BEGIN_INFO, globalData.GetPlatformPlugin()->PopulateNextFieldForStruct(XR_TYPE_SESSION_BEGIN_INFO), globalData.options.viewConfigurationValue}; - XrResult result = xrBeginSession(autoBasicSession->session, &sessionBeginInfo); - if (XR_FAILED(result)) - return RunResult::Error; + REQUIRE(xrBeginSession(autoBasicSession->session, &sessionBeginInfo) == XR_SUCCESS); } // Fall-through because frames must be submitted to get promoted from READY to SYNCHRONIZED. @@ -894,10 +891,8 @@ namespace Conformance case XR_SESSION_STATE_FOCUSED: { // In these states we need to submit frames. Otherwise the runtime won't // necessarily move us from synchronized to visible or focused. - RunResult runResult = SubmitFrame(); + REQUIRE(SubmitFrame() == RunResult::Success); - if (runResult == RunResult::Error) - return RunResult::Error; // Just keep going. We haven't reached the target state yet. break; } @@ -910,7 +905,17 @@ namespace Conformance } } - return RunResult::Timeout; + // We have failed this check with the timeout. This is a pretty common place to fail + // so we will offer helpful hints for the most common errors - as well as a generic + // message. + + std::string extraInfo; + if (targetSessionState == XR_SESSION_STATE_FOCUSED && initialSessionState == XR_SESSION_STATE_READY && + sessionState == XR_SESSION_STATE_VISIBLE) { + extraInfo = " This might indicate that some other (maybe system) application still has focus for the user."; + } + FAIL("Timeout while waiting for session state transition to: " << enum_to_string(targetSessionState) << " from initial state: " + << enum_to_string(initialSessionState) << "." << extraInfo); } bool WaitUntilPredicateWithTimeout(const std::function& predicate, const std::chrono::nanoseconds timeout, diff --git a/src/conformance/framework/conformance_utils.h b/src/conformance/framework/conformance_utils.h index d770f113..bcb1386a 100644 --- a/src/conformance/framework/conformance_utils.h +++ b/src/conformance/framework/conformance_utils.h @@ -398,11 +398,7 @@ namespace Conformance class CountdownTimer { public: - CountdownTimer() : stopwatch(), timeoutDuration() - { - } - - CountdownTimer(std::chrono::nanoseconds timeout) : stopwatch(), timeoutDuration(timeout) + explicit CountdownTimer(std::chrono::nanoseconds timeout) : stopwatch(), timeoutDuration(timeout) { stopwatch.Restart(); } @@ -717,11 +713,10 @@ namespace Conformance /// /// // Get frames iterating to the point of app focused state. This will draw frames along the way. /// FrameIterator frameIterator(&session); - /// FrameIterator::RunResult runResult = frameIterator.RunToSessionState(XR_SESSION_STATE_FOCUSED, timeoutMicroseconds); - /// REQUIRE(runResult == FrameIterator::RunResult::Success); + /// frameIterator.RunToSessionState(XR_SESSION_STATE_FOCUSED); /// /// // Let's have the FrameIterator draw one more frame itself. - /// runResult = frameIterator.SubmitFrame(); + /// FrameIterator::RunResult runResult = frameIterator.SubmitFrame(); /// REQUIRE(runResult == FrameIterator::RunResult::Success); /// /// // Now let's draw a frame ourselves. @@ -739,12 +734,9 @@ namespace Conformance class FrameIterator { public: - FrameIterator(AutoBasicSession* autoBasicSession_ = nullptr); + explicit FrameIterator(AutoBasicSession* autoBasicSession_ = nullptr); ~FrameIterator() = default; - /// Must not be called after calling any other member function. - void SetAutoBasicSession(AutoBasicSession* autoBasicSession_); - XrSessionState GetCurrentSessionState() const; enum class TickResult @@ -797,16 +789,16 @@ namespace Conformance /// an example of this. RunResult SubmitFrame(); - /// Runs until the given XrSessionState is achieved or timesout before so. + /// Runs until the given XrSessionState is achieved or times out before so. /// targetSessionState may be any XrSessionState, but some session states may require /// special handling in order to get to, such as XR_SESSION_STATE_LOSS_PENDING. /// Will repeatedly call SubmitFrame if necessary to get to the desired state. - RunResult RunToSessionState(XrSessionState targetSessionState, std::chrono::nanoseconds timeout); + /// Will fail test if targetSessionState is not reached. + void RunToSessionState(XrSessionState targetSessionState); protected: - AutoBasicSession* autoBasicSession; + AutoBasicSession* const autoBasicSession; XrSessionState sessionState; - CountdownTimer countdownTimer; public: XrFrameState frameState; //< xrWaitFrame from WaitAndBeginFrame fills this in. diff --git a/src/conformance/framework/graphics_plugin.h b/src/conformance/framework/graphics_plugin.h index a26b00f4..baf6e864 100644 --- a/src/conformance/framework/graphics_plugin.h +++ b/src/conformance/framework/graphics_plugin.h @@ -18,6 +18,7 @@ #include "platform_plugin.h" #include "conformance_utils.h" +#include "conformance_framework.h" #include "utilities/Geometry.h" #include "RGBAImage.h" @@ -307,8 +308,15 @@ namespace Conformance AllocateSwapchainImageDataWithDepthSwapchain(size_t size, const XrSwapchainCreateInfo& colorSwapchainCreateInfo, XrSwapchain depthSwapchain, const XrSwapchainCreateInfo& depthSwapchainCreateInfo) = 0; - virtual void ClearImageSlice(const XrSwapchainImageBaseHeader* colorSwapchainImage, uint32_t imageArrayIndex = 0, - XrColor4f bgColor = DarkSlateGrey) = 0; + /// Clears a slice to an arbitrary color + virtual void ClearImageSlice(const XrSwapchainImageBaseHeader* colorSwapchainImage, uint32_t imageArrayIndex, XrColor4f color) = 0; + + /// Clears to the background color which varies depending on the environment blend mode that is active. + void ClearImageSlice(const XrSwapchainImageBaseHeader* colorSwapchainImage, uint32_t imageArrayIndex = 0) + { + GlobalData& globalData = GetGlobalData(); + ClearImageSlice(colorSwapchainImage, imageArrayIndex, globalData.GetClearColorForBackground()); + } /// Create internal data for a mesh, returning a handle to refer to it. /// This handle expires when the internal data is cleared in Shutdown() and ShutdownDevice(). diff --git a/src/conformance/framework/graphics_plugin_d3d11.cpp b/src/conformance/framework/graphics_plugin_d3d11.cpp index a2dee28e..b5c9b551 100644 --- a/src/conformance/framework/graphics_plugin_d3d11.cpp +++ b/src/conformance/framework/graphics_plugin_d3d11.cpp @@ -200,8 +200,7 @@ namespace Conformance XrSwapchain depthSwapchain, const XrSwapchainCreateInfo& depthSwapchainCreateInfo) override; - void ClearImageSlice(const XrSwapchainImageBaseHeader* colorSwapchainImage, uint32_t imageArrayIndex, - XrColor4f bgColor = DarkSlateGrey) override; + void ClearImageSlice(const XrSwapchainImageBaseHeader* colorSwapchainImage, uint32_t imageArrayIndex, XrColor4f color) override; MeshHandle MakeSimpleMesh(span idx, span vtx) override; @@ -643,7 +642,7 @@ namespace Conformance } void D3D11GraphicsPlugin::ClearImageSlice(const XrSwapchainImageBaseHeader* colorSwapchainImage, uint32_t imageArrayIndex, - XrColor4f bgColor) + XrColor4f color) { D3D11SwapchainImageData* swapchainData; @@ -655,7 +654,7 @@ namespace Conformance // Create RenderTargetView with original swapchain format (swapchain is typeless). ComPtr renderTargetView = CreateRenderTargetView(*swapchainData, imageIndex, imageArrayIndex); // TODO: Do not clear to a color when using a pass-through view configuration. - FLOAT bg[] = {bgColor.r, bgColor.g, bgColor.b, bgColor.a}; + FLOAT bg[] = {color.r, color.g, color.b, color.a}; d3d11DeviceContext->ClearRenderTargetView(renderTargetView.Get(), bg); // Clear depth buffer. diff --git a/src/conformance/framework/graphics_plugin_d3d12.cpp b/src/conformance/framework/graphics_plugin_d3d12.cpp index cd7b438d..0fd9e210 100644 --- a/src/conformance/framework/graphics_plugin_d3d12.cpp +++ b/src/conformance/framework/graphics_plugin_d3d12.cpp @@ -355,8 +355,7 @@ namespace Conformance XrSwapchain depthSwapchain, const XrSwapchainCreateInfo& depthSwapchainCreateInfo) override; - void ClearImageSlice(const XrSwapchainImageBaseHeader* colorSwapchainImage, uint32_t imageArrayIndex, - XrColor4f bgColor = DarkSlateGrey) override; + void ClearImageSlice(const XrSwapchainImageBaseHeader* colorSwapchainImage, uint32_t imageArrayIndex, XrColor4f color) override; MeshHandle MakeSimpleMesh(span idx, span vtx) override; @@ -937,7 +936,7 @@ namespace Conformance } void D3D12GraphicsPlugin::ClearImageSlice(const XrSwapchainImageBaseHeader* colorSwapchainImage, uint32_t imageArrayIndex, - XrColor4f bgColor) + XrColor4f color) { D3D12SwapchainImageData* swapchainData; @@ -957,7 +956,7 @@ namespace Conformance D3D12_CPU_DESCRIPTOR_HANDLE renderTargetView = CreateRenderTargetView(colorTexture, imageArrayIndex, swapchainData->GetCreateInfo().format); // TODO: Do not clear to a color when using a pass-through view configuration. - FLOAT bg[] = {bgColor.r, bgColor.g, bgColor.b, bgColor.a}; + FLOAT bg[] = {color.r, color.g, color.b, color.a}; cmdList->ClearRenderTargetView(renderTargetView, bg, 0, nullptr); // Clear depth buffer. diff --git a/src/conformance/framework/graphics_plugin_opengl.cpp b/src/conformance/framework/graphics_plugin_opengl.cpp index 3ce587ab..b2d06d96 100644 --- a/src/conformance/framework/graphics_plugin_opengl.cpp +++ b/src/conformance/framework/graphics_plugin_opengl.cpp @@ -443,8 +443,7 @@ namespace Conformance XrSwapchain depthSwapchain, const XrSwapchainCreateInfo& depthSwapchainCreateInfo) override; - void ClearImageSlice(const XrSwapchainImageBaseHeader* colorSwapchainImage, uint32_t imageArrayIndex, - XrColor4f bgColor = DarkSlateGrey) override; + void ClearImageSlice(const XrSwapchainImageBaseHeader* colorSwapchainImage, uint32_t imageArrayIndex, XrColor4f color) override; MeshHandle MakeSimpleMesh(span idx, span vtx) override; @@ -1024,7 +1023,7 @@ namespace Conformance } void OpenGLGraphicsPlugin::ClearImageSlice(const XrSwapchainImageBaseHeader* colorSwapchainImage, uint32_t imageArrayIndex, - XrColor4f bgColor) + XrColor4f color) { OpenGLSwapchainImageData* swapchainData; uint32_t imageIndex; @@ -1058,7 +1057,7 @@ namespace Conformance XRC_CHECK_THROW_GLCMD(glEnable(GL_SCISSOR_TEST)); // Clear swapchain and depth buffer. - XRC_CHECK_THROW_GLCMD(glClearColor(bgColor.r, bgColor.g, bgColor.b, bgColor.a)); + XRC_CHECK_THROW_GLCMD(glClearColor(color.r, color.g, color.b, color.a)); XRC_CHECK_THROW_GLCMD(glClearDepth(1.0f)); XRC_CHECK_THROW_GLCMD(glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT)); diff --git a/src/conformance/framework/graphics_plugin_opengles.cpp b/src/conformance/framework/graphics_plugin_opengles.cpp index 5632dd29..6930513e 100644 --- a/src/conformance/framework/graphics_plugin_opengles.cpp +++ b/src/conformance/framework/graphics_plugin_opengles.cpp @@ -291,8 +291,7 @@ namespace Conformance XrSwapchain depthSwapchain, const XrSwapchainCreateInfo& depthSwapchainCreateInfo) override; - void ClearImageSlice(const XrSwapchainImageBaseHeader* colorSwapchainImage, uint32_t imageArrayIndex, - XrColor4f bgColor = DarkSlateGrey) override; + void ClearImageSlice(const XrSwapchainImageBaseHeader* colorSwapchainImage, uint32_t imageArrayIndex, XrColor4f color) override; MeshHandle MakeSimpleMesh(span idx, span vtx) override; @@ -1088,7 +1087,7 @@ namespace Conformance } void OpenGLESGraphicsPlugin::ClearImageSlice(const XrSwapchainImageBaseHeader* colorSwapchainImage, uint32_t imageArrayIndex, - XrColor4f bgColor) + XrColor4f color) { OpenGLESSwapchainImageData* swapchainData; uint32_t imageIndex; @@ -1121,7 +1120,7 @@ namespace Conformance GL(glEnable(GL_SCISSOR_TEST)); // Clear swapchain and depth buffer. - GL(glClearColor(bgColor.r, bgColor.g, bgColor.b, bgColor.a)); + GL(glClearColor(color.r, color.g, color.b, color.a)); GL(glClearDepthf(1.0f)); GL(glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)); diff --git a/src/conformance/framework/graphics_plugin_vulkan.cpp b/src/conformance/framework/graphics_plugin_vulkan.cpp index 4db260a5..e8f869ef 100644 --- a/src/conformance/framework/graphics_plugin_vulkan.cpp +++ b/src/conformance/framework/graphics_plugin_vulkan.cpp @@ -629,8 +629,7 @@ namespace Conformance void SetViewportAndScissor(const VkRect2D& rect); - void ClearImageSlice(const XrSwapchainImageBaseHeader* colorSwapchainImage, uint32_t imageArrayIndex, - XrColor4f bgColor = DarkSlateGrey) override; + void ClearImageSlice(const XrSwapchainImageBaseHeader* colorSwapchainImage, uint32_t imageArrayIndex, XrColor4f color) override; MeshHandle MakeSimpleMesh(span idx, span vtx) override; @@ -1847,7 +1846,7 @@ namespace Conformance } void VulkanGraphicsPlugin::ClearImageSlice(const XrSwapchainImageBaseHeader* colorSwapchainImage, uint32_t imageArrayIndex, - XrColor4f bgColor) + XrColor4f color) { VulkanSwapchainImageData* swapchainData; uint32_t imageIndex; @@ -1882,10 +1881,10 @@ namespace Conformance // Clear the buffers static std::array clearValues; - clearValues[0].color.float32[0] = bgColor.r; - clearValues[0].color.float32[1] = bgColor.g; - clearValues[0].color.float32[2] = bgColor.b; - clearValues[0].color.float32[3] = bgColor.a; + clearValues[0].color.float32[0] = color.r; + clearValues[0].color.float32[1] = color.g; + clearValues[0].color.float32[2] = color.b; + clearValues[0].color.float32[3] = color.a; clearValues[1].depthStencil.depth = 1.0f; clearValues[1].depthStencil.stencil = 0; std::array clearAttachments{{ diff --git a/src/conformance/framework/mesh_projection_layer.cpp b/src/conformance/framework/mesh_projection_layer.cpp index af3990f6..5d572723 100644 --- a/src/conformance/framework/mesh_projection_layer.cpp +++ b/src/conformance/framework/mesh_projection_layer.cpp @@ -65,7 +65,7 @@ namespace Conformance MeshProjectionLayerHelper::MeshProjectionLayerHelper(CompositionHelper& compositionHelper) : m_baseHelper(compositionHelper, XR_REFERENCE_SPACE_TYPE_LOCAL) { - m_bgColors.resize(GetViewCount(), DarkSlateGrey); + m_bgColors.resize(GetViewCount(), Colors::Magenta); // should be overwritten before render } void MeshProjectionLayerHelper::SetMeshes(std::vector&& meshes) diff --git a/src/conformance/framework/xml_test_environment.cpp b/src/conformance/framework/xml_test_environment.cpp index 165f09a8..d7064d98 100644 --- a/src/conformance/framework/xml_test_environment.cpp +++ b/src/conformance/framework/xml_test_environment.cpp @@ -182,7 +182,6 @@ namespace Conformance WriteAvailableInstanceExtensions(xml, globalData.availableInstanceExtensions); if (globalData.IsGraphicsPluginRequired()) { - AutoBasicInstance instance(AutoBasicInstance::createSystemId); auto graphicsPlugin = globalData.GetGraphicsPlugin(); if (graphicsPlugin) { // DescribeGraphics may report only minimal info (name) due to not having a running instance, but this is OK for now. diff --git a/src/conformance/gradle.properties b/src/conformance/gradle.properties new file mode 100644 index 00000000..f4e35f07 --- /dev/null +++ b/src/conformance/gradle.properties @@ -0,0 +1,10 @@ +# Copyright (c) 2020-2023 The Khronos Group Inc. +# +# SPDX-License-Identifier: Apache-2.0 + +org.gradle.jvmargs=-Xmx2048m +android.useAndroidX=true +android.enableJetifier=false +android.defaults.buildfeatures.buildconfig=false +android.nonFinalResIds=true +android.nonTransitiveRClass=true diff --git a/src/conformance/gradle/wrapper/gradle-wrapper.jar b/src/conformance/gradle/wrapper/gradle-wrapper.jar index 249e5832f090a2944b7473328c07c9755baa3196..ccebba7710deaf9f98673a68957ea02138b60d0a 100644 GIT binary patch delta 39304 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 O;LQ#O9OtPYE&l^NyjRr# delta 38549 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 ltSba#rbH)&uY59oK&k!`i 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 diff --git a/src/conformance/gradle/wrapper/gradle-wrapper.properties b/src/conformance/gradle/wrapper/gradle-wrapper.properties index 8049c684..91dc3417 100644 --- a/src/conformance/gradle/wrapper/gradle-wrapper.properties +++ b/src/conformance/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists distributionUrl=https\://services.gradle.org/distributions/gradle-7.5-bin.zip +networkTimeout=10000 zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/src/conformance/gradlew b/src/conformance/gradlew index a69d9cb6..79a61d42 100755 --- a/src/conformance/gradlew +++ b/src/conformance/gradlew @@ -55,7 +55,7 @@ # Darwin, MinGW, and NonStop. # # (3) This script is generated from the Groovy template -# https://github.com/gradle/gradle/blob/master/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt # within the Gradle project. # # You can find Gradle at https://github.com/gradle/gradle/. @@ -80,10 +80,10 @@ do esac done -APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit - -APP_NAME="Gradle" +# This is normally unused +# shellcheck disable=SC2034 APP_BASE_NAME=${0##*/} +APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' @@ -143,12 +143,16 @@ fi if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then case $MAX_FD in #( max*) + # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC3045 MAX_FD=$( ulimit -H -n ) || warn "Could not query maximum file descriptor limit" esac case $MAX_FD in #( '' | soft) :;; #( *) + # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC3045 ulimit -n "$MAX_FD" || warn "Could not set maximum file descriptor limit to $MAX_FD" esac diff --git a/src/conformance/gradlew.bat b/src/conformance/gradlew.bat index f127cfd4..93e3f59f 100644 --- a/src/conformance/gradlew.bat +++ b/src/conformance/gradlew.bat @@ -26,6 +26,7 @@ if "%OS%"=="Windows_NT" setlocal set DIRNAME=%~dp0 if "%DIRNAME%"=="" set DIRNAME=. +@rem This is normally unused set APP_BASE_NAME=%~n0 set APP_HOME=%DIRNAME% diff --git a/src/conformance/platform_specific/AndroidManifest.xml b/src/conformance/platform_specific/AndroidManifest.xml index 66737109..94cb24b1 100644 --- a/src/conformance/platform_specific/AndroidManifest.xml +++ b/src/conformance/platform_specific/AndroidManifest.xml @@ -1,6 +1,6 @@ @@ -56,11 +56,14 @@ + android:theme="@android:style/Theme.Black.NoTitleBar.Fullscreen" + tools:ignore="NonResizeableActivity"> ()` consistently between Windows and WSL, instead of `__uuidof()`. -* `/src/dxguids.cpp`: This cpp file can be used as a replacement for linking against `dxguid.lib` on Windows, and as a convenient translation unit to define GUIDs without multiple definitions for WSL. -* `/test`: Simple CMake/Meson projects for validating the headers can be included in a given environment - -## Use on Windows - -Note that these headers may conflict with the headers from the Windows SDK, depending on include ordering. These headers should be added to the include directory list before the SDK, and should be included before other graphics headers (e.g. `d3d11.h`) from the Windows SDK. Otherwise, the corresponding header from the Windows SDK may be included first, and will define the include guards which prevents these headers from being used. - -## Use on WSL - -Note: WSL support is not intended for general purpose application development. At this time, the only recommended usage is for frameworks wishing to provide hardware acceleration for a Linux graphics/compute API in a WSL2 virtualization environment. - -Note: WSL support is only available for 64-bit binaries. - -The headers in the `/include/wsl` directory provide alternative definitions to macros and typedefs normally found in the Windows SDK. For the most part, they should be straightforward, but there are a couple to call attention to: - -|Type|Reason| -|---|---| -|`LONG`/`ULONG`|On 64-bit Windows, a `long` is 4 bytes, but on Linux it is typically 8 bytes. The D3D12 ABI for WSL uses `long` and therefore these should be 8 bytes.| -|`WCHAR`/`WCSTR`|On Windows, a `wchar_t` is 2 bytes, but on Linux it is typically 4 bytes. The D3D12 ABI for WSL uses the native 4-byte `wchar_t`, to enable applications and the runtime to use the system C library to perform string manipulation.| - -Additionally, APIs taking `HANDLE` (`void*`) for Win32 types should instead use `reinterpret_cast(fd)` for an appropriate type of file descriptor. For `ID3D12Fence::SetEventOnCompletion` this should be an `eventfd`, and for shared resources will be an opaque fd. - -## Ways to consume - -There are various ways to consume the headers in this project: - -* Manually: Just copy the headers somewhere and point your project at them. -* CMake subproject: Add this entire project as a subdirectory of your larger project, e.g. as a git submodule, and `add_subdirectory` into it. Use the resulting `DirectX-Headers` and/or `DirectX-Guids` targets as a link dependency -* Installed CMake: After building/installing this project, it can be found through CMake's `find_package` functionality and will expose the same `DirectX-Headers` and `DirectX-Guids` targets. -* FetchContent CMake (3.11+): Fetch this library using Git and easily add it to your project. -* Meson subproject/wrap: Add this entire project as a subproject of your larger project, and use `subproject` or `dependency` to consume it. -* Pkg-config: Use Meson to build this project, and the resulting installed package can be found via pkg-config. -* vcpkg: A vcpkg port has [been added](https://github.com/microsoft/vcpkg/pull/15222). - -Contributions for new mechanisms are welcome. - -## Contributing - -This project welcomes contributions and suggestions. Most contributions require you to agree to a Contributor License Agreement (CLA) declaring that you have the right to, and actually do, grant us the rights to use your contribution. For details, visit https://cla.opensource.microsoft.com. - -When you submit a pull request, a CLA bot will automatically determine whether you need to provide a CLA and decorate the PR appropriately (e.g., status check, comment). Simply follow the instructions provided by the bot. You will only need to do this once across all repos using our CLA. - -This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/). For more information see the [Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) or contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with any additional questions or comments. - -## Trademarks - -This project may contain trademarks or logos for projects, products, or services. Authorized use of Microsoft trademarks or logos is subject to and must follow [Microsoft's Trademark & Brand Guidelines](https://www.microsoft.com/en-us/legal/intellectualproperty/trademarks/usage/general). Use of Microsoft trademarks or logos in modified versions of this project must not cause confusion or imply Microsoft sponsorship. Any use of third-party trademarks or logos are subject to those third-party's policies. \ No newline at end of file diff --git a/src/external/d3dx12/d3dx12.h b/src/external/d3dx12/d3dx12.h index b1355989..562fb53a 100644 --- a/src/external/d3dx12/d3dx12.h +++ b/src/external/d3dx12/d3dx12.h @@ -217,6 +217,8 @@ struct CD3DX12_DEPTH_STENCIL_DESC : public D3D12_DEPTH_STENCIL_DESC }; //------------------------------------------------------------------------------------------------ +// Requires the Windows 10 Creators Update SDK (15063) +#if defined(NTDDI_WIN10_RS2) && (NTDDI_VERSION >= NTDDI_WIN10_RS2) struct CD3DX12_DEPTH_STENCIL_DESC1 : public D3D12_DEPTH_STENCIL_DESC1 { CD3DX12_DEPTH_STENCIL_DESC1() = default; @@ -308,6 +310,7 @@ struct CD3DX12_DEPTH_STENCIL_DESC1 : public D3D12_DEPTH_STENCIL_DESC1 return D; } }; +#endif // NTDDI_WIN10_RS2 //------------------------------------------------------------------------------------------------ struct CD3DX12_BLEND_DESC : public D3D12_BLEND_DESC @@ -572,6 +575,7 @@ struct CD3DX12_RANGE : public D3D12_RANGE }; //------------------------------------------------------------------------------------------------ +#if defined(NTDDI_WIN10_RS2) && (NTDDI_VERSION >= NTDDI_WIN10_RS2) struct CD3DX12_RANGE_UINT64 : public D3D12_RANGE_UINT64 { CD3DX12_RANGE_UINT64() = default; @@ -611,6 +615,7 @@ struct CD3DX12_SUBRESOURCE_RANGE_UINT64 : public D3D12_SUBRESOURCE_RANGE_UINT64 Range.End = end; } }; +#endif // NTDDI_WIN10_RS2 //------------------------------------------------------------------------------------------------ struct CD3DX12_SHADER_BYTECODE : public D3D12_SHADER_BYTECODE @@ -1823,6 +1828,8 @@ inline bool operator!=( const D3D12_RESOURCE_DESC& l, const D3D12_RESOURCE_DESC& { return !( l == r ); } //------------------------------------------------------------------------------------------------ +// Requires the Windows 10 SDK (19041) +#if defined(NTDDI_WIN10_VB) && (NTDDI_VERSION >= NTDDI_WIN10_VB) struct CD3DX12_RESOURCE_DESC1 : public D3D12_RESOURCE_DESC1 { CD3DX12_RESOURCE_DESC1() = default; @@ -1949,8 +1956,11 @@ inline bool operator==( const D3D12_RESOURCE_DESC1& l, const D3D12_RESOURCE_DESC } inline bool operator!=( const D3D12_RESOURCE_DESC1& l, const D3D12_RESOURCE_DESC1& r ) noexcept { return !( l == r ); } +#endif // NTDDI_WIN10_VB //------------------------------------------------------------------------------------------------ +// Requires the Windows 10 Fall Creators Update SDK (16299) +#if defined(NTDDI_WIN10_RS3) && (NTDDI_VERSION >= NTDDI_WIN10_RS3) struct CD3DX12_VIEW_INSTANCING_DESC : public D3D12_VIEW_INSTANCING_DESC { CD3DX12_VIEW_INSTANCING_DESC() = default; @@ -1973,6 +1983,7 @@ struct CD3DX12_VIEW_INSTANCING_DESC : public D3D12_VIEW_INSTANCING_DESC Flags = InFlags; } }; +#endif // NTDDI_WIN10_RS3 //------------------------------------------------------------------------------------------------ // Row-by-row memcpy @@ -2419,6 +2430,7 @@ inline HRESULT D3DX12SerializeVersionedRootSignature( } //------------------------------------------------------------------------------------------------ +#if defined(NTDDI_WIN10_RS2) && (NTDDI_VERSION >= NTDDI_WIN10_RS2) struct CD3DX12_RT_FORMAT_ARRAY : public D3D12_RT_FORMAT_ARRAY { CD3DX12_RT_FORMAT_ARRAY() = default; @@ -2473,8 +2485,10 @@ typedef CD3DX12_PIPELINE_STATE_STREAM_SUBOBJECT< D3D12_STREAM_OUTPUT_DESC, typedef CD3DX12_PIPELINE_STATE_STREAM_SUBOBJECT< D3D12_SHADER_BYTECODE, D3D12_PIPELINE_STATE_SUBOBJECT_TYPE_HS> CD3DX12_PIPELINE_STATE_STREAM_HS; typedef CD3DX12_PIPELINE_STATE_STREAM_SUBOBJECT< D3D12_SHADER_BYTECODE, D3D12_PIPELINE_STATE_SUBOBJECT_TYPE_DS> CD3DX12_PIPELINE_STATE_STREAM_DS; typedef CD3DX12_PIPELINE_STATE_STREAM_SUBOBJECT< D3D12_SHADER_BYTECODE, D3D12_PIPELINE_STATE_SUBOBJECT_TYPE_PS> CD3DX12_PIPELINE_STATE_STREAM_PS; +#if defined(NTDDI_WIN10_VB) && (NTDDI_VERSION >= NTDDI_WIN10_VB) typedef CD3DX12_PIPELINE_STATE_STREAM_SUBOBJECT< D3D12_SHADER_BYTECODE, D3D12_PIPELINE_STATE_SUBOBJECT_TYPE_AS> CD3DX12_PIPELINE_STATE_STREAM_AS; typedef CD3DX12_PIPELINE_STATE_STREAM_SUBOBJECT< D3D12_SHADER_BYTECODE, D3D12_PIPELINE_STATE_SUBOBJECT_TYPE_MS> CD3DX12_PIPELINE_STATE_STREAM_MS; +#endif typedef CD3DX12_PIPELINE_STATE_STREAM_SUBOBJECT< D3D12_SHADER_BYTECODE, D3D12_PIPELINE_STATE_SUBOBJECT_TYPE_CS> CD3DX12_PIPELINE_STATE_STREAM_CS; typedef CD3DX12_PIPELINE_STATE_STREAM_SUBOBJECT< CD3DX12_BLEND_DESC, D3D12_PIPELINE_STATE_SUBOBJECT_TYPE_BLEND, CD3DX12_DEFAULT> CD3DX12_PIPELINE_STATE_STREAM_BLEND_DESC; typedef CD3DX12_PIPELINE_STATE_STREAM_SUBOBJECT< CD3DX12_DEPTH_STENCIL_DESC, D3D12_PIPELINE_STATE_SUBOBJECT_TYPE_DEPTH_STENCIL, CD3DX12_DEFAULT> CD3DX12_PIPELINE_STATE_STREAM_DEPTH_STENCIL; @@ -2485,7 +2499,9 @@ typedef CD3DX12_PIPELINE_STATE_STREAM_SUBOBJECT< D3D12_RT_FORMAT_ARRAY, typedef CD3DX12_PIPELINE_STATE_STREAM_SUBOBJECT< DXGI_SAMPLE_DESC, D3D12_PIPELINE_STATE_SUBOBJECT_TYPE_SAMPLE_DESC, DefaultSampleDesc> CD3DX12_PIPELINE_STATE_STREAM_SAMPLE_DESC; typedef CD3DX12_PIPELINE_STATE_STREAM_SUBOBJECT< UINT, D3D12_PIPELINE_STATE_SUBOBJECT_TYPE_SAMPLE_MASK, DefaultSampleMask> CD3DX12_PIPELINE_STATE_STREAM_SAMPLE_MASK; typedef CD3DX12_PIPELINE_STATE_STREAM_SUBOBJECT< D3D12_CACHED_PIPELINE_STATE, D3D12_PIPELINE_STATE_SUBOBJECT_TYPE_CACHED_PSO> CD3DX12_PIPELINE_STATE_STREAM_CACHED_PSO; +#if defined(NTDDI_WIN10_RS3) && (NTDDI_VERSION >= NTDDI_WIN10_RS3) typedef CD3DX12_PIPELINE_STATE_STREAM_SUBOBJECT< CD3DX12_VIEW_INSTANCING_DESC, D3D12_PIPELINE_STATE_SUBOBJECT_TYPE_VIEW_INSTANCING, CD3DX12_DEFAULT> CD3DX12_PIPELINE_STATE_STREAM_VIEW_INSTANCING; +#endif //------------------------------------------------------------------------------------------------ // Stream Parser Helpers @@ -2506,8 +2522,10 @@ struct ID3DX12PipelineParserCallbacks virtual void DSCb(const D3D12_SHADER_BYTECODE&) {} virtual void PSCb(const D3D12_SHADER_BYTECODE&) {} virtual void CSCb(const D3D12_SHADER_BYTECODE&) {} +#if defined(NTDDI_WIN10_VB) && (NTDDI_VERSION >= NTDDI_WIN10_VB) virtual void ASCb(const D3D12_SHADER_BYTECODE&) {} virtual void MSCb(const D3D12_SHADER_BYTECODE&) {} +#endif virtual void BlendStateCb(const D3D12_BLEND_DESC&) {} virtual void DepthStencilStateCb(const D3D12_DEPTH_STENCIL_DESC&) {} virtual void DepthStencilState1Cb(const D3D12_DEPTH_STENCIL_DESC1&) {} @@ -2516,7 +2534,9 @@ struct ID3DX12PipelineParserCallbacks virtual void RTVFormatsCb(const D3D12_RT_FORMAT_ARRAY&) {} virtual void SampleDescCb(const DXGI_SAMPLE_DESC&) {} virtual void SampleMaskCb(UINT) {} +#if defined(NTDDI_WIN10_RS3) && (NTDDI_VERSION >= NTDDI_WIN10_RS3) virtual void ViewInstancingCb(const D3D12_VIEW_INSTANCING_DESC&) {} +#endif virtual void CachedPSOCb(const D3D12_CACHED_PIPELINE_STATE&) {} // Error Callbacks @@ -2527,6 +2547,7 @@ struct ID3DX12PipelineParserCallbacks virtual ~ID3DX12PipelineParserCallbacks() = default; }; +#if defined(NTDDI_WIN10_VB) && (NTDDI_VERSION >= NTDDI_WIN10_VB) struct D3DX12_MESH_SHADER_PIPELINE_STATE_DESC { ID3D12RootSignature* pRootSignature; @@ -2665,7 +2686,9 @@ struct CD3DX12_PIPELINE_STATE_STREAM2 return D; } }; +#endif // NTDDI_WIN10_VB +#if defined(NTDDI_WIN10_RS3) && (NTDDI_VERSION >= NTDDI_WIN10_RS3) // CD3DX12_PIPELINE_STATE_STREAM1 Works on OS Build 16299+ (where there is a new view instancing subobject). // Use CD3DX12_PIPELINE_STATE_STREAM for OS Build 15063+ support. struct CD3DX12_PIPELINE_STATE_STREAM1 @@ -2695,6 +2718,7 @@ struct CD3DX12_PIPELINE_STATE_STREAM1 , CachedPSO(Desc.CachedPSO) , ViewInstancingDesc(CD3DX12_VIEW_INSTANCING_DESC(CD3DX12_DEFAULT())) {} +#if defined(NTDDI_WIN10_VB) && (NTDDI_VERSION >= NTDDI_WIN10_VB) CD3DX12_PIPELINE_STATE_STREAM1(const D3DX12_MESH_SHADER_PIPELINE_STATE_DESC& Desc) noexcept : Flags(Desc.Flags) , NodeMask(Desc.NodeMask) @@ -2711,6 +2735,7 @@ struct CD3DX12_PIPELINE_STATE_STREAM1 , CachedPSO(Desc.CachedPSO) , ViewInstancingDesc(CD3DX12_VIEW_INSTANCING_DESC(CD3DX12_DEFAULT())) {} +#endif CD3DX12_PIPELINE_STATE_STREAM1(const D3D12_COMPUTE_PIPELINE_STATE_DESC& Desc) noexcept : Flags(Desc.Flags) , NodeMask(Desc.NodeMask) @@ -2779,8 +2804,9 @@ struct CD3DX12_PIPELINE_STATE_STREAM1 return D; } }; +#endif // NTDDI_WIN10_RS3 - +#if defined(NTDDI_WIN10_VB) && (NTDDI_VERSION >= NTDDI_WIN10_VB) struct CD3DX12_PIPELINE_MESH_STATE_STREAM { CD3DX12_PIPELINE_MESH_STATE_STREAM() = default; @@ -2837,6 +2863,7 @@ struct CD3DX12_PIPELINE_MESH_STATE_STREAM return D; } }; +#endif // NTDDI_WIN10_VB // CD3DX12_PIPELINE_STATE_STREAM works on OS Build 15063+ but does not support new subobject(s) added in OS Build 16299+. // See CD3DX12_PIPELINE_STATE_STREAM1 for instance. @@ -2931,6 +2958,7 @@ struct CD3DX12_PIPELINE_STATE_STREAM } }; +#if defined(NTDDI_WIN10_VB) && (NTDDI_VERSION >= NTDDI_WIN10_VB) struct CD3DX12_PIPELINE_STATE_STREAM2_PARSE_HELPER : public ID3DX12PipelineParserCallbacks { CD3DX12_PIPELINE_STATE_STREAM2 PipelineStream; @@ -2990,11 +3018,15 @@ struct CD3DX12_PIPELINE_STATE_STREAM2_PARSE_HELPER : public ID3DX12PipelineParse private: bool SeenDSS; }; - +#endif // NTDDI_WIN10_VB struct CD3DX12_PIPELINE_STATE_STREAM_PARSE_HELPER : public ID3DX12PipelineParserCallbacks { +#if defined(NTDDI_WIN10_RS3) && (NTDDI_VERSION >= NTDDI_WIN10_RS3) CD3DX12_PIPELINE_STATE_STREAM1 PipelineStream; +#else + CD3DX12_PIPELINE_STATE_STREAM PipelineStream; +#endif CD3DX12_PIPELINE_STATE_STREAM_PARSE_HELPER() noexcept : SeenDSS(false) { @@ -3043,7 +3075,9 @@ struct CD3DX12_PIPELINE_STATE_STREAM_PARSE_HELPER : public ID3DX12PipelineParser void RTVFormatsCb(const D3D12_RT_FORMAT_ARRAY& RTVFormats) override {PipelineStream.RTVFormats = RTVFormats;} void SampleDescCb(const DXGI_SAMPLE_DESC& SampleDesc) override {PipelineStream.SampleDesc = SampleDesc;} void SampleMaskCb(UINT SampleMask) override {PipelineStream.SampleMask = SampleMask;} +#if defined(NTDDI_WIN10_RS3) && (NTDDI_VERSION >= NTDDI_WIN10_RS3) void ViewInstancingCb(const D3D12_VIEW_INSTANCING_DESC& ViewInstancingDesc) override {PipelineStream.ViewInstancingDesc = CD3DX12_VIEW_INSTANCING_DESC(ViewInstancingDesc);} +#endif void CachedPSOCb(const D3D12_CACHED_PIPELINE_STATE& CachedPSO) override {PipelineStream.CachedPSO = CachedPSO;} private: @@ -3120,6 +3154,7 @@ inline HRESULT D3DX12ParsePipelineStream(const D3D12_PIPELINE_STATE_STREAM_DESC& pCallbacks->CSCb(*reinterpret_cast(pStream)); SizeOfSubobject = sizeof(CD3DX12_PIPELINE_STATE_STREAM::CS); break; +#if defined(NTDDI_WIN10_VB) && (NTDDI_VERSION >= NTDDI_WIN10_VB) case D3D12_PIPELINE_STATE_SUBOBJECT_TYPE_AS: pCallbacks->ASCb(*reinterpret_cast(pStream)); SizeOfSubobject = sizeof(CD3DX12_PIPELINE_STATE_STREAM2::AS); @@ -3128,6 +3163,7 @@ inline HRESULT D3DX12ParsePipelineStream(const D3D12_PIPELINE_STATE_STREAM_DESC& pCallbacks->MSCb(*reinterpret_cast(pStream)); SizeOfSubobject = sizeof(CD3DX12_PIPELINE_STATE_STREAM2::MS); break; +#endif case D3D12_PIPELINE_STATE_SUBOBJECT_TYPE_STREAM_OUTPUT: pCallbacks->StreamOutputCb(*reinterpret_cast(pStream)); SizeOfSubobject = sizeof(CD3DX12_PIPELINE_STATE_STREAM::StreamOutput); @@ -3188,10 +3224,12 @@ inline HRESULT D3DX12ParsePipelineStream(const D3D12_PIPELINE_STATE_STREAM_DESC& pCallbacks->FlagsCb(*reinterpret_cast(pStream)); SizeOfSubobject = sizeof(CD3DX12_PIPELINE_STATE_STREAM::Flags); break; +#if defined(NTDDI_WIN10_RS3) && (NTDDI_VERSION >= NTDDI_WIN10_RS3) case D3D12_PIPELINE_STATE_SUBOBJECT_TYPE_VIEW_INSTANCING: pCallbacks->ViewInstancingCb(*reinterpret_cast(pStream)); SizeOfSubobject = sizeof(CD3DX12_PIPELINE_STATE_STREAM1::ViewInstancingDesc); break; +#endif default: pCallbacks->ErrorUnknownSubobject(SubobjectType); return E_INVALIDARG; @@ -3200,7 +3238,10 @@ inline HRESULT D3DX12ParsePipelineStream(const D3D12_PIPELINE_STATE_STREAM_DESC& return S_OK; } +#endif // NTDDI_WIN10_RS2 +// Requires the Windows 10 October 2018 Update SDK (17763) +#if defined(NTDDI_WIN10_RS5) && (NTDDI_VERSION >= NTDDI_WIN10_RS5) //------------------------------------------------------------------------------------------------ inline bool operator==( const D3D12_CLEAR_VALUE &a, const D3D12_CLEAR_VALUE &b) noexcept { @@ -3282,17 +3323,7 @@ inline bool operator==( const D3D12_RENDER_PASS_DEPTH_STENCIL_DESC &a, const D3D #include #include #include -#ifndef D3DX12_USE_ATL #include -#define D3DX12_COM_PTR Microsoft::WRL::ComPtr -#define D3DX12_COM_PTR_GET(x) x.Get() -#define D3DX12_COM_PTR_ADDRESSOF(x) x.GetAddressOf() -#else -#include -#define D3DX12_COM_PTR ATL::CComPtr -#define D3DX12_COM_PTR_GET(x) x.p -#define D3DX12_COM_PTR_ADDRESSOF(x) &x.p -#endif //------------------------------------------------------------------------------------------------ class CD3DX12_STATE_OBJECT_DESC @@ -3480,7 +3511,9 @@ class CD3DX12_STATE_OBJECT_DESC friend class CD3DX12_HIT_GROUP_SUBOBJECT; friend class CD3DX12_RAYTRACING_SHADER_CONFIG_SUBOBJECT; friend class CD3DX12_RAYTRACING_PIPELINE_CONFIG_SUBOBJECT; +#if defined(NTDDI_WIN10_VB) && (NTDDI_VERSION >= NTDDI_WIN10_VB) friend class CD3DX12_RAYTRACING_PIPELINE_CONFIG1_SUBOBJECT; +#endif friend class CD3DX12_GLOBAL_ROOT_SIGNATURE_SUBOBJECT; friend class CD3DX12_LOCAL_ROOT_SIGNATURE_SUBOBJECT; friend class CD3DX12_STATE_OBJECT_CONFIG_SUBOBJECT; @@ -3618,7 +3651,7 @@ class CD3DX12_EXISTING_COLLECTION_SUBOBJECT } void* Data() noexcept override { return &m_Desc; } D3D12_EXISTING_COLLECTION_DESC m_Desc; - D3DX12_COM_PTR m_CollectionRef; + Microsoft::WRL::ComPtr m_CollectionRef; CD3DX12_STATE_OBJECT_DESC::StringContainer m_Strings; std::vector m_Exports; }; @@ -3867,6 +3900,7 @@ class CD3DX12_RAYTRACING_PIPELINE_CONFIG_SUBOBJECT }; //------------------------------------------------------------------------------------------------ +#if defined(NTDDI_WIN10_VB) && (NTDDI_VERSION >= NTDDI_WIN10_VB) class CD3DX12_RAYTRACING_PIPELINE_CONFIG1_SUBOBJECT : public CD3DX12_STATE_OBJECT_DESC::SUBOBJECT_HELPER_BASE { @@ -3900,6 +3934,7 @@ class CD3DX12_RAYTRACING_PIPELINE_CONFIG1_SUBOBJECT void* Data() noexcept override { return &m_Desc; } D3D12_RAYTRACING_PIPELINE_CONFIG1 m_Desc; }; +#endif // NTDDI_WIN10_VB //------------------------------------------------------------------------------------------------ class CD3DX12_GLOBAL_ROOT_SIGNATURE_SUBOBJECT @@ -3924,15 +3959,15 @@ class CD3DX12_GLOBAL_ROOT_SIGNATURE_SUBOBJECT return D3D12_STATE_SUBOBJECT_TYPE_GLOBAL_ROOT_SIGNATURE; } operator const D3D12_STATE_SUBOBJECT&() const noexcept { return *m_pSubobject; } - operator ID3D12RootSignature*() const noexcept { return D3DX12_COM_PTR_GET(m_pRootSig); } + operator ID3D12RootSignature*() const noexcept { return m_pRootSig.Get(); } private: void Init() noexcept { SUBOBJECT_HELPER_BASE::Init(); m_pRootSig = nullptr; } - void* Data() noexcept override { return D3DX12_COM_PTR_ADDRESSOF(m_pRootSig); } - D3DX12_COM_PTR m_pRootSig; + void* Data() noexcept override { return m_pRootSig.GetAddressOf(); } + Microsoft::WRL::ComPtr m_pRootSig; }; //------------------------------------------------------------------------------------------------ @@ -3958,15 +3993,15 @@ class CD3DX12_LOCAL_ROOT_SIGNATURE_SUBOBJECT return D3D12_STATE_SUBOBJECT_TYPE_LOCAL_ROOT_SIGNATURE; } operator const D3D12_STATE_SUBOBJECT&() const noexcept { return *m_pSubobject; } - operator ID3D12RootSignature*() const noexcept { return D3DX12_COM_PTR_GET(m_pRootSig); } + operator ID3D12RootSignature*() const noexcept { return m_pRootSig.Get(); } private: void Init() noexcept { SUBOBJECT_HELPER_BASE::Init(); m_pRootSig = nullptr; } - void* Data() noexcept override { return D3DX12_COM_PTR_ADDRESSOF(m_pRootSig); } - D3DX12_COM_PTR m_pRootSig; + void* Data() noexcept override { return m_pRootSig.GetAddressOf(); } + Microsoft::WRL::ComPtr m_pRootSig; }; //------------------------------------------------------------------------------------------------ @@ -4037,13 +4072,9 @@ class CD3DX12_NODE_MASK_SUBOBJECT D3D12_NODE_MASK m_Desc; }; -#undef D3DX12_COM_PTR -#undef D3DX12_COM_PTR_GET -#undef D3DX12_COM_PTR_ADDRESSOF #endif // #ifndef D3DX12_NO_STATE_OBJECT_HELPERS +#endif // NTDDI_WIN10_RS5 #endif // defined( __cplusplus ) #endif //__D3DX12_H__ - -