diff --git a/.github/workflows/android-cts-build.yml b/.github/workflows/android-cts-build.yml index 8d8c1796..a978e48d 100644 --- a/.github/workflows/android-cts-build.yml +++ b/.github/workflows/android-cts-build.yml @@ -19,10 +19,10 @@ jobs: build-android-cts: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Get modern CMake and Ninja - uses: lukka/get-cmake@v3.26.4 + uses: lukka/get-cmake@v3.27.7 - name: set up JDK 11 uses: actions/setup-java@v3 diff --git a/.github/workflows/android-cts-pr.yml b/.github/workflows/android-cts-pr.yml index 42f53207..34af35a4 100644 --- a/.github/workflows/android-cts-pr.yml +++ b/.github/workflows/android-cts-pr.yml @@ -14,10 +14,10 @@ jobs: build-loader: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Get modern CMake and Ninja - uses: lukka/get-cmake@v3.26.4 + uses: lukka/get-cmake@v3.27.7 - name: set up JDK 11 uses: actions/setup-java@v3 diff --git a/.github/workflows/check_clang_format_and_codespell.yml b/.github/workflows/check_clang_format_and_codespell.yml index b9f22d64..9eb30428 100644 --- a/.github/workflows/check_clang_format_and_codespell.yml +++ b/.github/workflows/check_clang_format_and_codespell.yml @@ -12,7 +12,7 @@ jobs: container: image: khronosgroup/docker-images:openxr-sdk.20230209 steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - run: ./runClangFormat.sh name: Run clang-format diff --git a/.github/workflows/gradle-wrapper-validation.yml b/.github/workflows/gradle-wrapper-validation.yml index db4f9aa0..a020debf 100644 --- a/.github/workflows/gradle-wrapper-validation.yml +++ b/.github/workflows/gradle-wrapper-validation.yml @@ -15,5 +15,5 @@ jobs: name: Validation runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - uses: gradle/wrapper-validation-action@v1 diff --git a/.github/workflows/msvc-build-preset.yml b/.github/workflows/msvc-build-preset.yml index c427dca1..56501d09 100644 --- a/.github/workflows/msvc-build-preset.yml +++ b/.github/workflows/msvc-build-preset.yml @@ -29,10 +29,10 @@ jobs: VULKAN_SDK_VERSION: "1.1.114.0" INSTALL_DIR: "${{ github.workspace }}/install" steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Get modern CMake and Ninja - uses: lukka/get-cmake@v3.26.4 + uses: lukka/get-cmake@v3.27.7 - name: Add msbuild to PATH uses: microsoft/setup-msbuild@v1.3 diff --git a/.gitignore b/.gitignore index fbafc27f..9af0dd0d 100644 --- a/.gitignore +++ b/.gitignore @@ -77,3 +77,7 @@ local.properties # Output artifact *.aar *.pom +clang-format-patches/ + +# Key stores +*.jks diff --git a/CMakeLists.txt b/CMakeLists.txt index 03df11be..f2d5180d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -21,7 +21,7 @@ # It should contain only definitions that are applicable to the # entire project and includes for the sub-directories. -cmake_minimum_required(VERSION 3.0) +cmake_minimum_required(VERSION 3.0...3.16) project(OPENXR) find_package(PythonInterp 3) diff --git a/README.md b/README.md index f31a052d..41497546 100644 --- a/README.md +++ b/README.md @@ -164,6 +164,26 @@ however, this does not allow you to specify arguments with spaces and does not allow you to set the output filename. A property set this way persists until the device restarts. +Interactive self-tests +---------------------- + +Some interactive tests are primarily a test of mechanisms within the CTS, rather +than runtime functionality. These are labeled with the tag `[self_test]` rather +than `[scenario]`, `[actions]`, or `[composition]`. While it is good to run +these, and doing so may help troubleshoot failures with tests that build on, +submission of a CTS results package. Currently, the only self-tests are for the +PBR/glTF rendering subsystem. They synchronously load very large, artificial +test assets, originally from the "glTF-Sample-Models" repository, to test +specific details of the renderer. + +To run the self-tests, commands similar to the following can be used: + + conformance_cli "[self_test][interactive]" -G d3d11 --reporter ctsxml::out=interactive_self_test_d3d11.xml + conformance_cli "[self_test][interactive]" -G d3d12 --reporter ctsxml::out=interactive_self_test_d3d12.xml + conformance_cli "[self_test][interactive]" -G vulkan --reporter ctsxml::out=interactive_self_test_vulkan.xml + conformance_cli "[self_test][interactive]" -G vulkan2 --reporter ctsxml::out=interactive_self_test_vulkan2.xml + conformance_cli "[self_test][interactive]" -G opengl --reporter ctsxml::out=interactive_self_test_opengl.xml + Conformance Submission Package Requirements ------------------------------------------- diff --git a/changes/conformance/mr.2501.gl.md b/changes/conformance/mr.2501.gl.md new file mode 100644 index 00000000..3434722b --- /dev/null +++ b/changes/conformance/mr.2501.gl.md @@ -0,0 +1,6 @@ +--- +- issue.1726.gl +- mr.2758.gl +--- +- Improvement: Add PBR rendering subsystem for loading and rendering of glTF assets. +- New test: Interactive (rendering) test of `XR_MSFT_controller_model` as an initial usage of the glTF/PBR rendering. diff --git a/changes/conformance/mr.2988.gl.md b/changes/conformance/mr.2988.gl.md new file mode 100644 index 00000000..bd61c8a0 --- /dev/null +++ b/changes/conformance/mr.2988.gl.md @@ -0,0 +1 @@ +Improvement: Update configuration for Doxygen source-code documentation generator/extractor. diff --git a/changes/conformance/mr.2991.gl.md b/changes/conformance/mr.2991.gl.md new file mode 100644 index 00000000..c6d5e973 --- /dev/null +++ b/changes/conformance/mr.2991.gl.md @@ -0,0 +1 @@ +Fix: comment typo in environment source. diff --git a/specification/Makefile b/specification/Makefile index c9f16147..8b33904f 100644 --- a/specification/Makefile +++ b/specification/Makefile @@ -32,7 +32,7 @@ ifneq (,$(strip $(VERY_STRICT))) ASCIIDOC := $(ASCIIDOC) --failure-level WARN endif -SPECREVISION = 1.0.30 +SPECREVISION = 1.0.31 REVISION_COMPONENTS = $(subst ., ,$(SPECREVISION)) MAJORMINORVER = $(word 1,$(REVISION_COMPONENTS)).$(word 2,$(REVISION_COMPONENTS)) @@ -344,7 +344,6 @@ GITREMARK ?= from git branch: $(GITBRANCH) ATTRIBOPTS = -a revnumber="$(SPECREVISION)" \ -a revremark="$(SPECREMARK)" \ -a apititle="$(APITITLE)" \ - -a stem=latexmath \ -a config=$(CURDIR)/config \ -a pdf-page-size=$(PAGESIZE) \ -a pdf-stylesdir=config \ diff --git a/specification/registry/xr.xml b/specification/registry/xr.xml index 7bbddee4..1a0cd301 100644 --- a/specification/registry/xr.xml +++ b/specification/registry/xr.xml @@ -131,7 +131,7 @@ maintained in the default branch of the Khronos OpenXR GitHub project. updates them automatically by processing a line at a time. --> // OpenXR current version number. -#define XR_CURRENT_API_VERSION XR_MAKE_VERSION(1, 0, 30) +#define XR_CURRENT_API_VERSION XR_MAKE_VERSION(1, 0, 31) + + + float x @@ -1707,16 +1711,16 @@ maintained in the default branch of the Khronos OpenXR GitHub project. XrHandMeshVertexBufferMSFT vertexBuffer - uint32_t indexBufferKey - uint32_t indexCapacityInput - uint32_t indexCountOutput - uint32_t* indices + uint32_t indexBufferKey + uint32_t indexCapacityInput + uint32_t indexCountOutput + uint32_t* indices - XrTime vertexUpdateTime - uint32_t vertexCapacityInput - uint32_t vertexCountOutput - XrHandMeshVertexMSFT* vertices + XrTime vertexUpdateTime + uint32_t vertexCapacityInput + uint32_t vertexCountOutput + XrHandMeshVertexMSFT* vertices XrVector3f position @@ -1978,10 +1982,10 @@ maintained in the default branch of the Khronos OpenXR GitHub project. XrTime updateTime - XrStructureType type - void* next - uint32_t componentCapacityInput - uint32_t componentCountOutput + XrStructureType type + void* next + uint32_t componentCapacityInput + uint32_t componentCountOutput XrSceneComponentMSFT* components @@ -2066,23 +2070,23 @@ maintained in the default branch of the Khronos OpenXR GitHub project. XrStructureType type - void* next - uint32_t vertexCapacityInput - uint32_t vertexCountOutput + void* next + uint32_t vertexCapacityInput + uint32_t vertexCountOutput XrVector3f* vertices XrStructureType type - void* next - uint32_t indexCapacityInput - uint32_t indexCountOutput + void* next + uint32_t indexCapacityInput + uint32_t indexCountOutput uint32_t* indices XrStructureType type - void* next - uint32_t indexCapacityInput - uint32_t indexCountOutput + void* next + uint32_t indexCapacityInput + uint32_t indexCountOutput uint16_t* indices @@ -2103,6 +2107,38 @@ maintained in the default branch of the Khronos OpenXR GitHub project. const XrDeserializeSceneFragmentMSFT* fragments + + + + + XrSceneMarkerTypeMSFT markerType + XrTime lastSeenTime + XrOffset2Df center + XrExtent2Df size + + + XrStructureType type + const void* next + uint32_t sceneMarkerCapacityInput + XrSceneMarkerMSFT* sceneMarkers + + + XrStructureType type + const void* next + uint32_t markerTypeCount + XrSceneMarkerTypeMSFT* markerTypes + + + XrSceneMarkerQRCodeSymbolTypeMSFT symbolType + uint8_t version + + + XrStructureType type + const void* next + uint32_t qrCodeCapacityInput + XrSceneMarkerQRCodeMSFT* qrCodes + + @@ -2285,6 +2321,11 @@ maintained in the default branch of the Khronos OpenXR GitHub project. uint32_t modelVersion XrRenderModelFlagsFB flags + + XrStructureType type + void* next + XrRenderModelFlagsFB flags + XrStructureType type void* next @@ -2302,11 +2343,6 @@ maintained in the default branch of the Khronos OpenXR GitHub project. void* next XrBool32 supportsRenderModelLoading - - XrStructureType type - void* next - XrRenderModelFlagsFB flags - @@ -3206,8 +3242,31 @@ maintained in the default branch of the Khronos OpenXR GitHub project. const void* next XrVirtualKeyboardMETA keyboard + + + + XrStructureType type + const void* next + XrBool32 enabled + + + + XrStructureType type + const void* next + XrHeadsetFitStatusML status + XrTime time + + + + XrStructureType type + const void* next + XrEyeCalibrationStatusML status + + + + @@ -3846,6 +3905,15 @@ maintained in the default branch of the Khronos OpenXR GitHub project. + + + + + + + + + @@ -4249,6 +4317,21 @@ maintained in the default branch of the Khronos OpenXR GitHub project. + + + + + + + + + + + + + + + @@ -5044,6 +5127,24 @@ maintained in the default branch of the Khronos OpenXR GitHub project. uint8_t* buffer + + + XrResult xrGetSceneMarkerRawDataMSFT + XrSceneMSFT scene + const XrUuidMSFT* markerId + uint32_t bufferCapacityInput + uint32_t* bufferCountOutput + uint8_t* buffer + + + XrResult xrGetSceneMarkerDecodedStringMSFT + XrSceneMSFT scene + const XrUuidMSFT* markerId + uint32_t bufferCapacityInput + uint32_t* bufferCountOutput + char* buffer + + XrResult xrEnumerateDisplayRefreshRatesFB @@ -5447,9 +5548,9 @@ maintained in the default branch of the Khronos OpenXR GitHub project. XrResult xrEnumeratePersistedSpatialAnchorNamesMSFT XrSpatialAnchorStoreConnectionMSFT spatialAnchorStore - uint32_t spatialAnchorNamesCapacityInput - uint32_t* spatialAnchorNamesCountOutput - XrSpatialAnchorPersistenceNameMSFT* persistedAnchorNames + uint32_t spatialAnchorNameCapacityInput + uint32_t* spatialAnchorNameCountOutput + XrSpatialAnchorPersistenceNameMSFT* spatialAnchorNames XrResult xrCreateSpatialAnchorFromPersistedNameMSFT @@ -5786,6 +5887,13 @@ maintained in the default branch of the Khronos OpenXR GitHub project. XrVirtualKeyboardMETA keyboard const XrVirtualKeyboardTextContextChangeInfoMETA* changeInfo + + + XrResult xrEnableUserCalibrationEventsML + XrInstance instance + const XrUserCalibrationEnableEventsInfoML* enableInfo + + @@ -8391,6 +8499,29 @@ maintained in the default branch of the Khronos OpenXR GitHub project. + + + + + + + + + + + + + + + + + + + + + + + @@ -8448,10 +8579,25 @@ maintained in the default branch of the Khronos OpenXR GitHub project. - + - - + + + + + + + + + + + + + + + + + @@ -11257,13 +11403,6 @@ maintained in the default branch of the Khronos OpenXR GitHub project. - - - - - - - @@ -11637,6 +11776,1314 @@ maintained in the default branch of the Khronos OpenXR GitHub project. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/specification/scripts/creflectiongenerator.py b/specification/scripts/creflectiongenerator.py index 748b56de..37087390 100644 --- a/specification/scripts/creflectiongenerator.py +++ b/specification/scripts/creflectiongenerator.py @@ -90,7 +90,8 @@ def _get_structs_for_protect(self, protect=None): return ret def endFile(self): - assert(self.template) + assert self.template + assert self.registry file_data = '' unprotected_structs = self._get_structs_for_protect() diff --git a/specification/scripts/generator.py b/specification/scripts/generator.py index 1ec7e08e..4f5019ed 100644 --- a/specification/scripts/generator.py +++ b/specification/scripts/generator.py @@ -14,6 +14,7 @@ import shutil import sys import tempfile +from typing import Optional try: from pathlib import Path except ImportError: @@ -1289,4 +1290,4 @@ def newline(self): write('', file=self.outFile) def setRegistry(self, registry): - self.registry = registry + self.registry: Optional["Registry"] = registry diff --git a/specification/scripts/jinja_helpers.py b/specification/scripts/jinja_helpers.py index bd01f96b..dcc954ae 100644 --- a/specification/scripts/jinja_helpers.py +++ b/specification/scripts/jinja_helpers.py @@ -31,7 +31,7 @@ def _add_to_path(): def _undecorate(name): """Undecorate a name by removing the leading Xr and making it lowercase.""" lower = name.lower() - assert(lower.startswith('xr')) + assert lower.startswith('xr') return lower[2:] diff --git a/specification/scripts/pdf_chapter_diff.py b/specification/scripts/pdf_chapter_diff.py index 657dae82..b9431355 100755 --- a/specification/scripts/pdf_chapter_diff.py +++ b/specification/scripts/pdf_chapter_diff.py @@ -382,8 +382,8 @@ def fill_pair_gaps(pairs): new_pages = [new_page for _, new_page in pairs if new_page is not None] - assert(orig_pages == sorted(orig_pages)) - assert(new_pages == sorted(new_pages)) + assert orig_pages == sorted(orig_pages) + assert new_pages == sorted(new_pages) fixed_pairs = [] @@ -452,7 +452,7 @@ def generate_diff_from_pairs(self, pairs): if __name__ == "__main__": SPECDIR = Path(__file__).resolve().parent.parent - assert(SPECDIR.name == "specification") + assert SPECDIR.name == "specification" ORIG = SPECDIR / 'compare-base' / 'openxr.pdf' NEW = SPECDIR / 'generated' / 'out' / '1.0' / 'openxr.pdf' DIFFDIR = SPECDIR / 'diffs' diff --git a/specification/scripts/reserve_extensions.py b/specification/scripts/reserve_extensions.py index cb3bf8da..abe3dde2 100755 --- a/specification/scripts/reserve_extensions.py +++ b/specification/scripts/reserve_extensions.py @@ -30,3 +30,6 @@ def generate_reserved_extensions(vendor: str, first_extension: int, extensions: """.strip()) print() + +if __name__ == "__main__": + generate_reserved_extensions() diff --git a/specification/scripts/spec_tools/algo.py b/specification/scripts/spec_tools/algo.py index e9dff4a1..cea9a98e 100644 --- a/specification/scripts/spec_tools/algo.py +++ b/specification/scripts/spec_tools/algo.py @@ -83,7 +83,7 @@ def longest_common_prefix(strings): 'abc' """ - assert(len(strings) > 1) + assert len(strings) > 1 a = min(strings) b = max(strings) prefix = [] @@ -127,7 +127,7 @@ def longest_common_token_prefix(strings, delimiter='_'): '' """ - assert(len(strings) > 1) + assert len(strings) > 1 a = min(strings).split(delimiter) b = max(strings).split(delimiter) prefix_tokens = [] diff --git a/specification/scripts/spec_tools/attributes.py b/specification/scripts/spec_tools/attributes.py index 9267247a..63b6cb1a 100644 --- a/specification/scripts/spec_tools/attributes.py +++ b/specification/scripts/spec_tools/attributes.py @@ -64,7 +64,7 @@ def __str__(self): return self.full_reference def get_human_readable(self, make_param_name=None): - assert(self.other_param_name) + assert self.other_param_name return _human_readable_deref(self.full_reference, make_param_name=make_param_name) def __repr__(self): @@ -97,7 +97,7 @@ def __init__(self, val): self.member = self.param_ref_parts[0] def get_human_readable(self, make_param_name=None): - assert(not self.entirely_extern_sync) + assert not self.entirely_extern_sync return _human_readable_deref(self.full_reference, make_param_name=make_param_name) @staticmethod diff --git a/specification/scripts/spec_tools/consistency_tools.py b/specification/scripts/spec_tools/consistency_tools.py index b15b06bd..cc123c4a 100644 --- a/specification/scripts/spec_tools/consistency_tools.py +++ b/specification/scripts/spec_tools/consistency_tools.py @@ -1,6 +1,7 @@ #!/usr/bin/python3 -i # # Copyright (c) 2019 Collabora, Ltd. +# Copyright (c) 2018-2023, The Khronos Group Inc. # # SPDX-License-Identifier: Apache-2.0 # diff --git a/specification/scripts/spec_tools/conventions.py b/specification/scripts/spec_tools/conventions.py index 8d0f0726..bb862c80 100644 --- a/specification/scripts/spec_tools/conventions.py +++ b/specification/scripts/spec_tools/conventions.py @@ -179,7 +179,7 @@ def _implMakeProseList(self, elements, fmt, with_verb, comma_for_two_elts=False, Do not edit these defaults, override self.makeProseList(). """ - assert(serial_comma) # did not implement what we did not need + assert serial_comma # did not implement what we did not need if isinstance(fmt, str): fmt = ProseListFormats.from_string(fmt) diff --git a/specification/scripts/spec_tools/entity_db.py b/specification/scripts/spec_tools/entity_db.py index e408c0a6..128f87a6 100644 --- a/specification/scripts/spec_tools/entity_db.py +++ b/specification/scripts/spec_tools/entity_db.py @@ -249,7 +249,7 @@ def findEntity(self, entity): if alias in self._byEntity: return self.findEntity(alias) - assert(not "Alias without main entry!") + assert not "Alias without main entry!" return None @@ -400,7 +400,7 @@ def areAliases(self, first_entity_name, second_entity_name): alias_set = self._aliasSetsByEntity.get(first_entity_name) if not alias_set: # If this assert fails, we have goofed in addAlias - assert(second_entity_name not in self._aliasSetsByEntity) + assert second_entity_name not in self._aliasSetsByEntity return False @@ -452,7 +452,7 @@ def addAlias(self, entityName, aliasName): other_alias_set = self._aliasSetsByEntity.get(entityName) if alias_set and other_alias_set: # If this fails, we need to merge sets and update. - assert(alias_set is other_alias_set) + assert alias_set is other_alias_set if not alias_set: # Try looking by the other name. diff --git a/specification/scripts/test_entity_db.py b/specification/scripts/test_entity_db.py index 76fb6ade..76ac5ae9 100644 --- a/specification/scripts/test_entity_db.py +++ b/specification/scripts/test_entity_db.py @@ -20,11 +20,11 @@ def db(): def test_likely_recognized(db): - assert(db.likelyRecognizedEntity('xrBla')) - assert(db.likelyRecognizedEntity('XrBla')) - assert(db.likelyRecognizedEntity('XR_BLA')) + assert db.likelyRecognizedEntity('xrBla') + assert db.likelyRecognizedEntity('XrBla') + assert db.likelyRecognizedEntity('XR_BLA') def test_db(db): - assert(db.findEntity('xrCreateInstance')) - assert(db.findEntity('XRAPI_CALL')) + assert db.findEntity('xrCreateInstance') + assert db.findEntity('XRAPI_CALL') diff --git a/specification/scripts/validitygenerator.py b/specification/scripts/validitygenerator.py index 5b18c112..fee73641 100644 --- a/specification/scripts/validitygenerator.py +++ b/specification/scripts/validitygenerator.py @@ -237,7 +237,7 @@ def generateStateValidity(self, validity, command_name): if begins_states: for state_name in sorted(begins_states): end_commands = self.states.get_commands(state_name, StateRelationship.END) - assert(end_commands) + assert end_commands entry = ValidityEntry(anchor=(state_name, 'beginstate')) entry += 'flink:{} must: not be called more than once without first successfully calling '.format( @@ -249,7 +249,7 @@ def generateStateValidity(self, validity, command_name): if ends_states: for state_name in sorted(ends_states): begin_commands = self.states.get_commands(state_name, StateRelationship.BEGIN) - assert(begin_commands) + assert begin_commands entry = ValidityEntry(anchor=(state_name, 'beginstate')) entry += 'flink:{} must: only be called after a successful call to '.format( @@ -262,8 +262,8 @@ def generateStateValidity(self, validity, command_name): for state_name in sorted(checks_states): begin_commands = self.states.get_commands(state_name, StateRelationship.BEGIN) end_commands = self.states.get_commands(state_name, StateRelationship.END) - assert(begin_commands) - assert(end_commands) + assert begin_commands + assert end_commands begin_command_list = '/'.join('flink:{}'.format(command) for command in begin_commands) @@ -746,6 +746,7 @@ def handleRequiredBitmask(self, blockname, param, paramtype, entry): def isBaseHeaderType(self, typename): """Returns true if the type is a struct that is a "base header" type.""" + assert self.registry info = self.registry.typedict.get(typename) if not info: return False @@ -759,6 +760,7 @@ def isBaseHeaderType(self, typename): def createValidationLineForParameter(self, blockname, param, params, typecategory): """Make an entire validation entry for a given parameter.""" + assert self.registry param_name = getElemName(param) paramtype = getElemType(param) see_also = None @@ -998,17 +1000,17 @@ def makeStructureTypeValidity(self, structname): Creates VUID named like the member name. """ info = self.registry.typedict.get(structname) - assert(info is not None) + assert info is not None # If this fails (meaning we have something other than a struct in here), # then the caller is wrong: # probably passing the wrong value for structname. members = info.getMembers() - assert(members) + assert members param = findNamedElem(members, self.structtype_member_name) # OpenXR gets some structs without a type field in here, so cannot assert - # assert(param is not None) + # assert param is not None if param is None: return None @@ -1026,7 +1028,7 @@ def makeStructureTypeValidity(self, structname): if child_structs: if values: print('The struct: {} has children, it may not have a "values" attribute itself.'.format(structname)) - assert(not values) + assert not values if len(child_structs) > 1: entry += 'one of the following XrStructureType values: ' entry += ', '.join(self.makeStructureTypeFromName(child) diff --git a/specification/scripts/xml_consistency.py b/specification/scripts/xml_consistency.py index dda22c74..60e8f660 100755 --- a/specification/scripts/xml_consistency.py +++ b/specification/scripts/xml_consistency.py @@ -1,6 +1,7 @@ #!/usr/bin/python3 # # Copyright (c) 2019 Collabora, Ltd. +# Copyright (c) 2018-2023, The Khronos Group Inc. # # SPDX-License-Identifier: Apache-2.0 # @@ -473,7 +474,7 @@ def check_command(self, name, info): Called from check.""" t = info.elem.find('proto/type') if t is None: - self.record_warning("Got a command without a return type?") + self.record_error("Got a command without a return type?") else: return_type = t.text if return_type != 'XrResult': @@ -566,7 +567,7 @@ def check_extension(self, name, info, supported): "but XML says", ver_from_xml) else: if ver_from_xml == '1': - self.record_warning( + self.record_error( "Cannot find version history in spec text - make sure it has lines starting exactly like '* Revision 1, ....'", filename=fn) else: @@ -601,8 +602,8 @@ def check_extension(self, name, info, supported): continue # print(name, item_name) if not item_name.endswith(vendor): - self.record_warning("Extension-defined name", item_name, - "has wrong or missing vendor tag: expected to end with", vendor) + self.record_error("Extension-defined name", item_name, + "has wrong or missing vendor tag: expected to end with", vendor) if __name__ == "__main__": diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 0891b294..f94dbf95 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -28,6 +28,12 @@ include(StdFilesystemFlags) ### Dependencies +set(XR_USE_GRAPHICS_API_OPENGL FALSE) +set(XR_USE_GRAPHICS_API_OPENGL_ES FALSE) +set(XR_USE_GRAPHICS_API_VULKAN FALSE) +set(XR_USE_GRAPHICS_API_D3D11 FALSE) +set(XR_USE_GRAPHICS_API_D3D12 FALSE) + set(OPENGLES_INCOMPATIBLE TRUE) set(OPENGL_INCOMPATIBLE FALSE) set(VULKAN_INCOMPATIBLE FALSE) @@ -58,6 +64,7 @@ if(NOT OPENGL_INCOMPATIBLE) find_package(OpenGL) if(OPENGL_FOUND) + set(XR_USE_GRAPHICS_API_OPENGL TRUE) add_definitions(-DXR_USE_GRAPHICS_API_OPENGL) message(STATUS "Enabling OpenGL support") elseif(BUILD_ALL_EXTENSIONS) @@ -69,6 +76,7 @@ if(NOT OPENGLES_INCOMPATIBLE) find_package(OpenGLES COMPONENTS V3 V2) find_package(EGL) if(OPENGLES_FOUND AND EGL_FOUND) + set(XR_USE_GRAPHICS_API_OPENGL_ES TRUE) add_definitions(-DXR_USE_GRAPHICS_API_OPENGL_ES) message(STATUS "Enabling OpenGL|ES support") elseif(BUILD_ALL_EXTENSIONS) @@ -82,6 +90,7 @@ if(NOT VULKAN_INCOMPATIBLE) find_package(Vulkan) endif() if(Vulkan_FOUND) + set(XR_USE_GRAPHICS_API_VULKAN TRUE) add_definitions(-DXR_USE_GRAPHICS_API_VULKAN) message(STATUS "Enabling Vulkan support") elseif(BUILD_ALL_EXTENSIONS) @@ -261,9 +270,9 @@ if(ANDROID) ) else() file(GLOB glslc_folders $ENV{VULKAN_SDK}/*) - find_program(GLSL_COMPILER glslc PATHS ${glslc_folders}) + find_program(GLSL_COMPILER glslc PATHS ${glslc_folders} HINTS ${Vulkan_GLSLC_EXECUTABLE}) endif() -find_program(GLSLANG_VALIDATOR glslangValidator) +find_program(GLSLANG_VALIDATOR glslangValidator HINTS ${Vulkan_GLSLANG_VALIDATOR_EXECUTABLE}) if(GLSL_COMPILER) message(STATUS "Found glslc: ${GLSL_COMPILER}") elseif(GLSLANG_VALIDATOR) @@ -307,7 +316,9 @@ endfunction() # Not available in MinGW if(MSVC) + set(XR_USE_GRAPHICS_API_D3D11 TRUE) add_definitions(-DXR_USE_GRAPHICS_API_D3D11) + set(XR_USE_GRAPHICS_API_D3D12 TRUE) add_definitions(-DXR_USE_GRAPHICS_API_D3D12) endif() diff --git a/src/cmake/fxc_shader.cmake b/src/cmake/fxc_shader.cmake new file mode 100644 index 00000000..5d4cfa66 --- /dev/null +++ b/src/cmake/fxc_shader.cmake @@ -0,0 +1,32 @@ +# Copyright (c) 2019-2023, The Khronos Group Inc. +# +# SPDX-License-Identifier: Apache-2.0 + +find_program(FXC_EXECUTABLE fxc) +function(fxc_shader) + set(options) + set(oneValueArgs INPUT OUTPUT TYPE VARIABLE PROFILE) + set(multiValueArgs EXTRA_DEPENDS) + cmake_parse_arguments(_fxc "${options}" "${oneValueArgs}" + "${multiValueArgs}" ${ARGN}) + if(FXC_EXECUTABLE) + set(_fxc "${FXC_EXECUTABLE}") + else() + # Hope/assume that it will be in the path at build time + set(_fxc "fxc.exe") + endif() + add_custom_command( + OUTPUT "${_fxc_OUTPUT}" + BYPRODUCTS "${_fxc_OUTPUT}.pdb" + COMMAND + "${_fxc}" /nologo "/Fh${_fxc_OUTPUT}" + "$<$,$>:/Fd${_fxc_OUTPUT}.pdb>" + "/T${_fxc_PROFILE}" + "/Vn" "${_fxc_VARIABLE}" + $<$:/Od> $<$:/Zss> + $<$,$>:/Zi> "${_fxc_INPUT}" + MAIN_DEPENDENCY "${_fxc_INPUT}" + DEPENDS "${_fxc_INPUT}" ${_fxc_EXTRA_DEPENDS} + USES_TERMINAL) + +endfunction() diff --git a/src/cmake/glsl_shader.cmake b/src/cmake/glsl_shader.cmake new file mode 100644 index 00000000..7fa56360 --- /dev/null +++ b/src/cmake/glsl_shader.cmake @@ -0,0 +1,55 @@ +# Copyright (c) 2019-2023, The Khronos Group Inc. +# +# SPDX-License-Identifier: Apache-2.0 + +function(glsl_spv_shader) + set(options) + set(oneValueArgs INPUT OUTPUT STAGE VARIABLE TARGET_ENV) + set(multiValueArgs EXTRA_DEPENDS) + cmake_parse_arguments(_glsl_spv "${options}" "${oneValueArgs}" + "${multiValueArgs}" ${ARGN}) + + if(GLSL_COMPILER) + add_custom_command( + OUTPUT "${_glsl_spv_OUTPUT}" + COMMAND + "${GLSL_COMPILER}" # + "-mfmt=c" # + "-fshader-stage=${_glsl_spv_STAGE}" # + "${_glsl_spv_INPUT}" # + -o "${_glsl_spv_OUTPUT}" # + "--target-env=${_glsl_spv_TARGET_ENV}" # + $,$>,-g,> # + $,-O0,-O> # + MAIN_DEPENDENCY "${_glsl_spv_INPUT}" + DEPENDS "${_glsl_spv_INPUT}" ${_glsl_spv_EXTRA_DEPENDS} + USES_TERMINAL) + + elseif(GLSLANG_VALIDATOR) + # Run glslangValidator if we can find it + add_custom_command( + OUTPUT "${_glsl_spv_OUTPUT}" + COMMAND + "${GLSLANG_VALIDATOR}" # + -S "${_glsl_spv_STAGE}" # + #--nan-clamp # + -x # output as hex + -o "${_glsl_spv_OUTPUT}" # + $<$:-gVS> # + $<$:-Od> # + $<$:-g0> # + "--target-env" "${_glsl_spv_TARGET_ENV}" # + "${_glsl_spv_INPUT}" # + MAIN_DEPENDENCY "${_glsl_spv_INPUT}" + DEPENDS "${_glsl_spv_INPUT}" ${_glsl_spv_EXTRA_DEPENDS} + USES_TERMINAL) + + else() + # Use the precompiled .spv files + get_filename_component(glsl_src_dir "${_glsl_spv_INPUT}" DIRECTORY) + + get_filename_component(glsl_name_we "${_glsl_spv_INPUT}" NAME_WE) + set(precompiled_file ${glsl_src_dir}/${glsl_name_we}.spv) + configure_file("${precompiled_file}" "${_glsl_spv_OUTPUT}" COPYONLY) + endif() +endfunction() diff --git a/src/cmake/make_includable.cmake b/src/cmake/make_includable.cmake new file mode 100644 index 00000000..4002c3a2 --- /dev/null +++ b/src/cmake/make_includable.cmake @@ -0,0 +1,26 @@ +# Copyright (c) 2019-2023, The Khronos Group Inc. +# +# SPDX-License-Identifier: Apache-2.0 + +# inspired by: https://stackoverflow.com/a/47801116 +function(make_includable input_file output_file) + cmake_parse_arguments( + PARSE_ARGV 2 MAKE_INCLUDABLE "" "" "REPLACE") + + file(READ "${input_file}" content) + + # regex replace + list(LENGTH MAKE_INCLUDABLE_REPLACE length) + while(length GREATER_EQUAL 2) + list(GET MAKE_INCLUDABLE_REPLACE 0 find) + list(GET MAKE_INCLUDABLE_REPLACE 1 replace) + list(REMOVE_AT MAKE_INCLUDABLE_REPLACE 0 1) + string(REGEX REPLACE "${find}" "${replace}" content "${content}") + list(LENGTH MAKE_INCLUDABLE_REPLACE length) + endwhile() + + file(WRITE "${output_file}" "R\"raw_text(\n${content})raw_text\"") + # using https://stackoverflow.com/a/65945763 + # see https://stackoverflow.com/a/56828572 for a different approach + set_property(DIRECTORY APPEND PROPERTY CMAKE_CONFIGURE_DEPENDS "${input_file}") +endfunction(make_includable) diff --git a/src/common/gfxwrapper_opengl.h b/src/common/gfxwrapper_opengl.h index 3a229833..a8aa7386 100644 --- a/src/common/gfxwrapper_opengl.h +++ b/src/common/gfxwrapper_opengl.h @@ -270,12 +270,6 @@ Platform headers / declarations #define GRAPHICS_API_OPENGL 1 #define OUTPUT_PATH "" -#if !defined(__USE_GNU) -// These prototypes are only included when __USE_GNU is defined but that causes other compile errors. -extern int pthread_setname_np(pthread_t __target_thread, __const char *__name); -extern int pthread_setaffinity_np(pthread_t thread, size_t cpusetsize, const cpu_set_t *cpuset); -#endif // !__USE_GNU - #elif defined(OS_APPLE_MACOS) // Apple is still at OpenGL 4.1 diff --git a/src/common/platform_utils.hpp b/src/common/platform_utils.hpp index 883baef8..0b295f5c 100644 --- a/src/common/platform_utils.hpp +++ b/src/common/platform_utils.hpp @@ -212,7 +212,7 @@ inline std::wstring utf8_to_wide(const std::string& utf8Text) { return {}; } - // MultiByteToWideChar returns number of chars of the input buffer, regardless of null terminitor + // MultiByteToWideChar returns number of chars of the input buffer, regardless of null terminator wideText.resize(wideLength, 0); wchar_t* wideString = const_cast(wideText.data()); // mutable data() only exists in c++17 const int length = ::MultiByteToWideChar(CP_UTF8, 0, utf8Text.data(), (int)utf8Text.size(), wideString, wideLength); @@ -236,7 +236,7 @@ inline std::string wide_to_utf8(const std::wstring& wideText) { return {}; } - // WideCharToMultiByte returns number of chars of the input buffer, regardless of null terminitor + // WideCharToMultiByte returns number of chars of the input buffer, regardless of null terminator narrowText.resize(narrowLength, 0); char* narrowString = const_cast(narrowText.data()); // mutable data() only exists in c++17 const int length = diff --git a/src/conformance/CMakeLists.txt b/src/conformance/CMakeLists.txt index 49d2c566..3a68b571 100644 --- a/src/conformance/CMakeLists.txt +++ b/src/conformance/CMakeLists.txt @@ -17,6 +17,14 @@ # Author: # +add_library(conformance_framework_mikktspace STATIC + ${PROJECT_SOURCE_DIR}/src/external/mikktspace/mikktspace.c) + +target_include_directories(conformance_framework_mikktspace + PUBLIC ${PROJECT_SOURCE_DIR}/src/external/mikktspace) +set_target_properties(conformance_framework_mikktspace + PROPERTIES FOLDER ${CONFORMANCE_TESTS_FOLDER}) + add_subdirectory(conformance_layer) add_subdirectory(utilities) add_subdirectory(framework) diff --git a/src/conformance/Doxyfile b/src/conformance/Doxyfile index 9461af39..ab92206e 100644 --- a/src/conformance/Doxyfile +++ b/src/conformance/Doxyfile @@ -1,40 +1,56 @@ # Copyright (c) 2019-2023, The Khronos Group Inc. # SPDX-License-Identifier: Apache-2.0 -QUIET = YES PROJECT_NAME = "OpenXR CTS Framework" OUTPUT_DIRECTORY = doc INPUT = \ framework \ + framework/pbr \ + framework/pbr/D3D11 \ + framework/pbr/D3D12 \ + framework/pbr/Vulkan \ + framework/pbr/OpenGL \ + framework/gltf \ conformance_cli +ALWAYS_DETAILED_SEC = YES + STRIP_FROM_INC_PATH = framework -PREDEFINED = XRC_DOXYGEN +JAVADOC_AUTOBRIEF = YES + +QT_AUTOBRIEF = YES + +BUILTIN_STL_SUPPORT = YES -SHOW_GROUPED_MEMB_INC = YES -STRIP_CODE_COMMENTS = NO -REFERENCED_BY_RELATION = YES -REFERENCES_RELATION = YES -ALWAYS_DETAILED_SEC = YES -WARN_IF_UNDOCUMENTED = NO EXTRACT_ALL = YES -HIDE_UNDOC_RELATIONS = NO + EXTRACT_STATIC = YES -INCLUDED_BY_GRAPH = YES -MACRO_EXPANSION = YES +CASE_SENSE_NAMES = NO +SHOW_GROUPED_MEMB_INC = YES -JAVADOC_AUTOBRIEF = YES +QUIET = YES -QT_AUTOBRIEF = YES +WARN_IF_UNDOCUMENTED = NO -TAB_SIZE = 4 +STRIP_CODE_COMMENTS = NO -BUILTIN_STL_SUPPORT = YES +REFERENCED_BY_RELATION = YES -CASE_SENSE_NAMES = NO +REFERENCES_RELATION = YES GENERATE_LATEX = NO + +MACRO_EXPANSION = YES + +PREDEFINED = XRC_DOXYGEN \ + XR_USE_GRAPHICS_API_VULKAN + +HIDE_UNDOC_RELATIONS = NO + +HAVE_DOT = YES + +CALLER_GRAPH = YES diff --git a/src/conformance/build.gradle b/src/conformance/build.gradle index 4a4c7060..1abfba0e 100644 --- a/src/conformance/build.gradle +++ b/src/conformance/build.gradle @@ -26,6 +26,13 @@ task copyAssets(type: Copy) { from('conformance_test/composition_examples') { include '*.png' } + from('conformance_test/gltf_examples') { + include '*.png' + include '*.glb' + } + from('conformance_test/pbr_assets') { + include '*.png' + } // layer manifests from('conformance_test/android_assets') { diff --git a/src/conformance/conformance_layer/Common.h b/src/conformance/conformance_layer/Common.h index b1b6cb03..21f7c490 100644 --- a/src/conformance/conformance_layer/Common.h +++ b/src/conformance/conformance_layer/Common.h @@ -30,7 +30,7 @@ #include #include -#include "xr_dependencies.h" +#include "common/xr_dependencies.h" #include #include #include diff --git a/src/conformance/conformance_test/CMakeLists.txt b/src/conformance/conformance_test/CMakeLists.txt index fab414c3..8dbfcef6 100644 --- a/src/conformance/conformance_test/CMakeLists.txt +++ b/src/conformance/conformance_test/CMakeLists.txt @@ -18,7 +18,18 @@ file(GLOB LOCAL_HEADERS "*.h") file(GLOB LOCAL_SOURCE "*.cpp") file(GLOB ASSETS "composition_examples/*.png" - "SourceCodePro-Regular.otf") + "SourceCodePro-Regular.otf" + "gltf_examples/*.glb" + "gltf_examples/*.png" + "pbr_assets/*.png" + "d3d_shaders/*.hlsl") +file(GLOB VULKAN_SHADERS "vulkan_shaders/*.glsl") + +# Check to see if git-lfs is working right +file(STRINGS gltf_examples/VertexColorTest.glb LFS_CHECK_STRINGS) +if(LFS_CHECK_STRINGS MATCHES "https://git-lfs[.]github[.]com/spec/v1") + message(FATAL_ERROR "Found a git-lfs pointer file instead of the binary file that should replace it. Please install git-lfs, run 'git lfs install', and 'git lfs checkout'") +endif() # For including compiled shaders include_directories(${CMAKE_CURRENT_BINARY_DIR}) diff --git a/src/conformance/conformance_test/conformance_test.cpp b/src/conformance/conformance_test/conformance_test.cpp index f04ea6f2..eb826d55 100644 --- a/src/conformance/conformance_test/conformance_test.cpp +++ b/src/conformance/conformance_test/conformance_test.cpp @@ -14,7 +14,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -#include "openxr/openxr_platform_defines.h" +#include #define CATCH_CONFIG_NOSTDOUT #include "conformance_framework.h" @@ -37,7 +37,7 @@ #include #include -#include "xr_dependencies.h" +#include "common/xr_dependencies.h" #include #include @@ -194,12 +194,14 @@ namespace INFO(testCase.testName); INFO(testTags); - // readme.md instructions use [interactive] with [actions], [composition], and [scenario] + // readme.md instructions use [interactive] with [actions], [composition], and [scenario]. + // (self_test are not required for submission.) // Let's ensure that these cover all of the possible test cases. - const std::array interactiveTestTypes = { + const std::array interactiveTestTypes = { "[actions]", "[composition]", "[scenario]", + "[self_test]", }; if (testTags.find("[interactive]") != std::string::npos) { { diff --git a/src/conformance/conformance_test/gltf_examples/AlphaBlendModeTest.glb b/src/conformance/conformance_test/gltf_examples/AlphaBlendModeTest.glb new file mode 100644 index 00000000..3c7e82a2 --- /dev/null +++ b/src/conformance/conformance_test/gltf_examples/AlphaBlendModeTest.glb @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:688786ffa64990071cfd93c96f29917fddd3d254d3f0e48039d80d3b5ac0d8c7 +size 3017136 diff --git a/src/conformance/conformance_test/gltf_examples/AlphaBlendModeTest.glb.license b/src/conformance/conformance_test/gltf_examples/AlphaBlendModeTest.glb.license new file mode 100644 index 00000000..18a304e2 --- /dev/null +++ b/src/conformance/conformance_test/gltf_examples/AlphaBlendModeTest.glb.license @@ -0,0 +1,3 @@ +SPDX-FileCopyrightText: Copyright 2018 Analytical Graphics, Inc. Model and textures by Ed Mackey. + +SPDX-License-Identifier: CC-BY-4.0 diff --git a/src/conformance/conformance_test/gltf_examples/AlphaBlendModeTest.png b/src/conformance/conformance_test/gltf_examples/AlphaBlendModeTest.png new file mode 100644 index 00000000..ae72e16c Binary files /dev/null and b/src/conformance/conformance_test/gltf_examples/AlphaBlendModeTest.png differ diff --git a/src/conformance/conformance_test/gltf_examples/AlphaBlendModeTest.png.license b/src/conformance/conformance_test/gltf_examples/AlphaBlendModeTest.png.license new file mode 100644 index 00000000..f479a621 --- /dev/null +++ b/src/conformance/conformance_test/gltf_examples/AlphaBlendModeTest.png.license @@ -0,0 +1,3 @@ +SPDX-FileCopyrightText: 2023, The Khronos Group Inc. + +SPDX-License-Identifier: Apache-2.0 diff --git a/src/conformance/conformance_test/gltf_examples/MetalRoughSpheres.glb b/src/conformance/conformance_test/gltf_examples/MetalRoughSpheres.glb new file mode 100644 index 00000000..3437cc6b --- /dev/null +++ b/src/conformance/conformance_test/gltf_examples/MetalRoughSpheres.glb @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:450c05557b0823a10835e32c05caf77a7b175eba94516555c1c9bb084f563f01 +size 11221356 diff --git a/src/conformance/conformance_test/gltf_examples/MetalRoughSpheres.glb.license b/src/conformance/conformance_test/gltf_examples/MetalRoughSpheres.glb.license new file mode 100644 index 00000000..e18bff8b --- /dev/null +++ b/src/conformance/conformance_test/gltf_examples/MetalRoughSpheres.glb.license @@ -0,0 +1,3 @@ +SPDX-FileCopyrightText: Copyright 2017 Analytical Graphics, Inc. Model and textures by Ed Mackey. + +SPDX-License-Identifier: CC-BY-4.0 diff --git a/src/conformance/conformance_test/gltf_examples/MetalRoughSpheres.png b/src/conformance/conformance_test/gltf_examples/MetalRoughSpheres.png new file mode 100644 index 00000000..f48b789b Binary files /dev/null and b/src/conformance/conformance_test/gltf_examples/MetalRoughSpheres.png differ diff --git a/src/conformance/conformance_test/gltf_examples/MetalRoughSpheres.png.license b/src/conformance/conformance_test/gltf_examples/MetalRoughSpheres.png.license new file mode 100644 index 00000000..f479a621 --- /dev/null +++ b/src/conformance/conformance_test/gltf_examples/MetalRoughSpheres.png.license @@ -0,0 +1,3 @@ +SPDX-FileCopyrightText: 2023, The Khronos Group Inc. + +SPDX-License-Identifier: Apache-2.0 diff --git a/src/conformance/conformance_test/gltf_examples/MetalRoughSpheresNoTextures.glb b/src/conformance/conformance_test/gltf_examples/MetalRoughSpheresNoTextures.glb new file mode 100644 index 00000000..fd3305d5 --- /dev/null +++ b/src/conformance/conformance_test/gltf_examples/MetalRoughSpheresNoTextures.glb @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:5e677f260ec0f366967a6e9545de2f3dab2a160e307b9bcb9d050cd2028f63f8 +size 291316 diff --git a/src/conformance/conformance_test/gltf_examples/MetalRoughSpheresNoTextures.glb.license b/src/conformance/conformance_test/gltf_examples/MetalRoughSpheresNoTextures.glb.license new file mode 100644 index 00000000..e89b8399 --- /dev/null +++ b/src/conformance/conformance_test/gltf_examples/MetalRoughSpheresNoTextures.glb.license @@ -0,0 +1,7 @@ +SPDX-FileCopyrightText: Donated by Kirill Gavrilov for glTF testing. CAD Model generated by Draw Harness script using Open CASCADE Technology. + +SPDX-License-Identifier: CC0-1.0 + + + + diff --git a/src/conformance/conformance_test/gltf_examples/MetalRoughSpheresNoTextures.png b/src/conformance/conformance_test/gltf_examples/MetalRoughSpheresNoTextures.png new file mode 100644 index 00000000..99b214ca Binary files /dev/null and b/src/conformance/conformance_test/gltf_examples/MetalRoughSpheresNoTextures.png differ diff --git a/src/conformance/conformance_test/gltf_examples/MetalRoughSpheresNoTextures.png.license b/src/conformance/conformance_test/gltf_examples/MetalRoughSpheresNoTextures.png.license new file mode 100644 index 00000000..f479a621 --- /dev/null +++ b/src/conformance/conformance_test/gltf_examples/MetalRoughSpheresNoTextures.png.license @@ -0,0 +1,3 @@ +SPDX-FileCopyrightText: 2023, The Khronos Group Inc. + +SPDX-License-Identifier: Apache-2.0 diff --git a/src/conformance/conformance_test/gltf_examples/NormalTangentMirrorTest.glb b/src/conformance/conformance_test/gltf_examples/NormalTangentMirrorTest.glb new file mode 100644 index 00000000..124d30c7 --- /dev/null +++ b/src/conformance/conformance_test/gltf_examples/NormalTangentMirrorTest.glb @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:705465c5045c113c680be8631e9295db8fe8e6342b0e5f9b094b89d33d9bb12a +size 2019856 diff --git a/src/conformance/conformance_test/gltf_examples/NormalTangentMirrorTest.glb.license b/src/conformance/conformance_test/gltf_examples/NormalTangentMirrorTest.glb.license new file mode 100644 index 00000000..e8c85e81 --- /dev/null +++ b/src/conformance/conformance_test/gltf_examples/NormalTangentMirrorTest.glb.license @@ -0,0 +1,3 @@ +SPDX-FileCopyrightText: Copyright 2017-2018 Analytical Graphics, Inc. Model and textures by Ed Mackey. + +SPDX-License-Identifier: CC-BY-4.0 diff --git a/src/conformance/conformance_test/gltf_examples/NormalTangentMirrorTest.png b/src/conformance/conformance_test/gltf_examples/NormalTangentMirrorTest.png new file mode 100644 index 00000000..0dd664b1 Binary files /dev/null and b/src/conformance/conformance_test/gltf_examples/NormalTangentMirrorTest.png differ diff --git a/src/conformance/conformance_test/gltf_examples/NormalTangentMirrorTest.png.license b/src/conformance/conformance_test/gltf_examples/NormalTangentMirrorTest.png.license new file mode 100644 index 00000000..f479a621 --- /dev/null +++ b/src/conformance/conformance_test/gltf_examples/NormalTangentMirrorTest.png.license @@ -0,0 +1,3 @@ +SPDX-FileCopyrightText: 2023, The Khronos Group Inc. + +SPDX-License-Identifier: Apache-2.0 diff --git a/src/conformance/conformance_test/gltf_examples/NormalTangentTest.glb b/src/conformance/conformance_test/gltf_examples/NormalTangentTest.glb new file mode 100644 index 00000000..bc5c33ef --- /dev/null +++ b/src/conformance/conformance_test/gltf_examples/NormalTangentTest.glb @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:4476f994b8789e0130313f8b24908f50b492bd3df9bfca258bad59c142955281 +size 1921176 diff --git a/src/conformance/conformance_test/gltf_examples/NormalTangentTest.glb.license b/src/conformance/conformance_test/gltf_examples/NormalTangentTest.glb.license new file mode 100644 index 00000000..e8c85e81 --- /dev/null +++ b/src/conformance/conformance_test/gltf_examples/NormalTangentTest.glb.license @@ -0,0 +1,3 @@ +SPDX-FileCopyrightText: Copyright 2017-2018 Analytical Graphics, Inc. Model and textures by Ed Mackey. + +SPDX-License-Identifier: CC-BY-4.0 diff --git a/src/conformance/conformance_test/gltf_examples/NormalTangentTest.png b/src/conformance/conformance_test/gltf_examples/NormalTangentTest.png new file mode 100644 index 00000000..3c063ce5 Binary files /dev/null and b/src/conformance/conformance_test/gltf_examples/NormalTangentTest.png differ diff --git a/src/conformance/conformance_test/gltf_examples/NormalTangentTest.png.license b/src/conformance/conformance_test/gltf_examples/NormalTangentTest.png.license new file mode 100644 index 00000000..f479a621 --- /dev/null +++ b/src/conformance/conformance_test/gltf_examples/NormalTangentTest.png.license @@ -0,0 +1,3 @@ +SPDX-FileCopyrightText: 2023, The Khronos Group Inc. + +SPDX-License-Identifier: Apache-2.0 diff --git a/src/conformance/conformance_test/gltf_examples/TextureSettingsTest.glb b/src/conformance/conformance_test/gltf_examples/TextureSettingsTest.glb new file mode 100644 index 00000000..72a957e8 --- /dev/null +++ b/src/conformance/conformance_test/gltf_examples/TextureSettingsTest.glb @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:44013a0602cbbdcaf703905ed47cc6b6fb49f067d7de0e1a0a568a5bbfa9d769 +size 42840 diff --git a/src/conformance/conformance_test/gltf_examples/TextureSettingsTest.glb.license b/src/conformance/conformance_test/gltf_examples/TextureSettingsTest.glb.license new file mode 100644 index 00000000..e18bff8b --- /dev/null +++ b/src/conformance/conformance_test/gltf_examples/TextureSettingsTest.glb.license @@ -0,0 +1,3 @@ +SPDX-FileCopyrightText: Copyright 2017 Analytical Graphics, Inc. Model and textures by Ed Mackey. + +SPDX-License-Identifier: CC-BY-4.0 diff --git a/src/conformance/conformance_test/gltf_examples/TextureSettingsTest.png b/src/conformance/conformance_test/gltf_examples/TextureSettingsTest.png new file mode 100644 index 00000000..b9f56fe6 Binary files /dev/null and b/src/conformance/conformance_test/gltf_examples/TextureSettingsTest.png differ diff --git a/src/conformance/conformance_test/gltf_examples/TextureSettingsTest.png.license b/src/conformance/conformance_test/gltf_examples/TextureSettingsTest.png.license new file mode 100644 index 00000000..f479a621 --- /dev/null +++ b/src/conformance/conformance_test/gltf_examples/TextureSettingsTest.png.license @@ -0,0 +1,3 @@ +SPDX-FileCopyrightText: 2023, The Khronos Group Inc. + +SPDX-License-Identifier: Apache-2.0 diff --git a/src/conformance/conformance_test/gltf_examples/VertexColorTest.glb b/src/conformance/conformance_test/gltf_examples/VertexColorTest.glb new file mode 100644 index 00000000..ae9ef79a --- /dev/null +++ b/src/conformance/conformance_test/gltf_examples/VertexColorTest.glb @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:58006bdcff8084339da0f6e24400160890638c16dbcb83c362ccaf150e8c6e10 +size 26220 diff --git a/src/conformance/conformance_test/gltf_examples/VertexColorTest.glb.license b/src/conformance/conformance_test/gltf_examples/VertexColorTest.glb.license new file mode 100644 index 00000000..18a304e2 --- /dev/null +++ b/src/conformance/conformance_test/gltf_examples/VertexColorTest.glb.license @@ -0,0 +1,3 @@ +SPDX-FileCopyrightText: Copyright 2018 Analytical Graphics, Inc. Model and textures by Ed Mackey. + +SPDX-License-Identifier: CC-BY-4.0 diff --git a/src/conformance/conformance_test/gltf_examples/VertexColorTest.png b/src/conformance/conformance_test/gltf_examples/VertexColorTest.png new file mode 100644 index 00000000..4f982406 Binary files /dev/null and b/src/conformance/conformance_test/gltf_examples/VertexColorTest.png differ diff --git a/src/conformance/conformance_test/gltf_examples/VertexColorTest.png.license b/src/conformance/conformance_test/gltf_examples/VertexColorTest.png.license new file mode 100644 index 00000000..f479a621 --- /dev/null +++ b/src/conformance/conformance_test/gltf_examples/VertexColorTest.png.license @@ -0,0 +1,3 @@ +SPDX-FileCopyrightText: 2023, The Khronos Group Inc. + +SPDX-License-Identifier: Apache-2.0 diff --git a/src/conformance/conformance_test/pbr_assets/brdf_lut.png b/src/conformance/conformance_test/pbr_assets/brdf_lut.png new file mode 100644 index 00000000..bb5d52f0 Binary files /dev/null and b/src/conformance/conformance_test/pbr_assets/brdf_lut.png differ diff --git a/src/conformance/conformance_test/pbr_assets/brdf_lut.png.license b/src/conformance/conformance_test/pbr_assets/brdf_lut.png.license new file mode 100644 index 00000000..bc07946a --- /dev/null +++ b/src/conformance/conformance_test/pbr_assets/brdf_lut.png.license @@ -0,0 +1,3 @@ +SPDX-FileCopyrightText: 2019-2023, The Khronos Group Inc. + +SPDX-License-Identifier: Apache-2.0 diff --git a/src/conformance/conformance_test/readme.md b/src/conformance/conformance_test/readme.md index f31a052d..41497546 100644 --- a/src/conformance/conformance_test/readme.md +++ b/src/conformance/conformance_test/readme.md @@ -164,6 +164,26 @@ however, this does not allow you to specify arguments with spaces and does not allow you to set the output filename. A property set this way persists until the device restarts. +Interactive self-tests +---------------------- + +Some interactive tests are primarily a test of mechanisms within the CTS, rather +than runtime functionality. These are labeled with the tag `[self_test]` rather +than `[scenario]`, `[actions]`, or `[composition]`. While it is good to run +these, and doing so may help troubleshoot failures with tests that build on, +submission of a CTS results package. Currently, the only self-tests are for the +PBR/glTF rendering subsystem. They synchronously load very large, artificial +test assets, originally from the "glTF-Sample-Models" repository, to test +specific details of the renderer. + +To run the self-tests, commands similar to the following can be used: + + conformance_cli "[self_test][interactive]" -G d3d11 --reporter ctsxml::out=interactive_self_test_d3d11.xml + conformance_cli "[self_test][interactive]" -G d3d12 --reporter ctsxml::out=interactive_self_test_d3d12.xml + conformance_cli "[self_test][interactive]" -G vulkan --reporter ctsxml::out=interactive_self_test_vulkan.xml + conformance_cli "[self_test][interactive]" -G vulkan2 --reporter ctsxml::out=interactive_self_test_vulkan2.xml + conformance_cli "[self_test][interactive]" -G opengl --reporter ctsxml::out=interactive_self_test_opengl.xml + Conformance Submission Package Requirements ------------------------------------------- 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 63947d5b..828824b3 100644 --- a/src/conformance/conformance_test/test_XR_EXT_debug_utils.cpp +++ b/src/conformance/conformance_test/test_XR_EXT_debug_utils.cpp @@ -20,7 +20,7 @@ #include "conformance_utils.h" #include "conformance_framework.h" #include "utilities/throw_helpers.h" -#include "hex_and_handles.h" +#include "common/hex_and_handles.h" #include #include diff --git a/src/conformance/conformance_test/test_XR_EXT_plane_detection.cpp b/src/conformance/conformance_test/test_XR_EXT_plane_detection.cpp index 45119382..34c0448a 100644 --- a/src/conformance/conformance_test/test_XR_EXT_plane_detection.cpp +++ b/src/conformance/conformance_test/test_XR_EXT_plane_detection.cpp @@ -22,7 +22,7 @@ #include #include #include -#include +#include "common/xr_linear.h" #include 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 2d31abe0..01624d96 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 @@ -19,7 +19,7 @@ #include "conformance_utils.h" #include "graphics_plugin.h" #include "matchers.h" -#include "xr_dependencies.h" +#include "common/xr_dependencies.h" #include 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 bad989d0..7be84beb 100644 --- a/src/conformance/conformance_test/test_XR_KHR_OpenGL_enable.cpp +++ b/src/conformance/conformance_test/test_XR_KHR_OpenGL_enable.cpp @@ -21,10 +21,10 @@ #include "graphics_plugin.h" #include "matchers.h" #include "utilities/utils.h" +#include "common/xr_dependencies.h" #include #include -#include "xr_dependencies.h" #include #include 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 9af5aba1..b68c0c66 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 @@ -21,7 +21,7 @@ #include // Include all dependencies of openxr_platform as configured -#include "xr_dependencies.h" +#include "common/xr_dependencies.h" #include #ifdef XR_USE_PLATFORM_WIN32 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 2fd3a31e..204a0339 100644 --- a/src/conformance/conformance_test/test_XR_KHR_visibility_mask.cpp +++ b/src/conformance/conformance_test/test_XR_KHR_visibility_mask.cpp @@ -26,7 +26,7 @@ #include "utilities/Geometry.h" #include "utilities/types_and_constants.h" -#include "nonstd/type.hpp" +#include #include #include 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 3a6cc0f8..2a8622e7 100644 --- a/src/conformance/conformance_test/test_XR_KHR_vulkan_enable.cpp +++ b/src/conformance/conformance_test/test_XR_KHR_vulkan_enable.cpp @@ -20,8 +20,8 @@ #include "conformance_utils.h" #include "graphics_plugin.h" #include "matchers.h" +#include "common/xr_dependencies.h" #include "utilities/types_and_constants.h" -#include "xr_dependencies.h" #include #include 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 6f2b1f62..dfa1cbb8 100644 --- a/src/conformance/conformance_test/test_XR_KHR_vulkan_enable2.cpp +++ b/src/conformance/conformance_test/test_XR_KHR_vulkan_enable2.cpp @@ -20,8 +20,8 @@ #include "conformance_utils.h" #include "graphics_plugin.h" #include "matchers.h" +#include "common/xr_dependencies.h" #include "utilities/types_and_constants.h" -#include "xr_dependencies.h" #include #include 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 fb43b0a2..284cb2b9 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 @@ -22,7 +22,7 @@ #include // Include all dependencies of openxr_platform as configured -#include "xr_dependencies.h" +#include "common/xr_dependencies.h" #include #include diff --git a/src/conformance/conformance_test/test_XR_MSFT_controller_model.cpp b/src/conformance/conformance_test/test_XR_MSFT_controller_model.cpp index ce6833fc..8c9aef8c 100644 --- a/src/conformance/conformance_test/test_XR_MSFT_controller_model.cpp +++ b/src/conformance/conformance_test/test_XR_MSFT_controller_model.cpp @@ -6,25 +6,43 @@ #include "composition_utils.h" #include "conformance_framework.h" #include "conformance_utils.h" +#include "controller_animation_handler.h" +#include "gltf.h" +#include "graphics_plugin.h" +#include "input_testinputdevice.h" #include "report.h" #include "two_call.h" #include "two_call_struct_metadata.h" #include "two_call_struct_tests.h" #include "common/hex_and_handles.h" +#include "utilities/throw_helpers.h" +#include "utilities/types_and_constants.h" #include "utilities/utils.h" +#include #include #include +#include #include +#include +#include #include -#include +#include +#include +#include +#include +#include #include +#include +#include #include namespace Conformance { + constexpr XrVector3f Up{0, 1, 0}; + struct ExtensionDataForXR_MSFT_controller_model { XrInstance instance; @@ -256,6 +274,24 @@ namespace Conformance std::vector modelBuffer(modelBufferSize); REQUIRE_RESULT_UNQUALIFIED_SUCCESS( ext.xrLoadControllerModelMSFT_(session, modelKey, modelBufferSize, &modelBufferSize, modelBuffer.data())); + + tinygltf::Model model; + tinygltf::TinyGLTF loader; + std::string err; + std::string warn; + bool loadedModel = loader.LoadBinaryFromMemory(&model, &err, &warn, modelBuffer.data(), (unsigned int)modelBuffer.size()); + if (!warn.empty()) { + ReportF("glTF WARN: %s", &warn); + } + + if (!err.empty()) { + ReportF("glTF ERR: %s", &err); + } + + if (!loadedModel) { + FAIL("Failed to load glTF model provided."); + } + //! @todo Check that the model is valid, that it contains the nodes mentioned in the properties, and that the properties list is the same length as the state list } @@ -270,4 +306,220 @@ namespace Conformance } } } + + TEST_CASE("XR_MSFT_controller_model_interactive", "[scenario][interactive][no_auto]") + { + const char* instructions = + "Ensure the controller model is positioned in the same position as the physical controller. " + "Press menu to complete the validation."; + + CompositionHelper compositionHelper("XR_MSFT_controller_model_inte...", {"XR_MSFT_controller_model"}); + + XrInstance instance = compositionHelper.GetInstance(); + ExtensionDataForXR_MSFT_controller_model ext(instance); + + const XrSpace localSpace = compositionHelper.CreateReferenceSpace(XR_REFERENCE_SPACE_TYPE_LOCAL, XrPosefCPP{}); + + // Set up composition projection layer and swapchains (one swapchain per view). + std::vector swapchains; + XrCompositionLayerProjection* const projLayer = compositionHelper.CreateProjectionLayer(localSpace); + { + const std::vector viewProperties = compositionHelper.EnumerateConfigurationViews(); + for (uint32_t j = 0; j < projLayer->viewCount; j++) { + const XrSwapchain swapchain = compositionHelper.CreateSwapchain(compositionHelper.DefaultColorSwapchainCreateInfo( + viewProperties[j].recommendedImageRectWidth, viewProperties[j].recommendedImageRectHeight)); + const_cast(projLayer->views[j].subImage) = compositionHelper.MakeDefaultSubImage(swapchain, 0); + swapchains.push_back(swapchain); + } + } + + struct Hand + { + XrPath subactionPath; + XrSpace space; + XrControllerModelKeyMSFT modelKey; + GLTFHandle controllerModel; + ControllerAnimationHandler animationHandler; + }; + + Hand hands[2] = {}; + hands[0].subactionPath = StringToPath(compositionHelper.GetInstance(), "/user/hand/left"); + hands[1].subactionPath = StringToPath(compositionHelper.GetInstance(), "/user/hand/right"); + + // Set up the actions. + const std::array subactionPaths{hands[0].subactionPath, hands[1].subactionPath}; + XrActionSet actionSet; + XrAction completeAction, gripPoseAction; + { + XrActionSetCreateInfo actionSetInfo{XR_TYPE_ACTION_SET_CREATE_INFO}; + strcpy(actionSetInfo.actionSetName, "interaction_test"); + strcpy(actionSetInfo.localizedActionSetName, "Interaction Test"); + XRC_CHECK_THROW_XRCMD(xrCreateActionSet(compositionHelper.GetInstance(), &actionSetInfo, &actionSet)); + + XrActionCreateInfo actionInfo{XR_TYPE_ACTION_CREATE_INFO}; + actionInfo.actionType = XR_ACTION_TYPE_BOOLEAN_INPUT; + strcpy(actionInfo.actionName, "complete_test"); + strcpy(actionInfo.localizedActionName, "Complete test"); + XRC_CHECK_THROW_XRCMD(xrCreateAction(actionSet, &actionInfo, &completeAction)); + + // Remainder of actions use subaction. + actionInfo.subactionPaths = subactionPaths.data(); + actionInfo.countSubactionPaths = (uint32_t)subactionPaths.size(); + + actionInfo.actionType = XR_ACTION_TYPE_POSE_INPUT; + strcpy(actionInfo.actionName, "grip_pose"); + strcpy(actionInfo.localizedActionName, "Grip pose"); + actionInfo.subactionPaths = subactionPaths.data(); + actionInfo.countSubactionPaths = (uint32_t)subactionPaths.size(); + XRC_CHECK_THROW_XRCMD(xrCreateAction(actionSet, &actionInfo, &gripPoseAction)); + } + + const std::vector bindings = { + {completeAction, StringToPath(compositionHelper.GetInstance(), "/user/hand/left/input/menu/click")}, + {completeAction, StringToPath(compositionHelper.GetInstance(), "/user/hand/right/input/menu/click")}, + {gripPoseAction, StringToPath(compositionHelper.GetInstance(), "/user/hand/left/input/grip/pose")}, + {gripPoseAction, StringToPath(compositionHelper.GetInstance(), "/user/hand/right/input/grip/pose")}, + }; + + XrInteractionProfileSuggestedBinding suggestedBindings{XR_TYPE_INTERACTION_PROFILE_SUGGESTED_BINDING}; + suggestedBindings.interactionProfile = StringToPath(compositionHelper.GetInstance(), "/interaction_profiles/khr/simple_controller"); + suggestedBindings.suggestedBindings = bindings.data(); + suggestedBindings.countSuggestedBindings = (uint32_t)bindings.size(); + XRC_CHECK_THROW_XRCMD(xrSuggestInteractionProfileBindings(compositionHelper.GetInstance(), &suggestedBindings)); + + XrSessionActionSetsAttachInfo attachInfo{XR_TYPE_SESSION_ACTION_SETS_ATTACH_INFO}; + attachInfo.actionSets = &actionSet; + attachInfo.countActionSets = 1; + XRC_CHECK_THROW_XRCMD(xrAttachSessionActionSets(compositionHelper.GetSession(), &attachInfo)); + + compositionHelper.BeginSession(); + XrSession session = compositionHelper.GetSession(); + + // Create the instructional quad layer placed to the left. + XrCompositionLayerQuad* const instructionsQuad = + compositionHelper.CreateQuadLayer(compositionHelper.CreateStaticSwapchainImage(CreateTextImage(1024, 768, instructions, 48)), + localSpace, 1, {{0, 0, 0, 1}, {-1.5f, 0, -0.3f}}); + XrQuaternionf_CreateFromAxisAngle(&instructionsQuad->pose.orientation, &Up, 70 * MATH_PI / 180); + + // Initialize an XrSpace for each hand + for (Hand& hand : hands) { + XrActionSpaceCreateInfo spaceCreateInfo{XR_TYPE_ACTION_SPACE_CREATE_INFO}; + spaceCreateInfo.subactionPath = hand.subactionPath; + spaceCreateInfo.action = gripPoseAction; + spaceCreateInfo.poseInActionSpace = XrPosefCPP(); + XRC_CHECK_THROW_XRCMD(xrCreateActionSpace(compositionHelper.GetSession(), &spaceCreateInfo, &hand.space)); + } + + auto update = [&](const XrFrameState& frameState) { + std::vector renderedCubes; + std::vector renderedGLTFs; + + const std::array activeActionSets = {{{actionSet, XR_NULL_PATH}}}; + XrActionsSyncInfo syncInfo{XR_TYPE_ACTIONS_SYNC_INFO}; + syncInfo.activeActionSets = activeActionSets.data(); + syncInfo.countActiveActionSets = (uint32_t)activeActionSets.size(); + XRC_CHECK_THROW_XRCMD(xrSyncActions(compositionHelper.GetSession(), &syncInfo)); + + // Check if user has requested to complete the test. + { + XrActionStateGetInfo completeActionGetInfo{XR_TYPE_ACTION_STATE_GET_INFO}; + completeActionGetInfo.action = completeAction; + XrActionStateBoolean completeActionState{XR_TYPE_ACTION_STATE_BOOLEAN}; + XRC_CHECK_THROW_XRCMD( + xrGetActionStateBoolean(compositionHelper.GetSession(), &completeActionGetInfo, &completeActionState)); + if (completeActionState.currentState == XR_TRUE && completeActionState.changedSinceLastSync) { + return false; + } + } + + for (Hand& hand : hands) { + if (hand.modelKey != XR_NULL_CONTROLLER_MODEL_KEY_MSFT) { + continue; + } + XrControllerModelKeyStateMSFT modelKeyState{XR_TYPE_CONTROLLER_MODEL_KEY_STATE_MSFT}; + CHECK_RESULT_UNQUALIFIED_SUCCESS(ext.xrGetControllerModelKeyMSFT_(session, hand.subactionPath, &modelKeyState)); + if (modelKeyState.modelKey != XR_NULL_CONTROLLER_MODEL_KEY_MSFT) { + ReportF("Loaded model key"); + hand.modelKey = modelKeyState.modelKey; + + uint32_t modelBufferSize; + REQUIRE_RESULT_UNQUALIFIED_SUCCESS( + ext.xrLoadControllerModelMSFT_(session, hand.modelKey, 0, &modelBufferSize, nullptr)); + std::vector modelBuffer(modelBufferSize); + REQUIRE_RESULT_UNQUALIFIED_SUCCESS( + ext.xrLoadControllerModelMSFT_(session, hand.modelKey, modelBufferSize, &modelBufferSize, modelBuffer.data())); + + hand.controllerModel = GetGlobalData().graphicsPlugin->LoadGLTF(modelBuffer); + + XrControllerModelPropertiesMSFT modelProperties{XR_TYPE_CONTROLLER_MODEL_PROPERTIES_MSFT}; + REQUIRE_RESULT_UNQUALIFIED_SUCCESS(ext.xrGetControllerModelPropertiesMSFT_(session, hand.modelKey, &modelProperties)); + std::vector nodePropertiesBuffer(modelProperties.nodeCountOutput); + modelProperties.nodeCapacityInput = (uint32_t)nodePropertiesBuffer.size(); + modelProperties.nodeProperties = nodePropertiesBuffer.data(); + REQUIRE_RESULT_UNQUALIFIED_SUCCESS(ext.xrGetControllerModelPropertiesMSFT_(session, hand.modelKey, &modelProperties)); + + hand.animationHandler = + ControllerAnimationHandler(GetGlobalData().graphicsPlugin->GetModel(hand.controllerModel), nodePropertiesBuffer); + + ReportF("Loaded model for key"); + } + } + + for (Hand& hand : hands) { + XrSpaceVelocity spaceVelocity{XR_TYPE_SPACE_VELOCITY}; + XrSpaceLocation spaceLocation{XR_TYPE_SPACE_LOCATION, &spaceVelocity}; + XRC_CHECK_THROW_XRCMD(xrLocateSpace(hand.space, localSpace, frameState.predictedDisplayTime, &spaceLocation)); + if (spaceLocation.locationFlags & XR_SPACE_LOCATION_POSITION_VALID_BIT) { + if (hand.modelKey == XR_NULL_CONTROLLER_MODEL_KEY_MSFT) { + renderedCubes.push_back(Cube{spaceLocation.pose, {0.1f, 0.1f, 0.1f}}); + } + else { + XrControllerModelStateMSFT modelState{XR_TYPE_CONTROLLER_MODEL_STATE_MSFT}; + REQUIRE_RESULT_UNQUALIFIED_SUCCESS(ext.xrGetControllerModelStateMSFT_(session, hand.modelKey, &modelState)); + std::vector nodeStateBuffer(modelState.nodeCountOutput); + modelState.nodeCapacityInput = (uint32_t)nodeStateBuffer.size(); + modelState.nodeStates = nodeStateBuffer.data(); + REQUIRE_RESULT_UNQUALIFIED_SUCCESS(ext.xrGetControllerModelStateMSFT_(session, hand.modelKey, &modelState)); + + hand.animationHandler.UpdateControllerParts(nodeStateBuffer); + + renderedGLTFs.push_back(GLTFDrawable{hand.controllerModel, spaceLocation.pose}); + } + } + } + + auto viewData = compositionHelper.LocateViews(localSpace, frameState.predictedDisplayTime); + const auto& viewState = std::get(viewData); + + std::vector layers; + if (viewState.viewStateFlags & XR_VIEW_STATE_POSITION_VALID_BIT && + viewState.viewStateFlags & XR_VIEW_STATE_ORIENTATION_VALID_BIT) { + const auto& views = std::get>(viewData); + + // Render into each view port of the wide swapchain using the projection layer view fov and pose. + for (size_t view = 0; view < views.size(); view++) { + compositionHelper.AcquireWaitReleaseImage(swapchains[view], // + [&](const XrSwapchainImageBaseHeader* swapchainImage) { + GetGlobalData().graphicsPlugin->ClearImageSlice(swapchainImage, 0); + const_cast(projLayer->views[view].fov) = views[view].fov; + const_cast(projLayer->views[view].pose) = views[view].pose; + GetGlobalData().graphicsPlugin->RenderView( + projLayer->views[view], swapchainImage, + RenderParams().Draw(renderedCubes).Draw(renderedGLTFs)); + }); + } + + layers.push_back({reinterpret_cast(projLayer)}); + } + + layers.push_back({reinterpret_cast(instructionsQuad)}); + + compositionHelper.EndFrame(frameState.predictedDisplayTime, layers); + + return compositionHelper.PollEvents(); + }; + + RenderLoop(compositionHelper.GetSession(), update).Loop(); + } + } // namespace Conformance diff --git a/src/conformance/conformance_test/test_glTFRendering.cpp b/src/conformance/conformance_test/test_glTFRendering.cpp new file mode 100644 index 00000000..440d6f18 --- /dev/null +++ b/src/conformance/conformance_test/test_glTFRendering.cpp @@ -0,0 +1,244 @@ +// Copyright (c) 2019-2023, The Khronos Group Inc. +// +// SPDX-License-Identifier: Apache-2.0 + +#include "composition_utils.h" +#include "conformance_framework.h" +#include "graphics_plugin.h" + +#include "common/xr_linear.h" +#include "utilities/array_size.h" +#include "utilities/throw_helpers.h" +#include "utilities/types_and_constants.h" +#include "utilities/utils.h" + +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +namespace Conformance +{ + TEST_CASE("glTFRendering", "[self_test][interactive][no_auto]") + { + GlobalData& globalData = GetGlobalData(); + + CompositionHelper compositionHelper("glTF rendering"); + // Each test case will configure the layer manager with its own instructions and image + InteractiveLayerManager interactiveLayerManager(compositionHelper, nullptr, "glTF rendering"); + + const XrSpace localSpace = compositionHelper.CreateReferenceSpace(XR_REFERENCE_SPACE_TYPE_LOCAL, XrPosefCPP{}); + + // Set up composition projection layer and swapchains (one swapchain per view). + std::vector swapchains; + XrCompositionLayerProjection* const projLayer = compositionHelper.CreateProjectionLayer(localSpace); + { + const std::vector viewProperties = compositionHelper.EnumerateConfigurationViews(); + for (uint32_t j = 0; j < projLayer->viewCount; j++) { + const XrSwapchain swapchain = compositionHelper.CreateSwapchain(compositionHelper.DefaultColorSwapchainCreateInfo( + viewProperties[j].recommendedImageRectWidth, viewProperties[j].recommendedImageRectHeight)); + const_cast(projLayer->views[j].subImage) = compositionHelper.MakeDefaultSubImage(swapchain, 0); + swapchains.push_back(swapchain); + } + } + + const std::vector subactionPaths{StringToPath(compositionHelper.GetInstance(), "/user/hand/left"), + StringToPath(compositionHelper.GetInstance(), "/user/hand/right")}; + + XrActionSet actionSet; + XrAction gripPoseAction; + { + XrActionSetCreateInfo actionSetInfo{XR_TYPE_ACTION_SET_CREATE_INFO}; + strcpy(actionSetInfo.actionSetName, "gltf_rendering"); + strcpy(actionSetInfo.localizedActionSetName, "glTF rendering"); + XRC_CHECK_THROW_XRCMD(xrCreateActionSet(compositionHelper.GetInstance(), &actionSetInfo, &actionSet)); + + XrActionCreateInfo actionInfo{XR_TYPE_ACTION_CREATE_INFO}; + actionInfo.actionType = XR_ACTION_TYPE_POSE_INPUT; + strcpy(actionInfo.actionName, "grip_pose"); + strcpy(actionInfo.localizedActionName, "Grip pose"); + actionInfo.subactionPaths = subactionPaths.data(); + actionInfo.countSubactionPaths = (uint32_t)subactionPaths.size(); + XRC_CHECK_THROW_XRCMD(xrCreateAction(actionSet, &actionInfo, &gripPoseAction)); + } + + compositionHelper.GetInteractionManager().AddActionSet(actionSet); + XrPath simpleInteractionProfile = StringToPath(compositionHelper.GetInstance(), "/interaction_profiles/khr/simple_controller"); + compositionHelper.GetInteractionManager().AddActionBindings( + simpleInteractionProfile, + {{ + {gripPoseAction, StringToPath(compositionHelper.GetInstance(), "/user/hand/left/input/grip/pose")}, + {gripPoseAction, StringToPath(compositionHelper.GetInstance(), "/user/hand/right/input/grip/pose")}, + }}); + + compositionHelper.GetInteractionManager().AttachActionSets(); + compositionHelper.BeginSession(); + + // Spaces where we will draw the active gltf. Default to one on each controller. + std::vector gripSpaces; + + // Create XrSpaces for each grip pose + for (int i = 0; i < 2; i++) { + XrSpace space; + if ((i == 0 && globalData.leftHandUnderTest) || (i == 1 && globalData.rightHandUnderTest)) { + XrActionSpaceCreateInfo spaceCreateInfo{XR_TYPE_ACTION_SPACE_CREATE_INFO}; + spaceCreateInfo.action = gripPoseAction; + spaceCreateInfo.subactionPath = subactionPaths[i]; + spaceCreateInfo.poseInActionSpace = {{0, 0, 0, 1}, {0, 0, 0}}; + XRC_CHECK_THROW_XRCMD(xrCreateActionSpace(compositionHelper.GetSession(), &spaceCreateInfo, &space)); + gripSpaces.push_back(std::move(space)); + } + } + + struct glTFTestCase + { + const char* filePath; + const char* name; + const char* description; + const char* exampleImagePath; + XrPosef poseInGripSpace; + float scale; + }; + + glTFTestCase testCases[] = { + {"VertexColorTest.glb", + "Vertex Color Test", + "Ensure that each box in the \"Test\" row matches the \"Sample pass\" box below.", + "VertexColorTest.png", + {Quat::FromAxisAngle({1, 0, 0}, Math::DegToRad(-90)), {0, 0, 0}}, + 0.15f}, + {"MetalRoughSpheres.glb", + "Metal Rough Spheres", + "Ensure that the spheres follow a pattern from rough to shiny along one axis" + " and from metallic (like a steel ball) to dielectric (like a pool ball) on the other axis" + " like on the example image provided.", + "MetalRoughSpheres.png", + {Quat::FromAxisAngle({1, 0, 0}, Math::DegToRad(-90)), {0, 0, 0}}, + 0.03f}, + {"MetalRoughSpheresNoTextures.glb", + "Metal Rough Spheres (no textures)", + "Ensure that the spheres follow a pattern from rough to shiny along one axis" + " and from metallic (like a steel ball) to dielectric (like a pool ball) on the other axis" + " like on the example image provided.", + "MetalRoughSpheresNoTextures.png", + {Quat::FromAxisAngle({1, 0, 0}, Math::DegToRad(-90)), {-0.11f, 0, 0.11f}}, + 35.f}, + {"NormalTangentTest.glb", + "Normal Tangent Test", + "Ensure that in each column, the squares look identical, and that in each pair of columns," + " the lighting moves \"correctly\" (counter to controller rotation) and is consistent" + " between adjacent squares. The lighting should appear to be coming from diagonally above.", + "NormalTangentTest.png", + {Quat::FromAxisAngle({1, 0, 0}, Math::DegToRad(-90)), {0, 0, 0}}, + 0.075f}, + {"NormalTangentMirrorTest.glb", + "Normal Tangent Mirror Test", + "Ensure that in each column, the squares look identical, and that in each row of four squares," + " the lighting moves \"correctly\" (counter to controller rotation) and is consistent" + " between adjacent squares. The lighting should appear to be coming from diagonally above.", + "NormalTangentMirrorTest.png", + {Quat::FromAxisAngle({1, 0, 0}, Math::DegToRad(-90)), {0, 0, 0}}, + 0.075f}, + {"TextureSettingsTest.glb", + "Texture Settings Test", + "Ensure that the \"Test\" box in each row matches the \"Sample pass\" box.", + "TextureSettingsTest.png", + {Quat::FromAxisAngle({1, 0, 0}, Math::DegToRad(-90)), {0, 0, 0}}, + 0.025f}, + {"AlphaBlendModeTest.glb", + "Alpha Blend Mode Test", + "Ensure that the first rectangle is opaque, the second has a smooth gradient from transparent" + " at the top to opaque at the bottom, and that the last three are filled up to the green marker.", + "AlphaBlendModeTest.png", + {Quat::FromAxisAngle({1, 0, 0}, Math::DegToRad(-90)), {0, 0, 0}}, + 0.075f}, + }; + + size_t testCaseIdx = 0; + auto testCase = testCases[testCaseIdx]; + + bool testCaseInitialized = false; + GLTFHandle gltfModel; + + auto setupTest = [&]() { + // Load the model file into memory + auto modelData = ReadFileBytes(testCase.filePath, "glTF binary"); + + // Load the model + gltfModel = GetGlobalData().graphicsPlugin->LoadGLTF(modelData); + + // Configure the interactive layer manager with the corresponding description and image + std::ostringstream oss; + oss << "Subtest " << (testCaseIdx + 1) << "/" << ArraySize(testCases) << ": " << testCase.name << std::endl; + oss << testCase.description << std::endl; + interactiveLayerManager.Configure(testCase.exampleImagePath, oss.str().c_str()); + + testCaseInitialized = true; + }; + + setupTest(); + + auto updateLayers = [&](const XrFrameState& frameState) { + // do this first so if models take time to load, xrLocateViews doesn't complain about an old time + auto viewData = compositionHelper.LocateViews(localSpace, frameState.predictedDisplayTime); + const auto& viewState = std::get(viewData); + + // want our standard action sets on all subaction paths + compositionHelper.GetInteractionManager().SyncActions(XR_NULL_PATH); + + if (!testCaseInitialized) { + testCase = testCases[testCaseIdx]; + setupTest(); + } + + std::vector renderedGLTFs; + + for (const auto& space : gripSpaces) { + XrSpaceLocation location{XR_TYPE_SPACE_LOCATION}; + if (XR_SUCCEEDED(xrLocateSpace(space, localSpace, frameState.predictedDisplayTime, &location))) { + if ((location.locationFlags & XR_SPACE_LOCATION_POSITION_VALID_BIT) && + (location.locationFlags & XR_SPACE_LOCATION_ORIENTATION_VALID_BIT)) { + + XrPosef adjustedPose; + XrPosef_Multiply(&adjustedPose, &location.pose, &testCase.poseInGripSpace); + renderedGLTFs.push_back(GLTFDrawable{gltfModel, adjustedPose, {testCase.scale, testCase.scale, testCase.scale}}); + } + } + } + + std::vector layers; + if (viewState.viewStateFlags & XR_VIEW_STATE_POSITION_VALID_BIT && + viewState.viewStateFlags & XR_VIEW_STATE_ORIENTATION_VALID_BIT) { + const auto& views = std::get>(viewData); + + // Render into each view port of the wide swapchain using the projection layer view fov and pose. + for (size_t view = 0; view < views.size(); view++) { + compositionHelper.AcquireWaitReleaseImage(swapchains[view], [&](const XrSwapchainImageBaseHeader* swapchainImage) { + GetGlobalData().graphicsPlugin->ClearImageSlice(swapchainImage); + const_cast(projLayer->views[view].fov) = views[view].fov; + const_cast(projLayer->views[view].pose) = views[view].pose; + GetGlobalData().graphicsPlugin->RenderView(projLayer->views[view], swapchainImage, + RenderParams().Draw(renderedGLTFs)); + }); + } + + layers.push_back(reinterpret_cast(projLayer)); + } + + if (!interactiveLayerManager.EndFrame(frameState, layers)) { + // user has marked this test as complete + testCaseIdx++; + testCaseInitialized = false; + return (testCaseIdx < ArraySize(testCases)); + } + return true; + }; + RenderLoop(compositionHelper.GetSession(), updateLayers).Loop(); + } +} // namespace Conformance diff --git a/src/conformance/conformance_test/test_multithreading.cpp b/src/conformance/conformance_test/test_multithreading.cpp index 81c903bb..de7156ef 100644 --- a/src/conformance/conformance_test/test_multithreading.cpp +++ b/src/conformance/conformance_test/test_multithreading.cpp @@ -38,7 +38,7 @@ #include // Include all dependencies of openxr_platform as configured -#include "xr_dependencies.h" +#include "common/xr_dependencies.h" #include namespace Conformance diff --git a/src/conformance/framework/CMakeLists.txt b/src/conformance/framework/CMakeLists.txt index c2311e77..11cfb16e 100644 --- a/src/conformance/framework/CMakeLists.txt +++ b/src/conformance/framework/CMakeLists.txt @@ -2,6 +2,41 @@ # # SPDX-License-Identifier: Apache-2.0 +# tinygltf + +add_library(conformance_framework_tinygltf STATIC gltf.cpp) + +target_include_directories( + conformance_framework_tinygltf + # stb + PRIVATE ${PROJECT_SOURCE_DIR}/src/external/stb + # tinygltf impl needs bundled json.hpp + ${PROJECT_SOURCE_DIR}/src/external/tinygltf + # tinygltf itself + PUBLIC ${PROJECT_SOURCE_DIR}/src/external) + +target_compile_definitions( + conformance_framework_tinygltf + PUBLIC TINYGLTF_USE_CPP14 TINYGLTF_NO_STB_IMAGE_WRITE + # TODO once we have rapidjson to avoid stack overflow + # RAPIDJSON_PARSE_DEFAULT_FLAGS=kParseIterativeFlag TINYGLTF_USE_RAPIDJSON + # TINYGLTF_USE_RAPIDJSON_CRTALLOCATOR +) + +if(SUPPORTS_Werrorunusedparameter) + target_compile_options(conformance_framework_tinygltf + PRIVATE -Wno-unused-parameter) +endif() +set_target_properties(conformance_framework_tinygltf + PROPERTIES FOLDER ${CONFORMANCE_TESTS_FOLDER}) + + +# PBR subsystem and glTF handling +add_subdirectory(pbr) +add_subdirectory(gltf) + +# Main conformance framework + set(VULKAN_SHADERS ${CMAKE_CURRENT_SOURCE_DIR}/vulkan_shaders/frag.glsl ${CMAKE_CURRENT_SOURCE_DIR}/vulkan_shaders/vert.glsl) @@ -16,13 +51,18 @@ add_library( composition_utils.cpp conformance_framework.cpp conformance_utils.cpp + controller_animation_handler.cpp environment.cpp graphics_plugin_d3d11.cpp + graphics_plugin_d3d11_gltf.cpp graphics_plugin_d3d12.cpp + graphics_plugin_d3d12_gltf.cpp graphics_plugin_factory.cpp graphics_plugin_opengl.cpp + graphics_plugin_opengl_gltf.cpp graphics_plugin_opengles.cpp graphics_plugin_vulkan.cpp + graphics_plugin_vulkan_gltf.cpp input_testinputdevice.cpp mesh_projection_layer.cpp platform_plugin_android.cpp @@ -37,7 +77,12 @@ add_library( target_link_libraries( conformance_framework PUBLIC conformance_utilities OpenXR::openxr_loader - Threads::Threads Catch2) + Threads::Threads Catch2 conformance_framework_gltf + conformance_framework_tinygltf) + +if(GLSLANG_VALIDATOR AND NOT GLSL_COMPILER) + target_compile_definitions(conformance_framework PUBLIC USE_GLSLANGVALIDATOR) +endif() target_include_directories( conformance_framework diff --git a/src/conformance/framework/RGBAImage.cpp b/src/conformance/framework/RGBAImage.cpp index 32e9d709..ce2f9e8d 100644 --- a/src/conformance/framework/RGBAImage.cpp +++ b/src/conformance/framework/RGBAImage.cpp @@ -7,7 +7,7 @@ #include "conformance_framework.h" #ifdef XR_USE_PLATFORM_ANDROID -#include "unique_asset.h" +#include "common/unique_asset.h" #include #endif @@ -324,4 +324,18 @@ namespace Conformance } } + void RGBAImage::CopyWithStride(uint8_t* data, uint32_t rowPitch, uint32_t offset) const + { + Conformance::CopyWithStride(reinterpret_cast(pixels.data()), data + offset, width * sizeof(RGBA8Color), height, + rowPitch); + } + + void CopyWithStride(const uint8_t* source, uint8_t* dest, uint32_t rowSize, uint32_t rows, uint32_t rowPitch) + { + for (size_t row = 0; row < rows; ++row) { + uint8_t* rowPtr = &dest[row * rowPitch]; + memcpy(rowPtr, &source[row * rowSize], rowSize); + } + } + } // namespace Conformance diff --git a/src/conformance/framework/RGBAImage.h b/src/conformance/framework/RGBAImage.h index c40b7914..654b7d5f 100644 --- a/src/conformance/framework/RGBAImage.h +++ b/src/conformance/framework/RGBAImage.h @@ -42,10 +42,23 @@ namespace Conformance void DrawRectBorder(int x, int y, int w, int h, int thickness, XrColor4f color); void ConvertToSRGB(); + /// Copy image data row-by-row to a buffer with a (probably different) row pitch explicitly specified, + /// and optionally an offset from the start of that buffer. + void CopyWithStride(uint8_t* data, uint32_t rowPitch, uint32_t offset = 0) const; + bool isSrgb = false; std::vector pixels; - int width; - int height; + int32_t width; + int32_t height; }; + + /// Copy a contiguous image into a buffer for GPU usage - with stride/pitch. + /// + /// @param source Source buffer, with all pixels contiguous + /// @param dest Destination buffer (with offset applied, if applicable) + /// @param rowSize bytes in a row (bytes per pixel * width in pixels) + /// @param rows number of rows to copy (height in pixels) + /// @param rowPitch destination row pitch in bytes + void CopyWithStride(const uint8_t* source, uint8_t* dest, uint32_t rowSize, uint32_t rows, uint32_t rowPitch); /// @} } // namespace Conformance diff --git a/src/conformance/framework/catch_reporter_cts.h b/src/conformance/framework/catch_reporter_cts.h index 72697852..24610d38 100644 --- a/src/conformance/framework/catch_reporter_cts.h +++ b/src/conformance/framework/catch_reporter_cts.h @@ -17,7 +17,7 @@ XRC_DISABLE_MSVC_WARNING(4324) #include #include #include -#include "catch2/interfaces/catch_interfaces_reporter_factory.hpp" +#include namespace Catch { diff --git a/src/conformance/framework/composition_utils.cpp b/src/conformance/framework/composition_utils.cpp index e3ea3955..c0659991 100644 --- a/src/conformance/framework/composition_utils.cpp +++ b/src/conformance/framework/composition_utils.cpp @@ -18,13 +18,15 @@ #include "conformance_framework.h" #include "swapchain_image_data.h" + +#include "common/xr_dependencies.h" +#include "common/xr_linear.h" #include "utilities/event_reader.h" #include "utilities/throw_helpers.h" -#include "utilities/xrduration_literals.h" - -#include #include +#include +#include #include #include @@ -37,7 +39,7 @@ using namespace std::chrono_literals; namespace Conformance { - RGBAImage CreateTextImage(int width, int height, const char* text, int fontHeight) + RGBAImage CreateTextImage(int32_t width, int32_t height, const char* text, int32_t fontHeight) { constexpr int FontPaddingPixels = 4; constexpr int BorderPixels = 2; diff --git a/src/conformance/framework/composition_utils.h b/src/conformance/framework/composition_utils.h index 21f835de..78123d75 100644 --- a/src/conformance/framework/composition_utils.h +++ b/src/conformance/framework/composition_utils.h @@ -17,6 +17,7 @@ #pragma once #include "RGBAImage.h" +#include "utilities/colors.h" #include "common/xr_linear.h" #include "conformance_framework.h" #include "conformance_utils.h" @@ -24,7 +25,7 @@ #include "utilities/throw_helpers.h" #include "utilities/types_and_constants.h" -#include "catch2/catch_test_macros.hpp" +#include #include #include @@ -46,7 +47,7 @@ namespace Conformance class EventReader; class ISwapchainImageData; - RGBAImage CreateTextImage(int width, int height, const char* text, int fontHeight); + RGBAImage CreateTextImage(int32_t width, int32_t height, const char* text, int32_t fontHeight); XrPath StringToPath(XrInstance instance, const char* pathStr); @@ -340,22 +341,6 @@ namespace Conformance Complete }; - namespace Colors - { - constexpr XrColor4f Red = {1, 0, 0, 1}; - constexpr XrColor4f Green = {0, 1, 0, 1}; - constexpr XrColor4f GreenZeroAlpha = {0, 1, 0, 0}; - 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}; - } // namespace Colors - namespace Math { /// Do a linear conversion of a number from one range to another range. @@ -430,37 +415,53 @@ namespace Conformance m_exampleQuadSpace = compositionHelper.CreateReferenceSpace( XR_REFERENCE_SPACE_TYPE_VIEW, {Quat::FromAxisAngle(UpVector, -15 * MATH_PI / 180), {0.5f, 0, -1.5f}}); + Configure(exampleImage, descriptionText); + } + + void Configure(const char* exampleImage, const char* descriptionText) + { + if (m_descriptionQuad != nullptr && m_descriptionQuad->subImage.swapchain != XR_NULL_HANDLE) { + m_compositionHelper.DestroySwapchain(m_descriptionQuad->subImage.swapchain); + } + if (m_exampleQuad != nullptr && m_exampleQuad->subImage.swapchain != XR_NULL_HANDLE) { + m_compositionHelper.DestroySwapchain(m_exampleQuad->subImage.swapchain); + } + // Load example screenshot if available and set up the quad layer for it. { XrSwapchain exampleSwapchain; if (exampleImage) { - exampleSwapchain = compositionHelper.CreateStaticSwapchainImage(RGBAImage::Load(exampleImage)); + exampleSwapchain = m_compositionHelper.CreateStaticSwapchainImage(RGBAImage::Load(exampleImage)); } else { RGBAImage image(256, 256); image.PutText(XrRect2Di{{0, image.height / 2}, {image.width, image.height}}, "Example Not Available", 64, {1, 0, 0, 1}); - exampleSwapchain = compositionHelper.CreateStaticSwapchainImage(image); + exampleSwapchain = m_compositionHelper.CreateStaticSwapchainImage(image); } // Create a quad to the right of the help text. - m_exampleQuad = compositionHelper.CreateQuadLayer(exampleSwapchain, m_exampleQuadSpace, 1.25f); + m_exampleQuad = m_compositionHelper.CreateQuadLayer(exampleSwapchain, m_exampleQuadSpace, 1.25f); } + constexpr uint32_t width = 768; + constexpr uint32_t descriptionHeight = width; + constexpr uint32_t fontHeight = 48; + constexpr uint32_t actionsHeight = 128; + // Set up the quad layer for showing the help text to the left of the example image. - m_descriptionQuad = compositionHelper.CreateQuadLayer( - compositionHelper.CreateStaticSwapchainImage(CreateTextImage(768, 768, descriptionText, 48)), m_descriptionQuadSpace, - 0.75f); + m_descriptionQuad = m_compositionHelper.CreateQuadLayer( + m_compositionHelper.CreateStaticSwapchainImage(CreateTextImage(width, descriptionHeight, descriptionText, fontHeight)), + m_descriptionQuadSpace, 0.75f); m_descriptionQuad->layerFlags |= XR_COMPOSITION_LAYER_BLEND_TEXTURE_SOURCE_ALPHA_BIT; - constexpr uint32_t actionsWidth = 768, actionsHeight = 128; - m_sceneActionsSwapchain = compositionHelper.CreateStaticSwapchainImage( - CreateTextImage(actionsWidth, actionsHeight, "Press Select to PASS. Press Menu for description", 48)); + m_sceneActionsSwapchain = m_compositionHelper.CreateStaticSwapchainImage( + CreateTextImage(width, actionsHeight, "Press Select to PASS. Press Menu for description", fontHeight)); m_helpActionsSwapchain = - compositionHelper.CreateStaticSwapchainImage(CreateTextImage(actionsWidth, actionsHeight, "Press select to FAIL", 48)); + m_compositionHelper.CreateStaticSwapchainImage(CreateTextImage(width, actionsHeight, "Press select to FAIL", fontHeight)); // Set up the quad layer and swapchain for showing what actions the user can take in the Scene/Help mode. m_actionsQuad = - compositionHelper.CreateQuadLayer(m_sceneActionsSwapchain, m_viewSpace, 0.75f, {Quat::Identity, {0, -0.4f, -1}}); + m_compositionHelper.CreateQuadLayer(m_sceneActionsSwapchain, m_viewSpace, 0.75f, {Quat::Identity, {0, -0.4f, -1}}); m_actionsQuad->layerFlags |= XR_COMPOSITION_LAYER_BLEND_TEXTURE_SOURCE_ALPHA_BIT; } @@ -573,9 +574,9 @@ namespace Conformance XrSwapchain m_helpActionsSwapchain; LayerMode m_lastLayerMode{LayerMode::Scene}; XrCompositionLayerQuad* m_actionsQuad; - XrCompositionLayerQuad* m_descriptionQuad; + XrCompositionLayerQuad* m_descriptionQuad{}; XrSpace m_descriptionQuadSpace; - XrCompositionLayerQuad* m_exampleQuad; + XrCompositionLayerQuad* m_exampleQuad{}; XrSpace m_exampleQuadSpace; std::vector m_sceneLayers; }; diff --git a/src/conformance/framework/conformance_framework.h b/src/conformance/framework/conformance_framework.h index 8d9f96ce..53f0a0aa 100644 --- a/src/conformance/framework/conformance_framework.h +++ b/src/conformance/framework/conformance_framework.h @@ -20,6 +20,7 @@ #include "utilities/stringification.h" #include "utilities/types_and_constants.h" #include "utilities/utils.h" +#include "utilities/android_declarations.h" #include #include @@ -41,17 +42,6 @@ #include "windows.h" #endif -#if defined(XR_USE_PLATFORM_ANDROID) -// For Android, we require the following functions to be implemented -// in our library for accessing Android specific information. -void* Conformance_Android_Get_Application_VM(); -void* Conformance_Android_Get_Application_Context(); -void* Conformance_Android_Get_Application_Activity(); -void* Conformance_Android_Get_Asset_Manager(); -void Conformance_Android_Attach_Current_Thread(); -void Conformance_Android_Detach_Current_Thread(); -#endif // defined(XR_USE_PLATFORM_ANDROID) - /** * @defgroup cts_framework OpenXR CTS framework * @brief Functionality to use when building conformance tests. diff --git a/src/conformance/framework/controller_animation_handler.cpp b/src/conformance/framework/controller_animation_handler.cpp new file mode 100644 index 00000000..32fc6814 --- /dev/null +++ b/src/conformance/framework/controller_animation_handler.cpp @@ -0,0 +1,67 @@ + +// Copyright 2023, The Khronos Group, Inc. +// +// Based in part on code that is: +// +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +// +// SPDX-License-Identifier: MIT AND Apache-2.0 + +#include "controller_animation_handler.h" + +#include "common/xr_linear.h" +#include "pbr/PbrCommon.h" +#include "pbr/PbrModel.h" + +#include + +#include +#include +#include +#include + +using namespace std::literals::chrono_literals; + +namespace Conformance +{ + ControllerAnimationHandler::ControllerAnimationHandler(std::shared_ptr model, + std::vector properties) + : m_pbrModel(model), m_nodeProperties(properties) + + { + // Compute the index of each node reported by runtime to be animated. + // The order of m_nodeIndices exactly matches the order of the nodes properties and states. + m_nodeIndices.resize(m_nodeProperties.size(), Pbr::NodeIndex_npos); + for (size_t i = 0; i < m_nodeProperties.size(); ++i) { + const auto& nodeProperty = m_nodeProperties[i]; + Pbr::NodeIndex_t parentNodeIndex; + if (m_pbrModel->FindFirstNode(&parentNodeIndex, nodeProperty.parentNodeName)) { + Pbr::NodeIndex_t targetNodeIndex; + if (m_pbrModel->FindFirstNode(&targetNodeIndex, nodeProperty.nodeName, &parentNodeIndex)) { + m_nodeIndices[i] = targetNodeIndex; + } + } + } + } + + // Update transforms of nodes for the animatable parts in the controller model + void ControllerAnimationHandler::UpdateControllerParts(std::vector nodeStates) + { + m_nodeStates = nodeStates; + + assert(m_nodeStates.size() == m_nodeIndices.size()); + const size_t end = std::min(m_nodeStates.size(), m_nodeIndices.size()); + for (size_t i = 0; i < end; i++) { + const Pbr::NodeIndex_t nodeIndex = m_nodeIndices[i]; + if (nodeIndex != Pbr::NodeIndex_npos) { + Pbr::Node& node = m_pbrModel->GetNode(nodeIndex); + XrMatrix4x4f nodeTransform; + XrVector3f unitScale = {1, 1, 1}; + XrMatrix4x4f_CreateTranslationRotationScale(&nodeTransform, &m_nodeStates[i].nodePose.position, + &m_nodeStates[i].nodePose.orientation, &unitScale); + node.SetTransform(nodeTransform); + } + } + } +} // namespace Conformance diff --git a/src/conformance/framework/controller_animation_handler.h b/src/conformance/framework/controller_animation_handler.h new file mode 100644 index 00000000..dd2037ed --- /dev/null +++ b/src/conformance/framework/controller_animation_handler.h @@ -0,0 +1,41 @@ + +// Copyright 2023, The Khronos Group, Inc. +// +// Based in part on code that is: +// +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +// +// SPDX-License-Identifier: MIT AND Apache-2.0 + +#pragma once + +#include "pbr/PbrCommon.h" +#include "pbr/PbrModel.h" + +#include + +#include +#include + +namespace Pbr +{ + class Model; +} // namespace Pbr + +namespace Conformance +{ + class ControllerAnimationHandler + { + public: + ControllerAnimationHandler() = default; + ControllerAnimationHandler(std::shared_ptr model, std::vector properties); + void UpdateControllerParts(std::vector nodeStates); + + private: + std::shared_ptr m_pbrModel; + std::vector m_nodeIndices; + std::vector m_nodeProperties; + std::vector m_nodeStates; + }; +} // namespace Conformance diff --git a/src/conformance/framework/environment.cpp b/src/conformance/framework/environment.cpp index dd79dde8..2f70cbd4 100644 --- a/src/conformance/framework/environment.cpp +++ b/src/conformance/framework/environment.cpp @@ -20,7 +20,7 @@ #include -#include "catch2/catch_test_macros.hpp" +#include #endif @@ -80,7 +80,7 @@ namespace Conformance return {}; } - // MultiByteToWideChar returns number of chars of the input buffer, regardless of null terminitor + // MultiByteToWideChar returns number of chars of the input buffer, regardless of null terminator wideText.resize(wideLength, 0); wchar_t* wideString = const_cast(wideText.data()); // mutable data() only exists in c++17 const int length = ::MultiByteToWideChar(CP_UTF8, 0, utf8Text.data(), (int)utf8Text.size(), wideString, wideLength); @@ -105,7 +105,7 @@ namespace Conformance return {}; } - // WideCharToMultiByte returns number of chars of the input buffer, regardless of null terminitor + // WideCharToMultiByte returns number of chars of the input buffer, regardless of null terminator narrowText.resize(narrowLength, 0); char* narrowString = const_cast(narrowText.data()); // mutable data() only exists in c++17 const int length = diff --git a/src/conformance/framework/gltf.cpp b/src/conformance/framework/gltf.cpp new file mode 100644 index 00000000..f34e5069 --- /dev/null +++ b/src/conformance/framework/gltf.cpp @@ -0,0 +1,12 @@ +// Copyright (c) 2019-2023, The Khronos Group Inc. +// +// SPDX-License-Identifier: Apache-2.0 + +#define TINYGLTF_IMPLEMENTATION + +#if defined(_MSC_VER) +#pragma warning(disable : 4018) +#pragma warning(disable : 4189) +#endif // defined(_MSC_VER) + +#include "gltf.h" diff --git a/src/conformance/framework/gltf.h b/src/conformance/framework/gltf.h new file mode 100644 index 00000000..ffbff1a4 --- /dev/null +++ b/src/conformance/framework/gltf.h @@ -0,0 +1,7 @@ +// Copyright (c) 2019-2023, The Khronos Group Inc. +// +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +#include // IWYU pragma: export diff --git a/src/conformance/framework/gltf/CMakeLists.txt b/src/conformance/framework/gltf/CMakeLists.txt new file mode 100644 index 00000000..a519b31a --- /dev/null +++ b/src/conformance/framework/gltf/CMakeLists.txt @@ -0,0 +1,16 @@ +# Copyright (c) 2019-2023, The Khronos Group Inc. +# +# SPDX-License-Identifier: Apache-2.0 + +add_library(conformance_framework_gltf STATIC GltfHelper.cpp) + +target_link_libraries( + conformance_framework_gltf + PUBLIC conformance_framework_tinygltf conformance_framework_pbr + PRIVATE conformance_framework_mikktspace) + +target_include_directories(conformance_framework_gltf + PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}) + +set_target_properties(conformance_framework_gltf + PROPERTIES FOLDER ${CONFORMANCE_TESTS_FOLDER}) diff --git a/src/conformance/framework/gltf/GltfHelper.cpp b/src/conformance/framework/gltf/GltfHelper.cpp new file mode 100644 index 00000000..3c176a95 --- /dev/null +++ b/src/conformance/framework/gltf/GltfHelper.cpp @@ -0,0 +1,701 @@ +// Copyright 2022-2023, The Khronos Group, Inc. +// +// Based in part on code that is: +// Copyright (C) Microsoft Corporation. All Rights Reserved +// Licensed under the MIT License. See License.txt in the project root for license information. +// +// SPDX-License-Identifier: MIT AND Apache-2.0 + +#include "GltfHelper.h" + +#include "common/xr_linear.h" + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define TRIANGLE_VERTEX_COUNT 3 // #define so it can be used in lambdas without capture + +#ifdef _WIN32 + +#ifndef NOMINMAX +#define NOMINMAX +#endif +// only for size comparison static assert +#include + +static_assert(sizeof(XrVector2f) == sizeof(DirectX::XMFLOAT2), "Size of 2D vectors must match"); +static_assert(sizeof(XrVector3f) == sizeof(DirectX::XMFLOAT3), "Size of 3D vectors must match"); +static_assert(sizeof(XrVector4f) == sizeof(DirectX::XMFLOAT4), "Size of 4D vectors must match"); +#endif + +namespace +{ + // The glTF 2 specification recommends using the MikkTSpace algorithm to generate + // tangents when none are available. This function takes a GltfHelper Primitive which has + // no tangents and uses the MikkTSpace algorithm to generate the tangents. This can + // be computationally expensive. + void ComputeTriangleTangents(GltfHelper::Primitive& primitive) + { + // Set up the callbacks so that MikkTSpace can read the Primitive data. + SMikkTSpaceInterface mikkInterface{}; + mikkInterface.m_getNumFaces = [](const SMikkTSpaceContext* pContext) { + auto primitive = static_cast(pContext->m_pUserData); + assert((primitive->Indices.size() % TRIANGLE_VERTEX_COUNT) == 0); // Only triangles are supported. + return (int)(primitive->Indices.size() / TRIANGLE_VERTEX_COUNT); + }; + mikkInterface.m_getNumVerticesOfFace = [](const SMikkTSpaceContext* /* pContext */, int /* iFace */) { + return TRIANGLE_VERTEX_COUNT; + }; + mikkInterface.m_getPosition = [](const SMikkTSpaceContext* pContext, float fvPosOut[], const int iFace, const int iVert) { + auto primitive = static_cast(pContext->m_pUserData); + const auto vertexIndex = primitive->Indices[(iFace * TRIANGLE_VERTEX_COUNT) + iVert]; + memcpy(fvPosOut, &primitive->Vertices[vertexIndex].Position, sizeof(float) * 3); + }; + mikkInterface.m_getNormal = [](const SMikkTSpaceContext* pContext, float fvNormOut[], const int iFace, const int iVert) { + auto primitive = static_cast(pContext->m_pUserData); + const auto vertexIndex = primitive->Indices[(iFace * TRIANGLE_VERTEX_COUNT) + iVert]; + memcpy(fvNormOut, &primitive->Vertices[vertexIndex].Normal, sizeof(float) * 3); + }; + mikkInterface.m_getTexCoord = [](const SMikkTSpaceContext* pContext, float fvTexcOut[], const int iFace, const int iVert) { + auto primitive = static_cast(pContext->m_pUserData); + const auto vertexIndex = primitive->Indices[(iFace * TRIANGLE_VERTEX_COUNT) + iVert]; + memcpy(fvTexcOut, &primitive->Vertices[vertexIndex].TexCoord0, sizeof(float) * 2); + }; + mikkInterface.m_setTSpaceBasic = [](const SMikkTSpaceContext* pContext, const float fvTangent[], const float fSign, const int iFace, + const int iVert) { + auto primitive = static_cast(pContext->m_pUserData); + const auto vertexIndex = primitive->Indices[(iFace * TRIANGLE_VERTEX_COUNT) + iVert]; + primitive->Vertices[vertexIndex].Tangent.x = fvTangent[0]; + primitive->Vertices[vertexIndex].Tangent.y = fvTangent[1]; + primitive->Vertices[vertexIndex].Tangent.z = fvTangent[2]; + // handedness difference, see: + // https://github.com/KhronosGroup/glTF-Sample-Models/issues/174 + // https://github.com/KhronosGroup/glTF/issues/2056 + primitive->Vertices[vertexIndex].Tangent.w = -fSign; + }; + + // Run the MikkTSpace algorithm. + SMikkTSpaceContext mikkContext{}; + mikkContext.m_pUserData = &primitive; + mikkContext.m_pInterface = &mikkInterface; + if (genTangSpaceDefault(&mikkContext) == 0) { + throw std::runtime_error("Failed to generate tangents"); + } + } + + // Generates normals for the trianges in the GltfHelper Primitive object. + void ComputeTriangleNormals(GltfHelper::Primitive& primitive) + { + assert((primitive.Indices.size() % TRIANGLE_VERTEX_COUNT) == 0); // Only triangles are supported. + + // Loop through each triangle + for (uint32_t i = 0; i < primitive.Indices.size(); i += TRIANGLE_VERTEX_COUNT) { + // References to the three vertices of the triangle. + GltfHelper::Vertex& v0 = primitive.Vertices[primitive.Indices[i]]; + GltfHelper::Vertex& v1 = primitive.Vertices[primitive.Indices[i + 1]]; + GltfHelper::Vertex& v2 = primitive.Vertices[primitive.Indices[i + 2]]; + + // Compute normal. Normalization happens later. + XrVector3f d0; + XrVector3f_Sub(&d0, &v2.Position, &v0.Position); + XrVector3f d1; + XrVector3f_Sub(&d1, &v1.Position, &v0.Position); + XrVector3f normal; + XrVector3f_Cross(&normal, &d0, &d1); + + // Add the normal to the three vertices of the triangle. Normals are added + // so that reused vertices will get the average normal (done later). + // Note that the normals are not normalized at this point, so larger triangles + // will have more weight than small triangles which share a vertex. This + // appears to give better results. + XrVector3f_Add(&v0.Normal, &v0.Normal, &normal); + XrVector3f_Add(&v1.Normal, &v1.Normal, &normal); + XrVector3f_Add(&v2.Normal, &v2.Normal, &normal); + } + + // Since the same vertex may have been used by multiple triangles, and the cross product normals + // aren't normalized yet, normalize the computed normals. + for (GltfHelper::Vertex& vertex : primitive.Vertices) { + XrVector3f_Normalize(&vertex.Normal); + } + } + + // Some data, like texCoords, can be represented 32bit float or normalized unsigned short or byte. + // ReadNormalizedFloat provides overloads for all three types. + template + float ReadNormalizedFloat(const uint8_t* ptr); + template <> + float ReadNormalizedFloat(const uint8_t* ptr) + { + return *reinterpret_cast(ptr); + } + template <> + float ReadNormalizedFloat(const uint8_t* ptr) + { + return *reinterpret_cast(ptr) / (float)std::numeric_limits::max(); + } + template <> + float ReadNormalizedFloat(const uint8_t* ptr) + { + return *reinterpret_cast(ptr) / (float)std::numeric_limits::max(); + } + + XrMatrix4x4f Double4x4ToXrMatrix4x4f(const XrMatrix4x4f& defaultMatrix, const std::vector& doubleData) + { + if (doubleData.size() != 16) { + return defaultMatrix; + } + + return XrMatrix4x4f{{(float)doubleData[0], (float)doubleData[1], (float)doubleData[2], (float)doubleData[3], (float)doubleData[4], + (float)doubleData[5], (float)doubleData[6], (float)doubleData[7], (float)doubleData[8], (float)doubleData[9], + (float)doubleData[10], (float)doubleData[11], (float)doubleData[12], (float)doubleData[13], + (float)doubleData[14], (float)doubleData[15]}}; + } + + XrVector3f DoublesToXrVector3f(const XrVector3f& defaultVector, const std::vector& doubleData) + { + if (doubleData.size() != 3) { + return defaultVector; + } + + return XrVector3f{(float)doubleData[0], (float)doubleData[1], (float)doubleData[2]}; + } + + XrQuaternionf DoublesToXrQuaternionf(const XrQuaternionf& defaultVector, const std::vector& doubleData) + { + if (doubleData.size() != 4) { + return defaultVector; + } + + return XrQuaternionf{(float)doubleData[0], (float)doubleData[1], (float)doubleData[2], (float)doubleData[3]}; + } + + // Validate that an accessor does not go out of bounds of the buffer view that it references and that the buffer view does not exceed + // the bounds of the buffer that it references. + void ValidateAccessor(const tinygltf::Accessor& accessor, const tinygltf::BufferView& bufferView, const tinygltf::Buffer& buffer, + size_t byteStride, size_t elementSize) + { + // Make sure the accessor does not go out of range of the buffer view. + if (accessor.byteOffset + (accessor.count - 1) * byteStride + elementSize > bufferView.byteLength) { + throw std::out_of_range("Accessor goes out of range of bufferview."); + } + + // Make sure the buffer view does not go out of range of the buffer. + if (bufferView.byteOffset + bufferView.byteLength > buffer.data.size()) { + throw std::out_of_range("BufferView goes out of range of buffer."); + } + } + + // Reads the tangent data (VEC4) from a glTF primitive into a GltfHelper Primitive. + void ReadTangentToVertexField(const tinygltf::Accessor& accessor, const tinygltf::BufferView& bufferView, + const tinygltf::Buffer& buffer, GltfHelper::Primitive& primitive) + { + if (accessor.type != TINYGLTF_TYPE_VEC4) { + throw std::runtime_error("Accessor for primitive attribute has incorrect type (VEC4 expected)."); + } + + if (accessor.componentType != TINYGLTF_COMPONENT_TYPE_FLOAT) { + throw std::runtime_error("Accessor for primitive attribute has incorrect component type (FLOAT expected)."); + } + + // If stride is not specified, it is tightly packed. + constexpr size_t PackedSize = sizeof(XrVector4f); + const size_t stride = bufferView.byteStride == 0 ? PackedSize : bufferView.byteStride; + ValidateAccessor(accessor, bufferView, buffer, stride, PackedSize); + + // Resize the vertices vector, if necessary, to include room for the attribute data. + // If there are multiple attributes for a primitive, the first one will resize, and the subsequent will not need to. + primitive.Vertices.resize(accessor.count); + + // Copy the attribute value over from the glTF buffer into the appropriate vertex field. + const uint8_t* bufferPtr = buffer.data.data() + bufferView.byteOffset + accessor.byteOffset; + for (size_t i = 0; i < accessor.count; i++, bufferPtr += stride) { + primitive.Vertices[i].Tangent = *reinterpret_cast(bufferPtr); + } + } + + // Reads the TexCoord data (VEC2) from a glTF primitive into a GltfHelper Primitive. + // This function uses a template type to express the VEC2 component type (byte, ushort, or float). + template + void ReadTexCoordToVertexField(const tinygltf::Accessor& accessor, const tinygltf::BufferView& bufferView, + const tinygltf::Buffer& buffer, GltfHelper::Primitive& primitive) + { + // If stride is not specified, it is tightly packed. + constexpr size_t PackedSize = sizeof(TComponentType) * 2; + const size_t stride = bufferView.byteStride == 0 ? PackedSize : bufferView.byteStride; + ValidateAccessor(accessor, bufferView, buffer, stride, PackedSize); + + // Resize the vertices vector, if necessary, to include room for the attribute data. + // If there are multiple attributes for a primitive, the first one will resize, and the subsequent will not need to. + primitive.Vertices.resize(accessor.count); + + // Copy the attribute value over from the glTF buffer into the appropriate vertex field. + const uint8_t* bufferPtr = buffer.data.data() + bufferView.byteOffset + accessor.byteOffset; + for (size_t i = 0; i < accessor.count; i++, bufferPtr += stride) { + (primitive.Vertices[i].*field).x = ReadNormalizedFloat(bufferPtr + sizeof(TComponentType) * 0); + (primitive.Vertices[i].*field).y = ReadNormalizedFloat(bufferPtr + sizeof(TComponentType) * 1); + } + } + + // Reads the TexCoord data (VEC2) from a glTF primitive into a GltfHelper Primitive. + template + void ReadTexCoordToVertexField(const tinygltf::Accessor& accessor, const tinygltf::BufferView& bufferView, + const tinygltf::Buffer& buffer, GltfHelper::Primitive& primitive) + { + if (accessor.type != TINYGLTF_TYPE_VEC2) { + throw std::runtime_error("Accessor for primitive TexCoord must have VEC2 type."); + } + + if (accessor.componentType == TINYGLTF_COMPONENT_TYPE_FLOAT) { + ReadTexCoordToVertexField(accessor, bufferView, buffer, primitive); + } + else if (accessor.componentType == TINYGLTF_COMPONENT_TYPE_UNSIGNED_BYTE) { + if (!accessor.normalized) { + throw std::runtime_error("Accessor for TEXTCOORD_n unsigned byte must be normalized."); + } + ReadTexCoordToVertexField(accessor, bufferView, buffer, primitive); + } + else if (accessor.componentType == TINYGLTF_COMPONENT_TYPE_UNSIGNED_SHORT) { + if (!accessor.normalized) { + throw std::runtime_error("Accessor for TEXTCOORD_n unsigned short must be normalized."); + } + ReadTexCoordToVertexField(accessor, bufferView, buffer, primitive); + } + else { + throw std::runtime_error("Accessor for TEXTCOORD_n uses unsupported component type."); + } + } + + // Reads the Color data (VEC3 or VEC4) from a glTF primitive into a GltfHelper Primitive. + // This function uses a template type to express the VEC3/4 component type (byte, ushort, or float). + template + void ReadColorToVertexField(size_t componentCount, const tinygltf::Accessor& accessor, const tinygltf::BufferView& bufferView, + const tinygltf::Buffer& buffer, GltfHelper::Primitive& primitive) + { + // If stride is not specified, it is tightly packed. + const size_t packedSize = sizeof(TComponentType) * componentCount; + const size_t stride = bufferView.byteStride == 0 ? packedSize : bufferView.byteStride; + ValidateAccessor(accessor, bufferView, buffer, stride, packedSize); + + // Resize the vertices vector, if necessary, to include room for the attribute data. + // If there are multiple attributes for a primitive, the first one will resize, and the subsequent will not need to. + primitive.Vertices.resize(accessor.count); + + // Copy the attribute value over from the glTF buffer into the appropriate vertex field. + const uint8_t* bufferPtr = buffer.data.data() + bufferView.byteOffset + accessor.byteOffset; + for (size_t i = 0; i < accessor.count; i++, bufferPtr += stride) { + (primitive.Vertices[i].*field).r = ReadNormalizedFloat(bufferPtr + sizeof(TComponentType) * 0); + (primitive.Vertices[i].*field).g = ReadNormalizedFloat(bufferPtr + sizeof(TComponentType) * 1); + (primitive.Vertices[i].*field).b = ReadNormalizedFloat(bufferPtr + sizeof(TComponentType) * 2); + + if (componentCount == 4) // Alpha + { + (primitive.Vertices[i].*field).a = ReadNormalizedFloat(bufferPtr + sizeof(TComponentType) * 3); + } + } + } + + // Reads the Color data (VEC3/4) from a glTF primitive into a GltfHelper Primitive. + template + void ReadColorToVertexField(const tinygltf::Accessor& accessor, const tinygltf::BufferView& bufferView, const tinygltf::Buffer& buffer, + GltfHelper::Primitive& primitive) + { + int componentCount; + if (accessor.type == TINYGLTF_TYPE_VEC3) { + componentCount = 3; + } + else if (accessor.type == TINYGLTF_TYPE_VEC4) { + componentCount = 4; + } + else { + throw std::runtime_error("Accessor for primitive Color must have VEC3 or VEC4 type."); + } + + if (accessor.componentType == TINYGLTF_COMPONENT_TYPE_FLOAT) { + ReadColorToVertexField(componentCount, accessor, bufferView, buffer, primitive); + } + else if (accessor.componentType == TINYGLTF_COMPONENT_TYPE_UNSIGNED_BYTE) { + if (!accessor.normalized) { + throw std::runtime_error("Accessor for COLOR_0 unsigned byte must be normalized."); + } + ReadColorToVertexField(componentCount, accessor, bufferView, buffer, primitive); + } + else if (accessor.componentType == TINYGLTF_COMPONENT_TYPE_UNSIGNED_SHORT) { + if (!accessor.normalized) { + throw std::runtime_error("Accessor for COLOR_0 unsigned short must be normalized."); + } + ReadColorToVertexField(componentCount, accessor, bufferView, buffer, primitive); + } + else { + throw std::runtime_error("Accessor for COLOR_0 uses unsupported component type."); + } + } + + // Reads VEC3 attribute data (like POSITION and NORMAL) from a glTF primitive into a GltfHelper Primitive. The specific Vertex field is specified as a template parameter. + template + void ReadVec3ToVertexField(const tinygltf::Accessor& accessor, const tinygltf::BufferView& bufferView, const tinygltf::Buffer& buffer, + GltfHelper::Primitive& primitive) + { + if (accessor.type != TINYGLTF_TYPE_VEC3) { + throw std::runtime_error("Accessor for primitive attribute has incorrect type (VEC3 expected)."); + } + + if (accessor.componentType != TINYGLTF_COMPONENT_TYPE_FLOAT) { + throw std::runtime_error("Accessor for primitive attribute has incorrect component type (FLOAT expected)."); + } + + // If stride is not specified, it is tightly packed. + constexpr size_t PackedSize = sizeof(XrVector3f); + const size_t stride = bufferView.byteStride == 0 ? PackedSize : bufferView.byteStride; + ValidateAccessor(accessor, bufferView, buffer, stride, PackedSize); + + // Resize the vertices vector, if necessary, to include room for the attribute data. + // If there are multiple attributes for a primitive, the first one will resize, and the subsequent will not need to. + primitive.Vertices.resize(accessor.count); + + // Copy the attribute value over from the glTF buffer into the appropriate vertex field. + const uint8_t* bufferPtr = buffer.data.data() + bufferView.byteOffset + accessor.byteOffset; + for (size_t i = 0; i < accessor.count; i++, bufferPtr += stride) { + (primitive.Vertices[i].*field) = *reinterpret_cast(bufferPtr); + } + } + + // Load a primitive's (vertex) attributes. Vertex attributes can be positions, normals, tangents, texture coordinates, colors, and more. + void LoadAttributeAccessor(const tinygltf::Model& gltfModel, const std::string& attributeName, int accessorId, + GltfHelper::Primitive& primitive) + { + const auto& accessor = gltfModel.accessors.at(accessorId); + + if (accessor.bufferView == -1) { + throw std::runtime_error("Accessor for primitive attribute specifies no bufferview."); + } + + // WARNING: This version of the tinygltf loader does not support sparse accessors, so neither does this renderer. + + const tinygltf::BufferView& bufferView = gltfModel.bufferViews.at(accessor.bufferView); + if (bufferView.target != TINYGLTF_TARGET_ARRAY_BUFFER && + bufferView.target != 0) // Allow 0 (not specified) even though spec doesn't seem to allow this (BoomBox GLB fails) + { + throw std::runtime_error("Accessor for primitive attribute uses bufferview with invalid 'target' type."); + } + + const tinygltf::Buffer& buffer = gltfModel.buffers.at(bufferView.buffer); + + if (attributeName.compare("POSITION") == 0) { + ReadVec3ToVertexField<&GltfHelper::Vertex::Position>(accessor, bufferView, buffer, primitive); + } + else if (attributeName.compare("NORMAL") == 0) { + ReadVec3ToVertexField<&GltfHelper::Vertex::Normal>(accessor, bufferView, buffer, primitive); + } + else if (attributeName.compare("TANGENT") == 0) { + ReadTangentToVertexField(accessor, bufferView, buffer, primitive); + } + else if (attributeName.compare("TEXCOORD_0") == 0) { + ReadTexCoordToVertexField<&GltfHelper::Vertex::TexCoord0>(accessor, bufferView, buffer, primitive); + } + else if (attributeName.compare("COLOR_0") == 0) { + ReadColorToVertexField<&GltfHelper::Vertex::Color0>(accessor, bufferView, buffer, primitive); + } + else { + return; // Ignore unsupported vertex accessors like TEXCOORD_1. + } + } + + // Reads index data from a glTF primitive into a GltfHelper Primitive. glTF indices may be 8bit, 16bit or 32bit integers. + // This will coalesce indices from the source type(s) into a 32bit integer. + template + void ReadIndices(const tinygltf::Accessor& accessor, const tinygltf::BufferView& bufferView, const tinygltf::Buffer& buffer, + GltfHelper::Primitive& primitive) + { + if (bufferView.target != TINYGLTF_TARGET_ELEMENT_ARRAY_BUFFER && + bufferView.target != 0) // Allow 0 (not specified) even though spec doesn't seem to allow this (BoomBox GLB fails) + { + throw std::runtime_error("Accessor for indices uses bufferview with invalid 'target' type."); + } + + constexpr size_t ComponentSizeBytes = sizeof(TSrcIndex); + if (bufferView.byteStride != 0 && bufferView.byteStride != ComponentSizeBytes) // Index buffer must be packed per glTF spec. + { + throw std::runtime_error("Accessor for indices uses bufferview with invalid 'byteStride'."); + } + + ValidateAccessor(accessor, bufferView, buffer, ComponentSizeBytes, ComponentSizeBytes); + + if ((accessor.count % 3) != 0) // Since only triangles are supported, enforce that the number of indices is divisible by 3. + { + throw std::runtime_error("Unexpected number of indices for triangle primitive"); + } + + const TSrcIndex* indexBuffer = reinterpret_cast(buffer.data.data() + bufferView.byteOffset + accessor.byteOffset); + for (uint32_t i = 0; i < accessor.count; i++) { + primitive.Indices.push_back(*(indexBuffer + i)); + } + } + + // Reads index data from a glTF primitive into a GltfHelper Primitive. + void LoadIndexAccessor(const tinygltf::Model& gltfModel, const tinygltf::Accessor& accessor, GltfHelper::Primitive& primitive) + { + if (accessor.type != TINYGLTF_TYPE_SCALAR) { + throw std::runtime_error("Accessor for indices specifies invalid 'type'."); + } + + if (accessor.bufferView == -1) { + throw std::runtime_error("Index accessor without bufferView is currently not supported."); + } + + const tinygltf::BufferView& bufferView = gltfModel.bufferViews.at(accessor.bufferView); + const tinygltf::Buffer& buffer = gltfModel.buffers.at(bufferView.buffer); + + if (accessor.componentType == TINYGLTF_COMPONENT_TYPE_UNSIGNED_BYTE) { + ReadIndices(accessor, bufferView, buffer, primitive); + } + else if (accessor.componentType == TINYGLTF_COMPONENT_TYPE_UNSIGNED_SHORT) { + ReadIndices(accessor, bufferView, buffer, primitive); + } + else if (accessor.componentType == TINYGLTF_COMPONENT_TYPE_UNSIGNED_INT) { + ReadIndices(accessor, bufferView, buffer, primitive); + } + else { + throw std::runtime_error("Accessor for indices specifies invalid 'componentType'."); + } + } +} // namespace + +namespace GltfHelper +{ + XrMatrix4x4f ReadNodeLocalTransform(const tinygltf::Node& gltfNode) + { + // A node may specify either a 4x4 matrix or TRS (Translation-Rotation-Scale) values, but not both. + if (gltfNode.matrix.size() == 16) { + XrMatrix4x4f identityMatrix; + XrMatrix4x4f_CreateIdentity(&identityMatrix); + return Double4x4ToXrMatrix4x4f(identityMatrix, gltfNode.matrix); + } + else { + // No matrix is present, so construct a matrix from the TRS values (each one is optional). + XrVector3f translation = DoublesToXrVector3f(XrVector3f{0, 0, 0}, gltfNode.translation); + XrQuaternionf rotation = DoublesToXrQuaternionf(XrQuaternionf{0, 0, 0, 1}, gltfNode.rotation); + XrVector3f scale = DoublesToXrVector3f(XrVector3f{1, 1, 1}, gltfNode.scale); + XrMatrix4x4f mat; + XrMatrix4x4f_CreateTranslationRotationScale(&mat, &translation, &rotation, &scale); + return mat; + } + } + + Primitive ReadPrimitive(const tinygltf::Model& gltfModel, const tinygltf::Primitive& gltfPrimitive) + { + if (gltfPrimitive.mode != TINYGLTF_MODE_TRIANGLES) { + throw std::runtime_error("Unsupported primitive mode. Only TINYGLTF_MODE_TRIANGLES is supported."); + } + + Primitive primitive; + + // glTF vertex data is stored in an attribute dictionary. Loop through each attribute and insert it into the GltfHelper primitive. + for (const auto& attribute : gltfPrimitive.attributes) { + LoadAttributeAccessor(gltfModel, attribute.first /* attribute name */, attribute.second /* accessor index */, primitive); + } + + if (gltfPrimitive.indices != -1) { + // If indices are specified for the glTF primitive, read them into the GltfHelper Primitive. + LoadIndexAccessor(gltfModel, gltfModel.accessors.at(gltfPrimitive.indices), primitive); + } + else { + // When indices is not defined, the primitives should be rendered without indices using drawArrays() + // This is the equivalent to having an index in sequence for each vertex. + const uint32_t vertexCount = (uint32_t)primitive.Vertices.size(); + if ((vertexCount % 3) != 0) { + throw std::runtime_error("Non-indexed triangle-based primitive must have number of vertices divisible by 3."); + } + + primitive.Indices.reserve(primitive.Indices.size() + vertexCount); + for (uint32_t i = 0; i < vertexCount; i++) { + primitive.Indices.push_back(i); + } + } + + // If normals are missing, compute flat normals. Normals must be computed before tangents. + if (gltfPrimitive.attributes.find("NORMAL") == std::end(gltfPrimitive.attributes)) { + ComputeTriangleNormals(primitive); + } + + // If tangents are missing, compute tangents. + if (gltfPrimitive.attributes.find("TANGENT") == std::end(gltfPrimitive.attributes)) { + ComputeTriangleTangents(primitive); + } + + // If colors are missing, set to default. + if (gltfPrimitive.attributes.find("COLOR_0") == std::end(gltfPrimitive.attributes)) { + for (GltfHelper::Vertex& vertex : primitive.Vertices) { + vertex.Color0 = {1, 1, 1, 1}; + } + } + + return primitive; + } + + const Primitive& PrimitiveCache::ReadPrimitive(const tinygltf::Primitive& gltfPrimitive) + { + PrimitiveAttributesVec attributesVec{}; + for (auto const& attr : gltfPrimitive.attributes) { + attributesVec.push_back(std::make_pair(attr.first, attr.second)); + } + PrimitiveKey key = std::make_pair(attributesVec, gltfPrimitive.indices); + auto primitiveIt = m_primitiveCache.find(key); + if (primitiveIt != m_primitiveCache.end()) { + return primitiveIt->second; + } + + Primitive primitive = GltfHelper::ReadPrimitive(m_model, gltfPrimitive); + return m_primitiveCache.emplace(key, std::move(primitive)).first->second; + } + + Material ReadMaterial(const tinygltf::Model& gltfModel, const tinygltf::Material& gltfMaterial) + { + // Read an optional VEC4 parameter if available, otherwise use the default. + auto readParameterFactorAsColor4 = [](const tinygltf::ParameterMap& parameters, const char* name, const XrColor4f& defaultValue) { + auto c = parameters.find(name); + return (c != parameters.end() && c->second.number_array.size() == 4) + ? XrColor4f{(float)c->second.number_array[0], (float)c->second.number_array[1], (float)c->second.number_array[2], + (float)c->second.number_array[3]} + : defaultValue; + }; + + // Read an optional VEC3 parameter if available, otherwise use the default. + auto readParameterFactorAsVec3 = [](const tinygltf::ParameterMap& parameters, const char* name, const XrVector3f& defaultValue) { + auto c = parameters.find(name); + return (c != parameters.end() && c->second.number_array.size() == 3) + ? XrVector3f{(float)c->second.number_array[0], (float)c->second.number_array[1], (float)c->second.number_array[2]} + : defaultValue; + }; + + // Read an optional scalar parameter if available, otherwise use the default. + auto readParameterFactorAsScalar = [](const tinygltf::ParameterMap& parameters, const char* name, double defaultValue) { + auto c = parameters.find(name); + return (c != parameters.end() && c->second.has_number_value) ? c->second.number_value : defaultValue; + }; + + // Read an optional boolean parameter if available, otherwise use the default. + auto readParameterFactorAsBoolean = [](const tinygltf::ParameterMap& parameters, const char* name, bool defaultValue) { + auto c = parameters.find(name); + return c != parameters.end() ? c->second.bool_value : defaultValue; + }; + + // Read an optional boolean parameter if available, otherwise use the default. + auto readParameterFactorAsString = [](const tinygltf::ParameterMap& parameters, const char* name, const char* defaultValue) { + auto c = parameters.find(name); + return c != parameters.end() ? c->second.string_value : defaultValue; + }; + + // Read a specific texture from a tinygltf material parameter map. + auto loadTextureFromParameter = [&](const tinygltf::ParameterMap& parameterMap, const char* textureName) { + Material::Texture texture{}; + + const auto& textureIt = parameterMap.find(textureName); + if (textureIt != std::end(parameterMap)) { + const int textureIndex = (int)textureIt->second.json_double_value.at("index"); + const tinygltf::Texture& gltfTexture = gltfModel.textures.at(textureIndex); + if (gltfTexture.source != -1) { + texture.Image = &gltfModel.images.at(gltfTexture.source); + } + + if (gltfTexture.sampler != -1) { + texture.Sampler = &gltfModel.samplers.at(gltfTexture.sampler); + } + } + + return texture; + }; + + // Read a scalar value from a tinygltf material parameter map. + auto loadScalarFromParameter = [&](const tinygltf::ParameterMap& parameterMap, const char* name, const char* scalarField, + double defaultValue) { + const auto& textureIt = parameterMap.find(name); + if (textureIt != std::end(parameterMap)) { + const auto& jsonDoubleValues = textureIt->second.json_double_value; + const auto& jsonDoubleIt = jsonDoubleValues.find(scalarField); + if (jsonDoubleIt != std::end(jsonDoubleValues)) { + return jsonDoubleIt->second; + } + } + + return defaultValue; + }; + + // + // Read all of the optional material fields from the tinygltf object model and store them in a GltfHelper Material object + // coalesced with proper defaults when needed. + // + Material material; + + material.BaseColorTexture = loadTextureFromParameter(gltfMaterial.values, "baseColorTexture"); + material.BaseColorFactor = readParameterFactorAsColor4(gltfMaterial.values, "baseColorFactor", XrColor4f{1, 1, 1, 1}); + + material.MetallicRoughnessTexture = loadTextureFromParameter(gltfMaterial.values, "metallicRoughnessTexture"); + material.MetallicFactor = (float)readParameterFactorAsScalar(gltfMaterial.values, "metallicFactor", 1); + material.RoughnessFactor = (float)readParameterFactorAsScalar(gltfMaterial.values, "roughnessFactor", 1); + + material.EmissiveTexture = loadTextureFromParameter(gltfMaterial.additionalValues, "emissiveTexture"); + material.EmissiveFactor = readParameterFactorAsVec3(gltfMaterial.additionalValues, "emissiveFactor", XrVector3f{0, 0, 0}); + + material.NormalTexture = loadTextureFromParameter(gltfMaterial.additionalValues, "normalTexture"); + material.NormalScale = (float)loadScalarFromParameter(gltfMaterial.additionalValues, "normalTexture", "scale", 1.0); + + material.OcclusionTexture = loadTextureFromParameter(gltfMaterial.additionalValues, "occlusionTexture"); + material.OcclusionStrength = (float)loadScalarFromParameter(gltfMaterial.additionalValues, "occlusionTexture", "strength", 1.0); + + auto alphaMode = readParameterFactorAsString(gltfMaterial.additionalValues, "alphaMode", "OPAQUE"); + material.AlphaMode = + alphaMode == "MASK" ? AlphaModeType::Mask : alphaMode == "BLEND" ? AlphaModeType::Blend : AlphaModeType::Opaque; + material.DoubleSided = readParameterFactorAsBoolean(gltfMaterial.additionalValues, "doubleSided", false); + material.AlphaCutoff = (float)readParameterFactorAsScalar(gltfMaterial.additionalValues, "alphaCutoff", 0.5f); + + return material; + } + + const uint8_t* ReadImageAsRGBA(const tinygltf::Image& image, std::vector* tempBuffer) + { + // The image vector (image.image) will be populated if the image was successfully loaded by glTF. + if (image.width > 0 && image.height > 0) { + if (image.width * image.height * image.component != image.image.size()) { + throw std::runtime_error("Invalid image buffer size"); + } + + // Not supported: STBI_grey (DXGI_FORMAT_R8_UNORM?) and STBI_grey_alpha. + if (image.component == 3) { + // Convert RGB to RGBA. + tempBuffer->resize(image.width * image.height * 4); + for (int y = 0; y < image.height; ++y) { + const uint8_t* src = image.image.data() + y * image.width * 3; + uint8_t* dest = tempBuffer->data() + y * image.width * 4; + for (int x = image.width - 1; x >= 0; --x, src += 3, dest += 4) { + dest[0] = src[0]; + dest[1] = src[1]; + dest[2] = src[2]; + dest[3] = 255; + } + } + + return tempBuffer->data(); + } + else if (image.component == 4) { + // Already RGBA, no conversion needed + return image.image.data(); + } + } + + return nullptr; + } +} // namespace GltfHelper diff --git a/src/conformance/framework/gltf/GltfHelper.h b/src/conformance/framework/gltf/GltfHelper.h new file mode 100644 index 00000000..99017179 --- /dev/null +++ b/src/conformance/framework/gltf/GltfHelper.h @@ -0,0 +1,121 @@ +// Copyright 2022-2023, The Khronos Group, Inc. +// +// Based in part on code that is: +// Copyright (C) Microsoft Corporation. All Rights Reserved +// Licensed under the MIT License. See License.txt in the project root for license information. +// +// SPDX-License-Identifier: MIT AND Apache-2.0 + +// GltfHelper provides additional glTF parsing functionality, built on top of tinygltf. +// This library has no rendering dependencies and can be used for any purpose, such as +// format transcoding or by a rendering engine. + +#pragma once + +#include "common/xr_linear.h" + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace tinygltf +{ + class Node; + class Model; + struct Primitive; + struct Material; + struct Image; + struct Sampler; +} // namespace tinygltf + +namespace GltfHelper +{ + // Vertex data. + struct Vertex + { + XrVector3f Position; + XrVector3f Normal; + XrVector4f Tangent; + XrVector2f TexCoord0; + XrColor4f Color0; + // Note: This implementation does not currently support TexCoord1 attributes. + }; + + // A primitive is a collection of vertices and indices. + struct Primitive + { + std::vector Vertices; + std::vector Indices; + }; + + enum class AlphaModeType + { + Opaque, + Mask, + Blend + }; + + // Metallic-roughness material definition. + struct Material + { + struct Texture + { + const tinygltf::Image* Image; + const tinygltf::Sampler* Sampler; + }; + + Texture BaseColorTexture; + Texture MetallicRoughnessTexture; + Texture EmissiveTexture; + Texture NormalTexture; + Texture OcclusionTexture; + + XrColor4f BaseColorFactor; + float MetallicFactor; + float RoughnessFactor; + XrVector3f EmissiveFactor; + + float NormalScale; + float OcclusionStrength; + + AlphaModeType AlphaMode; + float AlphaCutoff; + bool DoubleSided; + }; + + class PrimitiveCache + { + public: + explicit PrimitiveCache(const tinygltf::Model& gltfModel) : m_model(gltfModel) + { + } + const Primitive& ReadPrimitive(const tinygltf::Primitive& gltfPrimitive); + + private: + using PrimitiveAttributesVec = std::vector>; // first is name, second is accessor + using PrimitiveKey = std::pair; // first is attributes, second is indices + std::reference_wrapper m_model; + std::map m_primitiveCache{}; + }; + + // Reads the "transform" or "TRS" data for a Node as an XrMatrix4x4f. + XrMatrix4x4f ReadNodeLocalTransform(const tinygltf::Node& gltfNode); + + // Parses the primitive attributes and indices from the glTF accessors/bufferviews/buffers into a common simplified data structure, the Primitive. + Primitive ReadPrimitive(const tinygltf::Model& gltfModel, const tinygltf::Primitive& gltfPrimitive); + + // Parses the material values into a simplified data structure, the Material. + Material ReadMaterial(const tinygltf::Model& gltfModel, const tinygltf::Material& gltfMaterial); + + // Converts the image to RGBA if necessary. Requires a temporary buffer only if it needs to be converted. + const uint8_t* ReadImageAsRGBA(const tinygltf::Image& image, std::vector* tempBuffer); +} // namespace GltfHelper diff --git a/src/conformance/framework/gltf_model.h b/src/conformance/framework/gltf_model.h new file mode 100644 index 00000000..6d9f9ad3 --- /dev/null +++ b/src/conformance/framework/gltf_model.h @@ -0,0 +1,63 @@ +// Copyright (c) 2022-2023, The Khronos Group Inc. +// +// SPDX-License-Identifier: MIT +#pragma once + +#include "gltf.h" + +#include "gltf/GltfHelper.h" +#include "pbr/GltfLoader.h" +#include "pbr/PbrSharedState.h" + +#include + +namespace Conformance +{ + + /// Templated base class for API-specific model objects in the main CTS code. + template + class GltfModelBase + { + + public: + GltfModelBase(ResourcesType& pbrResources, std::shared_ptr gltf, std::shared_ptr pbrModel = nullptr, + Pbr::FillMode fillMode = Pbr::FillMode::Solid) + : m_gltf(gltf) + , m_pbrModel(pbrModel != nullptr ? std::move(pbrModel) : Gltf::FromGltfObject(pbrResources, *gltf)) + , m_fillMode(fillMode) + { + } + + void SetModel(std::shared_ptr&& model) + { + m_pbrModel = std::move(model); + } + const std::shared_ptr& GetModel() const noexcept + { + return m_pbrModel; + } + + void SetFillMode(const Pbr::FillMode& fillMode) + { + m_fillMode = fillMode; + } + + Pbr::FillMode GetFillMode() const noexcept + { + return m_fillMode; + } + + void SetBaseColorFactor(ResourcesType& pbrResources, Pbr::RGBAColor color) + { + for (uint32_t k = 0; k < GetModel()->GetPrimitiveCount(); k++) { + auto& material = pbrResources.GetPrimitive(GetModel()->GetPrimitive(k)).GetMaterial(); + material->Parameters().BaseColorFactor = color; + } + } + + private: + std::shared_ptr m_gltf; + std::shared_ptr m_pbrModel; + Pbr::FillMode m_fillMode; + }; +} // namespace Conformance diff --git a/src/conformance/framework/graphics_plugin.h b/src/conformance/framework/graphics_plugin.h index baf6e864..669cba14 100644 --- a/src/conformance/framework/graphics_plugin.h +++ b/src/conformance/framework/graphics_plugin.h @@ -20,11 +20,14 @@ #include "conformance_utils.h" #include "conformance_framework.h" #include "utilities/Geometry.h" +#include "utilities/throw_helpers.h" +#include "gltf.h" #include "RGBAImage.h" +#include "pbr/PbrModel.h" #include -#include "nonstd/span.hpp" -#include "nonstd/type.hpp" +#include +#include #include #include #include @@ -54,7 +57,7 @@ #if defined(__APPLE__) #include #else -#include +#include "common/gfxwrapper_opengl.h" #endif // defined(__APPLE__) #endif // defined(XR_USE_GRAPHICS_API_OPENGL) @@ -145,12 +148,45 @@ namespace Conformance /// They are "null" by default, so may be tested for validity by comparison against a default-constructed instance. using GLTFHandle = nonstd::equality; + using NodeHandle = nonstd::ordered; + + struct NodeParams + { + XrPosef pose; + bool visible; + }; + + static inline std::shared_ptr LoadGLTF(span data) + { + tinygltf::Model model; + tinygltf::TinyGLTF loader; + std::string err; + std::string warn; + bool loadedModel = loader.LoadBinaryFromMemory(&model, &err, &warn, data.data(), (unsigned int)data.size()); + if (!warn.empty()) { + // ReportF("glTF WARN: %s", &warn); + } + + if (!err.empty()) { + XRC_THROW("glTF ERR: " + err); + } + + if (!loadedModel) { + XRC_THROW("Failed to load glTF model provided."); + } + + return std::make_shared(std::move(model)); + } + /// A drawable GLTF model, consisting of a reference to plugin-specific data for a GLTF model, plus pose and scale. struct GLTFDrawable { GLTFHandle handle; DrawableParams params; + // or unordered_map, probably not significant + std::map nodesAndParams; + GLTFDrawable(GLTFHandle handle, XrPosef pose = XrPosefCPP{}, XrVector3f scale = {1.0, 1.0, 1.0}) : handle(handle), params(pose, scale) { @@ -322,6 +358,12 @@ namespace Conformance /// This handle expires when the internal data is cleared in Shutdown() and ShutdownDevice(). virtual MeshHandle MakeSimpleMesh(span idx, span vtx) = 0; + /// Create internal data for a glTF model, returning a handle to refer to it. + /// This handle expires when the internal data is cleared in Shutdown() and ShutdownDevice(). + virtual GLTFHandle LoadGLTF(span data) = 0; + + virtual std::shared_ptr GetModel(GLTFHandle handle) const = 0; + /// Convenience helper function to make a mesh that is our standard cube (with R, G, B faces along X, Y, Z, respectively) MeshHandle MakeCubeMesh() { diff --git a/src/conformance/framework/graphics_plugin_d3d11.cpp b/src/conformance/framework/graphics_plugin_d3d11.cpp index b5c9b551..deca352a 100644 --- a/src/conformance/framework/graphics_plugin_d3d11.cpp +++ b/src/conformance/framework/graphics_plugin_d3d11.cpp @@ -16,28 +16,31 @@ #if defined(XR_USE_GRAPHICS_API_D3D11) -#include "graphics_plugin.h" -#include "common/xr_linear.h" #include "conformance_framework.h" +#include "graphics_plugin.h" +#include "graphics_plugin_d3d11_gltf.h" #include "graphics_plugin_impl_helpers.h" #include "swapchain_image_data.h" + +#include "common/xr_linear.h" +#include "common/xr_dependencies.h" +#include "pbr/D3D11/D3D11Resources.h" +#include "pbr/D3D11/D3D11Texture.h" #include "utilities/Geometry.h" #include "utilities/d3d_common.h" #include "utilities/swapchain_parameters.h" #include "utilities/throw_helpers.h" -#include - -#include - #include #include +#include #include -#include +#include #include // For Microsoft::WRL::ComPtr #include #include +#include using namespace Microsoft::WRL; using namespace DirectX; @@ -204,6 +207,10 @@ namespace Conformance MeshHandle MakeSimpleMesh(span idx, span vtx) override; + GLTFHandle LoadGLTF(span data) override; + + std::shared_ptr GetModel(GLTFHandle handle) const override; + void RenderView(const XrCompositionLayerProjectionView& layerView, const XrSwapchainImageBaseHeader* colorSwapchainImage, const RenderParams& params) override; @@ -218,7 +225,7 @@ namespace Conformance ComPtr d3d11Device; ComPtr d3d11DeviceContext; - // Resources needed for rendering cubes + // Resources needed for rendering cubes, meshes and glTFs ComPtr vertexShader; ComPtr pixelShader; ComPtr inputLayout; @@ -227,6 +234,9 @@ namespace Conformance MeshHandle m_cubeMesh; VectorWithGenerationCountedHandles m_meshes; + VectorWithGenerationCountedHandles m_gltfs; + + std::unique_ptr pbrResources; SwapchainImageDataMap m_swapchainImageDataMap; }; @@ -238,8 +248,8 @@ namespace Conformance D3D11GraphicsPlugin::~D3D11GraphicsPlugin() { - ShutdownDevice(); - Shutdown(); + D3D11GraphicsPlugin::ShutdownDevice(); + D3D11GraphicsPlugin::Shutdown(); } bool D3D11GraphicsPlugin::Initialize() @@ -373,6 +383,15 @@ namespace Conformance d3d11Device->CreateBuffer(&viewProjectionConstantBufferDesc, nullptr, viewProjectionCBuffer.ReleaseAndGetAddressOf())); m_cubeMesh = MakeCubeMesh(); + + pbrResources = std::make_unique(d3d11Device.Get()); + pbrResources->SetLight({0.0f, 0.7071067811865475f, 0.7071067811865475f}, Pbr::RGB::White); + + // Read the BRDF Lookup Table used by the PBR system into a DirectX texture. + std::vector brdfLutFileData = ReadFileBytes("brdf_lut.png"); + Microsoft::WRL::ComPtr brdfLutResourceView = + Pbr::D3D11Texture::LoadTextureImage(d3d11Device.Get(), brdfLutFileData.data(), (uint32_t)brdfLutFileData.size()); + pbrResources->SetBrdfLut(brdfLutResourceView.Get()); } return true; @@ -409,6 +428,8 @@ namespace Conformance m_cubeMesh = {}; m_meshes.clear(); + m_gltfs.clear(); + pbrResources.reset(); d3d11DeviceContext.Reset(); d3d11Device.Reset(); @@ -668,6 +689,17 @@ namespace Conformance return handle; } + inline GLTFHandle D3D11GraphicsPlugin::LoadGLTF(span data) + { + auto handle = m_gltfs.emplace_back(*pbrResources, Conformance::LoadGLTF(data)); + return handle; + } + + inline std::shared_ptr D3D11GraphicsPlugin::GetModel(GLTFHandle handle) const + { + return m_gltfs[handle].GetModel(); + } + void D3D11GraphicsPlugin::RenderView(const XrCompositionLayerProjectionView& layerView, const XrSwapchainImageBaseHeader* colorSwapchainImage, const RenderParams& params) { @@ -742,6 +774,24 @@ namespace Conformance for (const auto& mesh : params.meshes) { drawMesh(mesh); } + + // Render each gltf + for (const auto& gltfHandle : params.glTFs) { + D3D11GLTF& gltf = m_gltfs[gltfHandle.handle]; + // Compute and update the model transform. + + XrMatrix4x4f modelToWorld; + XrMatrix4x4f_CreateTranslationRotationScale(&modelToWorld, &gltfHandle.params.pose.position, + &gltfHandle.params.pose.orientation, &gltfHandle.params.scale); + XrMatrix4x4f viewMatrix; + XrVector3f unitScale = {1, 1, 1}; + XrMatrix4x4f_CreateTranslationRotationScale(&viewMatrix, &layerView.pose.position, &layerView.pose.orientation, &unitScale); + XrMatrix4x4f viewMatrixInverse; + XrMatrix4x4f_Invert(&viewMatrixInverse, &viewMatrix); + pbrResources->SetViewProjection(LoadXrMatrix(viewMatrixInverse), LoadXrMatrix(projectionMatrix)); + + gltf.Render(d3d11DeviceContext, *pbrResources, modelToWorld); + } } std::shared_ptr CreateGraphicsPlugin_D3D11(std::shared_ptr platformPlugin) diff --git a/src/conformance/framework/graphics_plugin_d3d11_gltf.cpp b/src/conformance/framework/graphics_plugin_d3d11_gltf.cpp new file mode 100644 index 00000000..2fd697e2 --- /dev/null +++ b/src/conformance/framework/graphics_plugin_d3d11_gltf.cpp @@ -0,0 +1,37 @@ +// Copyright (c) 2022-2023, The Khronos Group Inc. +// +// SPDX-License-Identifier: MIT +#if defined(XR_USE_GRAPHICS_API_D3D12) && !defined(MISSING_DIRECTX_COLORS) + +#include "graphics_plugin_d3d11_gltf.h" + +#include "conformance_framework.h" +#include "report.h" + +#include "pbr/D3D12/D3D12Primitive.h" +#include "pbr/D3D12/D3D12Resources.h" +#include "pbr/GltfLoader.h" +#include "utilities/d3d_common.h" +#include "utilities/throw_helpers.h" + +#include +#include + +using namespace DirectX; + +namespace Conformance +{ + void D3D11GLTF::Render(ComPtr deviceContext, Pbr::D3D11Resources& resources, XrMatrix4x4f& modelToWorld) const + { + if (!GetModel()) { + return; + } + + resources.SetFillMode(GetFillMode()); + resources.SetModelToWorld(LoadXrMatrix(modelToWorld), deviceContext.Get()); + resources.Bind(deviceContext.Get()); + GetModel()->Render(resources, deviceContext.Get()); + } + +} // namespace Conformance +#endif diff --git a/src/conformance/framework/graphics_plugin_d3d11_gltf.h b/src/conformance/framework/graphics_plugin_d3d11_gltf.h new file mode 100644 index 00000000..c8a29ac2 --- /dev/null +++ b/src/conformance/framework/graphics_plugin_d3d11_gltf.h @@ -0,0 +1,39 @@ +// Copyright (c) 2022-2023, The Khronos Group Inc. +// +// SPDX-License-Identifier: MIT + +#pragma once + +#if defined(XR_USE_GRAPHICS_API_D3D11) && !defined(MISSING_DIRECTX_COLORS) +#include "gltf.h" +#include "gltf_model.h" + +#include "gltf/GltfHelper.h" +#include "pbr/D3D11/D3D11Model.h" +#include "pbr/D3D11/D3D11Resources.h" +#include "pbr/PbrSharedState.h" + +#include +#include + +#include +#include +#include +#include +#include +#include + +using Microsoft::WRL::ComPtr; + +namespace Conformance +{ + + class D3D11GLTF : public GltfModelBase + { + public: + using GltfModelBase::GltfModelBase; + + void Render(ComPtr deviceContext, Pbr::D3D11Resources& resources, XrMatrix4x4f& modelToWorld) const; + }; +} // namespace Conformance +#endif diff --git a/src/conformance/framework/graphics_plugin_d3d12.cpp b/src/conformance/framework/graphics_plugin_d3d12.cpp index 0fd9e210..6c4ce4fe 100644 --- a/src/conformance/framework/graphics_plugin_d3d12.cpp +++ b/src/conformance/framework/graphics_plugin_d3d12.cpp @@ -16,31 +16,37 @@ #if defined(XR_USE_GRAPHICS_API_D3D12) -#include "graphics_plugin.h" -#include "common/xr_linear.h" #include "conformance_framework.h" +#include "gltf.h" +#include "graphics_plugin.h" +#include "graphics_plugin_d3d12_gltf.h" #include "graphics_plugin_impl_helpers.h" +#include "report.h" #include "swapchain_image_data.h" + +#include "common/xr_dependencies.h" +#include "common/xr_linear.h" +#include "pbr/D3D12/D3D12Texture.h" #include "utilities/Geometry.h" #include "utilities/align_to.h" #include "utilities/array_size.h" +#include "utilities/d3d12_queue_wrapper.h" +#include "utilities/d3d12_utils.h" #include "utilities/d3d_common.h" #include "utilities/swapchain_parameters.h" #include "utilities/throw_helpers.h" -#include - #include #include -#include -#include -#include // For Microsoft::WRL::ComPtr - +#include #include +#include // For Microsoft::WRL::ComPtr #include #include +#include #include +#include using namespace Microsoft::WRL; using namespace DirectX; @@ -94,7 +100,7 @@ namespace Conformance UINT numIndices; D3D12Mesh(ComPtr d3d12Device, span indices, span vertices, - const std::function& ExecuteCommandList) + const std::shared_ptr& queueWrapper) : device(d3d12Device), numIndices((UINT)indices.size()) { @@ -112,10 +118,10 @@ namespace Conformance vertexBufferSizeBytes = (uint32_t)vertices.size_bytes(); ComPtr vertexBufferUpload; - vertexBuffer = CreateBuffer(d3d12Device.Get(), vertexBufferSizeBytes, D3D12_HEAP_TYPE_DEFAULT); + vertexBuffer = D3D12CreateBuffer(d3d12Device.Get(), vertexBufferSizeBytes, D3D12_HEAP_TYPE_DEFAULT); XRC_CHECK_THROW_HRCMD(vertexBuffer->SetName(L"CTS mesh vertex buffer")); { - vertexBufferUpload = CreateBuffer(d3d12Device.Get(), vertexBufferSizeBytes, D3D12_HEAP_TYPE_UPLOAD); + vertexBufferUpload = D3D12CreateBuffer(d3d12Device.Get(), vertexBufferSizeBytes, D3D12_HEAP_TYPE_UPLOAD); XRC_CHECK_THROW_HRCMD(vertexBufferUpload->SetName(L"CTS mesh vertex buffer upload")); void* data; @@ -129,11 +135,12 @@ namespace Conformance indexBufferSizeBytes = (uint32_t)indices.size_bytes(); ComPtr indexBufferUpload; - indexBuffer = CreateBuffer(d3d12Device.Get(), indexBufferSizeBytes, D3D12_HEAP_TYPE_DEFAULT); + indexBuffer = D3D12CreateBuffer(d3d12Device.Get(), indexBufferSizeBytes, D3D12_HEAP_TYPE_DEFAULT); XRC_CHECK_THROW_HRCMD(indexBuffer->SetName(L"CTS mesh index buffer")); { - indexBufferUpload = CreateBuffer(d3d12Device.Get(), indexBufferSizeBytes, D3D12_HEAP_TYPE_UPLOAD); + indexBufferUpload = D3D12CreateBuffer(d3d12Device.Get(), indexBufferSizeBytes, D3D12_HEAP_TYPE_UPLOAD); XRC_CHECK_THROW_HRCMD(indexBufferUpload->SetName(L"CTS mesh index buffer upload")); + void* data; const D3D12_RANGE readRange{0, 0}; XRC_CHECK_THROW_HRCMD(indexBufferUpload->Map(0, &readRange, &data)); @@ -144,7 +151,8 @@ namespace Conformance } XRC_CHECK_THROW_HRCMD(cmdList->Close()); - XRC_CHECK_THROW(ExecuteCommandList(cmdList.Get())); + XRC_CHECK_THROW(queueWrapper->ExecuteCommandList(cmdList.Get())); + queueWrapper->CPUWaitOnFence(); } }; @@ -215,7 +223,7 @@ namespace Conformance reinterpret_cast(commandAllocator.ReleaseAndGetAddressOf()))); XRC_CHECK_THROW_HRCMD(commandAllocator->SetName(L"CTS swapchain command allocator")); - viewProjectionCBuffer = CreateBuffer(m_d3d12Device.Get(), sizeof(ViewProjectionConstantBuffer), D3D12_HEAP_TYPE_UPLOAD); + viewProjectionCBuffer = D3D12CreateBuffer(m_d3d12Device.Get(), sizeof(ViewProjectionConstantBuffer), D3D12_HEAP_TYPE_UPLOAD); XRC_CHECK_THROW_HRCMD(viewProjectionCBuffer->SetName(L"CTS view proj cbuffer")); } @@ -262,15 +270,6 @@ namespace Conformance return commandAllocator.Get(); } - uint64_t GetFrameFenceValue() const - { - return fenceValue; - } - void SetFrameFenceValue(uint64_t fenceVal) - { - fenceValue = fenceVal; - } - void ResetCommandAllocator() { XRC_CHECK_THROW_HRCMD(commandAllocator->Reset()); @@ -279,7 +278,7 @@ namespace Conformance void RequestModelCBuffer(uint32_t requiredSize) { if (!modelCBuffer || (requiredSize > modelCBuffer->GetDesc().Width)) { - modelCBuffer = CreateBuffer(m_d3d12Device.Get(), requiredSize, D3D12_HEAP_TYPE_UPLOAD); + modelCBuffer = D3D12CreateBuffer(m_d3d12Device.Get(), requiredSize, D3D12_HEAP_TYPE_UPLOAD); XRC_CHECK_THROW_HRCMD(modelCBuffer->SetName(L"CTS model cbuffer")); } } @@ -299,7 +298,6 @@ namespace Conformance ComPtr commandAllocator; ComPtr modelCBuffer; ComPtr viewProjectionCBuffer; - uint64_t fenceValue = 0; std::vector m_internalDepthTextures; }; @@ -359,6 +357,10 @@ namespace Conformance MeshHandle MakeSimpleMesh(span idx, span vtx) override; + GLTFHandle LoadGLTF(span data) override; + + std::shared_ptr GetModel(GLTFHandle handle) const override; + void RenderView(const XrCompositionLayerProjectionView& layerView, const XrSwapchainImageBaseHeader* colorSwapchainImage, const RenderParams& params) override; @@ -370,19 +372,15 @@ namespace Conformance D3D12_CPU_DESCRIPTOR_HANDLE CreateDepthStencilView(ID3D12Resource* depthStencilTexture, uint32_t imageArrayIndex, DXGI_FORMAT depthSwapchainFormat); + void SetupBasePipelineStateDesc(D3D12_GRAPHICS_PIPELINE_STATE_DESC& pipelineStateDesc); ID3D12PipelineState* GetOrCreatePipelineState(DXGI_FORMAT colorSwapchainFormat, DXGI_FORMAT dsvSwapchainFormat); - bool ExecuteCommandList(ID3D12CommandList* cmdList) const; - void CpuWaitForFence(uint64_t fenceVal) const; void WaitForGpu() const; protected: bool initialized = false; XrGraphicsBindingD3D12KHR graphicsBinding{XR_TYPE_GRAPHICS_BINDING_D3D12_KHR}; ComPtr d3d12Device; - ComPtr d3d12CmdQueue; - ComPtr fence; - mutable uint64_t fenceValue = 0; - HANDLE fenceEvent = INVALID_HANDLE_VALUE; + std::shared_ptr m_queueWrapper; SwapchainImageDataMap m_swapchainImageDataMap; const XrSwapchainImageBaseHeader* lastSwapchainImage = nullptr; @@ -397,6 +395,8 @@ namespace Conformance MeshHandle m_cubeMesh; VectorWithGenerationCountedHandles m_meshes; + VectorWithGenerationCountedHandles m_gltfs; + std::unique_ptr pbrResources; }; D3D12GraphicsPlugin::D3D12GraphicsPlugin(std::shared_ptr) @@ -406,8 +406,8 @@ namespace Conformance D3D12GraphicsPlugin::~D3D12GraphicsPlugin() { - ShutdownDevice(); - Shutdown(); + D3D12GraphicsPlugin::ShutdownDevice(); + D3D12GraphicsPlugin::Shutdown(); } bool D3D12GraphicsPlugin::Initialize() @@ -497,12 +497,9 @@ namespace Conformance reinterpret_cast(d3d12Device.ReleaseAndGetAddressOf()))); XRC_CHECK_THROW_HRCMD(d3d12Device->SetName(L"CTS device")); - D3D12_COMMAND_QUEUE_DESC queueDesc = {}; - queueDesc.Flags = D3D12_COMMAND_QUEUE_FLAG_NONE; - queueDesc.Type = D3D12_COMMAND_LIST_TYPE_DIRECT; - XRC_CHECK_THROW_HRCMD(d3d12Device->CreateCommandQueue(&queueDesc, __uuidof(ID3D12CommandQueue), - reinterpret_cast(d3d12CmdQueue.ReleaseAndGetAddressOf()))); - XRC_CHECK_THROW_HRCMD(d3d12CmdQueue->SetName(L"CTS direct cmd queue")); + m_queueWrapper = std::make_shared(d3d12Device, D3D12_COMMAND_LIST_TYPE_DIRECT); + XRC_CHECK_THROW_HRCMD(m_queueWrapper->GetCommandQueue()->SetName(L"CTS direct cmd queue")); + XRC_CHECK_THROW_HRCMD(m_queueWrapper->GetFence()->SetName(L"CTS fence")); { D3D12_DESCRIPTOR_HEAP_DESC heapDesc{}; @@ -552,17 +549,21 @@ namespace Conformance XrSwapchainCreateInfo emptyCreateInfo = {XR_TYPE_SWAPCHAIN_CREATE_INFO}; auto initImages = std::make_shared(d3d12Device.Get(), 1, emptyCreateInfo); - XRC_CHECK_THROW_HRCMD(d3d12Device->CreateFence(fenceValue, D3D12_FENCE_FLAG_NONE, __uuidof(ID3D12Fence), - reinterpret_cast(fence.ReleaseAndGetAddressOf()))); - XRC_CHECK_THROW_HRCMD(fence->SetName(L"CTS fence")); + m_cubeMesh = MakeCubeMesh(); - fenceEvent = ::CreateEvent(nullptr, FALSE, FALSE, nullptr); - XRC_CHECK_THROW(fenceEvent != nullptr); + D3D12_GRAPHICS_PIPELINE_STATE_DESC pipelineStateDesc{}; + SetupBasePipelineStateDesc(pipelineStateDesc); + pbrResources = std::make_unique(d3d12Device.Get(), pipelineStateDesc); + pbrResources->SetLight({0.0f, 0.7071067811865475f, 0.7071067811865475f}, Pbr::RGB::White); - m_cubeMesh = MakeCubeMesh(); + // Read the BRDF Lookup Table used by the PBR system into a DirectX texture. + std::vector brdfLutFileData = ReadFileBytes("brdf_lut.png"); + D3D12ResourceWithSRVDesc brdLutResourceView = + Pbr::D3D12Texture::LoadTextureImage(*pbrResources, brdfLutFileData.data(), (uint32_t)brdfLutFileData.size()); + pbrResources->SetBrdfLut(brdLutResourceView); graphicsBinding.device = d3d12Device.Get(); - graphicsBinding.queue = d3d12CmdQueue.Get(); + graphicsBinding.queue = m_queueWrapper->GetCommandQueue().Get(); return true; } @@ -582,20 +583,18 @@ namespace Conformance void D3D12GraphicsPlugin::ShutdownDevice() { graphicsBinding = XrGraphicsBindingD3D12KHR{XR_TYPE_GRAPHICS_BINDING_D3D12_KHR}; - d3d12CmdQueue.Reset(); - fence.Reset(); - if (fenceEvent != INVALID_HANDLE_VALUE) { - ::CloseHandle(fenceEvent); - fenceEvent = INVALID_HANDLE_VALUE; - } + m_queueWrapper.reset(); + rootSignature.Reset(); pipelineStates.clear(); m_cubeMesh = {}; m_meshes.clear(); + m_gltfs.clear(); rtvHeap.Reset(); dsvHeap.Reset(); m_swapchainImageDataMap.Reset(); + pbrResources.reset(); d3d12Device.Reset(); lastSwapchainImage = nullptr; } @@ -615,7 +614,7 @@ namespace Conformance uint64_t rowSizeInBytes = 0; d3d12Device->GetCopyableFootprints(&rgbaImageDesc, 0, 1, 0, &layout, nullptr, &rowSizeInBytes, &requiredSize); - ComPtr uploadBuffer = CreateBuffer(d3d12Device.Get(), (uint32_t)(requiredSize), D3D12_HEAP_TYPE_UPLOAD); + ComPtr uploadBuffer = D3D12CreateBuffer(d3d12Device.Get(), (uint32_t)(requiredSize), D3D12_HEAP_TYPE_UPLOAD); XRC_CHECK_THROW_HRCMD(uploadBuffer->SetName(L"CTS RGBA upload buffer")); { const uint8_t* src = reinterpret_cast(image.pixels.data()); @@ -649,12 +648,13 @@ namespace Conformance D3D12_TEXTURE_COPY_LOCATION dstLocation; dstLocation.pResource = destTexture; dstLocation.Type = D3D12_TEXTURE_COPY_TYPE_SUBRESOURCE_INDEX; - dstLocation.SubresourceIndex = D3D11CalcSubresource(0, arraySlice, rgbaImageDesc.MipLevels); + dstLocation.SubresourceIndex = + D3D11CalcSubresource(0, arraySlice, rgbaImageDesc.MipLevels); // todo: shouldn't this be D3D12CalcSubresource? cmdList->CopyTextureRegion(&dstLocation, 0 /* X */, 0 /* Y */, 0 /* Z */, &srcLocation, nullptr); XRC_CHECK_THROW_HRCMD(cmdList->Close()); - XRC_CHECK_THROW(ExecuteCommandList(cmdList.Get())); + XRC_CHECK_THROW(m_queueWrapper->ExecuteCommandList(cmdList.Get())); WaitForGpu(); } @@ -724,24 +724,6 @@ namespace Conformance return true; } - bool D3D12GraphicsPlugin::ExecuteCommandList(ID3D12CommandList* cmdList) const - { - bool success; - __try { - ID3D12CommandList* cmdLists[] = {cmdList}; - d3d12CmdQueue->ExecuteCommandLists((UINT)ArraySize(cmdLists), cmdLists); - success = true; - } - __except (EXCEPTION_EXECUTE_HANDLER) { - success = false; - } - - ++fenceValue; - XRC_CHECK_THROW_HRCMD(d3d12CmdQueue->Signal(fence.Get(), fenceValue)); - - return success; - } - bool D3D12GraphicsPlugin::ValidateSwapchainImageState(XrSwapchain swapchain, uint32_t index, int64_t imageFormat) const { // OK to use CHECK and REQUIRE in here because this is always called from within a test. @@ -788,7 +770,7 @@ namespace Conformance infoQueue->SetBreakOnSeverity(D3D12_MESSAGE_SEVERITY_ERROR, true); } - const bool success = ExecuteCommandList(cmdList.Get()); + const bool success = m_queueWrapper->ExecuteCommandList(cmdList.Get()); if (infoQueue) { infoQueue->SetBreakOnSeverity(D3D12_MESSAGE_SEVERITY_ERROR, oldBreakOnError); @@ -969,26 +951,32 @@ namespace Conformance cmdList->ClearDepthStencilView(depthStencilView, D3D12_CLEAR_FLAG_DEPTH, 1.0f, 0, 0, nullptr); XRC_CHECK_THROW_HRCMD(cmdList->Close()); - XRC_CHECK_THROW(ExecuteCommandList(cmdList.Get())); + XRC_CHECK_THROW(m_queueWrapper->ExecuteCommandList(cmdList.Get())); } inline MeshHandle D3D12GraphicsPlugin::MakeSimpleMesh(span idx, span vtx) { - auto handle = m_meshes.emplace_back(d3d12Device, idx, vtx, [&](ID3D12CommandList* cmdList) -> bool { - bool success = ExecuteCommandList(cmdList); - // Must wait in here so that we don't try to clean up the "upload"-related objects before they're finished. - WaitForGpu(); - return success; - }); + auto handle = m_meshes.emplace_back(d3d12Device, idx, vtx, m_queueWrapper); + + return handle; + } + inline GLTFHandle D3D12GraphicsPlugin::LoadGLTF(span data) + { + auto handle = m_gltfs.emplace_back(*pbrResources, Conformance::LoadGLTF(data)); return handle; } + inline std::shared_ptr D3D12GraphicsPlugin::GetModel(GLTFHandle handle) const + { + return m_gltfs[handle].GetModel(); + } + void D3D12GraphicsPlugin::RenderView(const XrCompositionLayerProjectionView& layerView, const XrSwapchainImageBaseHeader* colorSwapchainImage, const RenderParams& params) { - if (params.cubes.empty() && params.meshes.empty()) { + if (params.cubes.empty() && params.meshes.empty() && params.glTFs.empty()) { return; } D3D12SwapchainImageData* swapchainData; @@ -1058,13 +1046,19 @@ namespace Conformance cmdList->IASetPrimitiveTopology(D3D_PRIMITIVE_TOPOLOGY_TRIANGLELIST); constexpr uint32_t modelCBufferSize = AlignTo(sizeof(ModelConstantBuffer)); - swapchainData->RequestModelCBuffer(static_cast(modelCBufferSize * (params.cubes.size() + params.meshes.size()))); + swapchainData->RequestModelCBuffer( + static_cast(modelCBufferSize * (params.cubes.size() + params.meshes.size() + params.glTFs.size()))); ID3D12Resource* modelCBuffer = swapchainData->GetModelCBuffer(); // Render each cube uint32_t offset = 0; MeshHandle lastMeshHandle; + const auto calculateModelMat = [&](const DrawableParams params, DirectX::XMFLOAT4X4& mat) { + XMStoreFloat4x4(&mat, + XMMatrixTranspose(XMMatrixScaling(params.scale.x, params.scale.y, params.scale.z) * LoadXrPose(params.pose))); + }; + const auto drawMesh = [&, this](const MeshDrawable mesh) { D3D12Mesh& d3dMesh = m_meshes[mesh.handle]; if (mesh.handle != lastMeshHandle) { @@ -1083,8 +1077,7 @@ namespace Conformance // Compute and update the model transform. ModelConstantBuffer model; - XMStoreFloat4x4(&model.Model, XMMatrixTranspose(XMMatrixScaling(mesh.params.scale.x, mesh.params.scale.y, mesh.params.scale.z) * - LoadXrPose(mesh.params.pose))); + calculateModelMat(mesh.params, model.Model); { uint8_t* data; const D3D12_RANGE readRange{0, 0}; @@ -1112,8 +1105,31 @@ namespace Conformance drawMesh(mesh); } + // Render each gltf + for (const auto& gltfHandle : params.glTFs) { + D3D12GLTF& gltf = m_gltfs[gltfHandle.handle]; + // Compute and update the model transform. + + XrMatrix4x4f modelToWorld; + XrMatrix4x4f_CreateTranslationRotationScale(&modelToWorld, &gltfHandle.params.pose.position, + &gltfHandle.params.pose.orientation, &gltfHandle.params.scale); + XrMatrix4x4f viewMatrix; + XrVector3f unitScale = {1, 1, 1}; + XrMatrix4x4f_CreateTranslationRotationScale(&viewMatrix, &layerView.pose.position, &layerView.pose.orientation, &unitScale); + XrMatrix4x4f viewMatrixInverse; + XrMatrix4x4f_Invert(&viewMatrixInverse, &viewMatrix); + pbrResources->SetViewProjection(LoadXrMatrix(viewMatrixInverse), LoadXrMatrix(projectionMatrix)); + + DXGI_FORMAT depthSwapchainFormatDX = GetDepthStencilFormatOrDefault(depthCreateInfo); + + gltf.Render(cmdList, *pbrResources, modelToWorld, (DXGI_FORMAT)swapchainData->GetCreateInfo().format, depthSwapchainFormatDX); + + // wait in the direct queue for resources' internal copy queue to complete + m_queueWrapper->GPUWaitOnOtherFence(pbrResources->GetFenceAndValue()); + } + XRC_CHECK_THROW_HRCMD(cmdList->Close()); - XRC_CHECK_THROW(ExecuteCommandList(cmdList.Get())); + XRC_CHECK_THROW(m_queueWrapper->ExecuteCommandList(cmdList.Get())); // TODO: Track down exactly why this wait is needed. // On some drivers and/or hardware the test is generating the same image for the left and right eye, @@ -1124,24 +1140,15 @@ namespace Conformance void D3D12GraphicsPlugin::Flush() { - if (fence) { - WaitForGpu(); + if (m_queueWrapper) { + m_queueWrapper->CPUWaitOnFence(); } } - ID3D12PipelineState* D3D12GraphicsPlugin::GetOrCreatePipelineState(DXGI_FORMAT colorSwapchainFormat, DXGI_FORMAT dsvSwapchainFormat) + void D3D12GraphicsPlugin::SetupBasePipelineStateDesc(D3D12_GRAPHICS_PIPELINE_STATE_DESC& pipelineStateDesc) { - auto iter = pipelineStates.find({colorSwapchainFormat, dsvSwapchainFormat}); - if (iter != pipelineStates.end()) { - return iter->second.Get(); - } - - const D3D12_INPUT_ELEMENT_DESC inputElementDescs[] = { - {"POSITION", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, D3D12_APPEND_ALIGNED_ELEMENT, D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, 0}, - {"COLOR", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, D3D12_APPEND_ALIGNED_ELEMENT, D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, 0}, - }; + // separate get base descriptor and create - D3D12_GRAPHICS_PIPELINE_STATE_DESC pipelineStateDesc{}; pipelineStateDesc.pRootSignature = rootSignature.Get(); pipelineStateDesc.VS = {vertexShaderBytes->GetBufferPointer(), vertexShaderBytes->GetBufferSize()}; pipelineStateDesc.PS = {pixelShaderBytes->GetBufferPointer(), pixelShaderBytes->GetBufferSize()}; @@ -1188,19 +1195,33 @@ namespace Conformance pipelineStateDesc.DepthStencilState.FrontFace = pipelineStateDesc.DepthStencilState.BackFace = { D3D12_STENCIL_OP_KEEP, D3D12_STENCIL_OP_KEEP, D3D12_STENCIL_OP_KEEP, D3D12_COMPARISON_FUNC_ALWAYS}; } - { - pipelineStateDesc.InputLayout.pInputElementDescs = inputElementDescs; - pipelineStateDesc.InputLayout.NumElements = (UINT)ArraySize(inputElementDescs); - } - pipelineStateDesc.IBStripCutValue = D3D12_INDEX_BUFFER_STRIP_CUT_VALUE_0xFFFF; + pipelineStateDesc.IBStripCutValue = D3D12_INDEX_BUFFER_STRIP_CUT_VALUE_DISABLED; pipelineStateDesc.PrimitiveTopologyType = D3D12_PRIMITIVE_TOPOLOGY_TYPE_TRIANGLE; pipelineStateDesc.NumRenderTargets = 1; - pipelineStateDesc.RTVFormats[0] = colorSwapchainFormat; - pipelineStateDesc.DSVFormat = dsvSwapchainFormat; pipelineStateDesc.SampleDesc = {1, 0}; pipelineStateDesc.NodeMask = 0; pipelineStateDesc.CachedPSO = {nullptr, 0}; pipelineStateDesc.Flags = D3D12_PIPELINE_STATE_FLAG_NONE; + } + + ID3D12PipelineState* D3D12GraphicsPlugin::GetOrCreatePipelineState(DXGI_FORMAT colorSwapchainFormat, DXGI_FORMAT dsvSwapchainFormat) + { + auto iter = pipelineStates.find({colorSwapchainFormat, dsvSwapchainFormat}); + if (iter != pipelineStates.end()) { + return iter->second.Get(); + } + + D3D12_GRAPHICS_PIPELINE_STATE_DESC pipelineStateDesc{}; + SetupBasePipelineStateDesc(pipelineStateDesc); + pipelineStateDesc.RTVFormats[0] = colorSwapchainFormat; + pipelineStateDesc.DSVFormat = dsvSwapchainFormat; + + const D3D12_INPUT_ELEMENT_DESC inputElementDescs[] = { + {"POSITION", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, D3D12_APPEND_ALIGNED_ELEMENT, D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, 0}, + {"COLOR", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, D3D12_APPEND_ALIGNED_ELEMENT, D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, 0}, + }; + pipelineStateDesc.InputLayout.pInputElementDescs = inputElementDescs; + pipelineStateDesc.InputLayout.NumElements = (UINT)ArraySize(inputElementDescs); ComPtr pipelineState; XRC_CHECK_THROW_HRCMD(d3d12Device->CreateGraphicsPipelineState(&pipelineStateDesc, __uuidof(ID3D12PipelineState), @@ -1213,20 +1234,9 @@ namespace Conformance return pipelineStateRaw; } - void D3D12GraphicsPlugin::CpuWaitForFence(uint64_t fenceVal) const - { - if (fence->GetCompletedValue() < fenceVal) { - XRC_CHECK_THROW_HRCMD(fence->SetEventOnCompletion(fenceVal, fenceEvent)); - const uint32_t retVal = WaitForSingleObjectEx(fenceEvent, INFINITE, FALSE); - if (retVal != WAIT_OBJECT_0) { - XRC_CHECK_THROW_HRCMD(E_FAIL); - } - } - } - void D3D12GraphicsPlugin::WaitForGpu() const { - CpuWaitForFence(fenceValue); + m_queueWrapper->CPUWaitOnFence(); } std::shared_ptr CreateGraphicsPlugin_D3D12(std::shared_ptr platformPlugin) diff --git a/src/conformance/framework/graphics_plugin_d3d12_gltf.cpp b/src/conformance/framework/graphics_plugin_d3d12_gltf.cpp new file mode 100644 index 00000000..008af3c3 --- /dev/null +++ b/src/conformance/framework/graphics_plugin_d3d12_gltf.cpp @@ -0,0 +1,42 @@ +// Copyright (c) 2022-2023, The Khronos Group Inc. +// +// SPDX-License-Identifier: MIT +#if defined(XR_USE_GRAPHICS_API_D3D12) && !defined(MISSING_DIRECTX_COLORS) + +#include "graphics_plugin_d3d12_gltf.h" + +#include "conformance_framework.h" +#include "graphics_plugin_d3d12_gltf.h" +#include "report.h" + +#include "pbr/D3D12/D3D12Primitive.h" +#include "pbr/D3D12/D3D12Resources.h" +#include "pbr/GltfLoader.h" +#include "utilities/d3d_common.h" +#include "utilities/throw_helpers.h" + +#include +#include + +using namespace DirectX; + +namespace Conformance +{ + void D3D12GLTF::Render(ComPtr directCommandList, Pbr::D3D12Resources& resources, XrMatrix4x4f& modelToWorld, + DXGI_FORMAT colorRenderTargetFormat, DXGI_FORMAT depthRenderTargetFormat) + { + if (!GetModel()) { + return; + } + + // move these to a base class helper + resources.SetFillMode(GetFillMode()); + // end move + + resources.SetModelToWorld(LoadXrMatrix(modelToWorld)); + resources.Bind(directCommandList.Get()); + GetModel()->Render(resources, directCommandList.Get(), colorRenderTargetFormat, depthRenderTargetFormat); + } + +} // namespace Conformance +#endif diff --git a/src/conformance/framework/graphics_plugin_d3d12_gltf.h b/src/conformance/framework/graphics_plugin_d3d12_gltf.h new file mode 100644 index 00000000..f183f4e8 --- /dev/null +++ b/src/conformance/framework/graphics_plugin_d3d12_gltf.h @@ -0,0 +1,40 @@ +// Copyright (c) 2022-2023, The Khronos Group Inc. +// +// SPDX-License-Identifier: MIT + +#pragma once + +#if defined(XR_USE_GRAPHICS_API_D3D12) && !defined(MISSING_DIRECTX_COLORS) +#include "gltf.h" +#include "gltf_model.h" + +#include "gltf/GltfHelper.h" +#include "pbr/D3D12/D3D12Model.h" +#include "pbr/D3D12/D3D12Resources.h" +#include "pbr/PbrSharedState.h" + +#include +#include + +#include +#include +#include +#include +#include +#include + +using Microsoft::WRL::ComPtr; + +namespace Conformance +{ + + class D3D12GLTF : public GltfModelBase + { + public: + using GltfModelBase::GltfModelBase; + + void Render(ComPtr directCommandList, Pbr::D3D12Resources& resources, XrMatrix4x4f& modelToWorld, + DXGI_FORMAT colorRenderTargetFormat, DXGI_FORMAT depthRenderTargetFormat); + }; +} // namespace Conformance +#endif diff --git a/src/conformance/framework/graphics_plugin_impl_helpers.h b/src/conformance/framework/graphics_plugin_impl_helpers.h index bee311e0..246aceab 100644 --- a/src/conformance/framework/graphics_plugin_impl_helpers.h +++ b/src/conformance/framework/graphics_plugin_impl_helpers.h @@ -17,6 +17,7 @@ #pragma once #include +#include #include namespace Conformance @@ -30,6 +31,9 @@ namespace Conformance class VectorWithGenerationCountedHandles { public: + // TODO genericize + // static_assert(sizeof(HandleType) == sizeof(uint64_t), "Only works with 64-bit handles right now"); + using GenerationType = uint32_t; template HandleType emplace_back(Args&&... args) { @@ -61,17 +65,18 @@ namespace Conformance if (h == HandleType{}) { throw std::logic_error("Internal CTS error: Trying to use a null graphics handle!"); } - auto generation = static_cast(h.get() >> kGenerationShift); + auto generation = static_cast(h.get() >> kGenerationShift); if (generation != m_generationNumber) { throw std::logic_error( "Internal CTS error: Trying to use a graphics handle left over from before a Shutdown() or ShutdownDevice() call!"); } + // TODO implicit mask is here by truncating! auto index = static_cast(h.get()); return index; } static constexpr size_t kGenerationShift = 32; std::vector m_data; - uint32_t m_generationNumber{1}; + GenerationType m_generationNumber{1}; }; } // namespace Conformance diff --git a/src/conformance/framework/graphics_plugin_opengl.cpp b/src/conformance/framework/graphics_plugin_opengl.cpp index 670aa0b1..05c3e0ed 100644 --- a/src/conformance/framework/graphics_plugin_opengl.cpp +++ b/src/conformance/framework/graphics_plugin_opengl.cpp @@ -16,27 +16,57 @@ #ifdef XR_USE_GRAPHICS_API_OPENGL -#include "common/xr_linear.h" +#include "RGBAImage.h" #include "conformance_framework.h" +#include "conformance_utils.h" +#include "gltf.h" #include "graphics_plugin.h" #include "graphics_plugin_impl_helpers.h" +#include "graphics_plugin_opengl_gltf.h" #include "report.h" #include "swapchain_image_data.h" + +#include "common/gfxwrapper_opengl.h" +#include "common/xr_dependencies.h" +#include "common/xr_linear.h" +#include "pbr/OpenGL/GLCommon.h" +#include "pbr/OpenGL/GLResources.h" +#include "pbr/OpenGL/GLTexture.h" +#include "pbr/PbrCommon.h" #include "utilities/Geometry.h" +#include "utilities/opengl_utils.h" #include "utilities/swapchain_format_data.h" #include "utilities/swapchain_parameters.h" #include "utilities/throw_helpers.h" -#include "xr_dependencies.h" +#include "utilities/utils.h" +#include #include -#include +#include +#include #include +#include #include -#include +#include +#include +#include +#include #include +#include +#include +#include +#include +#include -#include "gfxwrapper_opengl.h" +namespace Conformance +{ + struct IPlatformPlugin; +} // namespace Conformance +namespace Pbr +{ + class Model; +} // namespace Pbr // Note: mapping of OpenXR usage flags to OpenGL // @@ -140,63 +170,6 @@ namespace Conformance return map; } - std::string glResultString(GLenum err) - { - switch (err) { - case GL_NO_ERROR: - return "GL_NO_ERROR"; - case GL_INVALID_ENUM: - return "GL_INVALID_ENUM"; - case GL_INVALID_VALUE: - return "GL_INVALID_VALUE"; - case GL_INVALID_OPERATION: - return "GL_INVALID_OPERATION"; - case GL_INVALID_FRAMEBUFFER_OPERATION: - return "GL_INVALID_FRAMEBUFFER_OPERATION"; - case GL_OUT_OF_MEMORY: - return "GL_OUT_OF_MEMORY"; - case GL_STACK_UNDERFLOW: - return "GL_STACK_UNDERFLOW"; - case GL_STACK_OVERFLOW: - return "GL_STACK_OVERFLOW"; - } - return ""; - } - - [[noreturn]] inline void ThrowGLResult(GLenum res, const char* originator = nullptr, const char* sourceLocation = nullptr) - { - Throw("GL failure " + glResultString(res), originator, sourceLocation); - } - - inline GLenum CheckThrowGLResult(GLenum res, const char* originator = nullptr, const char* sourceLocation = nullptr) - { - if ((res) != GL_NO_ERROR) { - ThrowGLResult(res, originator, sourceLocation); - } - - return res; - } - - inline GLenum TexTarget(bool isArray, bool isMultisample) - { - if (isArray && isMultisample) { - return GL_TEXTURE_2D_MULTISAMPLE_ARRAY; - } - else if (isMultisample) { - return GL_TEXTURE_2D_MULTISAMPLE; - } - else if (isArray) { - return GL_TEXTURE_2D_ARRAY; - } - else { - return GL_TEXTURE_2D; - } - } - -#define XRC_THROW_GL(res, cmd) ThrowGLResult(res, #cmd, XRC_FILE_AND_LINE) -#define XRC_CHECK_THROW_GLCMD(cmd) CheckThrowGLResult(((cmd), glGetError()), #cmd, XRC_FILE_AND_LINE) -#define XRC_CHECK_THROW_GLRESULT(res, cmdStr) CheckThrowGLResult(res, cmdStr, XRC_FILE_AND_LINE) - static const char* VertexShaderGlsl = R"_( #version 410 @@ -408,8 +381,6 @@ namespace Conformance void DebugMessageCallback(GLenum source, GLenum type, GLuint id, GLenum severity, GLsizei length, const GLchar* message) const; void InitializeResources(); void CheckFramebuffer(GLuint fb) const; - void CheckShader(GLuint shader) const; - void CheckProgram(GLuint prog) const; void ClearSwapchainCache() override; void ShutdownDevice() override; @@ -447,6 +418,10 @@ namespace Conformance MeshHandle MakeSimpleMesh(span idx, span vtx) override; + GLTFHandle LoadGLTF(span data) override; + + std::shared_ptr GetModel(GLTFHandle handle) const override; + void RenderView(const XrCompositionLayerProjectionView& layerView, const XrSwapchainImageBaseHeader* colorSwapchainImage, const RenderParams& params) override; @@ -480,6 +455,8 @@ namespace Conformance GLint m_vertexAttribColor{0}; MeshHandle m_cubeMesh{}; VectorWithGenerationCountedHandles m_meshes; + VectorWithGenerationCountedHandles m_gltfs; + std::unique_ptr m_pbrResources; }; OpenGLGraphicsPlugin::OpenGLGraphicsPlugin(const std::shared_ptr& /*unused*/) @@ -728,18 +705,18 @@ namespace Conformance GLuint vertexShader = glCreateShader(GL_VERTEX_SHADER); glShaderSource(vertexShader, 1, &VertexShaderGlsl, nullptr); glCompileShader(vertexShader); - CheckShader(vertexShader); + CheckGLShader(vertexShader); GLuint fragmentShader = glCreateShader(GL_FRAGMENT_SHADER); glShaderSource(fragmentShader, 1, &FragmentShaderGlsl, nullptr); glCompileShader(fragmentShader); - CheckShader(fragmentShader); + CheckGLShader(fragmentShader); m_program = glCreateProgram(); glAttachShader(m_program, vertexShader); glAttachShader(m_program, fragmentShader); glLinkProgram(m_program); - CheckProgram(m_program); + CheckGLProgram(m_program); glDeleteShader(vertexShader); glDeleteShader(fragmentShader); @@ -750,6 +727,18 @@ namespace Conformance m_vertexAttribColor = glGetAttribLocation(m_program, "VertexColor"); m_cubeMesh = MakeCubeMesh(); + + m_pbrResources = std::make_unique(); + m_pbrResources->SetLight({0.0f, 0.7071067811865475f, 0.7071067811865475f}, Pbr::RGB::White); + + auto blackCubeMap = std::make_shared(Pbr::GLTexture::CreateFlatCubeTexture(Pbr::RGBA::Black, GL_RGBA8)); + m_pbrResources->SetEnvironmentMap(blackCubeMap, blackCubeMap); + + // Read the BRDF Lookup Table used by the PBR system into a DirectX texture. + std::vector brdfLutFileData = ReadFileBytes("brdf_lut.png"); + auto brdLutResourceView = std::make_shared( + Pbr::GLTexture::LoadTextureImage(brdfLutFileData.data(), (uint32_t)brdfLutFileData.size())); + m_pbrResources->SetBrdfLut(brdLutResourceView); } void OpenGLGraphicsPlugin::CheckFramebuffer(GLuint fb) const @@ -797,30 +786,6 @@ namespace Conformance #endif // !defined(OS_APPLE_MACOS) } - void OpenGLGraphicsPlugin::CheckShader(GLuint shader) const - { - GLint r = 0; - glGetShaderiv(shader, GL_COMPILE_STATUS, &r); - if (r == GL_FALSE) { - GLchar msg[4096] = {}; - GLsizei length; - glGetShaderInfoLog(shader, sizeof(msg), &length, msg); - XRC_CHECK_THROW_MSG(r, msg); - } - } - - void OpenGLGraphicsPlugin::CheckProgram(GLuint prog) const - { - GLint r = 0; - glGetProgramiv(prog, GL_LINK_STATUS, &r); - if (r == GL_FALSE) { - GLchar msg[4096] = {}; - GLsizei length; - glGetProgramInfoLog(prog, sizeof(msg), &length, msg); - XRC_CHECK_THROW_MSG(r, msg); - } - } - void OpenGLGraphicsPlugin::ClearSwapchainCache() { m_swapchainImageDataMap.Reset(); @@ -840,6 +805,7 @@ namespace Conformance m_swapchainImageDataMap.Reset(); m_cubeMesh = {}; m_meshes.clear(); + m_gltfs.clear(); deleteGLContext(); } @@ -1072,6 +1038,17 @@ namespace Conformance return handle; } + inline GLTFHandle OpenGLGraphicsPlugin::LoadGLTF(span data) + { + auto handle = m_gltfs.emplace_back(*m_pbrResources, Conformance::LoadGLTF(data)); + return handle; + } + + inline std::shared_ptr OpenGLGraphicsPlugin::GetModel(GLTFHandle handle) const + { + return m_gltfs[handle].GetModel(); + } + void OpenGLGraphicsPlugin::RenderView(const XrCompositionLayerProjectionView& layerView, const XrSwapchainImageBaseHeader* colorSwapchainImage, const RenderParams& params) { @@ -1159,6 +1136,20 @@ namespace Conformance drawMesh(mesh); } + // Render each gltf + for (const auto& gltfHandle : params.glTFs) { + GLGLTF& gltf = m_gltfs[gltfHandle.handle]; + // Compute and update the model transform. + + XrMatrix4x4f modelToWorld; + XrMatrix4x4f_CreateTranslationRotationScale(&modelToWorld, &gltfHandle.params.pose.position, + &gltfHandle.params.pose.orientation, &gltfHandle.params.scale); + + m_pbrResources->SetViewProjection(view, proj); + + gltf.Render(*m_pbrResources, modelToWorld); + } + glBindVertexArray(0); glUseProgram(0); glBindFramebuffer(GL_FRAMEBUFFER, 0); diff --git a/src/conformance/framework/graphics_plugin_opengl_gltf.cpp b/src/conformance/framework/graphics_plugin_opengl_gltf.cpp new file mode 100644 index 00000000..8339f80b --- /dev/null +++ b/src/conformance/framework/graphics_plugin_opengl_gltf.cpp @@ -0,0 +1,36 @@ +// Copyright (c) 2022-2023, The Khronos Group Inc. +// +// SPDX-License-Identifier: MIT + +#if defined(XR_USE_GRAPHICS_API_OPENGL) || defined(XR_USE_GRAPHICS_API_OPENGL_ES) + +#include "graphics_plugin_opengl_gltf.h" + +#include "conformance_framework.h" +#include "graphics_plugin_opengl_gltf.h" +#include "report.h" + +#include "pbr/GltfLoader.h" +#include "pbr/OpenGL/GLModel.h" +#include "pbr/OpenGL/GLPrimitive.h" +#include "pbr/OpenGL/GLResources.h" +#include "utilities/throw_helpers.h" + +#include + +namespace Conformance +{ + void GLGLTF::Render(Pbr::GLResources& resources, XrMatrix4x4f& modelToWorld) const + { + if (!GetModel()) { + return; + } + + resources.SetFillMode(GetFillMode()); + resources.SetModelToWorld(modelToWorld); + resources.Bind(); + GetModel()->Render(resources); + } + +} // namespace Conformance +#endif diff --git a/src/conformance/framework/graphics_plugin_opengl_gltf.h b/src/conformance/framework/graphics_plugin_opengl_gltf.h new file mode 100644 index 00000000..cb3012a2 --- /dev/null +++ b/src/conformance/framework/graphics_plugin_opengl_gltf.h @@ -0,0 +1,39 @@ +// Copyright (c) 2022-2023, The Khronos Group Inc. +// +// SPDX-License-Identifier: MIT + +#pragma once + +#if defined(XR_USE_GRAPHICS_API_OPENGL) || defined(XR_USE_GRAPHICS_API_OPENGL_ES) +#include "gltf.h" +#include "gltf_model.h" + +#include "common/xr_linear.h" +#include "gltf/GltfHelper.h" +#include "pbr/OpenGL/GLModel.h" +#include "pbr/OpenGL/GLResources.h" +#include "pbr/PbrSharedState.h" + +#include +#include +#include +#include + +namespace Pbr +{ + class GLModel; + struct GLResources; +} // namespace Pbr + +namespace Conformance +{ + + class GLGLTF : public GltfModelBase + { + public: + using GltfModelBase::GltfModelBase; + + void Render(Pbr::GLResources& resources, XrMatrix4x4f& modelToWorld) const; + }; +} // namespace Conformance +#endif diff --git a/src/conformance/framework/graphics_plugin_opengles.cpp b/src/conformance/framework/graphics_plugin_opengles.cpp index 6930513e..e74e427d 100644 --- a/src/conformance/framework/graphics_plugin_opengles.cpp +++ b/src/conformance/framework/graphics_plugin_opengles.cpp @@ -14,21 +14,25 @@ // See the License for the specific language governing permissions and // limitations under the License. -#include "graphics_plugin.h" - #ifdef XR_USE_GRAPHICS_API_OPENGL_ES -#include "common/gfxwrapper_opengl.h" -#include "common/xr_linear.h" #include "conformance_framework.h" +#include "graphics_plugin.h" #include "graphics_plugin_impl_helpers.h" +#include "graphics_plugin_opengl_gltf.h" #include "report.h" #include "swapchain_image_data.h" + +#include "common/gfxwrapper_opengl.h" +#include "common/xr_dependencies.h" +#include "common/xr_linear.h" +#include "pbr/OpenGL/GLCommon.h" +#include "pbr/OpenGL/GLResources.h" +#include "pbr/OpenGL/GLTexture.h" #include "utilities/Geometry.h" #include "utilities/swapchain_format_data.h" #include "utilities/swapchain_parameters.h" #include "utilities/throw_helpers.h" -#include "xr_dependencies.h" #include #include @@ -295,6 +299,10 @@ namespace Conformance MeshHandle MakeSimpleMesh(span idx, span vtx) override; + GLTFHandle LoadGLTF(span data) override; + + std::shared_ptr GetModel(GLTFHandle handle) const override; + void RenderView(const XrCompositionLayerProjectionView& layerView, const XrSwapchainImageBaseHeader* colorSwapchainImage, const RenderParams& params) override; @@ -318,6 +326,8 @@ namespace Conformance GLint m_vertexAttribColor{0}; MeshHandle m_cubeMesh{}; VectorWithGenerationCountedHandles m_meshes; + VectorWithGenerationCountedHandles m_gltfs; + std::unique_ptr m_pbrResources; SwapchainImageDataMap m_swapchainImageDataMap; }; @@ -554,6 +564,18 @@ namespace Conformance m_vertexAttribColor = glGetAttribLocation(m_program, "VertexColor"); m_cubeMesh = MakeCubeMesh(); + + m_pbrResources = std::make_unique(); + m_pbrResources->SetLight({0.0f, 0.7071067811865475f, 0.7071067811865475f}, Pbr::RGB::White); + + auto blackCubeMap = std::make_shared(Pbr::GLTexture::CreateFlatCubeTexture(Pbr::RGBA::Black, GL_RGBA8)); + m_pbrResources->SetEnvironmentMap(blackCubeMap, blackCubeMap); + + // Read the BRDF Lookup Table used by the PBR system into a DirectX texture. + std::vector brdfLutFileData = ReadFileBytes("brdf_lut.png"); + auto brdLutResourceView = std::make_shared( + Pbr::GLTexture::LoadTextureImage(brdfLutFileData.data(), (uint32_t)brdfLutFileData.size())); + m_pbrResources->SetBrdfLut(brdLutResourceView); } void OpenGLESGraphicsPlugin::ShutdownResources() @@ -570,6 +592,7 @@ namespace Conformance m_cubeMesh = {}; m_meshes.clear(); + m_gltfs.clear(); ksGpuWindow_Destroy(&window); } @@ -1135,6 +1158,17 @@ namespace Conformance return handle; } + inline GLTFHandle OpenGLESGraphicsPlugin::LoadGLTF(span data) + { + auto handle = m_gltfs.emplace_back(*m_pbrResources, Conformance::LoadGLTF(data)); + return handle; + } + + inline std::shared_ptr OpenGLESGraphicsPlugin::GetModel(GLTFHandle handle) const + { + return m_gltfs[handle].GetModel(); + } + void OpenGLESGraphicsPlugin::RenderView(const XrCompositionLayerProjectionView& layerView, const XrSwapchainImageBaseHeader* colorSwapchainImage, const RenderParams& params) { @@ -1223,6 +1257,20 @@ namespace Conformance drawMesh(mesh); } + // Render each gltf + for (const auto& gltfHandle : params.glTFs) { + GLGLTF& gltf = m_gltfs[gltfHandle.handle]; + // Compute and update the model transform. + + XrMatrix4x4f modelToWorld; + XrMatrix4x4f_CreateTranslationRotationScale(&modelToWorld, &gltfHandle.params.pose.position, + &gltfHandle.params.pose.orientation, &gltfHandle.params.scale); + + m_pbrResources->SetViewProjection(view, proj); + + gltf.Render(*m_pbrResources, modelToWorld); + } + GL(glBindVertexArray(0)); GL(glUseProgram(0)); GL(glDisable(GL_SCISSOR_TEST)); diff --git a/src/conformance/framework/graphics_plugin_vulkan.cpp b/src/conformance/framework/graphics_plugin_vulkan.cpp index e8f869ef..4c132ce3 100644 --- a/src/conformance/framework/graphics_plugin_vulkan.cpp +++ b/src/conformance/framework/graphics_plugin_vulkan.cpp @@ -15,36 +15,51 @@ // limitations under the License. #ifdef XR_USE_GRAPHICS_API_VULKAN - #include "RGBAImage.h" -#include "common/hex_and_handles.h" -#include "common/xr_linear.h" +#include "conformance_utils.h" +#include "gltf.h" #include "graphics_plugin.h" #include "graphics_plugin_impl_helpers.h" +#include "graphics_plugin_vulkan_gltf.h" #include "report.h" #include "swapchain_image_data.h" + +#include "common/hex_and_handles.h" +#include "common/vulkan_debug_object_namer.hpp" +#include "common/xr_dependencies.h" +#include "common/xr_linear.h" +#include "pbr/PbrCommon.h" +#include "pbr/Vulkan/VkCommon.h" +#include "pbr/Vulkan/VkResources.h" +#include "pbr/Vulkan/VkTexture.h" #include "utilities/Geometry.h" #include "utilities/swapchain_format_data.h" #include "utilities/swapchain_parameters.h" #include "utilities/throw_helpers.h" +#include "utilities/utils.h" #include "utilities/vulkan_utils.h" -#include "xr_dependencies.h" -#include -#include #include #include +#include +#include +#include +#include +#include +#include #include #include +#include #include #include #include #include +#include #include +#include #include #include -#include #include #include @@ -52,6 +67,11 @@ #include #endif +namespace Pbr +{ + class Model; +} // namespace Pbr + namespace Conformance { struct IPlatformPlugin; @@ -113,9 +133,8 @@ namespace Conformance { m_renderTarget.resize(capacity); m_rp.Create(namer, device, colorFormat, depthFormat, sampleCount); - m_pipe.Dynamic(VK_DYNAMIC_STATE_SCISSOR); - m_pipe.Dynamic(VK_DYNAMIC_STATE_VIEWPORT); - m_pipe.Create(device, size, layout, m_rp, sp, bindDesc, attrDesc); + VkDynamicState dynamicStates[] = {VK_DYNAMIC_STATE_SCISSOR, VK_DYNAMIC_STATE_VIEWPORT}; + m_pipe.Create(device, size, layout, m_rp, sp, bindDesc, attrDesc, dynamicStates); } void Reset() @@ -126,6 +145,7 @@ namespace Conformance } }; + /// Vulkan data used per swapchain. One per XrSwapchain handle. class VulkanSwapchainImageData : public SwapchainImageDataBase { void init(uint32_t capacity, VkFormat colorFormat, const PipelineLayout& layout, const ShaderProgram& sp, @@ -508,8 +528,8 @@ namespace Conformance std::vector attrDesc(std::begin(c_attrDesc), std::end(c_attrDesc)); m_DrawBuffer.Init(device, memAllocator, attrDesc); m_DrawBuffer.Create(idx_count, vtx_count); - m_DrawBuffer.UpdateIndices(idx_data, idx_count, 0); - m_DrawBuffer.UpdateVertices(vtx_data, vtx_count, 0); + m_DrawBuffer.UpdateIndices(nonstd::span(idx_data, idx_count), 0); + m_DrawBuffer.UpdateVertices(nonstd::span(vtx_data, vtx_count), 0); } VulkanMesh(VulkanMesh&& other) noexcept @@ -633,6 +653,10 @@ namespace Conformance MeshHandle MakeSimpleMesh(span idx, span vtx) override; + GLTFHandle LoadGLTF(span data) override; + + std::shared_ptr GetModel(GLTFHandle handle) const override; + void RenderView(const XrCompositionLayerProjectionView& layerView, const XrSwapchainImageBaseHeader* colorSwapchainImage, const RenderParams& params) override; @@ -686,6 +710,8 @@ namespace Conformance PipelineLayout m_pipelineLayout{}; MeshHandle m_cubeMesh{}; VectorWithGenerationCountedHandles m_meshes; + VectorWithGenerationCountedHandles m_gltfs; + std::unique_ptr m_pbrResources; #if defined(USE_MIRROR_WINDOW) Swapchain m_swapchain{}; @@ -824,11 +850,12 @@ namespace Conformance VkPhysicalDevice* vulkanPhysicalDevice) override; XrResult CreateVulkanDeviceKHR(XrInstance instance, const XrVulkanDeviceCreateInfoKHR* createInfo, VkDevice* vulkanDevice, VkResult* vulkanResult) override; + + // do not override ShutdownDevice or Shutdown here! }; VulkanGraphicsPlugin::VulkanGraphicsPlugin(const std::shared_ptr& /*unused*/) { - m_graphicsBinding.type = GetGraphicsBindingType(); } VulkanGraphicsPlugin::~VulkanGraphicsPlugin() @@ -999,6 +1026,7 @@ namespace Conformance if (initialized) { return false; } + m_graphicsBinding.type = GetGraphicsBindingType(); // To do. initialized = true; @@ -1272,10 +1300,10 @@ namespace Conformance auto fragmentSPIRV = CompileGlslShader("fragment", shaderc_glsl_default_fragment_shader, FragmentShaderGlsl); #else std::vector vertexSPIRV = SPV_PREFIX -#include "vert.spv" +#include "vert.spv" // IWYU pragma: keep SPV_SUFFIX; std::vector fragmentSPIRV = SPV_PREFIX -#include "frag.spv" +#include "frag.spv" // IWYU pragma: keep SPV_SUFFIX; #endif if (vertexSPIRV.empty()) @@ -1301,6 +1329,19 @@ namespace Conformance m_cubeMesh = MakeCubeMesh(); + m_pbrResources = std::make_unique(m_namer, m_vkPhysicalDevice, m_vkDevice, m_queueFamilyIndex); + m_pbrResources->SetLight({0.0f, 0.7071067811865475f, 0.7071067811865475f}, Pbr::RGB::White); + + auto blackCubeMap = std::make_shared( + Pbr::VulkanTexture::CreateFlatCubeTexture(*m_pbrResources, Pbr::RGBA::Black, VK_FORMAT_R8G8B8A8_UNORM)); + m_pbrResources->SetEnvironmentMap(blackCubeMap, blackCubeMap); + + // Read the BRDF Lookup Table used by the PBR system into a DirectX texture. + std::vector brdfLutFileData = ReadFileBytes("brdf_lut.png"); + auto brdLutResourceView = std::make_shared( + Pbr::VulkanTexture::LoadTextureImage(*m_pbrResources, brdfLutFileData.data(), (uint32_t)brdfLutFileData.size())); + m_pbrResources->SetBrdfLut(brdLutResourceView); + #if defined(USE_MIRROR_WINDOW) m_swapchain.Create(m_vkInstance, m_vkPhysicalDevice, m_vkDevice, m_graphicsBinding.queueFamilyIndex); @@ -1327,9 +1368,12 @@ namespace Conformance // Reset the swapchains to avoid calling Vulkan functions in the dtors after // we've shut down the device. + m_pbrResources.reset(); + m_swapchainImageDataMap.Reset(); m_cubeMesh = {}; m_meshes.clear(); + m_gltfs.clear(); m_queueFamilyIndex = 0; m_vkQueue = VK_NULL_HANDLE; @@ -1718,12 +1762,7 @@ namespace Conformance uint8_t* data{nullptr}; XRC_CHECK_THROW_VKCMD(vkMapMemory(m_vkDevice, stagingMemory, layout.offset, layout.size, 0, (void**)&data)); - const size_t rowSize = w * sizeof(RGBA8Color); - for (size_t row = 0; row < h; ++row) { - uint8_t* rowPtr = &data[layout.offset + row * layout.rowPitch]; - // Note pixels is a vector - memcpy(rowPtr, &image.pixels[row * w], rowSize); - } + image.CopyWithStride(data, static_cast(layout.rowPitch), static_cast(layout.offset)); vkUnmapMemory(m_vkDevice, stagingMemory); m_cmdBuffer.Clear(); @@ -1911,6 +1950,17 @@ namespace Conformance return handle; } + inline GLTFHandle VulkanGraphicsPlugin::LoadGLTF(span data) + { + auto handle = m_gltfs.emplace_back(*m_pbrResources, Conformance::LoadGLTF(data)); + return handle; + } + + inline std::shared_ptr VulkanGraphicsPlugin::GetModel(GLTFHandle handle) const + { + return m_gltfs[handle].GetModel(); + } + void VulkanGraphicsPlugin::RenderView(const XrCompositionLayerProjectionView& layerView, const XrSwapchainImageBaseHeader* colorSwapchainImage, const RenderParams& params) { @@ -1968,12 +2018,12 @@ namespace Conformance // We are now rendering a new mesh // Bind index and vertex buffers - vkCmdBindIndexBuffer(m_cmdBuffer.buf, vkMesh.m_DrawBuffer.idxBuf, 0, VK_INDEX_TYPE_UINT16); + vkCmdBindIndexBuffer(m_cmdBuffer.buf, vkMesh.m_DrawBuffer.idx.buf, 0, VK_INDEX_TYPE_UINT16); CHECKPOINT(); VkDeviceSize offset = 0; - vkCmdBindVertexBuffers(m_cmdBuffer.buf, 0, 1, &vkMesh.m_DrawBuffer.vtxBuf, &offset); + vkCmdBindVertexBuffers(m_cmdBuffer.buf, 0, 1, &vkMesh.m_DrawBuffer.vtx.buf, &offset); CHECKPOINT(); lastMeshHandle = mesh.handle; @@ -2005,15 +2055,38 @@ namespace Conformance drawMesh(mesh); } + // Render each gltf + for (const auto& gltfHandle : params.glTFs) { + VulkanGLTF& gltf = m_gltfs[gltfHandle.handle]; + // Compute and update the model transform. + + XrMatrix4x4f modelToWorld; + XrMatrix4x4f_CreateTranslationRotationScale(&modelToWorld, &gltfHandle.params.pose.position, + &gltfHandle.params.pose.orientation, &gltfHandle.params.scale); + // XrMatrix4x4f viewMatrix; + // XrVector3f unitScale = {1, 1, 1}; + // XrMatrix4x4f_CreateTranslationRotationScale(&viewMatrix, &layerView.pose.position, &layerView.pose.orientation, &unitScale); + // XrMatrix4x4f viewMatrixInverse; + // XrMatrix4x4f_Invert(&viewMatrixInverse, &viewMatrix); + m_pbrResources->SetViewProjection(view, proj); + + gltf.Render(m_cmdBuffer, *m_pbrResources, modelToWorld, renderPassBeginInfo.renderPass, + (VkSampleCountFlagBits)swapchainData->GetCreateInfo().sampleCount); + } + vkCmdEndRenderPass(m_cmdBuffer.buf); CHECKPOINT(); + m_pbrResources->SubmitFrameResources(m_vkQueue); + m_cmdBuffer.End(); m_cmdBuffer.Exec(m_vkQueue); // XXX Should double-buffer the command buffers, for now just flush m_cmdBuffer.Wait(); + m_pbrResources->Wait(); + #if defined(USE_MIRROR_WINDOW) // Cycle the window's swapchain on the last view rendered if (swapchainData == &m_swapchainImageData.back()) { diff --git a/src/conformance/framework/graphics_plugin_vulkan_gltf.cpp b/src/conformance/framework/graphics_plugin_vulkan_gltf.cpp new file mode 100644 index 00000000..ee34cc16 --- /dev/null +++ b/src/conformance/framework/graphics_plugin_vulkan_gltf.cpp @@ -0,0 +1,39 @@ +// Copyright (c) 2022-2023, The Khronos Group Inc. +// +// SPDX-License-Identifier: MIT + +#ifdef XR_USE_GRAPHICS_API_VULKAN + +#include "graphics_plugin_vulkan_gltf.h" + +#include "conformance_framework.h" +#include "graphics_plugin_vulkan_gltf.h" +#include "report.h" + +#include "pbr/GltfLoader.h" +#include "pbr/Vulkan/VkModel.h" +#include "pbr/Vulkan/VkPrimitive.h" +#include "pbr/Vulkan/VkResources.h" +#include "utilities/throw_helpers.h" + +#include + +namespace Conformance +{ + struct CmdBuffer; + + void VulkanGLTF::Render(CmdBuffer& directCommandBuffer, Pbr::VulkanResources& resources, XrMatrix4x4f& modelToWorld, + VkRenderPass renderPass, VkSampleCountFlagBits sampleCount) const + { + if (!GetModel()) { + return; + } + + resources.SetFillMode(GetFillMode()); + resources.SetModelToWorld(modelToWorld); + // resources.Bind(directCommandBuffer); + GetModel()->Render(resources, directCommandBuffer, renderPass, sampleCount); + } + +} // namespace Conformance +#endif diff --git a/src/conformance/framework/graphics_plugin_vulkan_gltf.h b/src/conformance/framework/graphics_plugin_vulkan_gltf.h new file mode 100644 index 00000000..5a039a66 --- /dev/null +++ b/src/conformance/framework/graphics_plugin_vulkan_gltf.h @@ -0,0 +1,42 @@ +// Copyright (c) 2022-2023, The Khronos Group Inc. +// +// SPDX-License-Identifier: MIT + +#pragma once +#ifdef XR_USE_GRAPHICS_API_VULKAN +#include "gltf.h" +#include "gltf_model.h" + +#include "common/xr_linear.h" +#include "gltf/GltfHelper.h" +#include "pbr/PbrSharedState.h" +#include "pbr/Vulkan/VkModel.h" +#include "pbr/Vulkan/VkResources.h" + +#include + +#include +#include +#include +#include + +namespace Pbr +{ + class VulkanModel; + struct VulkanResources; +} // namespace Pbr + +namespace Conformance +{ + struct CmdBuffer; + + class VulkanGLTF : public GltfModelBase + { + public: + using GltfModelBase::GltfModelBase; + + void Render(CmdBuffer& directCommandBuffer, Pbr::VulkanResources& resources, XrMatrix4x4f& modelToWorld, VkRenderPass renderPass, + VkSampleCountFlagBits sampleCount) const; + }; +} // namespace Conformance +#endif diff --git a/src/conformance/framework/mesh_projection_layer.cpp b/src/conformance/framework/mesh_projection_layer.cpp index 5d572723..231fa454 100644 --- a/src/conformance/framework/mesh_projection_layer.cpp +++ b/src/conformance/framework/mesh_projection_layer.cpp @@ -20,7 +20,7 @@ #include "graphics_plugin.h" #include "utilities/utils.h" -#include "nonstd/span.hpp" +#include #include diff --git a/src/conformance/framework/pbr/CMakeLists.txt b/src/conformance/framework/pbr/CMakeLists.txt new file mode 100644 index 00000000..59c04bcf --- /dev/null +++ b/src/conformance/framework/pbr/CMakeLists.txt @@ -0,0 +1,179 @@ +# Copyright (c) 2019-2023, The Khronos Group Inc. +# +# SPDX-License-Identifier: Apache-2.0 + +add_library( + conformance_framework_pbr STATIC PbrCommon.cpp GltfLoader.cpp PbrMaterial.cpp + PbrModel.cpp PbrSharedState.cpp) + +set_target_properties(conformance_framework_pbr + PROPERTIES FOLDER ${CONFORMANCE_TESTS_FOLDER}) + +target_link_libraries( + conformance_framework_pbr + PUBLIC conformance_framework_tinygltf conformance_framework_gltf + conformance_utilities) + +if(MSVC) + # Turns off ABI compatibility warning + target_compile_definitions(conformance_framework_pbr + PUBLIC _ENABLE_EXTENDED_ALIGNED_STORAGE) +endif() + +if(XR_USE_GRAPHICS_API_D3D11 OR XR_USE_GRAPHICS_API_D3D12) + include(fxc_shader) + fxc_shader( + INPUT + "${CMAKE_CURRENT_SOURCE_DIR}/Shaders/PbrPixelShader.hlsl" + OUTPUT + "${CMAKE_CURRENT_BINARY_DIR}/PbrPixelShader_hlsl.h" + PROFILE + ps_5_0 + VARIABLE + g_PbrPixelShader + EXTRA_DEPENDS + "${CMAKE_CURRENT_SOURCE_DIR}/Shaders/Shared.hlsl" + "${CMAKE_CURRENT_SOURCE_DIR}/Shaders/PbrShared.hlsl") + + fxc_shader( + INPUT + "${CMAKE_CURRENT_SOURCE_DIR}/Shaders/PbrVertexShader.hlsl" + OUTPUT + "${CMAKE_CURRENT_BINARY_DIR}/PbrVertexShader_hlsl.h" + PROFILE + vs_5_0 + VARIABLE + g_PbrVertexShader + EXTRA_DEPENDS + "${CMAKE_CURRENT_SOURCE_DIR}/Shaders/Shared.hlsl" + "${CMAKE_CURRENT_SOURCE_DIR}/Shaders/PbrShared.hlsl") + + target_sources( + conformance_framework_pbr + PRIVATE "${CMAKE_CURRENT_BINARY_DIR}/PbrPixelShader_hlsl.h" + "${CMAKE_CURRENT_BINARY_DIR}/PbrVertexShader_hlsl.h") +endif() + +if(XR_USE_GRAPHICS_API_D3D11) + target_sources( + conformance_framework_pbr + PRIVATE D3D11/D3D11Resources.cpp D3D11/D3D11Texture.cpp + D3D11/D3D11TextureCache.cpp D3D11/D3D11Material.cpp + D3D11/D3D11Model.cpp D3D11/D3D11Primitive.cpp) +endif() + +if(XR_USE_GRAPHICS_API_D3D12) + target_sources( + conformance_framework_pbr + PRIVATE D3D12/D3D12PipelineStates.cpp + D3D12/D3D12Resources.cpp + D3D12/D3D12Texture.cpp + D3D12/D3D12TextureCache.cpp + D3D12/D3D12Material.cpp + D3D12/D3D12Model.cpp + D3D12/D3D12Primitive.cpp) + + target_include_directories( + conformance_framework_pbr + PRIVATE "${PROJECT_SOURCE_DIR}/src/external/d3dx12") +endif() + +if(XR_USE_GRAPHICS_API_OPENGL OR XR_USE_GRAPHICS_API_OPENGL_ES) + include(make_includable) + + make_includable( + "${CMAKE_CURRENT_SOURCE_DIR}/Shaders/PbrVertexShader_glsl.vert" + "${CMAKE_CURRENT_BINARY_DIR}/PbrVertexShader_glsl_src.h") + make_includable("${CMAKE_CURRENT_SOURCE_DIR}/Shaders/PbrPixelShader_glsl.frag" + "${CMAKE_CURRENT_BINARY_DIR}/PbrPixelShader_glsl_src.h") + + set(from_gl "^#version [0-9]+") + set(to_gles "#version 320 es") + make_includable( + "${CMAKE_CURRENT_SOURCE_DIR}/Shaders/PbrVertexShader_glsl.vert" + "${CMAKE_CURRENT_BINARY_DIR}/PbrVertexShader_glsl_src_es.h" REPLACE + "${from_gl}" "${to_gles}") + make_includable( + "${CMAKE_CURRENT_SOURCE_DIR}/Shaders/PbrPixelShader_glsl.frag" + "${CMAKE_CURRENT_BINARY_DIR}/PbrPixelShader_glsl_src_es.h" REPLACE + "${from_gl}" "${to_gles}") + + target_sources( + conformance_framework_pbr + PRIVATE "${CMAKE_CURRENT_BINARY_DIR}/PbrPixelShader_glsl_src.h" + "${CMAKE_CURRENT_BINARY_DIR}/PbrVertexShader_glsl_src.h" + OpenGL/GLResources.cpp + OpenGL/GLTexture.cpp + OpenGL/GLTextureCache.cpp + OpenGL/GLMaterial.cpp + OpenGL/GLModel.cpp + OpenGL/GLPrimitive.cpp) +endif() + +if(TARGET openxr-gfxwrapper) + target_link_libraries(conformance_framework_pbr PRIVATE openxr-gfxwrapper) +endif() + +if(XR_USE_GRAPHICS_API_VULKAN) + target_include_directories(conformance_framework_pbr + PRIVATE ${Vulkan_INCLUDE_DIRS}) + target_link_libraries(conformance_framework_pbr PRIVATE ${Vulkan_LIBRARY}) +endif() + +if(XR_USE_GRAPHICS_API_VULKAN) + include(glsl_shader) + + glsl_spv_shader( + INPUT + "${CMAKE_CURRENT_SOURCE_DIR}/Shaders/PbrVertexShader_glsl.vert" + OUTPUT + "${CMAKE_CURRENT_BINARY_DIR}/PbrVertexShader_glsl_spv.h" + STAGE + vert + VARIABLE + g_PbrVertexShader_vulkan + TARGET_ENV + vulkan1.0) + glsl_spv_shader( + INPUT + "${CMAKE_CURRENT_SOURCE_DIR}/Shaders/PbrPixelShader_glsl.frag" + OUTPUT + "${CMAKE_CURRENT_BINARY_DIR}/PbrPixelShader_glsl_spv.h" + STAGE + frag + VARIABLE + g_PbrPixelShader_vulkan + TARGET_ENV + vulkan1.0) + + if(GLSLANG_VALIDATOR AND NOT GLSL_COMPILER) + target_compile_definitions(conformance_framework_pbr + PRIVATE USE_GLSLANGVALIDATOR) + endif() + + target_sources( + conformance_framework_pbr + PRIVATE "${CMAKE_CURRENT_BINARY_DIR}/PbrPixelShader_glsl_spv.h" + "${CMAKE_CURRENT_BINARY_DIR}/PbrVertexShader_glsl_spv.h" + Vulkan/VkPipelineStates.cpp + Vulkan/VkResources.cpp + Vulkan/VkTexture.cpp + Vulkan/VkTextureCache.cpp + Vulkan/VkMaterial.cpp + Vulkan/VkModel.cpp + Vulkan/VkPrimitive.cpp) +endif() + +target_include_directories( + conformance_framework_pbr + PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}" + "${CMAKE_CURRENT_BINARY_DIR}" + "${PROJECT_SOURCE_DIR}/src/common" + "${PROJECT_SOURCE_DIR}/src/conformance" + # for openxr.h: + "${PROJECT_BINARY_DIR}/include" + # Strong types for integers, etc. + "${PROJECT_SOURCE_DIR}/src/external/type-lite/include" + # Backport span + "${PROJECT_SOURCE_DIR}/src/external/span-lite/include" + PRIVATE "${PROJECT_SOURCE_DIR}/src/external/stb") diff --git a/src/conformance/framework/pbr/D3D11/D3D11Material.cpp b/src/conformance/framework/pbr/D3D11/D3D11Material.cpp new file mode 100644 index 00000000..bb840804 --- /dev/null +++ b/src/conformance/framework/pbr/D3D11/D3D11Material.cpp @@ -0,0 +1,109 @@ +// Copyright 2022-2023, The Khronos Group, Inc. +// +// Based in part on code that is: +// Copyright (C) Microsoft Corporation. All Rights Reserved +// Licensed under the MIT License. See License.txt in the project root for license information. +// +// SPDX-License-Identifier: MIT AND Apache-2.0 + +#if defined(XR_USE_GRAPHICS_API_D3D11) + +#include "D3D11Material.h" + +#include "D3D11Resources.h" +#include "D3D11Texture.h" + +#include "../PbrMaterial.h" + +#include "utilities/throw_helpers.h" + +#include + +using namespace DirectX; + +namespace Pbr +{ + D3D11Material::D3D11Material(Pbr::D3D11Resources const& pbrResources) + { + const CD3D11_BUFFER_DESC constantBufferDesc(sizeof(ConstantBufferData), D3D11_BIND_CONSTANT_BUFFER); + XRC_CHECK_THROW_HRCMD( + pbrResources.GetDevice()->CreateBuffer(&constantBufferDesc, nullptr, m_constantBuffer.ReleaseAndGetAddressOf())); + } + + std::shared_ptr D3D11Material::Clone(Pbr::D3D11Resources const& pbrResources) const + { + auto clone = std::make_shared(pbrResources); + clone->CopyFrom(*this); + clone->m_textures = m_textures; + clone->m_samplers = m_samplers; + return clone; + } + + /* static */ + std::shared_ptr D3D11Material::CreateFlat(const D3D11Resources& pbrResources, RGBAColor baseColorFactor, + float roughnessFactor /* = 1.0f */, float metallicFactor /* = 0.0f */, + RGBColor emissiveFactor /* = XMFLOAT3(0, 0, 0) */) + { + std::shared_ptr material = std::make_shared(pbrResources); + + if (baseColorFactor.a < 1.0f) { // Alpha channel + material->SetAlphaBlended(BlendState::AlphaBlended); + } + + Pbr::D3D11Material::ConstantBufferData& parameters = material->Parameters(); + parameters.BaseColorFactor = baseColorFactor; + parameters.EmissiveFactor = emissiveFactor; + parameters.MetallicFactor = metallicFactor; + parameters.RoughnessFactor = roughnessFactor; + + const Microsoft::WRL::ComPtr defaultSampler = Pbr::D3D11Texture::CreateSampler(pbrResources.GetDevice().Get()); + material->SetTexture(ShaderSlots::BaseColor, pbrResources.CreateTypedSolidColorTexture(RGBA::White).Get(), defaultSampler.Get()); + material->SetTexture(ShaderSlots::MetallicRoughness, pbrResources.CreateTypedSolidColorTexture(RGBA::White).Get(), + defaultSampler.Get()); + // No occlusion. + material->SetTexture(ShaderSlots::Occlusion, pbrResources.CreateTypedSolidColorTexture(RGBA::White).Get(), defaultSampler.Get()); + // Flat normal. + material->SetTexture(ShaderSlots::Normal, pbrResources.CreateTypedSolidColorTexture(RGBA::FlatNormal).Get(), defaultSampler.Get()); + material->SetTexture(ShaderSlots::Emissive, pbrResources.CreateTypedSolidColorTexture(RGBA::White).Get(), defaultSampler.Get()); + + return material; + } + + void D3D11Material::SetTexture(ShaderSlots::PSMaterial slot, _In_ ID3D11ShaderResourceView* textureView, + _In_opt_ ID3D11SamplerState* sampler) + { + m_textures[slot] = textureView; + + if (sampler) { + m_samplers[slot] = sampler; + } + } + + void D3D11Material::Bind(_In_ ID3D11DeviceContext* context, const D3D11Resources& pbrResources) const + { + // If the parameters of the constant buffer have changed, update the constant buffer. + if (m_parametersChanged) { + m_parametersChanged = false; + context->UpdateSubresource(m_constantBuffer.Get(), 0, nullptr, &m_parameters, 0, 0); + } + + pbrResources.SetBlendState(context, m_alphaBlended == BlendState::AlphaBlended); + pbrResources.SetDepthStencilState(context, m_alphaBlended == BlendState::AlphaBlended); + pbrResources.SetRasterizerState(context, m_doubleSided == DoubleSided::DoubleSided); + + ID3D11Buffer* psConstantBuffers[] = {m_constantBuffer.Get()}; + context->PSSetConstantBuffers(Pbr::ShaderSlots::ConstantBuffers::Material, 1, psConstantBuffers); + + static_assert(Pbr::ShaderSlots::BaseColor == 0, "BaseColor must be the first slot"); + + std::array textures; + std::transform(m_textures.begin(), m_textures.end(), textures.begin(), [](const auto& texture) { return texture.Get(); }); + context->PSSetShaderResources(Pbr::ShaderSlots::BaseColor, (UINT)textures.size(), textures.data()); + + std::array samplers; + std::transform(m_samplers.begin(), m_samplers.end(), samplers.begin(), [](const auto& sampler) { return sampler.Get(); }); + context->PSSetSamplers(Pbr::ShaderSlots::BaseColor, (UINT)samplers.size(), samplers.data()); + } +} // namespace Pbr + +#endif // defined(XR_USE_GRAPHICS_API_D3D11) diff --git a/src/conformance/framework/pbr/D3D11/D3D11Material.h b/src/conformance/framework/pbr/D3D11/D3D11Material.h new file mode 100644 index 00000000..2b2c2a88 --- /dev/null +++ b/src/conformance/framework/pbr/D3D11/D3D11Material.h @@ -0,0 +1,59 @@ +// Copyright 2022-2023, The Khronos Group, Inc. +// +// Based in part on code that is: +// Copyright (C) Microsoft Corporation. All Rights Reserved +// Licensed under the MIT License. See License.txt in the project root for license information. +// +// SPDX-License-Identifier: MIT AND Apache-2.0 + +#pragma once + +#include "D3D11Resources.h" + +#include "../PbrMaterial.h" + +#include +#include +#include +#include // For Microsoft::WRL::ComPtr + +#include +#include +#include +#include + +namespace Pbr +{ + // A D3D11Material contains the metallic roughness parameters and textures. + // Primitives specify which D3D11Material to use when being rendered. + struct D3D11Material final : public Material + { + // Create a uninitialized material. Textures and shader coefficients must be set. + D3D11Material(Pbr::D3D11Resources const& pbrResources); + + // Create a clone of this material. + std::shared_ptr Clone(Pbr::D3D11Resources const& pbrResources) const; + + // Create a flat (no texture) material. + static std::shared_ptr CreateFlat(const D3D11Resources& pbrResources, RGBAColor baseColorFactor, + float roughnessFactor = 1.0f, float metallicFactor = 0.0f, + RGBColor emissiveFactor = RGB::Black); + + // Set a Metallic-Roughness texture. + void SetTexture(ShaderSlots::PSMaterial slot, _In_ ID3D11ShaderResourceView* textureView, + _In_opt_ ID3D11SamplerState* sampler = nullptr); + // void SetTexture(ShaderSlots::PSMaterial slot, ITexture& texture) override; + + // Bind this material to current context. + void Bind(_In_ ID3D11DeviceContext* context, const D3D11Resources& pbrResources) const; + + std::string Name; + bool Hidden{false}; + + private: + static constexpr size_t TextureCount = ShaderSlots::NumMaterialSlots; + std::array, TextureCount> m_textures; + std::array, TextureCount> m_samplers; + Microsoft::WRL::ComPtr m_constantBuffer; + }; +} // namespace Pbr diff --git a/src/conformance/framework/pbr/D3D11/D3D11Model.cpp b/src/conformance/framework/pbr/D3D11/D3D11Model.cpp new file mode 100644 index 00000000..f39dc652 --- /dev/null +++ b/src/conformance/framework/pbr/D3D11/D3D11Model.cpp @@ -0,0 +1,105 @@ +// Copyright 2022-2023, The Khronos Group, Inc. +// +// Based in part on code that is: +// Copyright (C) Microsoft Corporation. All Rights Reserved +// Licensed under the MIT License. See License.txt in the project root for license information. +// +// SPDX-License-Identifier: MIT AND Apache-2.0 + +#if defined(XR_USE_GRAPHICS_API_D3D11) + +#include "D3D11Model.h" + +#include "D3D11Primitive.h" +#include "D3D11Resources.h" + +#include "../PbrHandles.h" +#include "../PbrModel.h" + +#include "utilities/throw_helpers.h" + +#include + +namespace Pbr +{ + + void D3D11Model::Render(Pbr::D3D11Resources const& pbrResources, _In_ ID3D11DeviceContext* context) + { + UpdateTransforms(pbrResources, context); + + ID3D11ShaderResourceView* vsShaderResources[] = {m_modelTransformsResourceView.Get()}; + context->VSSetShaderResources(Pbr::ShaderSlots::Transforms, _countof(vsShaderResources), vsShaderResources); + + for (PrimitiveHandle primitiveHandle : GetPrimitives()) { + const Pbr::D3D11Primitive& primitive = pbrResources.GetPrimitive(primitiveHandle); + if (primitive.GetMaterial()->Hidden) + continue; + + primitive.GetMaterial()->Bind(context, pbrResources); + primitive.Render(context); + } + + // Expect the caller to reset other state, but the geometry shader is cleared specially. + //context->GSSetShader(nullptr, nullptr, 0); + } + + void D3D11Model::UpdateTransforms(Pbr::D3D11Resources const& pbrResources, _In_ ID3D11DeviceContext* context) + { + const auto& nodes = GetNodes(); + const uint32_t newTotalModifyCount = std::accumulate(nodes.begin(), nodes.end(), 0, [](uint32_t sumChangeCount, const Node& node) { + return sumChangeCount + node.GetModifyCount(); + }); + + // If none of the node transforms have changed, no need to recompute/update the model transform structured buffer. + if (newTotalModifyCount != TotalModifyCount || m_modelTransformsStructuredBufferInvalid) { + if (m_modelTransformsStructuredBufferInvalid) // The structured buffer is reset when a Node is added. + { + XrMatrix4x4f identityMatrix; + XrMatrix4x4f_CreateIdentity(&identityMatrix); // or better yet poison it + m_modelTransforms.resize(nodes.size(), identityMatrix); + + // Create/recreate the structured buffer and SRV which holds the node transforms. + // Use Usage=D3D11_USAGE_DYNAMIC and CPUAccessFlags=D3D11_CPU_ACCESS_WRITE with Map/Unmap instead? + D3D11_BUFFER_DESC desc{}; + desc.Usage = D3D11_USAGE_DEFAULT; + desc.BindFlags = D3D11_BIND_SHADER_RESOURCE; + desc.MiscFlags = D3D11_RESOURCE_MISC_BUFFER_STRUCTURED; + desc.StructureByteStride = sizeof(decltype(m_modelTransforms)::value_type); + desc.ByteWidth = (UINT)(m_modelTransforms.size() * desc.StructureByteStride); + XRC_CHECK_THROW_HRCMD( + pbrResources.GetDevice()->CreateBuffer(&desc, nullptr, m_modelTransformsStructuredBuffer.ReleaseAndGetAddressOf())); + + D3D11_SHADER_RESOURCE_VIEW_DESC srvDesc{}; + srvDesc.ViewDimension = D3D11_SRV_DIMENSION_BUFFER; + // TODO this looks weird + srvDesc.Buffer.NumElements = (UINT)m_modelTransforms.size(); + srvDesc.Buffer.ElementWidth = (UINT)m_modelTransforms.size(); + m_modelTransformsResourceView = nullptr; + XRC_CHECK_THROW_HRCMD(pbrResources.GetDevice()->CreateShaderResourceView( + m_modelTransformsStructuredBuffer.Get(), &srvDesc, m_modelTransformsResourceView.ReleaseAndGetAddressOf())); + + m_modelTransformsStructuredBufferInvalid = false; + } + + // Nodes are guaranteed to come after their parents, so each node transform can be multiplied by its parent transform in a single pass. + assert(nodes.size() == m_modelTransforms.size()); + XrMatrix4x4f identityMatrix; + XrMatrix4x4f_CreateIdentity(&identityMatrix); + for (const auto& node : nodes) { + assert(node.ParentNodeIndex == RootParentNodeIndex || node.ParentNodeIndex < node.Index); + const XrMatrix4x4f& parentTransform = + (node.ParentNodeIndex == RootParentNodeIndex) ? identityMatrix : m_modelTransforms[node.ParentNodeIndex]; + XrMatrix4x4f nodeTransform = node.GetTransform(); + XrMatrix4x4f nodeTransformTranspose; + XrMatrix4x4f_Transpose(&nodeTransformTranspose, &nodeTransform); + XrMatrix4x4f_Multiply(&m_modelTransforms[node.Index], &nodeTransformTranspose, &parentTransform); + } + + // Update node transform structured buffer. + context->UpdateSubresource(m_modelTransformsStructuredBuffer.Get(), 0, nullptr, this->m_modelTransforms.data(), 0, 0); + TotalModifyCount = newTotalModifyCount; + } + } +} // namespace Pbr + +#endif // defined(XR_USE_GRAPHICS_API_D3D11) diff --git a/src/conformance/framework/pbr/D3D11/D3D11Model.h b/src/conformance/framework/pbr/D3D11/D3D11Model.h new file mode 100644 index 00000000..171eac05 --- /dev/null +++ b/src/conformance/framework/pbr/D3D11/D3D11Model.h @@ -0,0 +1,37 @@ +// Copyright 2022-2023, The Khronos Group, Inc. +// +// Based in part on code that is: +// Copyright (C) Microsoft Corporation. All Rights Reserved +// Licensed under the MIT License. See License.txt in the project root for license information. +// +// SPDX-License-Identifier: MIT AND Apache-2.0 + +#pragma once +#include "D3D11Resources.h" + +#include "../PbrHandles.h" +#include "../PbrModel.h" + +namespace Pbr +{ + + struct D3D11Primitive; + + class D3D11Model final : public Model + { + public: + // Render the model. + void Render(Pbr::D3D11Resources const& pbrResources, _In_ ID3D11DeviceContext* context); + + private: + // Updated the transforms used to render the model. This needs to be called any time a node transform is changed. + void UpdateTransforms(Pbr::D3D11Resources const& pbrResources, _In_ ID3D11DeviceContext* context); + + // Temporary buffer holds the world transforms, computed from the node's local transforms. + mutable std::vector m_modelTransforms; + mutable Microsoft::WRL::ComPtr m_modelTransformsStructuredBuffer; + mutable Microsoft::WRL::ComPtr m_modelTransformsResourceView; + + mutable uint32_t TotalModifyCount{0}; + }; +} // namespace Pbr diff --git a/src/conformance/framework/pbr/D3D11/D3D11Primitive.cpp b/src/conformance/framework/pbr/D3D11/D3D11Primitive.cpp new file mode 100644 index 00000000..9854fe31 --- /dev/null +++ b/src/conformance/framework/pbr/D3D11/D3D11Primitive.cpp @@ -0,0 +1,147 @@ +// Copyright 2022-2023, The Khronos Group, Inc. +// +// Based in part on code that is: +// Copyright (C) Microsoft Corporation. All Rights Reserved +// Licensed under the MIT License. See License.txt in the project root for license information. +// +// SPDX-License-Identifier: MIT AND Apache-2.0 + +#if defined(XR_USE_GRAPHICS_API_D3D11) + +#include "D3D11Primitive.h" + +#include "D3D11Resources.h" + +#include "../PbrCommon.h" + +#include "utilities/throw_helpers.h" + +using namespace DirectX; + +namespace +{ + UINT GetPbrVertexByteSize(size_t size) + { + return (UINT)(sizeof(decltype(Pbr::PrimitiveBuilder::Vertices)::value_type) * size); + } + UINT GetPbrIndexByteSize(size_t size) + { + return (UINT)(sizeof(decltype(Pbr::PrimitiveBuilder::Indices)::value_type) * size); + } + + Microsoft::WRL::ComPtr CreateVertexBuffer(_In_ ID3D11Device* device, const Pbr::PrimitiveBuilder& primitiveBuilder, + bool updatableBuffers) + { + // Create Vertex Buffer + D3D11_BUFFER_DESC desc{}; + desc.Usage = D3D11_USAGE_DEFAULT; + desc.ByteWidth = GetPbrVertexByteSize(primitiveBuilder.Vertices.size()); + desc.BindFlags = D3D11_BIND_VERTEX_BUFFER; + + if (updatableBuffers) { + desc.Usage = D3D11_USAGE_DYNAMIC; + desc.CPUAccessFlags |= D3D11_CPU_ACCESS_WRITE; + } + + D3D11_SUBRESOURCE_DATA initData{}; + initData.pSysMem = primitiveBuilder.Vertices.data(); + + Microsoft::WRL::ComPtr vertexBuffer; + XRC_CHECK_THROW_HRCMD(device->CreateBuffer(&desc, &initData, vertexBuffer.ReleaseAndGetAddressOf())); + return vertexBuffer; + } + + Microsoft::WRL::ComPtr CreateIndexBuffer(_In_ ID3D11Device* device, const Pbr::PrimitiveBuilder& primitiveBuilder, + bool updatableBuffers) + { + // Create Index Buffer + D3D11_BUFFER_DESC desc{}; + desc.Usage = D3D11_USAGE_DEFAULT; + desc.ByteWidth = GetPbrIndexByteSize(primitiveBuilder.Indices.size()); + desc.BindFlags = D3D11_BIND_INDEX_BUFFER; + + if (updatableBuffers) { + desc.Usage = D3D11_USAGE_DYNAMIC; + desc.CPUAccessFlags |= D3D11_CPU_ACCESS_WRITE; + } + + D3D11_SUBRESOURCE_DATA initData{}; + initData.pSysMem = primitiveBuilder.Indices.data(); + + Microsoft::WRL::ComPtr indexBuffer; + XRC_CHECK_THROW_HRCMD(device->CreateBuffer(&desc, &initData, indexBuffer.ReleaseAndGetAddressOf())); + return indexBuffer; + } +} // namespace + +namespace Pbr +{ + D3D11Primitive::D3D11Primitive(UINT indexCount, Microsoft::WRL::ComPtr indexBuffer, + Microsoft::WRL::ComPtr vertexBuffer, std::shared_ptr material) + : m_indexCount(indexCount) + , m_indexBuffer(std::move(indexBuffer)) + , m_vertexBuffer(std::move(vertexBuffer)) + , m_material(std::move(material)) + { + } + + D3D11Primitive::D3D11Primitive(Pbr::D3D11Resources const& pbrResources, const Pbr::PrimitiveBuilder& primitiveBuilder, + const std::shared_ptr& material, bool updatableBuffers) + : D3D11Primitive((UINT)primitiveBuilder.Indices.size(), + CreateIndexBuffer(pbrResources.GetDevice().Get(), primitiveBuilder, updatableBuffers), + CreateVertexBuffer(pbrResources.GetDevice().Get(), primitiveBuilder, updatableBuffers), std::move(material)) + { + } + + D3D11Primitive D3D11Primitive::Clone(Pbr::D3D11Resources const& pbrResources) const + { + return D3D11Primitive(m_indexCount, m_indexBuffer, m_vertexBuffer, m_material->Clone(pbrResources)); + } + + void D3D11Primitive::UpdateBuffers(_In_ ID3D11Device* device, _In_ ID3D11DeviceContext* context, + const Pbr::PrimitiveBuilder& primitiveBuilder) + { + // Update vertex buffer. + { + D3D11_BUFFER_DESC vertDesc; + m_vertexBuffer->GetDesc(&vertDesc); + + UINT requiredSize = GetPbrVertexByteSize(primitiveBuilder.Vertices.size()); + if (vertDesc.ByteWidth >= requiredSize) { + context->UpdateSubresource(m_vertexBuffer.Get(), 0, nullptr, primitiveBuilder.Vertices.data(), requiredSize, requiredSize); + } + else { + m_vertexBuffer = CreateVertexBuffer(device, primitiveBuilder, true); + } + } + + // Update index buffer. + { + D3D11_BUFFER_DESC idxDesc; + m_indexBuffer->GetDesc(&idxDesc); + + UINT requiredSize = GetPbrIndexByteSize(primitiveBuilder.Indices.size()); + if (idxDesc.ByteWidth >= requiredSize) { + context->UpdateSubresource(m_indexBuffer.Get(), 0, nullptr, primitiveBuilder.Indices.data(), requiredSize, requiredSize); + } + else { + m_indexBuffer = CreateIndexBuffer(device, primitiveBuilder, true); + } + + m_indexCount = (UINT)primitiveBuilder.Indices.size(); + } + } + + void D3D11Primitive::Render(_In_ ID3D11DeviceContext* context) const + { + const UINT stride = sizeof(Pbr::Vertex); + const UINT offset = 0; + ID3D11Buffer* const vertexBuffers[] = {m_vertexBuffer.Get()}; + context->IASetVertexBuffers(0, 1, vertexBuffers, &stride, &offset); + context->IASetIndexBuffer(m_indexBuffer.Get(), DXGI_FORMAT_R32_UINT, 0); + context->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST); + context->DrawIndexedInstanced(m_indexCount, 1, 0, 0, 0); + } +} // namespace Pbr + +#endif // defined(XR_USE_GRAPHICS_API_D3D11) diff --git a/src/conformance/framework/pbr/D3D11/D3D11Primitive.h b/src/conformance/framework/pbr/D3D11/D3D11Primitive.h new file mode 100644 index 00000000..0a762491 --- /dev/null +++ b/src/conformance/framework/pbr/D3D11/D3D11Primitive.h @@ -0,0 +1,62 @@ +// Copyright 2022-2023, The Khronos Group, Inc. +// +// Based in part on code that is: +// Copyright (C) Microsoft Corporation. All Rights Reserved +// Licensed under the MIT License. See License.txt in the project root for license information. +// +// SPDX-License-Identifier: MIT AND Apache-2.0 + +#pragma once + +#include "D3D11Material.h" + +#include +#include +#include // For Microsoft::WRL::ComPtr + +#include + +namespace Pbr +{ + // A primitive holds a vertex buffer, index buffer, and a pointer to a PBR material. + struct D3D11Primitive final + { + using Collection = std::vector; + + D3D11Primitive() = delete; + D3D11Primitive(UINT indexCount, Microsoft::WRL::ComPtr indexBuffer, Microsoft::WRL::ComPtr vertexBuffer, + std::shared_ptr material); + D3D11Primitive(Pbr::D3D11Resources const& pbrResources, const Pbr::PrimitiveBuilder& primitiveBuilder, + const std::shared_ptr& material, bool updatableBuffers = false); + + void UpdateBuffers(_In_ ID3D11Device* device, _In_ ID3D11DeviceContext* context, const Pbr::PrimitiveBuilder& primitiveBuilder); + + // Get the material for the primitive. + std::shared_ptr& GetMaterial() + { + return m_material; + } + const std::shared_ptr& GetMaterial() const + { + return m_material; + } + + // Replace the material for the primitive + void SetMaterial(std::shared_ptr material) + { + m_material = std::move(material); + } + + protected: + // friend class Model; + friend class D3D11Model; + void Render(_In_ ID3D11DeviceContext* context) const; + D3D11Primitive Clone(Pbr::D3D11Resources const& pbrResources) const; + + private: + UINT m_indexCount; + Microsoft::WRL::ComPtr m_indexBuffer; + Microsoft::WRL::ComPtr m_vertexBuffer; + std::shared_ptr m_material; + }; +} // namespace Pbr diff --git a/src/conformance/framework/pbr/D3D11/D3D11Resources.cpp b/src/conformance/framework/pbr/D3D11/D3D11Resources.cpp new file mode 100644 index 00000000..73be98cd --- /dev/null +++ b/src/conformance/framework/pbr/D3D11/D3D11Resources.cpp @@ -0,0 +1,479 @@ +// Copyright 2022-2023, The Khronos Group, Inc. +// +// Based in part on code that is: +// Copyright (C) Microsoft Corporation. All Rights Reserved +// Licensed under the MIT License. See License.txt in the project root for license information. +// +// SPDX-License-Identifier: MIT AND Apache-2.0 + +#if defined(XR_USE_GRAPHICS_API_D3D11) + +#include "D3D11Resources.h" + +#include "D3D11Primitive.h" +#include "D3D11Texture.h" +#include "D3D11TextureCache.h" + +#include "../../gltf/GltfHelper.h" +#include "../PbrMaterial.h" + +#include "utilities/throw_helpers.h" + +#include + +#include +#include + +#include + +using namespace DirectX; + +namespace +{ + struct SceneConstantBuffer + { + DirectX::XMFLOAT4X4 ViewProjection; + DirectX::XMFLOAT4 EyePosition; + DirectX::XMFLOAT3 LightDirection{}; + float _pad0; + DirectX::XMFLOAT3 LightDiffuseColor{}; + float _pad1; + uint32_t NumSpecularMipLevels{1}; + float _pad2[3]; + }; + + static_assert(std::is_standard_layout::value, "Must be standard layout"); + static_assert(sizeof(float) == 4, "Single precision floats"); + static_assert((sizeof(SceneConstantBuffer) % 16) == 0, "Constant Buffer must be divisible by 16 bytes"); + static_assert(sizeof(SceneConstantBuffer) == 128, "Size must be the same as known"); + static_assert(offsetof(SceneConstantBuffer, ViewProjection) == 0, "Offsets must match shader"); + static_assert(offsetof(SceneConstantBuffer, EyePosition) == 64, "Offsets must match shader"); + static_assert(offsetof(SceneConstantBuffer, LightDirection) == 80, "Offsets must match shader"); + static_assert(offsetof(SceneConstantBuffer, LightDiffuseColor) == 96, "Offsets must match shader"); + static_assert(offsetof(SceneConstantBuffer, NumSpecularMipLevels) == 112, "Offsets must match shader"); + + struct ModelConstantBuffer + { + DirectX::XMFLOAT4X4 ModelToWorld; + }; + + static_assert((sizeof(ModelConstantBuffer) % 16) == 0, "Constant Buffer must be divisible by 16 bytes"); + + const D3D11_INPUT_ELEMENT_DESC s_vertexDesc[6] = { + {"POSITION", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, D3D11_APPEND_ALIGNED_ELEMENT, D3D11_INPUT_PER_VERTEX_DATA, 0}, + {"NORMAL", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, D3D11_APPEND_ALIGNED_ELEMENT, D3D11_INPUT_PER_VERTEX_DATA, 0}, + {"TANGENT", 0, DXGI_FORMAT_R32G32B32A32_FLOAT, 0, D3D11_APPEND_ALIGNED_ELEMENT, D3D11_INPUT_PER_VERTEX_DATA, 0}, + {"COLOR", 0, DXGI_FORMAT_R32G32B32A32_FLOAT, 0, D3D11_APPEND_ALIGNED_ELEMENT, D3D11_INPUT_PER_VERTEX_DATA, 0}, + {"TEXCOORD", 0, DXGI_FORMAT_R32G32_FLOAT, 0, D3D11_APPEND_ALIGNED_ELEMENT, D3D11_INPUT_PER_VERTEX_DATA, 0}, + {"TRANSFORMINDEX", 0, DXGI_FORMAT_R16_UINT, 0, D3D11_APPEND_ALIGNED_ELEMENT, D3D11_INPUT_PER_VERTEX_DATA, 0}, + }; +} // namespace + +namespace Pbr +{ + using ImageKey = std::tuple; // Item1 is a pointer to the image, Item2 is sRGB. + + struct D3D11Resources::Impl + { + void Initialize(_In_ ID3D11Device* device) + { + XRC_CHECK_THROW_HRCMD(device->CreateInputLayout(s_vertexDesc, ARRAYSIZE(s_vertexDesc), g_PbrVertexShader, + sizeof(g_PbrVertexShader), Resources.InputLayout.ReleaseAndGetAddressOf())); + + // Set up pixel shader. + XRC_CHECK_THROW_HRCMD(device->CreatePixelShader(g_PbrPixelShader, sizeof(g_PbrPixelShader), nullptr, + Resources.PbrPixelShader.ReleaseAndGetAddressOf())); + + XRC_CHECK_THROW_HRCMD(device->CreateVertexShader(g_PbrVertexShader, sizeof(g_PbrVertexShader), nullptr, + Resources.PbrVertexShader.ReleaseAndGetAddressOf())); + + // Set up the constant buffers. + const CD3D11_BUFFER_DESC pbrConstantBufferDesc(sizeof(SceneConstantBuffer), D3D11_BIND_CONSTANT_BUFFER); + XRC_CHECK_THROW_HRCMD( + device->CreateBuffer(&pbrConstantBufferDesc, nullptr, Resources.SceneConstantBuffer.ReleaseAndGetAddressOf())); + + const CD3D11_BUFFER_DESC modelConstantBufferDesc(sizeof(ModelConstantBuffer), D3D11_BIND_CONSTANT_BUFFER); + XRC_CHECK_THROW_HRCMD( + device->CreateBuffer(&modelConstantBufferDesc, nullptr, Resources.ModelConstantBuffer.ReleaseAndGetAddressOf())); + + // Samplers for environment map and BRDF. + Resources.EnvironmentMapSampler = D3D11Texture::CreateSampler(device); + Resources.BrdfSampler = D3D11Texture::CreateSampler(device); + + CD3D11_BLEND_DESC blendStateDesc(D3D11_DEFAULT); + XRC_CHECK_THROW_HRCMD(device->CreateBlendState(&blendStateDesc, Resources.DefaultBlendState.ReleaseAndGetAddressOf())); + + D3D11_RENDER_TARGET_BLEND_DESC rtBlendDesc; + rtBlendDesc.BlendEnable = TRUE; + rtBlendDesc.SrcBlend = D3D11_BLEND_SRC_ALPHA; + rtBlendDesc.DestBlend = D3D11_BLEND_INV_SRC_ALPHA; + rtBlendDesc.BlendOp = D3D11_BLEND_OP_ADD; + rtBlendDesc.SrcBlendAlpha = D3D11_BLEND_ZERO; + rtBlendDesc.DestBlendAlpha = D3D11_BLEND_ONE; + rtBlendDesc.BlendOpAlpha = D3D11_BLEND_OP_ADD; + rtBlendDesc.RenderTargetWriteMask = D3D11_COLOR_WRITE_ENABLE_ALL; + for (UINT i = 0; i < D3D11_SIMULTANEOUS_RENDER_TARGET_COUNT; ++i) { + blendStateDesc.RenderTarget[i] = rtBlendDesc; + } + XRC_CHECK_THROW_HRCMD(device->CreateBlendState(&blendStateDesc, Resources.AlphaBlendState.ReleaseAndGetAddressOf())); + + for (bool doubleSided : {false, true}) { + for (bool wireframe : {false, true}) { + for (bool frontCounterClockwise : {false, true}) { + CD3D11_RASTERIZER_DESC rasterizerDesc(D3D11_DEFAULT); + rasterizerDesc.CullMode = doubleSided ? D3D11_CULL_NONE : D3D11_CULL_BACK; + rasterizerDesc.FillMode = wireframe ? D3D11_FILL_WIREFRAME : D3D11_FILL_SOLID; + rasterizerDesc.FrontCounterClockwise = frontCounterClockwise; + XRC_CHECK_THROW_HRCMD(device->CreateRasterizerState( + &rasterizerDesc, + Resources.RasterizerStates[doubleSided][wireframe][frontCounterClockwise].ReleaseAndGetAddressOf())); + } + } + } + + for (bool reverseZ : {false, true}) { + for (bool noWrite : {false, true}) { + CD3D11_DEPTH_STENCIL_DESC depthStencilDesc(CD3D11_DEFAULT{}); + depthStencilDesc.DepthFunc = reverseZ ? D3D11_COMPARISON_GREATER : D3D11_COMPARISON_LESS; + depthStencilDesc.DepthWriteMask = noWrite ? D3D11_DEPTH_WRITE_MASK_ZERO : D3D11_DEPTH_WRITE_MASK_ALL; + XRC_CHECK_THROW_HRCMD(device->CreateDepthStencilState( + &depthStencilDesc, Resources.DepthStencilStates[reverseZ][noWrite].ReleaseAndGetAddressOf())); + } + } + + Resources.SolidColorTextureCache = D3D11TextureCache{device}; + } + + struct DeviceResources + { + Microsoft::WRL::ComPtr BrdfSampler; + Microsoft::WRL::ComPtr EnvironmentMapSampler; + Microsoft::WRL::ComPtr InputLayout; + Microsoft::WRL::ComPtr PbrVertexShader; + Microsoft::WRL::ComPtr PbrPixelShader; + Microsoft::WRL::ComPtr SceneConstantBuffer; + Microsoft::WRL::ComPtr ModelConstantBuffer; + Microsoft::WRL::ComPtr BrdfLut; + Microsoft::WRL::ComPtr SpecularEnvironmentMap; + Microsoft::WRL::ComPtr DiffuseEnvironmentMap; + Microsoft::WRL::ComPtr AlphaBlendState; + Microsoft::WRL::ComPtr DefaultBlendState; + Microsoft::WRL::ComPtr + RasterizerStates[2][2][2]; // Three dimensions for [DoubleSide][Wireframe][FrontCounterClockWise] + Microsoft::WRL::ComPtr DepthStencilStates[2][2]; // Two dimensions for [ReverseZ][NoWrite] + mutable D3D11TextureCache SolidColorTextureCache; + }; + PrimitiveCollection Primitives; + + DeviceResources Resources; + SceneConstantBuffer SceneBuffer; + ModelConstantBuffer ModelBuffer; + + struct LoaderResources + { + // Create D3D cache for reuse of texture views and samplers when possible. + std::map> imageMap; + std::map> samplerMap; + }; + LoaderResources loaderResources; + }; + + D3D11Resources::D3D11Resources(_In_ ID3D11Device* device) : m_impl(std::make_unique()) + { + m_impl->Initialize(device); + } + + D3D11Resources::D3D11Resources(D3D11Resources&& resources) = default; + + D3D11Resources::~D3D11Resources() = default; + + // Create a DirectX texture view from a tinygltf Image. + static Microsoft::WRL::ComPtr D3D11LoadGLTFImage(_In_ ID3D11Device* device, const tinygltf::Image& image, + bool sRGB) + { + // First convert the image to RGBA if it isn't already. + std::vector tempBuffer; + const uint8_t* rgbaBuffer = GltfHelper::ReadImageAsRGBA(image, &tempBuffer); + if (rgbaBuffer == nullptr) { + return nullptr; + } + + const DXGI_FORMAT format = sRGB ? DXGI_FORMAT_R8G8B8A8_UNORM_SRGB : DXGI_FORMAT_R8G8B8A8_UNORM; + return Pbr::D3D11Texture::CreateTexture(device, rgbaBuffer, image.width * image.height * 4, image.width, image.height, format); + } + + static D3D11_FILTER D3D11ConvertFilter(int glMinFilter, int glMagFilter) + { + const D3D11_FILTER_TYPE minFilter = glMinFilter == TINYGLTF_TEXTURE_FILTER_NEAREST + ? D3D11_FILTER_TYPE_POINT + : glMinFilter == TINYGLTF_TEXTURE_FILTER_LINEAR + ? D3D11_FILTER_TYPE_LINEAR + : glMinFilter == TINYGLTF_TEXTURE_FILTER_NEAREST_MIPMAP_NEAREST + ? D3D11_FILTER_TYPE_POINT + : glMinFilter == TINYGLTF_TEXTURE_FILTER_LINEAR_MIPMAP_NEAREST + ? D3D11_FILTER_TYPE_LINEAR + : glMinFilter == TINYGLTF_TEXTURE_FILTER_NEAREST_MIPMAP_LINEAR + ? D3D11_FILTER_TYPE_POINT + : glMinFilter == TINYGLTF_TEXTURE_FILTER_LINEAR_MIPMAP_LINEAR + ? D3D11_FILTER_TYPE_LINEAR + : D3D11_FILTER_TYPE_POINT; + const D3D11_FILTER_TYPE mipFilter = glMinFilter == TINYGLTF_TEXTURE_FILTER_NEAREST + ? D3D11_FILTER_TYPE_POINT + : glMinFilter == TINYGLTF_TEXTURE_FILTER_LINEAR + ? D3D11_FILTER_TYPE_POINT + : glMinFilter == TINYGLTF_TEXTURE_FILTER_NEAREST_MIPMAP_NEAREST + ? D3D11_FILTER_TYPE_POINT + : glMinFilter == TINYGLTF_TEXTURE_FILTER_LINEAR_MIPMAP_NEAREST + ? D3D11_FILTER_TYPE_POINT + : glMinFilter == TINYGLTF_TEXTURE_FILTER_NEAREST_MIPMAP_LINEAR + ? D3D11_FILTER_TYPE_LINEAR + : glMinFilter == TINYGLTF_TEXTURE_FILTER_LINEAR_MIPMAP_LINEAR + ? D3D11_FILTER_TYPE_LINEAR + : D3D11_FILTER_TYPE_POINT; + const D3D11_FILTER_TYPE magFilter = + glMagFilter == TINYGLTF_TEXTURE_FILTER_NEAREST + ? D3D11_FILTER_TYPE_POINT + : glMagFilter == TINYGLTF_TEXTURE_FILTER_LINEAR ? D3D11_FILTER_TYPE_LINEAR : D3D11_FILTER_TYPE_POINT; + + const D3D11_FILTER filter = D3D11_ENCODE_BASIC_FILTER(minFilter, magFilter, mipFilter, D3D11_FILTER_REDUCTION_TYPE_STANDARD); + return filter; + } + + // Create a DirectX sampler state from a tinygltf Sampler. + static Microsoft::WRL::ComPtr D3D11CreateGLTFSampler(_In_ ID3D11Device* device, const tinygltf::Sampler& sampler) + { + D3D11_SAMPLER_DESC samplerDesc{}; + + samplerDesc.Filter = D3D11ConvertFilter(sampler.minFilter, sampler.magFilter); + samplerDesc.AddressU = + sampler.wrapS == TINYGLTF_TEXTURE_WRAP_CLAMP_TO_EDGE + ? D3D11_TEXTURE_ADDRESS_CLAMP + : sampler.wrapS == TINYGLTF_TEXTURE_WRAP_MIRRORED_REPEAT ? D3D11_TEXTURE_ADDRESS_MIRROR : D3D11_TEXTURE_ADDRESS_WRAP; + samplerDesc.AddressV = + sampler.wrapT == TINYGLTF_TEXTURE_WRAP_CLAMP_TO_EDGE + ? D3D11_TEXTURE_ADDRESS_CLAMP + : sampler.wrapT == TINYGLTF_TEXTURE_WRAP_MIRRORED_REPEAT ? D3D11_TEXTURE_ADDRESS_MIRROR : D3D11_TEXTURE_ADDRESS_WRAP; + samplerDesc.AddressW = D3D11_TEXTURE_ADDRESS_WRAP; + samplerDesc.MaxAnisotropy = 1; + samplerDesc.ComparisonFunc = D3D11_COMPARISON_ALWAYS; + samplerDesc.MinLOD = 0; + samplerDesc.MaxLOD = D3D11_FLOAT32_MAX; + + Microsoft::WRL::ComPtr samplerState; + XRC_CHECK_THROW_HRCMD(device->CreateSamplerState(&samplerDesc, samplerState.ReleaseAndGetAddressOf())); + return samplerState; + } + + /* IResources implementations */ + std::shared_ptr D3D11Resources::CreateFlatMaterial(RGBAColor baseColorFactor, float roughnessFactor, float metallicFactor, + RGBColor emissiveFactor) + { + return D3D11Material::CreateFlat(*this, baseColorFactor, roughnessFactor, metallicFactor, emissiveFactor); + } + std::shared_ptr D3D11Resources::CreateMaterial() + { + return std::make_shared(*this); + } + std::shared_ptr D3D11Resources::CreateSolidColorTexture(RGBAColor color) + { + // TODO maybe unused + auto ret = std::make_shared(); + ret->srv = CreateTypedSolidColorTexture(color); + return ret; + } + + void D3D11Resources::LoadTexture(const std::shared_ptr& material, Pbr::ShaderSlots::PSMaterial slot, + const tinygltf::Image* image, const tinygltf::Sampler* sampler, bool sRGB, Pbr::RGBAColor defaultRGBA) + { + auto pbrMaterial = std::dynamic_pointer_cast(material); + if (!pbrMaterial) { + throw std::logic_error("Wrong type of material"); + } + // Find or load the image referenced by the texture. + const ImageKey imageKey = std::make_tuple(image, sRGB); + Microsoft::WRL::ComPtr textureView = + image != nullptr ? m_impl->loaderResources.imageMap[imageKey] : CreateTypedSolidColorTexture(defaultRGBA); + if (!textureView) // If not cached, load the image and store it in the texture cache. + { + // TODO: Generate mipmaps if sampler's minification filter (minFilter) uses mipmapping. + // TODO: If texture is not power-of-two and (sampler has wrapping=repeat/mirrored_repeat OR minFilter uses + // mipmapping), resize to power-of-two. + textureView = D3D11LoadGLTFImage(GetDevice().Get(), *image, sRGB); + m_impl->loaderResources.imageMap[imageKey] = textureView; + } + + // Find or create the sampler referenced by the texture. + Microsoft::WRL::ComPtr samplerState = m_impl->loaderResources.samplerMap[sampler]; + if (!samplerState) // If not cached, create the sampler and store it in the sampler cache. + { + samplerState = sampler != nullptr ? D3D11CreateGLTFSampler(GetDevice().Get(), *sampler) + : Pbr::D3D11Texture::CreateSampler(GetDevice().Get(), D3D11_TEXTURE_ADDRESS_WRAP); + m_impl->loaderResources.samplerMap[sampler] = samplerState; + } + + pbrMaterial->SetTexture(slot, textureView.Get(), samplerState.Get()); + } + void D3D11Resources::DropLoaderCaches() + { + m_impl->loaderResources = {}; + } + + void D3D11Resources::SetBrdfLut(_In_ ID3D11ShaderResourceView* brdfLut) + { + m_impl->Resources.BrdfLut = brdfLut; + } + + void D3D11Resources::CreateDeviceDependentResources(_In_ ID3D11Device* device) + { + m_impl->Initialize(device); + } + + void D3D11Resources::ReleaseDeviceDependentResources() + { + m_impl->Resources = {}; + m_impl->loaderResources = {}; + m_impl->Primitives.clear(); + } + + Microsoft::WRL::ComPtr D3D11Resources::GetDevice() const + { + Microsoft::WRL::ComPtr device; + m_impl->Resources.SceneConstantBuffer->GetDevice(device.ReleaseAndGetAddressOf()); + return device; + } + + void D3D11Resources::SetLight(DirectX::XMFLOAT3 direction, RGBColor diffuseColor) + { + m_impl->SceneBuffer.LightDirection = direction; + m_impl->SceneBuffer.LightDiffuseColor = {diffuseColor.x, diffuseColor.y, diffuseColor.z}; + } + + void XM_CALLCONV D3D11Resources::SetModelToWorld(DirectX::FXMMATRIX modelToWorld, _In_ ID3D11DeviceContext* context) const + { + XMStoreFloat4x4(&m_impl->ModelBuffer.ModelToWorld, XMMatrixTranspose(modelToWorld)); + context->UpdateSubresource(m_impl->Resources.ModelConstantBuffer.Get(), 0, nullptr, &m_impl->ModelBuffer, 0, 0); + } + + void XM_CALLCONV D3D11Resources::SetViewProjection(DirectX::FXMMATRIX view, DirectX::CXMMATRIX projection) + { + XMStoreFloat4x4(&m_impl->SceneBuffer.ViewProjection, XMMatrixTranspose(XMMatrixMultiply(view, projection))); + XMStoreFloat4(&m_impl->SceneBuffer.EyePosition, XMMatrixInverse(nullptr, view).r[3]); + } + + void D3D11Resources::SetEnvironmentMap(_In_ ID3D11ShaderResourceView* specularEnvironmentMap, + _In_ ID3D11ShaderResourceView* diffuseEnvironmentMap) + { + D3D11_SHADER_RESOURCE_VIEW_DESC desc; + diffuseEnvironmentMap->GetDesc(&desc); + if (desc.ViewDimension != D3D_SRV_DIMENSION_TEXTURECUBE) { + throw std::logic_error("Diffuse Resource View Type is not D3D_SRV_DIMENSION_TEXTURECUBE"); + } + + specularEnvironmentMap->GetDesc(&desc); + if (desc.ViewDimension != D3D_SRV_DIMENSION_TEXTURECUBE) { + throw std::logic_error("Specular Resource View Type is not D3D_SRV_DIMENSION_TEXTURECUBE"); + } + + m_impl->SceneBuffer.NumSpecularMipLevels = desc.TextureCube.MipLevels; + m_impl->Resources.SpecularEnvironmentMap = specularEnvironmentMap; + m_impl->Resources.DiffuseEnvironmentMap = diffuseEnvironmentMap; + } + + Microsoft::WRL::ComPtr D3D11Resources::CreateTypedSolidColorTexture(RGBAColor color) const + { + return m_impl->Resources.SolidColorTextureCache.CreateTypedSolidColorTexture(color); + } + + void D3D11Resources::Bind(_In_ ID3D11DeviceContext* context) const + { + context->UpdateSubresource(m_impl->Resources.SceneConstantBuffer.Get(), 0, nullptr, &m_impl->SceneBuffer, 0, 0); + + context->VSSetShader(m_impl->Resources.PbrVertexShader.Get(), nullptr, 0); + context->PSSetShader(m_impl->Resources.PbrPixelShader.Get(), nullptr, 0); + + ID3D11Buffer* vsBuffers[] = {m_impl->Resources.SceneConstantBuffer.Get(), m_impl->Resources.ModelConstantBuffer.Get()}; + context->VSSetConstantBuffers(Pbr::ShaderSlots::ConstantBuffers::Scene, _countof(vsBuffers), vsBuffers); + ID3D11Buffer* psBuffers[] = {m_impl->Resources.SceneConstantBuffer.Get()}; + context->PSSetConstantBuffers(Pbr::ShaderSlots::ConstantBuffers::Scene, _countof(psBuffers), psBuffers); + context->IASetInputLayout(m_impl->Resources.InputLayout.Get()); + + static_assert(ShaderSlots::DiffuseTexture == ShaderSlots::SpecularTexture + 1, "Diffuse must follow Specular slot"); + static_assert(ShaderSlots::SpecularTexture == ShaderSlots::Brdf + 1, "Specular must follow BRDF slot"); + ID3D11ShaderResourceView* shaderResources[] = {m_impl->Resources.BrdfLut.Get(), m_impl->Resources.SpecularEnvironmentMap.Get(), + m_impl->Resources.DiffuseEnvironmentMap.Get()}; + context->PSSetShaderResources(Pbr::ShaderSlots::Brdf, _countof(shaderResources), shaderResources); + ID3D11SamplerState* samplers[] = {m_impl->Resources.BrdfSampler.Get(), m_impl->Resources.EnvironmentMapSampler.Get()}; + context->PSSetSamplers(ShaderSlots::Brdf, _countof(samplers), samplers); + } + + PrimitiveHandle D3D11Resources::MakePrimitive(const Pbr::PrimitiveBuilder& primitiveBuilder, + const std::shared_ptr& material) + { + auto typedMaterial = std::dynamic_pointer_cast(material); + if (!typedMaterial) { + throw std::logic_error("Got the wrong type of material"); + } + return m_impl->Primitives.emplace_back(*this, primitiveBuilder, typedMaterial, false); + } + + D3D11Primitive& D3D11Resources::GetPrimitive(PrimitiveHandle p) + { + return m_impl->Primitives[p]; + } + + const D3D11Primitive& D3D11Resources::GetPrimitive(PrimitiveHandle p) const + { + return m_impl->Primitives[p]; + } + + void D3D11Resources::SetFillMode(FillMode mode) + { + m_sharedState.SetFillMode(mode); + } + + FillMode D3D11Resources::GetFillMode() const + { + return m_sharedState.GetFillMode(); + } + + void D3D11Resources::SetFrontFaceWindingOrder(FrontFaceWindingOrder windingOrder) + { + m_sharedState.SetFrontFaceWindingOrder(windingOrder); + } + + FrontFaceWindingOrder D3D11Resources::GetFrontFaceWindingOrder() const + { + return m_sharedState.GetFrontFaceWindingOrder(); + } + + void D3D11Resources::SetDepthDirection(DepthDirection depthDirection) + { + m_sharedState.SetDepthDirection(depthDirection); + } + + void D3D11Resources::SetBlendState(_In_ ID3D11DeviceContext* context, bool enabled) const + { + context->OMSetBlendState(enabled ? m_impl->Resources.AlphaBlendState.Get() : m_impl->Resources.DefaultBlendState.Get(), nullptr, + 0xFFFFFF); + } + + void D3D11Resources::SetRasterizerState(_In_ ID3D11DeviceContext* context, bool doubleSided) const + { + context->RSSetState( + m_impl->Resources + .RasterizerStates[doubleSided ? 1 : 0][m_sharedState.GetFillMode() == FillMode::Wireframe ? 1 : 0] + [m_sharedState.GetFrontFaceWindingOrder() == FrontFaceWindingOrder::CounterClockWise ? 1 : 0] + .Get()); + } + + void D3D11Resources::SetDepthStencilState(_In_ ID3D11DeviceContext* context, bool disableDepthWrite) const + { + context->OMSetDepthStencilState( + m_impl->Resources + .DepthStencilStates[m_sharedState.GetDepthDirection() == DepthDirection::Reversed ? 1 : 0][disableDepthWrite ? 1 : 0] + .Get(), + 1); + } +} // namespace Pbr + +#endif // defined(XR_USE_GRAPHICS_API_D3D11) diff --git a/src/conformance/framework/pbr/D3D11/D3D11Resources.h b/src/conformance/framework/pbr/D3D11/D3D11Resources.h new file mode 100644 index 00000000..e09f5a96 --- /dev/null +++ b/src/conformance/framework/pbr/D3D11/D3D11Resources.h @@ -0,0 +1,115 @@ +// Copyright 2022-2023, The Khronos Group, Inc. +// +// Based in part on code that is: +// Copyright (C) Microsoft Corporation. All Rights Reserved +// Licensed under the MIT License. See License.txt in the project root for license information. +// +// SPDX-License-Identifier: MIT AND Apache-2.0 +#pragma once + +#include "../IResources.h" +#include "../PbrCommon.h" +#include "../PbrHandles.h" +#include "../PbrSharedState.h" + +#include +#include +#include +#include // For Microsoft::WRL::ComPtr + +#include +#include +#include +#include +#include + +namespace Pbr +{ + struct Primitive; + using Duration = std::chrono::high_resolution_clock::duration; + struct D3D11Primitive; + struct D3D11Material; + + struct D3D11TextureAndSampler : public ITexture + { + ~D3D11TextureAndSampler() = default; + /// Required + Microsoft::WRL::ComPtr srv; + + /// Optional + Microsoft::WRL::ComPtr sampler; + }; + + // Global PBR resources required for rendering a scene. + struct D3D11Resources final : public IResources + { + explicit D3D11Resources(_In_ ID3D11Device* d3dDevice); + D3D11Resources(D3D11Resources&&); + + ~D3D11Resources() override; + + std::shared_ptr CreateFlatMaterial(RGBAColor baseColorFactor, float roughnessFactor = 1.0f, float metallicFactor = 0.0f, + RGBColor emissiveFactor = RGB::Black) override; + std::shared_ptr CreateMaterial() override; + std::shared_ptr CreateSolidColorTexture(RGBAColor color); + void LoadTexture(const std::shared_ptr& pbrMaterial, Pbr::ShaderSlots::PSMaterial slot, const tinygltf::Image* image, + const tinygltf::Sampler* sampler, bool sRGB, Pbr::RGBAColor defaultRGBA) override; + PrimitiveHandle MakePrimitive(const Pbr::PrimitiveBuilder& primitiveBuilder, + const std::shared_ptr& material) override; + void DropLoaderCaches() override; + + // Sets the Bidirectional Reflectance Distribution Function Lookup Table texture, required by the shader to compute surface + // reflectance from the IBL. + void SetBrdfLut(_In_ ID3D11ShaderResourceView* brdfLut); + + // Create device-dependent resources. + void CreateDeviceDependentResources(_In_ ID3D11Device* device); + + // Release device-dependent resources. + void ReleaseDeviceDependentResources(); + + // Get the D3D11Device that the PBR resources are associated with. + Microsoft::WRL::ComPtr GetDevice() const; + + // Set the directional light. + void SetLight(DirectX::XMFLOAT3 direction, RGBColor diffuseColor); + + // Set the specular and diffuse image-based lighting (IBL) maps. ShaderResourceViews must be TextureCubes. + void SetEnvironmentMap(_In_ ID3D11ShaderResourceView* specularEnvironmentMap, _In_ ID3D11ShaderResourceView* diffuseEnvironmentMap); + + // Set the current view and projection matrices. + void XM_CALLCONV SetViewProjection(DirectX::FXMMATRIX view, DirectX::CXMMATRIX projection); + + // Many 1x1 pixel colored textures are used in the PBR system. This is used to create textures backed by a cache to reduce the + // number of textures created. + Microsoft::WRL::ComPtr CreateTypedSolidColorTexture(RGBAColor color) const; + + // Bind the the PBR resources to the current context. + void Bind(_In_ ID3D11DeviceContext* context) const; + + // Set and update the model to world constant buffer value. + void XM_CALLCONV SetModelToWorld(DirectX::FXMMATRIX modelToWorld, _In_ ID3D11DeviceContext* context) const; + + D3D11Primitive& GetPrimitive(PrimitiveHandle p); + const D3D11Primitive& GetPrimitive(PrimitiveHandle p) const; + + // Set or get the shading and fill modes. + void SetFillMode(FillMode mode); + FillMode GetFillMode() const; + void SetFrontFaceWindingOrder(FrontFaceWindingOrder windingOrder); + FrontFaceWindingOrder GetFrontFaceWindingOrder() const; + void SetDepthDirection(DepthDirection depthDirection); + + private: + void SetBlendState(_In_ ID3D11DeviceContext* context, bool enabled) const; + void SetRasterizerState(_In_ ID3D11DeviceContext* context, bool doubleSided) const; + void SetDepthStencilState(_In_ ID3D11DeviceContext* context, bool disableDepthWrite) const; + + friend struct D3D11Material; + + struct Impl; + std::unique_ptr m_impl; + + SharedState m_sharedState; + }; +} // namespace Pbr diff --git a/src/conformance/framework/pbr/D3D11/D3D11Texture.cpp b/src/conformance/framework/pbr/D3D11/D3D11Texture.cpp new file mode 100644 index 00000000..8f93616a --- /dev/null +++ b/src/conformance/framework/pbr/D3D11/D3D11Texture.cpp @@ -0,0 +1,136 @@ +// Copyright 2022-2023, The Khronos Group, Inc. +// +// Based in part on code that is: +// Copyright (C) Microsoft Corporation. All Rights Reserved +// Licensed under the MIT License. See License.txt in the project root for license information. +// +// SPDX-License-Identifier: MIT AND Apache-2.0 + +#if defined(XR_USE_GRAPHICS_API_D3D11) + +#include "D3D11Texture.h" + +#include "stb_image.h" + +#include "utilities/throw_helpers.h" + +#include +#include + +using namespace DirectX; + +namespace Pbr +{ + namespace D3D11Texture + { + std::array LoadRGBAUI4(RGBAColor color) + { + return std::array{(uint8_t)(color.r * 255.), (uint8_t)(color.g * 255.), (uint8_t)(color.b * 255.), + (uint8_t)(color.a * 255.)}; + } + + Microsoft::WRL::ComPtr LoadTextureImage(_In_ ID3D11Device* device, + _In_reads_bytes_(fileSize) const uint8_t* fileData, + uint32_t fileSize) + { + auto freeImageData = [](unsigned char* ptr) { ::free(ptr); }; + using stbi_unique_ptr = std::unique_ptr; + + constexpr uint32_t DesiredComponentCount = 4; + + int w, h, c; + // If c == 3, a component will be padded with 1.0f + stbi_unique_ptr rgbaData(stbi_load_from_memory(fileData, fileSize, &w, &h, &c, DesiredComponentCount), freeImageData); + if (!rgbaData) { + throw std::runtime_error("Failed to load image file data."); + } + + return CreateTexture(device, rgbaData.get(), w * h * DesiredComponentCount, w, h, DXGI_FORMAT_R8G8B8A8_UNORM); + } + + Microsoft::WRL::ComPtr CreateFlatCubeTexture(_In_ ID3D11Device* device, RGBAColor color, + DXGI_FORMAT format) + { + D3D11_TEXTURE2D_DESC desc{}; + desc.Width = 1; + desc.Height = 1; + desc.MipLevels = 1; + desc.ArraySize = 6; + desc.Format = format; + desc.SampleDesc.Count = 1; + desc.SampleDesc.Quality = 0; + desc.Usage = D3D11_USAGE_DEFAULT; + desc.BindFlags = D3D11_BIND_SHADER_RESOURCE; + desc.MiscFlags = D3D11_RESOURCE_MISC_TEXTURECUBE; + + // Each side is a 1x1 pixel (RGBA) image. + const std::array rgbaColor = LoadRGBAUI4(color); + D3D11_SUBRESOURCE_DATA initData[6]; + for (int i = 0; i < _countof(initData); i++) { + initData[i].pSysMem = rgbaColor.data(); + initData[i].SysMemPitch = initData[i].SysMemSlicePitch = 4; + } + + Microsoft::WRL::ComPtr cubeTexture; + XRC_CHECK_THROW_HRCMD(device->CreateTexture2D(&desc, initData, cubeTexture.ReleaseAndGetAddressOf())); + + D3D11_SHADER_RESOURCE_VIEW_DESC srvDesc{}; + srvDesc.Format = desc.Format; + srvDesc.ViewDimension = D3D11_SRV_DIMENSION_TEXTURECUBE; + srvDesc.Texture2D.MipLevels = desc.MipLevels; + srvDesc.Texture2D.MostDetailedMip = 0; + + Microsoft::WRL::ComPtr textureView; + XRC_CHECK_THROW_HRCMD(device->CreateShaderResourceView(cubeTexture.Get(), &srvDesc, textureView.ReleaseAndGetAddressOf())); + + return textureView; + } + + Microsoft::WRL::ComPtr CreateTexture(_In_ ID3D11Device* device, + _In_reads_bytes_(size) const uint8_t* rgba, uint32_t size, int width, + int height, DXGI_FORMAT format) + { + D3D11_TEXTURE2D_DESC desc{}; + desc.Width = width; + desc.Height = height; + desc.MipLevels = 1; + desc.ArraySize = 1; + desc.Format = format; + desc.SampleDesc.Count = 1; + desc.SampleDesc.Quality = 0; + desc.Usage = D3D11_USAGE_DEFAULT; + desc.BindFlags = D3D11_BIND_SHADER_RESOURCE; + + D3D11_SUBRESOURCE_DATA initData{}; + initData.pSysMem = rgba; + initData.SysMemPitch = size / height; + initData.SysMemSlicePitch = size; + + Microsoft::WRL::ComPtr texture2D; + XRC_CHECK_THROW_HRCMD(device->CreateTexture2D(&desc, &initData, texture2D.ReleaseAndGetAddressOf())); + + D3D11_SHADER_RESOURCE_VIEW_DESC srvDesc{}; + srvDesc.Format = desc.Format; + srvDesc.ViewDimension = D3D11_SRV_DIMENSION_TEXTURE2D; + srvDesc.Texture2D.MipLevels = desc.MipLevels; + srvDesc.Texture2D.MostDetailedMip = desc.MipLevels - 1; + + Microsoft::WRL::ComPtr textureView; + XRC_CHECK_THROW_HRCMD(device->CreateShaderResourceView(texture2D.Get(), &srvDesc, textureView.ReleaseAndGetAddressOf())); + + return textureView; + } + + Microsoft::WRL::ComPtr CreateSampler(_In_ ID3D11Device* device, D3D11_TEXTURE_ADDRESS_MODE addressMode) + { + CD3D11_SAMPLER_DESC samplerDesc(CD3D11_DEFAULT{}); + samplerDesc.AddressU = samplerDesc.AddressV = samplerDesc.AddressW = addressMode; + + Microsoft::WRL::ComPtr samplerState; + XRC_CHECK_THROW_HRCMD(device->CreateSamplerState(&samplerDesc, samplerState.ReleaseAndGetAddressOf())); + return samplerState; + } + } // namespace D3D11Texture +} // namespace Pbr + +#endif // defined(XR_USE_GRAPHICS_API_D3D11) diff --git a/src/conformance/framework/pbr/D3D11/D3D11Texture.h b/src/conformance/framework/pbr/D3D11/D3D11Texture.h new file mode 100644 index 00000000..a33932ae --- /dev/null +++ b/src/conformance/framework/pbr/D3D11/D3D11Texture.h @@ -0,0 +1,43 @@ +// Copyright 2022-2023, The Khronos Group, Inc. +// +// Based in part on code that is: +// Copyright (C) Microsoft Corporation. All Rights Reserved +// Licensed under the MIT License. See License.txt in the project root for license information. +// +// SPDX-License-Identifier: MIT AND Apache-2.0 +// +// Shared data types and functions used throughout the Pbr rendering library. +// + +#pragma once + +#include "../PbrCommon.h" + +#include +#include +#include +#include +#include +#include // For Microsoft::WRL::ComPtr + +#include +#include + +namespace Pbr +{ + namespace D3D11Texture + { + std::array LoadRGBAUI4(RGBAColor color); + + Microsoft::WRL::ComPtr LoadTextureImage(_In_ ID3D11Device* device, + _In_reads_bytes_(fileSize) const uint8_t* fileData, + uint32_t fileSize); + Microsoft::WRL::ComPtr CreateFlatCubeTexture(_In_ ID3D11Device* device, RGBAColor color, + DXGI_FORMAT format = DXGI_FORMAT_R8G8B8A8_UNORM); + Microsoft::WRL::ComPtr CreateTexture(_In_ ID3D11Device* device, + _In_reads_bytes_(size) const uint8_t* rgba, uint32_t size, int width, + int height, DXGI_FORMAT format); + Microsoft::WRL::ComPtr CreateSampler(_In_ ID3D11Device* device, + D3D11_TEXTURE_ADDRESS_MODE addressMode = D3D11_TEXTURE_ADDRESS_CLAMP); + } // namespace D3D11Texture +} // namespace Pbr diff --git a/src/conformance/framework/pbr/D3D11/D3D11TextureCache.cpp b/src/conformance/framework/pbr/D3D11/D3D11TextureCache.cpp new file mode 100644 index 00000000..4c80bc8d --- /dev/null +++ b/src/conformance/framework/pbr/D3D11/D3D11TextureCache.cpp @@ -0,0 +1,55 @@ +// Copyright 2023, The Khronos Group, Inc. +// +// Based in part on code that is: +// +// Copyright (C) Microsoft Corporation. All Rights Reserved +// Licensed under the MIT License. See License.txt in the project root for license information. +// +// SPDX-License-Identifier: MIT AND Apache-2.0 + +#if defined(XR_USE_GRAPHICS_API_D3D11) + +#include "D3D11TextureCache.h" + +#include "D3D11Texture.h" + +#include "../PbrMaterial.h" + +#include +#include +#include + +namespace Pbr +{ + D3D11TextureCache::D3D11TextureCache(ID3D11Device* device) : m_cacheMutex(std::make_unique()) + { + m_device = device; + } + + ComPtr D3D11TextureCache::CreateTypedSolidColorTexture(XrColor4f color) + { + if (!IsValid()) { + throw std::logic_error("D3D11TextureCache accessed before initialization"); + } + const std::array rgba = D3D11Texture::LoadRGBAUI4(color); + + // Check cache to see if this flat texture already exists. + const uint32_t colorKey = *reinterpret_cast(rgba.data()); + { + std::lock_guard guard(*m_cacheMutex); + auto textureIt = m_solidColorTextureCache.find(colorKey); + if (textureIt != m_solidColorTextureCache.end()) { + return textureIt->second; + } + } + + Microsoft::WRL::ComPtr texture = + Pbr::D3D11Texture::CreateTexture(m_device.Get(), rgba.data(), 1, 1, 1, DXGI_FORMAT_R8G8B8A8_UNORM); + + std::lock_guard guard(*m_cacheMutex); + // If the key already exists then the existing texture will be returned. + return m_solidColorTextureCache.emplace(colorKey, texture).first->second; + } +} // namespace Pbr + +#endif // defined(XR_USE_GRAPHICS_API_D3D11) diff --git a/src/conformance/framework/pbr/D3D11/D3D11TextureCache.h b/src/conformance/framework/pbr/D3D11/D3D11TextureCache.h new file mode 100644 index 00000000..26f81f39 --- /dev/null +++ b/src/conformance/framework/pbr/D3D11/D3D11TextureCache.h @@ -0,0 +1,56 @@ +// Copyright 2023, The Khronos Group, Inc. +// +// Based in part on code that is: +// +// Copyright (C) Microsoft Corporation. All Rights Reserved +// Licensed under the MIT License. See License.txt in the project root for license information. +// +// SPDX-License-Identifier: MIT AND Apache-2.0 + +#ifdef _WIN32 +#include +#include +#include +#include // For Microsoft::WRL::ComPtr + +#include +#include +#include + +namespace Pbr +{ + template + using ComPtr = Microsoft::WRL::ComPtr; + // using Microsoft::WRL::ComPtr; + + /// Cache of single-color textures. + /// + /// Device-dependent, drop when device is lost or destroyed. + class D3D11TextureCache + { + public: + /// Default constructor makes an invalid cache. + D3D11TextureCache() = default; + + D3D11TextureCache(D3D11TextureCache&&) = default; + D3D11TextureCache& operator=(D3D11TextureCache&&) = default; + + explicit D3D11TextureCache(ID3D11Device* device); + + bool IsValid() const noexcept + { + return m_device != nullptr; + } + + /// Find or create a single pixel texture of the given color + ComPtr CreateTypedSolidColorTexture(XrColor4f color); + + private: + ComPtr m_device; + // in unique_ptr to make it moveable + std::unique_ptr m_cacheMutex; + std::map> m_solidColorTextureCache; + }; + +} // namespace Pbr +#endif diff --git a/src/conformance/framework/pbr/D3D12/D3D12Material.cpp b/src/conformance/framework/pbr/D3D12/D3D12Material.cpp new file mode 100644 index 00000000..7518c38e --- /dev/null +++ b/src/conformance/framework/pbr/D3D12/D3D12Material.cpp @@ -0,0 +1,133 @@ +// Copyright 2022-2023, The Khronos Group, Inc. +// +// Based in part on code that is: +// Copyright (C) Microsoft Corporation. All Rights Reserved +// Licensed under the MIT License. See License.txt in the project root for license information. +// +// SPDX-License-Identifier: MIT AND Apache-2.0 + +#if defined(XR_USE_GRAPHICS_API_D3D12) + +#include "D3D12Material.h" + +#include "D3D12Resources.h" +#include "D3D12Texture.h" + +#include "../PbrMaterial.h" + +#include "utilities/d3d12_utils.h" +#include "utilities/throw_helpers.h" + +#include + +using namespace DirectX; + +namespace Pbr +{ + D3D12Material::D3D12Material(Pbr::D3D12Resources const& pbrResources) + { + static_assert((sizeof(ConstantBufferData) % 16) == 0, "Constant Buffer must be divisible by 16 bytes"); + m_constantBuffer.Allocate(pbrResources.GetDevice().Get()); + + D3D12_DESCRIPTOR_HEAP_DESC textureHeapDesc; + textureHeapDesc.Type = D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV; + textureHeapDesc.Flags = D3D12_DESCRIPTOR_HEAP_FLAG_NONE; + textureHeapDesc.NumDescriptors = ShaderSlots::NumMaterialSlots; + textureHeapDesc.NodeMask = 1; + XRC_CHECK_THROW_HRCMD(pbrResources.GetDevice()->CreateDescriptorHeap(&textureHeapDesc, IID_PPV_ARGS(&m_textureHeap))); + + D3D12_DESCRIPTOR_HEAP_DESC samplerHeapDesc; + samplerHeapDesc.Type = D3D12_DESCRIPTOR_HEAP_TYPE_SAMPLER; + samplerHeapDesc.Flags = D3D12_DESCRIPTOR_HEAP_FLAG_NONE; + samplerHeapDesc.NumDescriptors = ShaderSlots::NumMaterialSlots; + samplerHeapDesc.NodeMask = 1; + XRC_CHECK_THROW_HRCMD(pbrResources.GetDevice()->CreateDescriptorHeap(&samplerHeapDesc, IID_PPV_ARGS(&m_samplerHeap))); + } + + std::shared_ptr D3D12Material::Clone(Pbr::D3D12Resources const& pbrResources) const + { + auto clone = std::make_shared(pbrResources); + clone->CopyFrom(*this); + clone->m_textureHeap = m_textureHeap; + clone->m_samplerHeap = m_samplerHeap; + return clone; + } + + /* static */ + std::shared_ptr D3D12Material::CreateFlat(D3D12Resources& pbrResources, RGBAColor baseColorFactor, + float roughnessFactor /* = 1.0f */, float metallicFactor /* = 0.0f */, + RGBColor emissiveFactor /* = XMFLOAT3(0, 0, 0) */) + { + std::shared_ptr material = std::make_shared(pbrResources); + + if (baseColorFactor.a < 1.0f) { // Alpha channel + material->SetAlphaBlended(BlendState::AlphaBlended); + } + + Pbr::D3D12Material::ConstantBufferData& parameters = material->Parameters(); + parameters.BaseColorFactor = baseColorFactor; + parameters.EmissiveFactor = emissiveFactor; + parameters.MetallicFactor = metallicFactor; + parameters.RoughnessFactor = roughnessFactor; + + D3D12_SAMPLER_DESC defaultSamplerDesc = Pbr::D3D12Texture::DefaultSamplerDesc(); + auto setDefaultTexture = [&](Pbr::ShaderSlots::PSMaterial slot, Pbr::RGBAColor defaultRGBA) { + auto solidTexture = pbrResources.CreateTypedSolidColorTexture(defaultRGBA); + material->SetTexture(pbrResources.GetDevice().Get(), slot, solidTexture, &defaultSamplerDesc); + }; + + setDefaultTexture(ShaderSlots::BaseColor, RGBA::White); + setDefaultTexture(ShaderSlots::MetallicRoughness, RGBA::White); + // No occlusion. + setDefaultTexture(ShaderSlots::Occlusion, RGBA::White); + // Flat normal. + setDefaultTexture(ShaderSlots::Normal, RGBA::FlatNormal); + setDefaultTexture(ShaderSlots::Emissive, RGBA::White); + + return material; + } + + void D3D12Material::SetTexture(_In_ ID3D12Device* device, ShaderSlots::PSMaterial slot, Conformance::D3D12ResourceWithSRVDesc& texture, + _In_opt_ D3D12_SAMPLER_DESC* sampler) + { + m_textures[slot] = texture.resource.Get(); + + CD3DX12_CPU_DESCRIPTOR_HANDLE textureHandle(m_textureHeap->GetCPUDescriptorHandleForHeapStart()); + auto textureDescriptorSize = device->GetDescriptorHandleIncrementSize(D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV); + textureHandle.Offset(slot, textureDescriptorSize); + device->CreateShaderResourceView(texture.resource.Get(), &texture.srvDesc, textureHandle); + + if (sampler) { + CD3DX12_CPU_DESCRIPTOR_HANDLE samplerHandle(m_samplerHeap->GetCPUDescriptorHandleForHeapStart()); + auto samplerDescriptorSize = device->GetDescriptorHandleIncrementSize(D3D12_DESCRIPTOR_HEAP_TYPE_SAMPLER); + samplerHandle.Offset(slot, samplerDescriptorSize); + device->CreateSampler(sampler, samplerHandle); + } + } + + void D3D12Material::GetDescriptors(_In_ ID3D12Device* device, D3D12_CPU_DESCRIPTOR_HANDLE destTextureDescriptors, + D3D12_CPU_DESCRIPTOR_HANDLE destSamplerDescriptors) + { + device->CopyDescriptorsSimple(ShaderSlots::NumMaterialSlots, destTextureDescriptors, + m_textureHeap->GetCPUDescriptorHandleForHeapStart(), D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV); + device->CopyDescriptorsSimple(ShaderSlots::NumMaterialSlots, destSamplerDescriptors, + m_samplerHeap->GetCPUDescriptorHandleForHeapStart(), D3D12_DESCRIPTOR_HEAP_TYPE_SAMPLER); + } + + void D3D12Material::Bind(_In_ ID3D12GraphicsCommandList* directCommandList, D3D12Resources& pbrResources) + { + // If the parameters of the constant buffer have changed, update the constant buffer. + if (m_parametersChanged) { + m_parametersChanged = false; + pbrResources.WithCopyCommandList( + [&](ID3D12GraphicsCommandList* cmdList) { m_constantBuffer.AsyncUpload(cmdList, &m_parameters); }); + } + + directCommandList->SetGraphicsRootConstantBufferView(Pbr::ShaderSlots::ConstantBuffers::Material, + m_constantBuffer.GetResource()->GetGPUVirtualAddress()); + + static_assert(Pbr::ShaderSlots::BaseColor == 0, "BaseColor must be the first slot"); + } +} // namespace Pbr + +#endif // defined(XR_USE_GRAPHICS_API_D3D12) diff --git a/src/conformance/framework/pbr/D3D12/D3D12Material.h b/src/conformance/framework/pbr/D3D12/D3D12Material.h new file mode 100644 index 00000000..2ec3f1cc --- /dev/null +++ b/src/conformance/framework/pbr/D3D12/D3D12Material.h @@ -0,0 +1,62 @@ +// Copyright 2022-2023, The Khronos Group, Inc. +// +// Based in part on code that is: +// Copyright (C) Microsoft Corporation. All Rights Reserved +// Licensed under the MIT License. See License.txt in the project root for license information. +// +// SPDX-License-Identifier: MIT AND Apache-2.0 +#pragma once + +#include "D3D12Resources.h" + +#include "../PbrMaterial.h" + +#include +#include +#include // For Microsoft::WRL::ComPtr + +#include +#include +#include +#include + +namespace Pbr +{ + // A D3D12Material contains the metallic roughness parameters and textures. + // Primitives specify which D3D12Material to use when being rendered. + struct D3D12Material final : public Material + { + // Create a uninitialized material. Textures and shader coefficients must be set. + D3D12Material(Pbr::D3D12Resources const& pbrResources); + + // Create a clone of this material. Shares the texture and sampler heap with this material. + std::shared_ptr Clone(Pbr::D3D12Resources const& pbrResources) const; + + // Create a flat (no texture) material. + static std::shared_ptr CreateFlat(D3D12Resources& pbrResources, RGBAColor baseColorFactor, + float roughnessFactor = 1.0f, float metallicFactor = 0.0f, + RGBColor emissiveFactor = RGB::Black); + + // Set a Metallic-Roughness texture. + void SetTexture(_In_ ID3D12Device* device, ShaderSlots::PSMaterial slot, Conformance::D3D12ResourceWithSRVDesc& texture, + _In_opt_ D3D12_SAMPLER_DESC* sampler); + // void SetTexture(ShaderSlots::PSMaterial slot, ITexture& texture) override; + + // Write the descriptors of this material to a texture and sampler heap + void GetDescriptors(_In_ ID3D12Device* device, D3D12_CPU_DESCRIPTOR_HANDLE destTextureDescriptors, + D3D12_CPU_DESCRIPTOR_HANDLE destSamplerDescriptors); + + // Bind this material to current context. + void Bind(_In_ ID3D12GraphicsCommandList* directCommandList, D3D12Resources& pbrResources); + + std::string Name; + bool Hidden{false}; + + private: + static constexpr size_t TextureCount = ShaderSlots::NumMaterialSlots; + std::array, TextureCount> m_textures; + Microsoft::WRL::ComPtr m_textureHeap; + Microsoft::WRL::ComPtr m_samplerHeap; + Conformance::D3D12BufferWithUpload m_constantBuffer; + }; +} // namespace Pbr diff --git a/src/conformance/framework/pbr/D3D12/D3D12Model.cpp b/src/conformance/framework/pbr/D3D12/D3D12Model.cpp new file mode 100644 index 00000000..d4639ffa --- /dev/null +++ b/src/conformance/framework/pbr/D3D12/D3D12Model.cpp @@ -0,0 +1,118 @@ +// Copyright 2022-2023, The Khronos Group, Inc. +// +// Based in part on code that is: +// Copyright (C) Microsoft Corporation. All Rights Reserved +// Licensed under the MIT License. See License.txt in the project root for license information. +// +// SPDX-License-Identifier: MIT AND Apache-2.0 + +#if defined(XR_USE_GRAPHICS_API_D3D12) + +#include "D3D12Model.h" + +#include "D3D12Primitive.h" +#include "D3D12Resources.h" + +#include "../PbrHandles.h" +#include "../PbrModel.h" + +#include "utilities/d3d12_utils.h" +#include "utilities/throw_helpers.h" + +#include + +#include + +namespace Pbr +{ + + void D3D12Model::Render(Pbr::D3D12Resources& pbrResources, _In_ ID3D12GraphicsCommandList* directCommandList, + DXGI_FORMAT colorRenderTargetFormat, DXGI_FORMAT depthRenderTargetFormat) + { + UpdateTransforms(pbrResources); + + pbrResources.SetTransforms(m_modelTransformsResourceViewHeap->GetCPUDescriptorHandleForHeapStart()); + + for (PrimitiveHandle primitiveHandle : GetPrimitives()) { + const Pbr::D3D12Primitive& primitive = pbrResources.GetPrimitive(primitiveHandle); + if (primitive.GetMaterial()->Hidden) + continue; + + primitive.Render(directCommandList, pbrResources, colorRenderTargetFormat, depthRenderTargetFormat); + } + + // Expect the caller to reset other state, but the geometry shader is cleared specially. + //context->GSSetShader(nullptr, nullptr, 0); + } + + void D3D12Model::UpdateTransforms(Pbr::D3D12Resources& pbrResources) + { + const auto& nodes = GetNodes(); + const uint32_t newTotalModifyCount = std::accumulate(nodes.begin(), nodes.end(), 0, [](uint32_t sumChangeCount, const Node& node) { + return sumChangeCount + node.GetModifyCount(); + }); + + // If none of the node transforms have changed, no need to recompute/update the model transform structured buffer. + if (newTotalModifyCount != TotalModifyCount || m_modelTransformsStructuredBufferInvalid) { + if (m_modelTransformsStructuredBufferInvalid) // The structured buffer is reset when a Node is added. + { + XrMatrix4x4f identityMatrix; + XrMatrix4x4f_CreateIdentity(&identityMatrix); // or better yet poison it + m_modelTransforms.resize(nodes.size(), identityMatrix); + + // Create/recreate the structured buffer and SRV which holds the node transforms. + UINT elemSize = sizeof(decltype(m_modelTransforms)::value_type); + UINT count = (UINT)(m_modelTransforms.size()); + UINT size = (UINT)(count * elemSize); + + m_modelTransformsStructuredBuffer = Conformance::D3D12BufferWithUpload(pbrResources.GetDevice().Get(), size); + + D3D12_SHADER_RESOURCE_VIEW_DESC srvDesc{}; + srvDesc.Shader4ComponentMapping = D3D12_DEFAULT_SHADER_4_COMPONENT_MAPPING; + srvDesc.Format = DXGI_FORMAT_UNKNOWN; + srvDesc.ViewDimension = D3D12_SRV_DIMENSION_BUFFER; + srvDesc.Buffer.NumElements = count; + srvDesc.Buffer.StructureByteStride = elemSize; + + if (m_modelTransformsResourceViewHeap == nullptr) { + D3D12_DESCRIPTOR_HEAP_DESC transformHeapDesc; + transformHeapDesc.Type = D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV; + transformHeapDesc.Flags = D3D12_DESCRIPTOR_HEAP_FLAG_NONE; + transformHeapDesc.NumDescriptors = ShaderSlots::NumTextures; + transformHeapDesc.NodeMask = 1; + + XRC_CHECK_THROW_HRCMD(pbrResources.GetDevice()->CreateDescriptorHeap(&transformHeapDesc, + IID_PPV_ARGS(&m_modelTransformsResourceViewHeap))); + } + + pbrResources.GetDevice()->CreateShaderResourceView( + m_modelTransformsStructuredBuffer.GetResource(), &srvDesc, + CD3DX12_CPU_DESCRIPTOR_HANDLE(m_modelTransformsResourceViewHeap->GetCPUDescriptorHandleForHeapStart())); + + m_modelTransformsStructuredBufferInvalid = false; + } + + // Nodes are guaranteed to come after their parents, so each node transform can be multiplied by its parent transform in a single pass. + assert(nodes.size() == m_modelTransforms.size()); + XrMatrix4x4f identityMatrix; + XrMatrix4x4f_CreateIdentity(&identityMatrix); + for (const auto& node : nodes) { + assert(node.ParentNodeIndex == RootParentNodeIndex || node.ParentNodeIndex < node.Index); + const XrMatrix4x4f& parentTransform = + (node.ParentNodeIndex == RootParentNodeIndex) ? identityMatrix : m_modelTransforms[node.ParentNodeIndex]; + XrMatrix4x4f nodeTransform = node.GetTransform(); + XrMatrix4x4f nodeTransformTranspose; + XrMatrix4x4f_Transpose(&nodeTransformTranspose, &nodeTransform); + XrMatrix4x4f_Multiply(&m_modelTransforms[node.Index], &nodeTransformTranspose, &parentTransform); + } + + // Update node transform structured buffer. + pbrResources.WithCopyCommandList([&](ID3D12GraphicsCommandList* cmdList) { + m_modelTransformsStructuredBuffer.AsyncUpload(cmdList, this->m_modelTransforms.data(), this->m_modelTransforms.size()); + }); + TotalModifyCount = newTotalModifyCount; + } + } +} // namespace Pbr + +#endif // defined(XR_USE_GRAPHICS_API_D3D12) diff --git a/src/conformance/framework/pbr/D3D12/D3D12Model.h b/src/conformance/framework/pbr/D3D12/D3D12Model.h new file mode 100644 index 00000000..39f0e765 --- /dev/null +++ b/src/conformance/framework/pbr/D3D12/D3D12Model.h @@ -0,0 +1,37 @@ +// Copyright 2022-2023, The Khronos Group, Inc. +// +// Based in part on code that is: +// Copyright (C) Microsoft Corporation. All Rights Reserved +// Licensed under the MIT License. See License.txt in the project root for license information. +// +// SPDX-License-Identifier: MIT AND Apache-2.0 +#pragma once +#include "D3D12Resources.h" + +#include "../PbrHandles.h" +#include "../PbrModel.h" + +namespace Pbr +{ + + struct D3D12Primitive; + + class D3D12Model final : public Model + { + public: + // Render the model. + void Render(Pbr::D3D12Resources& pbrResources, _In_ ID3D12GraphicsCommandList* directCommandList, + DXGI_FORMAT colorRenderTargetFormat, DXGI_FORMAT depthRenderTargetFormat); + + private: + // Updated the transforms used to render the model. This needs to be called any time a node transform is changed. + void UpdateTransforms(Pbr::D3D12Resources& pbrResources); + + // Temporary buffer holds the world transforms, computed from the node's local transforms. + mutable std::vector m_modelTransforms; + mutable Conformance::D3D12BufferWithUpload m_modelTransformsStructuredBuffer; + mutable Microsoft::WRL::ComPtr m_modelTransformsResourceViewHeap; + + mutable uint32_t TotalModifyCount{0}; + }; +} // namespace Pbr diff --git a/src/conformance/framework/pbr/D3D12/D3D12PipelineStates.cpp b/src/conformance/framework/pbr/D3D12/D3D12PipelineStates.cpp new file mode 100644 index 00000000..6a26ac6f --- /dev/null +++ b/src/conformance/framework/pbr/D3D12/D3D12PipelineStates.cpp @@ -0,0 +1,96 @@ +// Copyright 2023, The Khronos Group, Inc. +// +// Based in part on code that is: +// +// Copyright (C) Microsoft Corporation. All Rights Reserved +// Licensed under the MIT License. See License.txt in the project root for license information. +// +// SPDX-License-Identifier: MIT AND Apache-2.0 + +#if defined(XR_USE_GRAPHICS_API_D3D12) + +#include "D3D12PipelineStates.h" + +#include "../PbrCommon.h" + +#include "utilities/throw_helpers.h" + +#include + +namespace Pbr +{ + Microsoft::WRL::ComPtr D3D12PipelineStates::GetOrCreatePipelineState( + DXGI_FORMAT colorRenderTargetFormat, DXGI_FORMAT depthRenderTargetFormat, FillMode fillMode, + FrontFaceWindingOrder frontFaceWindingOrder, BlendState blendState, DoubleSided doubleSided, DepthDirection depthDirection) + { + const PipelineStateKey state{ + colorRenderTargetFormat, depthRenderTargetFormat, fillMode, frontFaceWindingOrder, blendState, doubleSided, depthDirection}; + auto iter = m_pipelineStates.find(state); + if (iter != m_pipelineStates.end()) { + return iter->second; + } + Microsoft::WRL::ComPtr device; + XRC_CHECK_THROW_HRCMD( + m_rootSignature->GetDevice(__uuidof(ID3D12Device), reinterpret_cast(device.ReleaseAndGetAddressOf()))); + + static_assert(std::is_same>::value, + "This function copies all fields to the desc and must be updated if the fieldset is changed"); + + D3D12_GRAPHICS_PIPELINE_STATE_DESC pipelineStateDesc = m_basePipelineStateDesc; + + pipelineStateDesc.VS = {m_pbrVS.data(), m_pbrVS.size()}; + pipelineStateDesc.PS = {m_pbrPS.data(), m_pbrPS.size()}; + + for (UINT i = 0; i < pipelineStateDesc.NumRenderTargets; ++i) { + pipelineStateDesc.RTVFormats[i] = colorRenderTargetFormat; + } + + pipelineStateDesc.DSVFormat = depthRenderTargetFormat; + + pipelineStateDesc.RasterizerState.CullMode = + (doubleSided == DoubleSided::DoubleSided) ? D3D12_CULL_MODE_NONE : D3D12_CULL_MODE_BACK; + + pipelineStateDesc.RasterizerState.FillMode = (fillMode == FillMode::Wireframe) ? D3D12_FILL_MODE_WIREFRAME : D3D12_FILL_MODE_SOLID; + + pipelineStateDesc.RasterizerState.FrontCounterClockwise = (frontFaceWindingOrder == FrontFaceWindingOrder::CounterClockWise); + + pipelineStateDesc.DepthStencilState.DepthFunc = + (depthDirection == DepthDirection::Reversed) ? D3D12_COMPARISON_FUNC_GREATER : D3D12_COMPARISON_FUNC_LESS; + + if (blendState == BlendState::AlphaBlended) { + D3D12_RENDER_TARGET_BLEND_DESC rtBlendDesc{}; + rtBlendDesc.BlendEnable = TRUE; + rtBlendDesc.LogicOpEnable = FALSE; + rtBlendDesc.LogicOp = D3D12_LOGIC_OP_NOOP; + + rtBlendDesc.SrcBlend = D3D12_BLEND_SRC_ALPHA; + rtBlendDesc.DestBlend = D3D12_BLEND_INV_SRC_ALPHA; + rtBlendDesc.BlendOp = D3D12_BLEND_OP_ADD; + rtBlendDesc.SrcBlendAlpha = D3D12_BLEND_ZERO; + rtBlendDesc.DestBlendAlpha = D3D12_BLEND_ONE; + rtBlendDesc.BlendOpAlpha = D3D12_BLEND_OP_ADD; + + rtBlendDesc.RenderTargetWriteMask = D3D12_COLOR_WRITE_ENABLE_ALL; + for (UINT i = 0; i < D3D12_SIMULTANEOUS_RENDER_TARGET_COUNT; ++i) { + pipelineStateDesc.BlendState.RenderTarget[i] = rtBlendDesc; + } + pipelineStateDesc.DepthStencilState.DepthWriteMask = D3D12_DEPTH_WRITE_MASK_ZERO; + } + else { + // already set up by default + } + + pipelineStateDesc.IBStripCutValue = D3D12_INDEX_BUFFER_STRIP_CUT_VALUE_DISABLED; + + Microsoft::WRL::ComPtr pipelineState; + XRC_CHECK_THROW_HRCMD(device->CreateGraphicsPipelineState(&pipelineStateDesc, __uuidof(ID3D12PipelineState), + reinterpret_cast(pipelineState.ReleaseAndGetAddressOf()))); + + m_pipelineStates.emplace(state, pipelineState); + + return pipelineState; + } +} // namespace Pbr + +#endif // defined(XR_USE_GRAPHICS_API_D3D12) diff --git a/src/conformance/framework/pbr/D3D12/D3D12PipelineStates.h b/src/conformance/framework/pbr/D3D12/D3D12PipelineStates.h new file mode 100644 index 00000000..c59b3eaf --- /dev/null +++ b/src/conformance/framework/pbr/D3D12/D3D12PipelineStates.h @@ -0,0 +1,74 @@ +// Copyright 2023, The Khronos Group, Inc. +// +// Based in part on code that is: +// +// Copyright (C) Microsoft Corporation. All Rights Reserved +// Licensed under the MIT License. See License.txt in the project root for license information. +// +// SPDX-License-Identifier: MIT AND Apache-2.0 + +#pragma once +#include "../PbrSharedState.h" + +#include +#include +#include // For Microsoft::WRL::ComPtr + +#include +#include +#include + +namespace Pbr +{ + using nonstd::span; + + /// A factory/cache for pipeline state objects that differ in a few dimensions. + class D3D12PipelineStates + { + public: + /// Note: Make sure your shaders are global/static! + D3D12PipelineStates(Microsoft::WRL::ComPtr rootSignature, + const D3D12_GRAPHICS_PIPELINE_STATE_DESC& basePipelineStateDesc, + span inputLayout, span pbrVS, + span pbrPS) + : m_rootSignature(std::move(rootSignature)) + , m_basePipelineStateDesc(basePipelineStateDesc) + , m_inputLayout(inputLayout) + , m_pbrVS(pbrVS) + , m_pbrPS(pbrPS) + { + m_basePipelineStateDesc.pRootSignature = m_rootSignature.Get(); + + m_basePipelineStateDesc.InputLayout.pInputElementDescs = m_inputLayout.data(); + m_basePipelineStateDesc.InputLayout.NumElements = (UINT)m_inputLayout.size(); + } + + Microsoft::WRL::ComPtr GetOrCreatePipelineState(DXGI_FORMAT colorRenderTargetFormat, + DXGI_FORMAT depthRenderTargetFormat, FillMode fillMode, + FrontFaceWindingOrder frontFaceWindingOrder, + BlendState blendState, DoubleSided doubleSided, + DepthDirection depthDirection); + + // void DropStates() + // { + // m_pipelineStates.clear(); + // } + // void Reset(Microsoft::WRL::ComPtr rootSignature = nullptr) + // { + // DropStates(); + // m_rootSignature = std::move(rootSignature); + // m_basePipelineStateDesc.pRootSignature = m_rootSignature.Get(); + // } + + private: + using PipelineStateKey = + std::tuple; + Microsoft::WRL::ComPtr m_rootSignature; + D3D12_GRAPHICS_PIPELINE_STATE_DESC m_basePipelineStateDesc; + span m_inputLayout; + span m_pbrVS; + span m_pbrPS; + + std::map> m_pipelineStates; + }; +} // namespace Pbr diff --git a/src/conformance/framework/pbr/D3D12/D3D12Primitive.cpp b/src/conformance/framework/pbr/D3D12/D3D12Primitive.cpp new file mode 100644 index 00000000..ad80f334 --- /dev/null +++ b/src/conformance/framework/pbr/D3D12/D3D12Primitive.cpp @@ -0,0 +1,139 @@ +// Copyright 2022-2023, The Khronos Group, Inc. +// +// Based in part on code that is: +// Copyright (C) Microsoft Corporation. All Rights Reserved +// Licensed under the MIT License. See License.txt in the project root for license information. +// +// SPDX-License-Identifier: MIT AND Apache-2.0 + +#if defined(XR_USE_GRAPHICS_API_D3D12) + +#include "D3D12Primitive.h" + +#include "D3D12Resources.h" + +#include "../PbrCommon.h" + +#include "utilities/array_size.h" +#include "utilities/throw_helpers.h" + +#include + +using namespace DirectX; + +namespace Pbr +{ + D3D12Primitive::D3D12Primitive(UINT indexCount, Conformance::D3D12BufferWithUpload indexBuffer, UINT vertexCount, + Conformance::D3D12BufferWithUpload vertexBuffer, std::shared_ptr material) + : m_indexCount(indexCount) + , m_indexBuffer(std::move(indexBuffer)) + , m_vertexCount(vertexCount) + , m_vertexBuffer(std::move(vertexBuffer)) + , m_material(std::move(material)) + { + } + + D3D12Primitive::D3D12Primitive(Pbr::D3D12Resources& pbrResources, const Pbr::PrimitiveBuilder& primitiveBuilder, + const std::shared_ptr& material) + : D3D12Primitive((UINT)primitiveBuilder.Indices.size(), {}, (UINT)primitiveBuilder.Vertices.size(), {}, std::move(material)) + { + m_indexBuffer.Allocate(pbrResources.GetDevice().Get(), primitiveBuilder.Indices.size()); + m_vertexBuffer.Allocate(pbrResources.GetDevice().Get(), primitiveBuilder.Vertices.size()); + UpdateBuffers(pbrResources, primitiveBuilder); + + D3D12_DESCRIPTOR_HEAP_DESC srvHeapDesc; + srvHeapDesc.Type = D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV; + srvHeapDesc.Flags = D3D12_DESCRIPTOR_HEAP_FLAG_SHADER_VISIBLE; + srvHeapDesc.NumDescriptors = ShaderSlots::NumVSResourceViews + ShaderSlots::NumTextures; + srvHeapDesc.NodeMask = 1; + XRC_CHECK_THROW_HRCMD(pbrResources.GetDevice()->CreateDescriptorHeap(&srvHeapDesc, IID_PPV_ARGS(&m_srvHeap))); + + D3D12_DESCRIPTOR_HEAP_DESC samplerHeapDesc; + samplerHeapDesc.Type = D3D12_DESCRIPTOR_HEAP_TYPE_SAMPLER; + samplerHeapDesc.Flags = D3D12_DESCRIPTOR_HEAP_FLAG_SHADER_VISIBLE; + samplerHeapDesc.NumDescriptors = ShaderSlots::NumSamplers; + samplerHeapDesc.NodeMask = 1; + XRC_CHECK_THROW_HRCMD(pbrResources.GetDevice()->CreateDescriptorHeap(&samplerHeapDesc, IID_PPV_ARGS(&m_samplerHeap))); + } + + D3D12Primitive D3D12Primitive::Clone(Pbr::D3D12Resources const& pbrResources) const + { + return D3D12Primitive(m_indexCount, m_indexBuffer, m_vertexCount, m_vertexBuffer, m_material->Clone(pbrResources)); + } + + void D3D12Primitive::UpdateBuffers(Pbr::D3D12Resources& pbrResources, const Pbr::PrimitiveBuilder& primitiveBuilder) + { + // Update vertex buffer. + { + size_t elemCount = primitiveBuilder.Vertices.size(); + if (m_vertexBuffer.Fits(elemCount)) { + + pbrResources.WithCopyCommandList([&](ID3D12GraphicsCommandList* cmdList) { + m_vertexBuffer.AsyncUpload(cmdList, primitiveBuilder.Vertices.data(), elemCount); + }); + } + else { + m_vertexBuffer.Allocate(pbrResources.GetDevice().Get(), elemCount); + } + } + + // Update index buffer. + { + size_t elemCount = primitiveBuilder.Indices.size(); + if (m_indexBuffer.Fits(elemCount)) { + + pbrResources.WithCopyCommandList([&](ID3D12GraphicsCommandList* cmdList) { + m_indexBuffer.AsyncUpload(cmdList, primitiveBuilder.Indices.data(), elemCount); + }); + } + else { + m_indexBuffer.Allocate(pbrResources.GetDevice().Get(), elemCount); + } + + m_indexCount = (UINT)primitiveBuilder.Indices.size(); + } + } + + void D3D12Primitive::Render(_In_ ID3D12GraphicsCommandList* directCommandList, D3D12Resources& pbrResources, + DXGI_FORMAT colorRenderTargetFormat, DXGI_FORMAT depthRenderTargetFormat) const + { + GetMaterial()->Bind(directCommandList, pbrResources); + pbrResources.BindDescriptorHeaps(directCommandList, m_srvHeap.Get(), m_samplerHeap.Get()); + + UINT srvDescriptorSize = pbrResources.GetDevice()->GetDescriptorHandleIncrementSize(D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV); + UINT samplerDescriptorSize = pbrResources.GetDevice()->GetDescriptorHandleIncrementSize(D3D12_DESCRIPTOR_HEAP_TYPE_SAMPLER); + + CD3DX12_CPU_DESCRIPTOR_HANDLE srvHandle(m_srvHeap->GetCPUDescriptorHandleForHeapStart()); + CD3DX12_CPU_DESCRIPTOR_HANDLE samplerHandle(m_samplerHeap->GetCPUDescriptorHandleForHeapStart()); + + // vertex shader resource views + pbrResources.GetTransforms(srvHandle); + srvHandle.Offset(ShaderSlots::NumVSResourceViews, srvDescriptorSize); + + GetMaterial()->GetDescriptors(pbrResources.GetDevice().Get(), srvHandle, samplerHandle); + srvHandle.Offset(ShaderSlots::NumMaterialSlots, srvDescriptorSize); + samplerHandle.Offset(ShaderSlots::NumMaterialSlots, samplerDescriptorSize); + + pbrResources.GetGlobalTexturesAndSamplers(srvHandle, samplerHandle); + + BlendState blendState = GetMaterial()->GetAlphaBlended(); + DoubleSided doubleSided = GetMaterial()->GetDoubleSided(); + + Microsoft::WRL::ComPtr pipelineState = + pbrResources.GetOrCreatePipelineState(colorRenderTargetFormat, depthRenderTargetFormat, blendState, doubleSided); + + const UINT vertexStride = sizeof(Pbr::Vertex); + const UINT indexStride = sizeof(uint32_t); + const D3D12_VERTEX_BUFFER_VIEW vertexBufferView[] = { + {m_vertexBuffer.GetResource()->GetGPUVirtualAddress(), m_vertexCount * vertexStride, vertexStride}}; + directCommandList->IASetVertexBuffers(0, (UINT)Conformance::ArraySize(vertexBufferView), vertexBufferView); + D3D12_INDEX_BUFFER_VIEW indexBufferView{m_indexBuffer.GetResource()->GetGPUVirtualAddress(), m_indexCount * indexStride, + DXGI_FORMAT_R32_UINT}; + directCommandList->IASetIndexBuffer(&indexBufferView); + directCommandList->SetPipelineState(pipelineState.Get()); + directCommandList->IASetPrimitiveTopology(D3D_PRIMITIVE_TOPOLOGY_TRIANGLELIST); + directCommandList->DrawIndexedInstanced(m_indexCount, 1, 0, 0, 0); + } +} // namespace Pbr + +#endif // defined(XR_USE_GRAPHICS_API_D3D12) diff --git a/src/conformance/framework/pbr/D3D12/D3D12Primitive.h b/src/conformance/framework/pbr/D3D12/D3D12Primitive.h new file mode 100644 index 00000000..769babdd --- /dev/null +++ b/src/conformance/framework/pbr/D3D12/D3D12Primitive.h @@ -0,0 +1,66 @@ +// Copyright 2022-2023, The Khronos Group, Inc. +// +// Based in part on code that is: +// Copyright (C) Microsoft Corporation. All Rights Reserved +// Licensed under the MIT License. See License.txt in the project root for license information. +// +// SPDX-License-Identifier: MIT AND Apache-2.0 +#pragma once + +#include "D3D12Material.h" + +#include +#include // For Microsoft::WRL::ComPtr + +#include + +namespace Pbr +{ + // A primitive holds a vertex buffer, index buffer, and a pointer to a PBR material. + struct D3D12Primitive final + { + using Collection = std::vector; + + D3D12Primitive() = delete; + D3D12Primitive(UINT indexCount, Conformance::D3D12BufferWithUpload indexBuffer, UINT vertexCount, + Conformance::D3D12BufferWithUpload vertexBuffer, std::shared_ptr material); + D3D12Primitive(Pbr::D3D12Resources& pbrResources, const Pbr::PrimitiveBuilder& primitiveBuilder, + const std::shared_ptr& material); + + void UpdateBuffers(Pbr::D3D12Resources& pbrResources, const Pbr::PrimitiveBuilder& primitiveBuilder); + + // Get the material for the primitive. + std::shared_ptr& GetMaterial() + { + return m_material; + } + const std::shared_ptr& GetMaterial() const + { + return m_material; + } + + // Replace the material for the primitive + void SetMaterial(std::shared_ptr material) + { + m_material = std::move(material); + } + + protected: + // friend class Model; + friend class D3D12Model; + void Render(_In_ ID3D12GraphicsCommandList* directCommandList, D3D12Resources& pbrResources, DXGI_FORMAT colorRenderTargetFormat, + DXGI_FORMAT depthRenderTargetFormat) const; + + /// The clone shares the vertex and index buffers - they are not cloned + D3D12Primitive Clone(Pbr::D3D12Resources const& pbrResources) const; + + private: + UINT m_indexCount; + Conformance::D3D12BufferWithUpload m_indexBuffer; + UINT m_vertexCount; + Conformance::D3D12BufferWithUpload m_vertexBuffer; + std::shared_ptr m_material; + Microsoft::WRL::ComPtr m_srvHeap; + Microsoft::WRL::ComPtr m_samplerHeap; + }; +} // namespace Pbr diff --git a/src/conformance/framework/pbr/D3D12/D3D12Resources.cpp b/src/conformance/framework/pbr/D3D12/D3D12Resources.cpp new file mode 100644 index 00000000..e2cd5150 --- /dev/null +++ b/src/conformance/framework/pbr/D3D12/D3D12Resources.cpp @@ -0,0 +1,607 @@ +// Copyright 2022-2023, The Khronos Group, Inc. +// +// Based in part on code that is: +// Copyright (C) Microsoft Corporation. All Rights Reserved +// Licensed under the MIT License. See License.txt in the project root for license information. +// +// SPDX-License-Identifier: MIT AND Apache-2.0 + +#if defined(XR_USE_GRAPHICS_API_D3D12) + +#include "D3D12Resources.h" + +#include "D3D12PipelineStates.h" +#include "D3D12Primitive.h" +#include "D3D12Texture.h" +#include "D3D12TextureCache.h" + +#include "../../RGBAImage.h" +#include "../../gltf/GltfHelper.h" +#include "../PbrMaterial.h" + +#include "utilities/d3d12_queue_wrapper.h" +#include "utilities/d3d12_utils.h" +#include "utilities/destruction_queue.h" +#include "utilities/throw_helpers.h" + +#include +#include + +#include +#include + +#include + +using namespace DirectX; + +namespace +{ + struct SceneConstantBuffer + { + DirectX::XMFLOAT4X4 ViewProjection; + DirectX::XMFLOAT4 EyePosition; + DirectX::XMFLOAT3 LightDirection{}; + float _pad0; + DirectX::XMFLOAT3 LightDiffuseColor{}; + float _pad1; + uint32_t NumSpecularMipLevels{1}; + float _pad2[3]; + }; + + static_assert(std::is_standard_layout::value, "Must be standard layout"); + static_assert(sizeof(float) == 4, "Single precision floats"); + static_assert((sizeof(SceneConstantBuffer) % 16) == 0, "Constant Buffer must be divisible by 16 bytes"); + static_assert(sizeof(SceneConstantBuffer) == 128, "Size must be the same as known"); + static_assert(offsetof(SceneConstantBuffer, ViewProjection) == 0, "Offsets must match shader"); + static_assert(offsetof(SceneConstantBuffer, EyePosition) == 64, "Offsets must match shader"); + static_assert(offsetof(SceneConstantBuffer, LightDirection) == 80, "Offsets must match shader"); + static_assert(offsetof(SceneConstantBuffer, LightDiffuseColor) == 96, "Offsets must match shader"); + static_assert(offsetof(SceneConstantBuffer, NumSpecularMipLevels) == 112, "Offsets must match shader"); + + struct ModelConstantBuffer + { + DirectX::XMFLOAT4X4 ModelToWorld; + }; + + static_assert((sizeof(ModelConstantBuffer) % 16) == 0, "Constant Buffer must be divisible by 16 bytes"); + + const D3D12_INPUT_ELEMENT_DESC s_vertexDesc[6] = { + {"POSITION", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, D3D12_APPEND_ALIGNED_ELEMENT, D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, 0}, + {"NORMAL", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, D3D12_APPEND_ALIGNED_ELEMENT, D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, 0}, + {"TANGENT", 0, DXGI_FORMAT_R32G32B32A32_FLOAT, 0, D3D12_APPEND_ALIGNED_ELEMENT, D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, 0}, + {"COLOR", 0, DXGI_FORMAT_R32G32B32A32_FLOAT, 0, D3D12_APPEND_ALIGNED_ELEMENT, D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, 0}, + {"TEXCOORD", 0, DXGI_FORMAT_R32G32_FLOAT, 0, D3D12_APPEND_ALIGNED_ELEMENT, D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, 0}, + {"TRANSFORMINDEX", 0, DXGI_FORMAT_R16_UINT, 0, D3D12_APPEND_ALIGNED_ELEMENT, D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, 0}, + }; + + const CD3DX12_DESCRIPTOR_RANGE s_constantBufferDesc = CD3DX12_DESCRIPTOR_RANGE{D3D12_DESCRIPTOR_RANGE_TYPE_CBV, 1, 0}; + +} // namespace + +namespace Pbr +{ + using ImageKey = std::tuple; // Item1 is a pointer to the image, Item2 is sRGB. + + namespace RootSig + { + enum RootParamIndex + { + SceneConstantBuffer, + ModelConstantBuffer, + MaterialConstantBuffer, + TransformsBuffer, + TextureSRVs, + TextureSamplers, + RootParameterCount, + + }; + + static Microsoft::WRL::ComPtr CreateRootSig(_In_ ID3D12Device* device) + { + // root signature has one parameter for each RootParamIndex + CD3DX12_ROOT_PARAMETER rootParams[RootParameterCount] = {}; + + // constant buffers + rootParams[SceneConstantBuffer].InitAsConstantBufferView(ShaderSlots::ConstantBuffers::Scene, 0, D3D12_SHADER_VISIBILITY_ALL); + rootParams[ModelConstantBuffer].InitAsConstantBufferView(ShaderSlots::ConstantBuffers::Model, 0, + D3D12_SHADER_VISIBILITY_VERTEX); + rootParams[MaterialConstantBuffer].InitAsConstantBufferView(ShaderSlots::ConstantBuffers::Material, 0, + D3D12_SHADER_VISIBILITY_PIXEL); + + // transform register index overlaps with textures, but that's fine because their visibility is disjoint + // preferrring DescriptorTable over ShaderResourceView because root ShaderResourceView doesn't let you specify stride + CD3DX12_DESCRIPTOR_RANGE vsrvRange = + CD3DX12_DESCRIPTOR_RANGE(D3D12_DESCRIPTOR_RANGE_TYPE_SRV, ShaderSlots::NumVSResourceViews, 0); + rootParams[TransformsBuffer].InitAsDescriptorTable(1, &vsrvRange, D3D12_SHADER_VISIBILITY_VERTEX); + + // textures and samplers are out-of-line in descriptor tables + CD3DX12_DESCRIPTOR_RANGE psrvRange = CD3DX12_DESCRIPTOR_RANGE(D3D12_DESCRIPTOR_RANGE_TYPE_SRV, ShaderSlots::NumTextures, 0); + CD3DX12_DESCRIPTOR_RANGE sRange = CD3DX12_DESCRIPTOR_RANGE(D3D12_DESCRIPTOR_RANGE_TYPE_SAMPLER, ShaderSlots::NumSamplers, 0); + rootParams[TextureSRVs].InitAsDescriptorTable(1, &psrvRange, D3D12_SHADER_VISIBILITY_PIXEL); + rootParams[TextureSamplers].InitAsDescriptorTable(1, &sRange, D3D12_SHADER_VISIBILITY_PIXEL); + + D3D12_ROOT_SIGNATURE_FLAGS rootSignatureFlags = + D3D12_ROOT_SIGNATURE_FLAG_ALLOW_INPUT_ASSEMBLER_INPUT_LAYOUT | D3D12_ROOT_SIGNATURE_FLAG_DENY_DOMAIN_SHADER_ROOT_ACCESS | + D3D12_ROOT_SIGNATURE_FLAG_DENY_GEOMETRY_SHADER_ROOT_ACCESS | D3D12_ROOT_SIGNATURE_FLAG_DENY_HULL_SHADER_ROOT_ACCESS; + + CD3DX12_ROOT_SIGNATURE_DESC rsigDesc = {}; + rsigDesc.Init(static_cast(std::size(rootParams)), rootParams, 0, nullptr, rootSignatureFlags); + + Microsoft::WRL::ComPtr rootSigBlob; + Microsoft::WRL::ComPtr errorBlob; + Microsoft::WRL::ComPtr rootSig; + XRC_CHECK_THROW_HRCMD(D3D12SerializeRootSignature(&rsigDesc, D3D_ROOT_SIGNATURE_VERSION_1_0, + rootSigBlob.ReleaseAndGetAddressOf(), errorBlob.ReleaseAndGetAddressOf())); + + XRC_CHECK_THROW_HRCMD(device->CreateRootSignature(0, rootSigBlob->GetBufferPointer(), rootSigBlob->GetBufferSize(), + __uuidof(ID3D12RootSignature), + reinterpret_cast(rootSig.ReleaseAndGetAddressOf()))); + + return rootSig; + } + } // namespace RootSig + + struct D3D12Resources::Impl + { + // TODO: make this a constructor + void Initialize(_In_ ID3D12Device* device, const D3D12_GRAPHICS_PIPELINE_STATE_DESC& basePipelineStateDesc) + { + Resources.Device = device; + + Resources.CopyQueue = std::make_unique(device, D3D12_COMMAND_LIST_TYPE_COPY); + + XRC_CHECK_THROW_HRCMD( + device->CreateCommandAllocator(D3D12_COMMAND_LIST_TYPE_COPY, __uuidof(ID3D12CommandAllocator), + reinterpret_cast(Resources.CopyAllocator.ReleaseAndGetAddressOf()))); + + Resources.RootSignature = RootSig::CreateRootSig(device); + Resources.PipelineStates = std::make_unique(Resources.RootSignature, basePipelineStateDesc, s_vertexDesc, + g_PbrVertexShader, g_PbrPixelShader); + + // Set up the constant buffers. + static_assert((sizeof(SceneConstantBuffer) % 16) == 0, "Constant Buffer must be divisible by 16 bytes"); + Resources.SceneConstantBuffer.Allocate(device); + + static_assert((sizeof(ModelConstantBuffer) % 16) == 0, "Constant Buffer must be divisible by 16 bytes"); + Resources.ModelConstantBuffer.Allocate(device); + + D3D12_DESCRIPTOR_HEAP_DESC transformHeapDesc; + transformHeapDesc.Type = D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV; + transformHeapDesc.Flags = D3D12_DESCRIPTOR_HEAP_FLAG_NONE; + transformHeapDesc.NumDescriptors = ShaderSlots::NumVSResourceViews; + transformHeapDesc.NodeMask = 1; + + XRC_CHECK_THROW_HRCMD(device->CreateDescriptorHeap(&transformHeapDesc, IID_PPV_ARGS(&Resources.TransformHeap))); + + D3D12_DESCRIPTOR_HEAP_DESC textureHeapDesc; + textureHeapDesc.Type = D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV; + textureHeapDesc.Flags = D3D12_DESCRIPTOR_HEAP_FLAG_NONE; + textureHeapDesc.NumDescriptors = ShaderSlots::NumTextures - ShaderSlots::NumMaterialSlots; + textureHeapDesc.NodeMask = 1; + + XRC_CHECK_THROW_HRCMD(device->CreateDescriptorHeap(&textureHeapDesc, IID_PPV_ARGS(&Resources.TextureHeap))); + UINT textureDescriptorSize = device->GetDescriptorHandleIncrementSize(textureHeapDesc.Type); + CD3DX12_CPU_DESCRIPTOR_HANDLE textureBaseHandle(Resources.TextureHeap->GetCPUDescriptorHandleForHeapStart()); + Resources.BrdfLutTextureDescriptor = CD3DX12_CPU_DESCRIPTOR_HANDLE( // + textureBaseHandle, ShaderSlots::Brdf - ShaderSlots::NumMaterialSlots, textureDescriptorSize); + Resources.SpecularEnvMapTextureDescriptor = CD3DX12_CPU_DESCRIPTOR_HANDLE( // + textureBaseHandle, ShaderSlots::SpecularTexture - ShaderSlots::NumMaterialSlots, textureDescriptorSize); + Resources.DiffuseEnvMapTextureDescriptor = CD3DX12_CPU_DESCRIPTOR_HANDLE( // + textureBaseHandle, ShaderSlots::DiffuseTexture - ShaderSlots::NumMaterialSlots, textureDescriptorSize); + + D3D12_DESCRIPTOR_HEAP_DESC samplerHeapDesc; + samplerHeapDesc.Type = D3D12_DESCRIPTOR_HEAP_TYPE_SAMPLER; + samplerHeapDesc.Flags = D3D12_DESCRIPTOR_HEAP_FLAG_NONE; + samplerHeapDesc.NumDescriptors = ShaderSlots::NumSamplers - ShaderSlots::NumMaterialSlots; + samplerHeapDesc.NodeMask = 1; + + XRC_CHECK_THROW_HRCMD(device->CreateDescriptorHeap(&samplerHeapDesc, IID_PPV_ARGS(&Resources.SamplerHeap))); + UINT samplerDescriptorSize = device->GetDescriptorHandleIncrementSize(samplerHeapDesc.Type); + CD3DX12_CPU_DESCRIPTOR_HANDLE samplerBaseHandle(Resources.SamplerHeap->GetCPUDescriptorHandleForHeapStart()); + Resources.BrdfSamplerDescriptor = CD3DX12_CPU_DESCRIPTOR_HANDLE( // + samplerBaseHandle, ShaderSlots::Brdf - ShaderSlots::NumMaterialSlots, samplerDescriptorSize); + Resources.EnvironmentMapSamplerDescriptor = CD3DX12_CPU_DESCRIPTOR_HANDLE( // + samplerBaseHandle, ShaderSlots::EnvironmentMapSampler - ShaderSlots::NumMaterialSlots, samplerDescriptorSize); + + D3D12Texture::CreateSampler(device, Resources.BrdfSamplerDescriptor); + D3D12Texture::CreateSampler(device, Resources.EnvironmentMapSamplerDescriptor); + + Resources.SolidColorTextureCache = D3D12TextureCache{device}; + } + + /// Things we might want per frame eventually + struct FrameDeviceResources + { + Microsoft::WRL::ComPtr MainHeap; + Microsoft::WRL::ComPtr ConstantBufferUploadHeap; + + void Allocate(const Microsoft::WRL::ComPtr& /*device*/) + { + // D3d12x + } + }; + + struct DeviceResources + { + Microsoft::WRL::ComPtr Device; + + std::unique_ptr CopyQueue; + Microsoft::WRL::ComPtr CopyAllocator; + Conformance::DestructionQueue> DestructionQueue; + Microsoft::WRL::ComPtr TransformHeap; + Microsoft::WRL::ComPtr TextureHeap; + Microsoft::WRL::ComPtr SamplerHeap; + Microsoft::WRL::ComPtr BrdfLutTexture; + Microsoft::WRL::ComPtr SpecularEnvMapTexture; + Microsoft::WRL::ComPtr DiffuseEnvMapTexture; + D3D12_CPU_DESCRIPTOR_HANDLE BrdfLutTextureDescriptor; + D3D12_CPU_DESCRIPTOR_HANDLE SpecularEnvMapTextureDescriptor; + D3D12_CPU_DESCRIPTOR_HANDLE DiffuseEnvMapTextureDescriptor; + D3D12_CPU_DESCRIPTOR_HANDLE BrdfSamplerDescriptor; + D3D12_CPU_DESCRIPTOR_HANDLE EnvironmentMapSamplerDescriptor; + Microsoft::WRL::ComPtr RootSignature; + Conformance::D3D12BufferWithUpload SceneConstantBuffer; + Conformance::D3D12BufferWithUpload ModelConstantBuffer; + std::unique_ptr PipelineStates{}; + mutable D3D12TextureCache SolidColorTextureCache; + }; + PrimitiveCollection Primitives; + + D3D12_GRAPHICS_PIPELINE_STATE_DESC BasePipelineStateDesc; + DeviceResources Resources; + SceneConstantBuffer SceneBuffer; + ModelConstantBuffer ModelBuffer; + + struct LoaderResources + { + // Create D3D cache for reuse of texture views and samplers when possible. + std::map> imageMap; + std::map> samplerMap; + }; + LoaderResources loaderResources; + }; + + D3D12Resources::D3D12Resources(_In_ ID3D12Device* device, const D3D12_GRAPHICS_PIPELINE_STATE_DESC& basePipelineStateDesc) + : m_impl(std::make_unique()) + { + m_impl->Initialize(device, basePipelineStateDesc); + } + + D3D12Resources::D3D12Resources(D3D12Resources&& resources) = default; + + D3D12Resources::~D3D12Resources() = default; + + /* IResources implementations */ + std::shared_ptr D3D12Resources::CreateFlatMaterial(RGBAColor baseColorFactor, float roughnessFactor, float metallicFactor, + RGBColor emissiveFactor) + { + return D3D12Material::CreateFlat(*this, baseColorFactor, roughnessFactor, metallicFactor, emissiveFactor); + } + std::shared_ptr D3D12Resources::CreateMaterial() + { + return std::make_shared(*this); + } + std::shared_ptr D3D12Resources::CreateSolidColorTexture(RGBAColor color) + { + // TODO maybe unused + auto ret = std::make_shared(); + ret->texture = CreateTypedSolidColorTexture(color); + return ret; + } + + // Create a DirectX texture view from a tinygltf Image. + static Conformance::D3D12ResourceWithSRVDesc LoadGLTFImage(D3D12Resources& pbrResources, const tinygltf::Image& image, bool sRGB) + { + // First convert the image to RGBA if it isn't already. + std::vector tempBuffer; + const uint8_t* rgbaBuffer = GltfHelper::ReadImageAsRGBA(image, &tempBuffer); + Internal::ThrowIf(rgbaBuffer == nullptr, "Failed to read image"); + + const DXGI_FORMAT format = sRGB ? DXGI_FORMAT_R8G8B8A8_UNORM_SRGB : DXGI_FORMAT_R8G8B8A8_UNORM; + return D3D12Texture::CreateTexture(pbrResources, rgbaBuffer, 4, image.width, image.height, format); + } + + static D3D12_FILTER ConvertFilter(int glMinFilter, int glMagFilter) + { + const D3D12_FILTER_TYPE minFilter = glMinFilter == TINYGLTF_TEXTURE_FILTER_NEAREST + ? D3D12_FILTER_TYPE_POINT + : glMinFilter == TINYGLTF_TEXTURE_FILTER_LINEAR + ? D3D12_FILTER_TYPE_LINEAR + : glMinFilter == TINYGLTF_TEXTURE_FILTER_NEAREST_MIPMAP_NEAREST + ? D3D12_FILTER_TYPE_POINT + : glMinFilter == TINYGLTF_TEXTURE_FILTER_LINEAR_MIPMAP_NEAREST + ? D3D12_FILTER_TYPE_LINEAR + : glMinFilter == TINYGLTF_TEXTURE_FILTER_NEAREST_MIPMAP_LINEAR + ? D3D12_FILTER_TYPE_POINT + : glMinFilter == TINYGLTF_TEXTURE_FILTER_LINEAR_MIPMAP_LINEAR + ? D3D12_FILTER_TYPE_LINEAR + : D3D12_FILTER_TYPE_POINT; + const D3D12_FILTER_TYPE mipFilter = glMinFilter == TINYGLTF_TEXTURE_FILTER_NEAREST + ? D3D12_FILTER_TYPE_POINT + : glMinFilter == TINYGLTF_TEXTURE_FILTER_LINEAR + ? D3D12_FILTER_TYPE_POINT + : glMinFilter == TINYGLTF_TEXTURE_FILTER_NEAREST_MIPMAP_NEAREST + ? D3D12_FILTER_TYPE_POINT + : glMinFilter == TINYGLTF_TEXTURE_FILTER_LINEAR_MIPMAP_NEAREST + ? D3D12_FILTER_TYPE_POINT + : glMinFilter == TINYGLTF_TEXTURE_FILTER_NEAREST_MIPMAP_LINEAR + ? D3D12_FILTER_TYPE_LINEAR + : glMinFilter == TINYGLTF_TEXTURE_FILTER_LINEAR_MIPMAP_LINEAR + ? D3D12_FILTER_TYPE_LINEAR + : D3D12_FILTER_TYPE_POINT; + const D3D12_FILTER_TYPE magFilter = + glMagFilter == TINYGLTF_TEXTURE_FILTER_NEAREST + ? D3D12_FILTER_TYPE_POINT + : glMagFilter == TINYGLTF_TEXTURE_FILTER_LINEAR ? D3D12_FILTER_TYPE_LINEAR : D3D12_FILTER_TYPE_POINT; + + const D3D12_FILTER filter = D3D12_ENCODE_BASIC_FILTER(minFilter, magFilter, mipFilter, D3D12_FILTER_REDUCTION_TYPE_STANDARD); + return filter; + } + + // Create a DirectX sampler state from a tinygltf Sampler. + static D3D12_SAMPLER_DESC CreateGLTFSampler(_In_ ID3D12Device* /*device*/, const tinygltf::Sampler& sampler) + { + D3D12_SAMPLER_DESC samplerDesc{}; + + samplerDesc.Filter = ConvertFilter(sampler.minFilter, sampler.magFilter); + samplerDesc.AddressU = sampler.wrapS == TINYGLTF_TEXTURE_WRAP_CLAMP_TO_EDGE + ? D3D12_TEXTURE_ADDRESS_MODE_CLAMP + : sampler.wrapS == TINYGLTF_TEXTURE_WRAP_MIRRORED_REPEAT ? D3D12_TEXTURE_ADDRESS_MODE_MIRROR + : D3D12_TEXTURE_ADDRESS_MODE_WRAP; + samplerDesc.AddressV = sampler.wrapT == TINYGLTF_TEXTURE_WRAP_CLAMP_TO_EDGE + ? D3D12_TEXTURE_ADDRESS_MODE_CLAMP + : sampler.wrapT == TINYGLTF_TEXTURE_WRAP_MIRRORED_REPEAT ? D3D12_TEXTURE_ADDRESS_MODE_MIRROR + : D3D12_TEXTURE_ADDRESS_MODE_WRAP; + samplerDesc.AddressW = D3D12_TEXTURE_ADDRESS_MODE_WRAP; + samplerDesc.MaxAnisotropy = 1; + samplerDesc.ComparisonFunc = D3D12_COMPARISON_FUNC_ALWAYS; + samplerDesc.MinLOD = 0; + samplerDesc.MaxLOD = D3D12_FLOAT32_MAX; + + return samplerDesc; + } + void D3D12Resources::LoadTexture(const std::shared_ptr& material, Pbr::ShaderSlots::PSMaterial slot, + const tinygltf::Image* image, const tinygltf::Sampler* sampler, bool sRGB, Pbr::RGBAColor defaultRGBA) + { + auto pbrMaterial = std::dynamic_pointer_cast(material); + if (!pbrMaterial) { + throw std::logic_error("Wrong type of material"); + } + // Find or load the image referenced by the texture. + const ImageKey imageKey = std::make_tuple(image, sRGB); + std::shared_ptr textureView = + image != nullptr ? m_impl->loaderResources.imageMap[imageKey] + : std::make_shared(CreateTypedSolidColorTexture(defaultRGBA)); + if (!textureView) // If not cached, load the image and store it in the texture cache. + { + // TODO: Generate mipmaps if sampler's minification filter (minFilter) uses mipmapping. + // TODO: If texture is not power-of-two and (sampler has wrapping=repeat/mirrored_repeat OR minFilter uses + // mipmapping), resize to power-of-two. + textureView = std::make_shared(LoadGLTFImage(*this, *image, sRGB)); + m_impl->loaderResources.imageMap[imageKey] = textureView; + } + + // Find or create the sampler referenced by the texture. + std::shared_ptr samplerState = m_impl->loaderResources.samplerMap[sampler]; + if (!samplerState) // If not cached, create the sampler and store it in the sampler cache. + { + samplerState = std::make_shared(sampler != nullptr ? CreateGLTFSampler(GetDevice().Get(), *sampler) + : D3D12Texture::DefaultSamplerDesc()); + m_impl->loaderResources.samplerMap[sampler] = samplerState; + } + + pbrMaterial->SetTexture(GetDevice().Get(), slot, *textureView, samplerState.get()); + } + void D3D12Resources::DropLoaderCaches() + { + m_impl->loaderResources = {}; + } + + void D3D12Resources::SetBrdfLut(_In_ Conformance::D3D12ResourceWithSRVDesc brdfLut) + { + m_impl->Resources.BrdfLutTexture = brdfLut.resource; + + GetDevice()->CreateShaderResourceView(m_impl->Resources.BrdfLutTexture.Get(), &brdfLut.srvDesc, + m_impl->Resources.BrdfLutTextureDescriptor); + } + + void D3D12Resources::CreateDeviceDependentResources(_In_ ID3D12Device* device) + { + m_impl->Initialize(device, m_impl->BasePipelineStateDesc); + } + + void D3D12Resources::ReleaseDeviceDependentResources() + { + m_impl->Resources = {}; + m_impl->loaderResources = {}; + m_impl->Primitives.clear(); + } + + Microsoft::WRL::ComPtr D3D12Resources::GetDevice() const + { + return m_impl->Resources.Device; + } + + Microsoft::WRL::ComPtr D3D12Resources::CreateCopyCommandList() const + { + Microsoft::WRL::ComPtr cmdList; + XRC_CHECK_THROW_HRCMD(GetDevice()->CreateCommandList(0, D3D12_COMMAND_LIST_TYPE_COPY, m_impl->Resources.CopyAllocator.Get(), + nullptr, __uuidof(ID3D12GraphicsCommandList), + reinterpret_cast(cmdList.ReleaseAndGetAddressOf()))); + return cmdList; + } + + void D3D12Resources::ExecuteCopyCommandList(ID3D12GraphicsCommandList* cmdList, + std::vector> destroyAfterCopy) const + { + Internal::ThrowIf(!m_impl->Resources.CopyQueue->ExecuteCommandList(cmdList), "ExecuteCommandList failed"); + m_impl->Resources.DestructionQueue.PushResources(m_impl->Resources.CopyQueue->GetSignaledFenceValue(), std::move(destroyAfterCopy)); + } + + void D3D12Resources::SetTransforms(D3D12_CPU_DESCRIPTOR_HANDLE transformDescriptor) + { + GetDevice()->CopyDescriptorsSimple(ShaderSlots::NumVSResourceViews, + m_impl->Resources.TransformHeap->GetCPUDescriptorHandleForHeapStart(), transformDescriptor, + D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV); + } + + void D3D12Resources::GetTransforms(D3D12_CPU_DESCRIPTOR_HANDLE destTransformDescriptor) + { + GetDevice()->CopyDescriptorsSimple(ShaderSlots::NumVSResourceViews, destTransformDescriptor, + m_impl->Resources.TransformHeap->GetCPUDescriptorHandleForHeapStart(), + D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV); + } + + void D3D12Resources::GetGlobalTexturesAndSamplers(D3D12_CPU_DESCRIPTOR_HANDLE destTextureDescriptors, + D3D12_CPU_DESCRIPTOR_HANDLE destSamplerDescriptors) + { + GetDevice()->CopyDescriptorsSimple(ShaderSlots::NumTextures - ShaderSlots::NumMaterialSlots, destTextureDescriptors, + m_impl->Resources.TextureHeap->GetCPUDescriptorHandleForHeapStart(), + D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV); + GetDevice()->CopyDescriptorsSimple(ShaderSlots::NumSamplers - ShaderSlots::NumMaterialSlots, destSamplerDescriptors, + m_impl->Resources.SamplerHeap->GetCPUDescriptorHandleForHeapStart(), + D3D12_DESCRIPTOR_HEAP_TYPE_SAMPLER); + } + + Microsoft::WRL::ComPtr D3D12Resources::GetOrCreatePipelineState(DXGI_FORMAT colorRenderTargetFormat, + DXGI_FORMAT depthRenderTargetFormat, + BlendState blendState, DoubleSided doubleSided) + { + return m_impl->Resources.PipelineStates->GetOrCreatePipelineState( + colorRenderTargetFormat, depthRenderTargetFormat, m_sharedState.GetFillMode(), m_sharedState.GetFrontFaceWindingOrder(), + blendState, doubleSided, m_sharedState.GetDepthDirection()); + } + + void D3D12Resources::SetLight(DirectX::XMFLOAT3 direction, RGBColor diffuseColor) + { + m_impl->SceneBuffer.LightDirection = direction; + m_impl->SceneBuffer.LightDiffuseColor = {diffuseColor.x, diffuseColor.y, diffuseColor.z}; + } + + void XM_CALLCONV D3D12Resources::SetModelToWorld(DirectX::FXMMATRIX modelToWorld) const + { + XMStoreFloat4x4(&m_impl->ModelBuffer.ModelToWorld, XMMatrixTranspose(modelToWorld)); + WithCopyCommandList( + [&](ID3D12GraphicsCommandList* cmdList) { m_impl->Resources.ModelConstantBuffer.AsyncUpload(cmdList, &m_impl->ModelBuffer); }); + } + + void XM_CALLCONV D3D12Resources::SetViewProjection(DirectX::FXMMATRIX view, DirectX::CXMMATRIX projection) const + { + XMStoreFloat4x4(&m_impl->SceneBuffer.ViewProjection, XMMatrixTranspose(XMMatrixMultiply(view, projection))); + XMStoreFloat4(&m_impl->SceneBuffer.EyePosition, XMMatrixInverse(nullptr, view).r[3]); + } + + void D3D12Resources::SetEnvironmentMap(_In_ Conformance::D3D12ResourceWithSRVDesc specularEnvironmentMap, + _In_ Conformance::D3D12ResourceWithSRVDesc diffuseEnvironmentMap) + { + if (diffuseEnvironmentMap.srvDesc.ViewDimension != D3D12_SRV_DIMENSION_TEXTURECUBE) { + throw std::logic_error("Diffuse Resource View Type is not D3D_SRV_DIMENSION_TEXTURECUBE"); + } + + if (specularEnvironmentMap.srvDesc.ViewDimension != D3D12_SRV_DIMENSION_TEXTURECUBE) { + throw std::logic_error("Specular Resource View Type is not D3D_SRV_DIMENSION_TEXTURECUBE"); + } + auto desc = specularEnvironmentMap.resource->GetDesc(); + m_impl->SceneBuffer.NumSpecularMipLevels = desc.MipLevels; + m_impl->Resources.SpecularEnvMapTexture = specularEnvironmentMap.resource; + m_impl->Resources.DiffuseEnvMapTexture = diffuseEnvironmentMap.resource; + + GetDevice()->CreateShaderResourceView(m_impl->Resources.SpecularEnvMapTexture.Get(), &specularEnvironmentMap.srvDesc, + m_impl->Resources.SpecularEnvMapTextureDescriptor); + GetDevice()->CreateShaderResourceView(m_impl->Resources.DiffuseEnvMapTexture.Get(), &diffuseEnvironmentMap.srvDesc, + m_impl->Resources.DiffuseEnvMapTextureDescriptor); + } + + Conformance::D3D12ResourceWithSRVDesc D3D12Resources::CreateTypedSolidColorTexture(RGBAColor color) + { + return m_impl->Resources.SolidColorTextureCache.CreateTypedSolidColorTexture(*this, color); + } + + void D3D12Resources::Bind(_In_ ID3D12GraphicsCommandList* directCommandList) const + { + directCommandList->SetGraphicsRootSignature(m_impl->Resources.RootSignature.Get()); + + WithCopyCommandList( + [&](ID3D12GraphicsCommandList* cmdList) { m_impl->Resources.SceneConstantBuffer.AsyncUpload(cmdList, &m_impl->SceneBuffer); }); + + directCommandList->SetGraphicsRootConstantBufferView(ShaderSlots::ConstantBuffers::Scene, + m_impl->Resources.SceneConstantBuffer.GetResource()->GetGPUVirtualAddress()); + directCommandList->SetGraphicsRootConstantBufferView(ShaderSlots::ConstantBuffers::Model, + m_impl->Resources.ModelConstantBuffer.GetResource()->GetGPUVirtualAddress()); + } + + void D3D12Resources::BindDescriptorHeaps(_In_ ID3D12GraphicsCommandList* directCommandList, ID3D12DescriptorHeap* srvDescriptorHeap, + ID3D12DescriptorHeap* samplerDescriptorHeap) const + { + using RootSig::RootParamIndex; + + static_assert(ShaderSlots::DiffuseTexture == ShaderSlots::SpecularTexture + 1, "Diffuse must follow Specular slot"); + static_assert(ShaderSlots::SpecularTexture == ShaderSlots::Brdf + 1, "Specular must follow BRDF slot"); + + ID3D12DescriptorHeap* descriptorHeaps[] = {srvDescriptorHeap, samplerDescriptorHeap}; + directCommandList->SetDescriptorHeaps(_countof(descriptorHeaps), descriptorHeaps); + + auto srvDescriptorSize = GetDevice()->GetDescriptorHandleIncrementSize(D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV); + + // count is defined by InitAsDescriptorTable + directCommandList->SetGraphicsRootDescriptorTable(RootParamIndex::TransformsBuffer, + srvDescriptorHeap->GetGPUDescriptorHandleForHeapStart()); + directCommandList->SetGraphicsRootDescriptorTable( + RootParamIndex::TextureSRVs, CD3DX12_GPU_DESCRIPTOR_HANDLE(srvDescriptorHeap->GetGPUDescriptorHandleForHeapStart(), + ShaderSlots::NumVSResourceViews, srvDescriptorSize)); + directCommandList->SetGraphicsRootDescriptorTable(RootParamIndex::TextureSamplers, + samplerDescriptorHeap->GetGPUDescriptorHandleForHeapStart()); + } + + std::pair D3D12Resources::GetFenceAndValue() const + { + return std::make_pair(m_impl->Resources.CopyQueue->GetFence().Get(), m_impl->Resources.CopyQueue->GetSignaledFenceValue()); + } + + PrimitiveHandle D3D12Resources::MakePrimitive(const Pbr::PrimitiveBuilder& primitiveBuilder, + const std::shared_ptr& material) + { + auto typedMaterial = std::dynamic_pointer_cast(material); + if (!typedMaterial) { + throw std::logic_error("Got the wrong type of material"); + } + return m_impl->Primitives.emplace_back(*this, primitiveBuilder, typedMaterial); + } + + D3D12Primitive& D3D12Resources::GetPrimitive(PrimitiveHandle p) + { + return m_impl->Primitives[p]; + } + + const D3D12Primitive& D3D12Resources::GetPrimitive(PrimitiveHandle p) const + { + return m_impl->Primitives[p]; + } + + void D3D12Resources::SetFillMode(FillMode mode) + { + m_sharedState.SetFillMode(mode); + } + + FillMode D3D12Resources::GetFillMode() const + { + return m_sharedState.GetFillMode(); + } + + void D3D12Resources::SetFrontFaceWindingOrder(FrontFaceWindingOrder windingOrder) + { + m_sharedState.SetFrontFaceWindingOrder(windingOrder); + } + + FrontFaceWindingOrder D3D12Resources::GetFrontFaceWindingOrder() const + { + return m_sharedState.GetFrontFaceWindingOrder(); + } + + void D3D12Resources::SetDepthDirection(DepthDirection depthDirection) + { + m_sharedState.SetDepthDirection(depthDirection); + } +} // namespace Pbr + +#endif // defined(XR_USE_GRAPHICS_API_D3D12) diff --git a/src/conformance/framework/pbr/D3D12/D3D12Resources.h b/src/conformance/framework/pbr/D3D12/D3D12Resources.h new file mode 100644 index 00000000..791a4fc5 --- /dev/null +++ b/src/conformance/framework/pbr/D3D12/D3D12Resources.h @@ -0,0 +1,153 @@ +// Copyright 2022-2023, The Khronos Group, Inc. +// +// Based in part on code that is: +// Copyright (C) Microsoft Corporation. All Rights Reserved +// Licensed under the MIT License. See License.txt in the project root for license information. +// +// SPDX-License-Identifier: MIT AND Apache-2.0 +#pragma once + +#include "../IResources.h" +#include "../PbrCommon.h" +#include "../PbrHandles.h" +#include "../PbrSharedState.h" + +#include "utilities/d3d12_utils.h" +#include "utilities/throw_helpers.h" + +#include +#include +#include // For Microsoft::WRL::ComPtr + +#include +#include +#include +#include + +namespace Pbr +{ + struct Primitive; + using Duration = std::chrono::high_resolution_clock::duration; + struct D3D12Primitive; + struct D3D12Material; + + struct D3D12TextureAndSampler : public ITexture + { + ~D3D12TextureAndSampler() = default; + /// Required + Conformance::D3D12ResourceWithSRVDesc texture; + + /// Optional + D3D12_SAMPLER_DESC sampler; + bool samplerSet; + }; + + // Global PBR resources required for rendering a scene. + struct D3D12Resources final : public IResources + { + D3D12Resources(_In_ ID3D12Device* device, const D3D12_GRAPHICS_PIPELINE_STATE_DESC& basePipelineStateDesc); + D3D12Resources(D3D12Resources&&); + + ~D3D12Resources() override; + + std::shared_ptr CreateFlatMaterial(RGBAColor baseColorFactor, float roughnessFactor = 1.0f, float metallicFactor = 0.0f, + RGBColor emissiveFactor = RGB::Black) override; + std::shared_ptr CreateMaterial() override; + std::shared_ptr CreateSolidColorTexture(RGBAColor color); + + void LoadTexture(const std::shared_ptr& pbrMaterial, Pbr::ShaderSlots::PSMaterial slot, const tinygltf::Image* image, + const tinygltf::Sampler* sampler, bool sRGB, Pbr::RGBAColor defaultRGBA) override; + PrimitiveHandle MakePrimitive(const Pbr::PrimitiveBuilder& primitiveBuilder, + const std::shared_ptr& material) override; + void DropLoaderCaches() override; + + // Sets the Bidirectional Reflectance Distribution Function Lookup Table texture, required by the shader to compute surface + // reflectance from the IBL. + void SetBrdfLut(_In_ Conformance::D3D12ResourceWithSRVDesc brdfLut); + + // Create device-dependent resources. + void CreateDeviceDependentResources(_In_ ID3D12Device* device); + + // Release device-dependent resources. + void ReleaseDeviceDependentResources(); + + // Get the D3D12Device that the PBR resources are associated with. + Microsoft::WRL::ComPtr GetDevice() const; + + // Create a new copy command list, which can later be executed with ExecuteCopyCommandList + Microsoft::WRL::ComPtr CreateCopyCommandList() const; + + // Execute a copy command list on the internal copy queue, which can be waited on using GetFenceAndValue + void ExecuteCopyCommandList(ID3D12GraphicsCommandList* cmdList, + std::vector> destroyAfterCopy = {}) const; + + /// Create a command list, apply the functor to it, close it, and execute it. + /// Functor must take a single argument of type ID3D12GraphicsCommandList* or compatible. + template + void WithCopyCommandList(F&& commandListFunctor) const + { + Microsoft::WRL::ComPtr cmdList = CreateCopyCommandList(); + + commandListFunctor(cmdList.Get()); + + XRC_CHECK_THROW_HRCMD(cmdList->Close()); + ExecuteCopyCommandList(cmdList.Get()); + } + + // Get a pipeline state matching some parameters as well as the current settings inside D3D12Resources. + Microsoft::WRL::ComPtr GetOrCreatePipelineState(DXGI_FORMAT colorRenderTargetFormat, + DXGI_FORMAT depthRenderTargetFormat, BlendState blendState, + DoubleSided doubleSided); + + // Set the directional light. + void SetLight(DirectX::XMFLOAT3 direction, RGBColor diffuseColor); + + // Set the specular and diffuse image-based lighting (IBL) maps. ShaderResourceViews must be TextureCubes. + void SetEnvironmentMap(_In_ Conformance::D3D12ResourceWithSRVDesc specularEnvironmentMap, + _In_ Conformance::D3D12ResourceWithSRVDesc diffuseEnvironmentMap); + + // Set the current view and projection matrices. + void XM_CALLCONV SetViewProjection(DirectX::FXMMATRIX view, DirectX::CXMMATRIX projection) const; + + // Many 1x1 pixel colored textures are used in the PBR system. This is used to create textures backed by a cache to reduce the + // number of textures created. + Conformance::D3D12ResourceWithSRVDesc CreateTypedSolidColorTexture(RGBAColor color); + + // Bind the the PBR resources to the current context. + void Bind(_In_ ID3D12GraphicsCommandList* directCommandList) const; + + // Get the fence to wait on before executing any command list built on this Resources. + std::pair GetFenceAndValue() const; + + // Set and update the model to world constant buffer value. + void XM_CALLCONV SetModelToWorld(DirectX::FXMMATRIX modelToWorld) const; + + D3D12Primitive& GetPrimitive(PrimitiveHandle p); + const D3D12Primitive& GetPrimitive(PrimitiveHandle p) const; + + // Set or get the shading and fill modes. + void SetFillMode(FillMode mode); + FillMode GetFillMode() const; + void SetFrontFaceWindingOrder(FrontFaceWindingOrder windingOrder); + FrontFaceWindingOrder GetFrontFaceWindingOrder() const; + void SetDepthDirection(DepthDirection depthDirection); + + private: + void SetTransforms(D3D12_CPU_DESCRIPTOR_HANDLE transformDescriptor); + void GetTransforms(D3D12_CPU_DESCRIPTOR_HANDLE destTransformDescriptor); + void GetGlobalTexturesAndSamplers(D3D12_CPU_DESCRIPTOR_HANDLE destTextureDescriptors, + D3D12_CPU_DESCRIPTOR_HANDLE destSamplerDescriptors); + // Bind a material's descriptors according to the root signature. + void BindDescriptorHeaps(_In_ ID3D12GraphicsCommandList* directCommandList, ID3D12DescriptorHeap* srvDescriptorHeap, + ID3D12DescriptorHeap* samplerDescriptorHeap) const; + + friend struct D3D12Material; + friend class D3D12Model; + friend struct D3D12Primitive; + + struct Impl; + std::unique_ptr m_impl; + + SharedState m_sharedState; + }; +} // namespace Pbr diff --git a/src/conformance/framework/pbr/D3D12/D3D12Texture.cpp b/src/conformance/framework/pbr/D3D12/D3D12Texture.cpp new file mode 100644 index 00000000..de17b3fc --- /dev/null +++ b/src/conformance/framework/pbr/D3D12/D3D12Texture.cpp @@ -0,0 +1,173 @@ +// Copyright 2022-2023, The Khronos Group, Inc. +// +// Based in part on code that is: +// Copyright (C) Microsoft Corporation. All Rights Reserved +// Licensed under the MIT License. See License.txt in the project root for license information. +// +// SPDX-License-Identifier: MIT AND Apache-2.0 + +#if defined(XR_USE_GRAPHICS_API_D3D12) + +#include "D3D12Texture.h" + +#include "D3D12Resources.h" +#include "stb_image.h" + +#include "utilities/throw_helpers.h" + +#include +#include + +using namespace DirectX; + +namespace Pbr +{ + namespace D3D12Texture + { + std::array LoadRGBAUI4(RGBAColor color) + { + return std::array{(uint8_t)(color.r * 255.), (uint8_t)(color.g * 255.), (uint8_t)(color.b * 255.), + (uint8_t)(color.a * 255.)}; + } + + Conformance::D3D12ResourceWithSRVDesc LoadTextureImage(Pbr::D3D12Resources& pbrResources, + _In_reads_bytes_(fileSize) const uint8_t* fileData, uint32_t fileSize) + { + auto freeImageData = [](unsigned char* ptr) { ::free(ptr); }; + using stbi_unique_ptr = std::unique_ptr; + + constexpr uint32_t DesiredComponentCount = 4; + + int w, h, c; + // If c == 3, a component will be padded with 1.0f + stbi_unique_ptr rgbaData(stbi_load_from_memory(fileData, fileSize, &w, &h, &c, DesiredComponentCount), freeImageData); + if (!rgbaData) { + throw std::runtime_error("Failed to load image file data."); + } + + return CreateTexture(pbrResources, rgbaData.get(), DesiredComponentCount, w, h, DXGI_FORMAT_R8G8B8A8_UNORM); + } + + /// Creates a texture and fills all array members with the data in rgba + Microsoft::WRL::ComPtr CreateTextureArrayRepeat(D3D12Resources& pbrResources, + _In_reads_bytes_(elemSize* width* height) const uint8_t* rgba, + int elemSize, int width, int height, int arraySize, + DXGI_FORMAT format) + { + Microsoft::WRL::ComPtr device = pbrResources.GetDevice(); + + Microsoft::WRL::ComPtr cmdList = pbrResources.CreateCopyCommandList(); + + std::vector> imageUploadBuffers; + Microsoft::WRL::ComPtr image = + Conformance::D3D12CreateImage(device.Get(), width, height, arraySize, format, D3D12_HEAP_TYPE_DEFAULT); + + D3D12_RESOURCE_DESC imageDesc = image->GetDesc(); + assert(imageDesc.DepthOrArraySize == arraySize); + imageUploadBuffers.reserve(arraySize); + // TODO: maybe call GetCopyableFootprints only once, as all out fields accept arrays + // TODO: put the upload buffer in a staging resources vector and make async + for (int arrayIndex = 0; arrayIndex < arraySize; arrayIndex++) { + UINT subresourceIndex = D3D12CalcSubresource(0, arrayIndex, 0, imageDesc.MipLevels, arraySize); + + D3D12_PLACED_SUBRESOURCE_FOOTPRINT footprint; + UINT rowCount; + UINT64 rowSize; + UINT64 uploadBufferSize; + device->GetCopyableFootprints(&imageDesc, subresourceIndex, 1, 0, &footprint, &rowCount, &rowSize, &uploadBufferSize); + + // doesn't hold for compressed textures, see: https://www.gamedev.net/forums/topic/677932-getcopyablefootprints-question/ + assert((size_t)rowCount == (size_t)height); + + // assert this for now, probably doesn't hold for e.g. compressed textures + assert(rowSize == width * elemSize); + + Microsoft::WRL::ComPtr imageUpload = + Conformance::D3D12CreateBuffer(device.Get(), (uint32_t)uploadBufferSize, D3D12_HEAP_TYPE_UPLOAD); + imageUploadBuffers.push_back(imageUpload); + + D3D12_SUBRESOURCE_DATA initData{}; + initData.pData = rgba; + initData.RowPitch = elemSize * width; + initData.SlicePitch = elemSize * width * height; + + // this does a row-by-row memcpy internally or we would have used our own CopyWithStride + Internal::ThrowIf(!UpdateSubresources(cmdList.Get(), image.Get(), imageUpload.Get(), 0, 1, uploadBufferSize, &footprint, + &rowCount, &rowSize, &initData), + "Call to UpdateSubresources helper failed"); + } + + XRC_CHECK_THROW_HRCMD(cmdList->Close()); + pbrResources.ExecuteCopyCommandList(cmdList.Get(), std::move(imageUploadBuffers)); + + return image; + } + + Conformance::D3D12ResourceWithSRVDesc CreateFlatCubeTexture(D3D12Resources& pbrResources, RGBAColor color, DXGI_FORMAT format) + { + // Each side is a 1x1 pixel (RGBA) image. + const std::array rgbaColor = D3D12Texture::LoadRGBAUI4(color); + Microsoft::WRL::ComPtr texture = CreateTextureArrayRepeat(pbrResources, rgbaColor.data(), 4, 1, 1, 6, format); + + D3D12_SHADER_RESOURCE_VIEW_DESC srvDesc{}; + srvDesc.Format = format; + srvDesc.ViewDimension = D3D12_SRV_DIMENSION_TEXTURECUBE; + srvDesc.Shader4ComponentMapping = D3D12_DEFAULT_SHADER_4_COMPONENT_MAPPING; + srvDesc.Texture2D.MipLevels = 1; + srvDesc.Texture2D.MostDetailedMip = 0; + srvDesc.Texture2D.ResourceMinLODClamp = 0.0f; + + return Conformance::D3D12ResourceWithSRVDesc{std::move(texture), srvDesc}; + } + + Conformance::D3D12ResourceWithSRVDesc CreateTexture(D3D12Resources& pbrResources, + _In_reads_bytes_(elemSize* width* height) const uint8_t* rgba, int elemSize, + int width, int height, DXGI_FORMAT format) + { + Microsoft::WRL::ComPtr texture = + CreateTextureArrayRepeat(pbrResources, rgba, elemSize, width, height, 1, format); + + D3D12_SHADER_RESOURCE_VIEW_DESC srvDesc{}; + srvDesc.Format = format; + srvDesc.ViewDimension = D3D12_SRV_DIMENSION_TEXTURE2D; + srvDesc.Shader4ComponentMapping = D3D12_DEFAULT_SHADER_4_COMPONENT_MAPPING; + srvDesc.TextureCube.MipLevels = 1; + srvDesc.TextureCube.MostDetailedMip = 0; + srvDesc.TextureCube.ResourceMinLODClamp = 0.0f; + + return Conformance::D3D12ResourceWithSRVDesc{std::move(texture), srvDesc}; + } + + D3D12_SAMPLER_DESC DefaultSamplerDesc() + { + D3D12_SAMPLER_DESC samplerDesc; + + samplerDesc.Filter = D3D12_FILTER_ANISOTROPIC; + samplerDesc.AddressU = D3D12_TEXTURE_ADDRESS_MODE_WRAP; + samplerDesc.AddressV = D3D12_TEXTURE_ADDRESS_MODE_WRAP; + samplerDesc.AddressW = D3D12_TEXTURE_ADDRESS_MODE_WRAP; + samplerDesc.MipLODBias = 0.0f; + samplerDesc.MaxAnisotropy = 16; + samplerDesc.ComparisonFunc = D3D12_COMPARISON_FUNC_LESS_EQUAL; + samplerDesc.BorderColor[0] = 1.0f; + samplerDesc.BorderColor[1] = 1.0f; + samplerDesc.BorderColor[2] = 1.0f; + samplerDesc.BorderColor[3] = 1.0f; + samplerDesc.MinLOD = 0.0f; + samplerDesc.MaxLOD = D3D12_FLOAT32_MAX; + + return samplerDesc; + } + + void CreateSampler(_In_ ID3D12Device* device, D3D12_CPU_DESCRIPTOR_HANDLE destDescriptor, D3D12_TEXTURE_ADDRESS_MODE addressMode) + { + D3D12_SAMPLER_DESC samplerDesc = DefaultSamplerDesc(); + + samplerDesc.AddressU = samplerDesc.AddressV = samplerDesc.AddressW = addressMode; + + device->CreateSampler(&samplerDesc, destDescriptor); + } + } // namespace D3D12Texture +} // namespace Pbr + +#endif // defined(XR_USE_GRAPHICS_API_D3D12) diff --git a/src/conformance/framework/pbr/D3D12/D3D12Texture.h b/src/conformance/framework/pbr/D3D12/D3D12Texture.h new file mode 100644 index 00000000..b907257a --- /dev/null +++ b/src/conformance/framework/pbr/D3D12/D3D12Texture.h @@ -0,0 +1,46 @@ +// Copyright 2022-2023, The Khronos Group, Inc. +// +// Based in part on code that is: +// Copyright (C) Microsoft Corporation. All Rights Reserved +// Licensed under the MIT License. See License.txt in the project root for license information. +// +// SPDX-License-Identifier: MIT AND Apache-2.0 +// +// Shared data types and functions used throughout the Pbr rendering library. +// + +#pragma once + +#include "D3D12Resources.h" + +#include "../PbrCommon.h" + +#include +#include +#include +#include +#include // For Microsoft::WRL::ComPtr + +#include +#include + +namespace Pbr +{ + namespace D3D12Texture + { + std::array LoadRGBAUI4(RGBAColor color); + + Conformance::D3D12ResourceWithSRVDesc LoadTextureImage(D3D12Resources& pbrResources, + _In_reads_bytes_(fileSize) const uint8_t* fileData, uint32_t fileSize); + + Conformance::D3D12ResourceWithSRVDesc CreateFlatCubeTexture(D3D12Resources& pbrResources, RGBAColor color, DXGI_FORMAT format); + + Conformance::D3D12ResourceWithSRVDesc CreateTexture(D3D12Resources& pbrResources, + _In_reads_bytes_(elemSize* width* height) const uint8_t* rgba, int elemSize, + int width, int height, DXGI_FORMAT format); + + D3D12_SAMPLER_DESC DefaultSamplerDesc(); + void CreateSampler(_In_ ID3D12Device* device, D3D12_CPU_DESCRIPTOR_HANDLE destDescriptor, + D3D12_TEXTURE_ADDRESS_MODE addressMode = D3D12_TEXTURE_ADDRESS_MODE_CLAMP); + } // namespace D3D12Texture +} // namespace Pbr diff --git a/src/conformance/framework/pbr/D3D12/D3D12TextureCache.cpp b/src/conformance/framework/pbr/D3D12/D3D12TextureCache.cpp new file mode 100644 index 00000000..a2624ba0 --- /dev/null +++ b/src/conformance/framework/pbr/D3D12/D3D12TextureCache.cpp @@ -0,0 +1,59 @@ +// Copyright 2023, The Khronos Group, Inc. +// +// Based in part on code that is: +// +// Copyright (C) Microsoft Corporation. All Rights Reserved +// Licensed under the MIT License. See License.txt in the project root for license information. +// +// SPDX-License-Identifier: MIT AND Apache-2.0 + +#if defined(XR_USE_GRAPHICS_API_D3D12) + +#include "D3D12TextureCache.h" + +#include "D3D12Resources.h" +#include "D3D12Texture.h" + +#include "../PbrMaterial.h" + +#include "utilities/d3d12_utils.h" + +#include +#include +#include + +namespace Pbr +{ + D3D12TextureCache::D3D12TextureCache(ID3D12Device* device) : m_cacheMutex(std::make_unique()) + { + m_device = device; + } + + Conformance::D3D12ResourceWithSRVDesc D3D12TextureCache::CreateTypedSolidColorTexture(Pbr::D3D12Resources& pbrResources, + XrColor4f color) + { + if (!IsValid()) { + throw std::logic_error("D3D12TextureCache accessed before initialization"); + } + const std::array rgba = D3D12Texture::LoadRGBAUI4(color); + + // Check cache to see if this flat texture already exists. + const uint32_t colorKey = *reinterpret_cast(rgba.data()); + { + std::lock_guard guard(*m_cacheMutex); + auto textureIt = m_solidColorTextureCache.find(colorKey); + if (textureIt != m_solidColorTextureCache.end()) { + return textureIt->second; + } + } + + Conformance::D3D12ResourceWithSRVDesc texture = + D3D12Texture::CreateTexture(pbrResources, rgba.data(), 4, 1, 1, DXGI_FORMAT_R8G8B8A8_UNORM); + + std::lock_guard guard(*m_cacheMutex); + // If the key already exists then the existing texture will be returned. + return m_solidColorTextureCache.emplace(colorKey, texture).first->second; + } +} // namespace Pbr + +#endif // defined(XR_USE_GRAPHICS_API_D3D12) diff --git a/src/conformance/framework/pbr/D3D12/D3D12TextureCache.h b/src/conformance/framework/pbr/D3D12/D3D12TextureCache.h new file mode 100644 index 00000000..1bcbb4ba --- /dev/null +++ b/src/conformance/framework/pbr/D3D12/D3D12TextureCache.h @@ -0,0 +1,60 @@ +// Copyright 2023, The Khronos Group, Inc. +// +// Based in part on code that is: +// +// Copyright (C) Microsoft Corporation. All Rights Reserved +// Licensed under the MIT License. See License.txt in the project root for license information. +// +// SPDX-License-Identifier: MIT AND Apache-2.0 + +#include "D3D12Resources.h" + +#include "utilities/d3d12_utils.h" + +#include +#include +#include // For Microsoft::WRL::ComPtr + +#include +#include +#include + +namespace Pbr +{ + template + using ComPtr = Microsoft::WRL::ComPtr; + // using Microsoft::WRL::ComPtr; + + /// Cache of single-color textures. + /// + /// Device-dependent, drop when device is lost or destroyed. + class D3D12TextureCache + { + public: + /// Default constructor makes an invalid cache. + D3D12TextureCache() = default; + + // D3D12TextureCache(const D3D12TextureCache&) = default; + // D3D12TextureCache& operator=(const D3D12TextureCache&) = default; + + D3D12TextureCache(D3D12TextureCache&&) = default; + D3D12TextureCache& operator=(D3D12TextureCache&&) = default; + + explicit D3D12TextureCache(ID3D12Device* device); + + bool IsValid() const noexcept + { + return m_device != nullptr; + } + + /// Find or create a single pixel texture of the given color + Conformance::D3D12ResourceWithSRVDesc CreateTypedSolidColorTexture(Pbr::D3D12Resources& pbrResources, XrColor4f color); + + private: + ComPtr m_device; + // in unique_ptr to make it moveable + std::unique_ptr m_cacheMutex; + std::map m_solidColorTextureCache; + }; + +} // namespace Pbr diff --git a/src/conformance/framework/pbr/GlslBuffers.h b/src/conformance/framework/pbr/GlslBuffers.h new file mode 100644 index 00000000..32cb1708 --- /dev/null +++ b/src/conformance/framework/pbr/GlslBuffers.h @@ -0,0 +1,58 @@ +// Copyright 2022-2023, The Khronos Group, Inc. +// +// Based in part on code that is: +// Copyright (C) Microsoft Corporation. All Rights Reserved +// Licensed under the MIT License. See License.txt in the project root for license information. +// +// SPDX-License-Identifier: MIT AND Apache-2.0 + +#pragma once + +#include "common/xr_linear.h" + +#include + +namespace Pbr +{ + namespace Glsl + { + + /// Parameters of the Scene + struct SceneConstantBuffer + { + XrMatrix4x4f ViewProjection; + XrVector3f EyePosition; + float _pad0; + XrVector3f LightDirection{}; + float _pad1; + XrVector3f LightDiffuseColor{}; + float _pad2; + uint32_t NumSpecularMipLevels{1}; // all glsl ints are 32 bit + float _pad3[3]; + }; + + // Following std140 layout rules, must match PbrPixelShader_glsl.frag and PbrVertexShader_glsl.vert + // Can get the offsets by passing -q to glslangValidator + static_assert(std::is_standard_layout::value, "Must be standard layout"); + static_assert(sizeof(float) == 4, "Single precision floats"); + static_assert(sizeof(XrVector3f) == 3 * sizeof(float), "No padding in vectors"); + static_assert(sizeof(XrVector4f) == 4 * sizeof(float), "No padding in vectors"); + static_assert(alignof(XrVector4f) == 4, "No padding in vectors"); + static_assert((sizeof(SceneConstantBuffer) % 16) == 0, "Constant Buffer must be divisible by 16 bytes"); + static_assert(sizeof(SceneConstantBuffer) == 128, "Size must be the same as known"); + static_assert(offsetof(SceneConstantBuffer, ViewProjection) == 0, "Offsets must match shader"); + static_assert(offsetof(SceneConstantBuffer, EyePosition) == 64, "Offsets must match shader"); + static_assert(offsetof(SceneConstantBuffer, LightDirection) == 80, "Offsets must match shader"); + static_assert(offsetof(SceneConstantBuffer, LightDiffuseColor) == 96, "Offsets must match shader"); + static_assert(offsetof(SceneConstantBuffer, NumSpecularMipLevels) == 112, "Offsets must match shader"); + + /// Constant parameter of the Model + struct ModelConstantBuffer + { + XrMatrix4x4f ModelToWorld; + }; + + static_assert((sizeof(ModelConstantBuffer) % 16) == 0, "Constant Buffer must be divisible by 16 bytes"); + + } // namespace Glsl +} // namespace Pbr diff --git a/src/conformance/framework/pbr/GltfLoader.cpp b/src/conformance/framework/pbr/GltfLoader.cpp new file mode 100644 index 00000000..9ba77080 --- /dev/null +++ b/src/conformance/framework/pbr/GltfLoader.cpp @@ -0,0 +1,196 @@ +// Copyright 2022-2023, The Khronos Group, Inc. +// +// Based in part on code that is: +// Copyright (C) Microsoft Corporation. All Rights Reserved +// Licensed under the MIT License. See License.txt in the project root for license information. +// +// SPDX-License-Identifier: MIT AND Apache-2.0 + +#include "GltfLoader.h" + +#include "IResources.h" +#include "PbrCommon.h" +#include "PbrMaterial.h" +#include "PbrModel.h" +#include "PbrSharedState.h" + +#include "../gltf/GltfHelper.h" + +#include "common/xr_linear.h" + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +namespace +{ + // Maps a glTF material to a PrimitiveBuilder. This optimization combines all primitives which use + // the same material into a single primitive for reduced draw calls. Each primitive's vertex specifies + // which node it corresponds to any appropriate node transformation be happen in the shader. + using PrimitiveBuilderMap = std::map; + + // Load a glTF node from the tinygltf object model. This will process the node's mesh (if specified) and then recursively load the child + // nodes too. + void LoadNode(Pbr::NodeIndex_t parentNodeIndex, const tinygltf::Model& gltfModel, int nodeId, + GltfHelper::PrimitiveCache& primitiveCache, PrimitiveBuilderMap& primitiveBuilderMap, Pbr::Model& model) + { + const tinygltf::Node& gltfNode = gltfModel.nodes.at(nodeId); + + // Read the local transform for this node and add it into the Pbr Model. + const XrMatrix4x4f nodeLocalTransform = GltfHelper::ReadNodeLocalTransform(gltfNode); + const Pbr::NodeIndex_t transformIndex = model.AddNode(nodeLocalTransform, parentNodeIndex, gltfNode.name); + + if (gltfNode.mesh != -1) // Load the node's optional mesh when specified. + { + // A glTF mesh is composed of primitives. + const tinygltf::Mesh& gltfMesh = gltfModel.meshes.at(gltfNode.mesh); + for (const tinygltf::Primitive& gltfPrimitive : gltfMesh.primitives) { + // Read the primitive data from the glTF buffers. + const GltfHelper::Primitive primitive = primitiveCache.ReadPrimitive(gltfPrimitive); + + // Insert or append the primitive into the PBR primitive builder. Primitives which use the same + // material are appended to reduce the number of draw calls. + Pbr::PrimitiveBuilder& primitiveBuilder = primitiveBuilderMap[gltfPrimitive.material]; + + // Use the starting offset for vertices and indices since multiple glTF primitives can + // be put into the same primitive builder. + const uint32_t startVertex = (uint32_t)primitiveBuilder.Vertices.size(); + const uint32_t startIndex = (uint32_t)primitiveBuilder.Indices.size(); + + // Convert the GltfHelper vertices into the PBR vertex format. + primitiveBuilder.Vertices.resize(startVertex + primitive.Vertices.size()); + for (size_t i = 0; i < primitive.Vertices.size(); i++) { + const GltfHelper::Vertex& vertex = primitive.Vertices[i]; + Pbr::Vertex pbrVertex; + pbrVertex.Position = vertex.Position; + pbrVertex.Normal = vertex.Normal; + pbrVertex.Tangent = vertex.Tangent; + pbrVertex.Color0 = vertex.Color0; + pbrVertex.TexCoord0 = vertex.TexCoord0; + pbrVertex.ModelTransformIndex = transformIndex; + + primitiveBuilder.Vertices[i + startVertex] = pbrVertex; + } + + // Insert indices with reverse winding order. + primitiveBuilder.Indices.resize(startIndex + primitive.Indices.size()); + for (size_t i = 0; i < primitive.Indices.size(); i += 3) { + primitiveBuilder.Indices[startIndex + i + 0] = startVertex + primitive.Indices[i + 0]; + primitiveBuilder.Indices[startIndex + i + 1] = startVertex + primitive.Indices[i + 2]; + primitiveBuilder.Indices[startIndex + i + 2] = startVertex + primitive.Indices[i + 1]; + } + } + } + + // Recursively load all children. + for (const int childNodeId : gltfNode.children) { + LoadNode(transformIndex, gltfModel, childNodeId, primitiveCache, primitiveBuilderMap, model); + } + } +} // namespace + +namespace Gltf +{ + void PopulateFromGltfObject(Pbr::Model& model, Pbr::IResources& pbrResources, const tinygltf::Model& gltfModel) + { + // Empty the model to ensure we're starting from scratch + model.Clear(); + + // Read and transform mesh/node data. Primitives with the same material are merged to reduce draw calls. + PrimitiveBuilderMap primitiveBuilderMap; + GltfHelper::PrimitiveCache primitiveCache{gltfModel}; + { + const int defaultSceneId = (gltfModel.defaultScene == -1) ? 0 : gltfModel.defaultScene; + const tinygltf::Scene& defaultScene = gltfModel.scenes.at(defaultSceneId); + + // Process the root scene nodes. The children will be processed recursively. + for (const int rootNodeId : defaultScene.nodes) { + LoadNode(Pbr::RootNodeIndex, gltfModel, rootNodeId, primitiveCache, primitiveBuilderMap, model); + } + } + + // Load the materials referenced by the primitives + std::map> materialMap; + { + // primitiveBuilderMap is grouped by material. Loop through the referenced materials and load their resources. This will only + // load materials which are used by the active scene. + for (const auto& primitiveBuilderPair : primitiveBuilderMap) { + std::shared_ptr pbrMaterial; + + const int materialIndex = primitiveBuilderPair.first; + if (materialIndex == -1) // No material was referenced. Make up a material for it. + { + // Default material is a grey material, 50% roughness, non-metallic. + pbrMaterial = pbrResources.CreateFlatMaterial({0.5f, 0.5f, 0.5f, 0.5f}, 0.5f); + } + else { + const tinygltf::Material& gltfMaterial = gltfModel.materials.at(materialIndex); + + const GltfHelper::Material material = GltfHelper::ReadMaterial(gltfModel, gltfMaterial); + pbrMaterial = pbrResources.CreateMaterial(); + + pbrMaterial->Name = gltfMaterial.name; + + auto loadTexture = [&](Pbr::ShaderSlots::PSMaterial slot, const GltfHelper::Material::Texture& texture, bool sRGB, + Pbr::RGBAColor defaultRGBA) { + pbrResources.LoadTexture(pbrMaterial, slot, texture.Image, texture.Sampler, sRGB, defaultRGBA); + }; + loadTexture(Pbr::ShaderSlots::BaseColor, material.BaseColorTexture, true /* sRGB */, Pbr::RGBA::White); + loadTexture(Pbr::ShaderSlots::MetallicRoughness, material.MetallicRoughnessTexture, false /* sRGB */, Pbr::RGBA::White); + loadTexture(Pbr::ShaderSlots::Emissive, material.EmissiveTexture, true /* sRGB */, Pbr::RGBA::White); + loadTexture(Pbr::ShaderSlots::Normal, material.NormalTexture, false /* sRGB */, Pbr::RGBA::FlatNormal); + loadTexture(Pbr::ShaderSlots::Occlusion, material.OcclusionTexture, false /* sRGB */, Pbr::RGBA::White); + + pbrMaterial->SetDoubleSided(material.DoubleSided ? Pbr::DoubleSided::DoubleSided : Pbr::DoubleSided::NotDoubleSided); + pbrMaterial->SetAlphaBlended(material.AlphaMode == GltfHelper::AlphaModeType::Blend ? Pbr::BlendState::AlphaBlended + : Pbr::BlendState::NotAlphaBlended); + + Pbr::Material::ConstantBufferData& parameters = pbrMaterial->Parameters(); + parameters.BaseColorFactor = material.BaseColorFactor; + parameters.MetallicFactor = material.MetallicFactor; + parameters.RoughnessFactor = material.RoughnessFactor; + parameters.EmissiveFactor = material.EmissiveFactor; + parameters.OcclusionStrength = material.OcclusionStrength; + parameters.NormalScale = material.NormalScale; + parameters.AlphaCutoff = + material.AlphaMode == GltfHelper::AlphaModeType::Mask ? material.AlphaCutoff : std::numeric_limits::lowest(); + } + + materialMap.insert(std::make_pair(materialIndex, std::move(pbrMaterial))); + } + } + + // Convert the primitive builders into primitives with their respective material and add it into the Pbr Model. + for (const auto& primitiveBuilderPair : primitiveBuilderMap) { + const Pbr::PrimitiveBuilder& primitiveBuilder = primitiveBuilderPair.second; + const std::shared_ptr& material = materialMap.find(primitiveBuilderPair.first)->second; + auto handle = pbrResources.MakePrimitive(primitiveBuilder, material); + model.AddPrimitive(handle); + } + + pbrResources.DropLoaderCaches(); + } + + void PopulateFromGltfBinary(Pbr::Model& model, Pbr::IResources& pbrResources, const uint8_t* buffer, uint32_t bufferBytes) + { + // Parse the GLB buffer data into a tinygltf model object. + tinygltf::Model gltfModel; + std::string errorMessage; + tinygltf::TinyGLTF loader; + if (!loader.LoadBinaryFromMemory(&gltfModel, &errorMessage, nullptr /*warn*/, buffer, bufferBytes, ".")) { + const auto msg = + std::string("\r\nFailed to load gltf model (") + std::to_string(bufferBytes) + " bytes). Error: " + errorMessage; + throw std::runtime_error(msg.c_str()); + } + + PopulateFromGltfObject(model, pbrResources, gltfModel); + } +} // namespace Gltf diff --git a/src/conformance/framework/pbr/GltfLoader.h b/src/conformance/framework/pbr/GltfLoader.h new file mode 100644 index 00000000..5fedadf8 --- /dev/null +++ b/src/conformance/framework/pbr/GltfLoader.h @@ -0,0 +1,68 @@ +// Copyright 2022-2023, The Khronos Group, Inc. +// +// Based in part on code that is: +// Copyright (C) Microsoft Corporation. All Rights Reserved +// Licensed under the MIT License. See License.txt in the project root for license information. +// +// SPDX-License-Identifier: MIT AND Apache-2.0 + +// +// Functions to load glTF 2.0 content into a renderable Pbr::Model. +// + +#pragma once + +#include "IResources.h" +#include "PbrModel.h" + +#include +#include +#include + +namespace Pbr +{ + class Model; + struct IResources; +} // namespace Pbr + +namespace tinygltf +{ + class Model; +} + +namespace Gltf +{ + // non-templated inner functions: + void PopulateFromGltfObject(Pbr::Model& model, Pbr::IResources& pbrResources, const tinygltf::Model& gltfModel); + void PopulateFromGltfBinary(Pbr::Model& model, Pbr::IResources& pbrResources, const uint8_t* buffer, uint32_t bufferBytes); + + // Creates a Pbr Model from tinygltf model. + template + std::shared_ptr FromGltfObject(Pbr::IResources& pbrResources, const tinygltf::Model& gltfModel) + { + static_assert(std::is_base_of::value, "DerivedModel not derived from Pbr::Model"); + + // Start off with an empty Model. + auto model = std::make_shared(); + PopulateFromGltfObject(*model, pbrResources, gltfModel); + return model; + } + + // Creates a Pbr Model from glTF 2.0 GLB file content. + template + std::shared_ptr FromGltfBinary(Pbr::IResources& pbrResources, const uint8_t* buffer, uint32_t bufferBytes) + { + static_assert(std::is_base_of::value, "DerivedModel not derived from Pbr::Model"); + + // Start off with an empty Model. + auto model = std::make_shared(); + PopulateFromGltfBinary(*model, pbrResources, buffer, bufferBytes); + return model; + } + + template + std::shared_ptr FromGltfBinary(Pbr::IResources& pbrResources, const Container& buffer) + { + return FromGltfBinary(pbrResources, buffer.data(), static_cast(buffer.size())); + } +} // namespace Gltf diff --git a/src/conformance/framework/pbr/IResources.h b/src/conformance/framework/pbr/IResources.h new file mode 100644 index 00000000..7def6115 --- /dev/null +++ b/src/conformance/framework/pbr/IResources.h @@ -0,0 +1,63 @@ +// Copyright 2023, The Khronos Group, Inc. +// +// Based in part on code: +// Copyright (C) Microsoft Corporation. All Rights Reserved +// Licensed under the MIT License. See License.txt in the project root for license information. +// +// SPDX-License-Identifier: MIT AND Apache-2.0 + +#pragma once + +#include "PbrCommon.h" +#include "PbrHandles.h" +#include "PbrSharedState.h" + +#include + +namespace tinygltf +{ + struct Image; + struct Sampler; +} // namespace tinygltf + +namespace Pbr +{ + + struct Material; + + /// The way various APIs track textures is totally distinct, so this base class is just for type erasure. + /// May also include a sampler + struct ITexture + { + virtual ~ITexture() = default; + }; + + /// TODO add a swapchain length param and ignore it for d3d11? + struct IResources + { + virtual ~IResources() = default; + virtual std::shared_ptr CreateFlatMaterial(RGBAColor baseColorFactor, float roughnessFactor = 1.0f, + float metallicFactor = 0.0f, RGBColor emissiveFactor = RGB::Black) = 0; + + virtual std::shared_ptr CreateMaterial() = 0; + + // virtual std::shared_ptr CreateSolidColorTexture(RGBAColor color) = 0; + + // Do we also need CreateSampler? + + virtual void LoadTexture(const std::shared_ptr& material, Pbr::ShaderSlots::PSMaterial slot, const tinygltf::Image* image, + const tinygltf::Sampler* sampler, bool sRGB, Pbr::RGBAColor defaultRGBA) = 0; + + virtual PrimitiveHandle MakePrimitive(const Pbr::PrimitiveBuilder& primitiveBuilder, + const std::shared_ptr& material) = 0; + + /// Optional optimization, can call at the end of loading a model to drop per-model caches. + virtual void DropLoaderCaches() + { + } + + protected: + // cannot instantiate except from derived class + IResources() = default; + }; +} // namespace Pbr diff --git a/src/conformance/framework/pbr/OpenGL/GLCommon.h b/src/conformance/framework/pbr/OpenGL/GLCommon.h new file mode 100644 index 00000000..c4bcb906 --- /dev/null +++ b/src/conformance/framework/pbr/OpenGL/GLCommon.h @@ -0,0 +1,388 @@ +// Copyright 2023, The Khronos Group, Inc. +// +// Based in part on code that is: +// +// Copyright (C) Microsoft Corporation. All Rights Reserved +// Licensed under the MIT License. See License.txt in the project root for license information. +// +// SPDX-License-Identifier: MIT AND Apache-2.0 +#pragma once + +#include "common/gfxwrapper_opengl.h" + +#if defined(APIENTRY) && !defined(GL_APIENTRY) +#define GL_APIENTRY APIENTRY +#endif + +#include +#include + +namespace Pbr +{ + static constexpr GLuint GLnull = 0; + + /// A stateless destroyer for OpenGL handles that have a destroy function we can refer to statically. + /// + /// @tparam DestroyFunction The function used to destroy the handle. + /// + /// @see ScopedGLWithDefaultDestroy + /// @see ScopedGL + /// @see GLDestroyerWithFuncPointer + /// + /// @ingroup cts_handle_helpers + template + class GLDefaultDestroyer + { + public: + void operator()(GLuint handle) const noexcept + { + DestroyFunction(handle); + } + }; + + /// A destroyer for OpenGL handles that holds state at runtime to contain a function pointer. + /// + /// This is mainly for things from extensions. + /// + /// @see ScopedGLWithPfn + /// @see GLDefaultDestroyer + /// @see ScopedGL + /// + /// @ingroup cts_handle_helpers + class GLDestroyerWithFuncPointer + { + public: + using DestroyFunction = void(GL_APIENTRY*)(GLuint); + + GLDestroyerWithFuncPointer(DestroyFunction pfn) : pfn_(pfn) + { + } + + void operator()(GLuint handle) const noexcept + { + pfn_(handle); + } + + private: + DestroyFunction pfn_; + }; + + /// A unique-ownership RAII helper for OpenGL handles. + /// + /// @tparam TagType A tag type to have a little bit of type safety + /// @tparam Destroyer a functor type that destroys the handle - may be stateless or have state + /// + /// @see GLDefaultDestroyer + /// @see GLDestroyerWithFuncPointer + /// + /// @ingroup cts_handle_helpers + template + class ScopedGL + { + public: + /// Default (empty) constructor + ScopedGL() = default; + + /// Empty constructor when we need a destroyer instance. + explicit ScopedGL(Destroyer d) : d_(d) + { + } + + /// Explicit constructor from handle, if we don't need a destroyer instance. + explicit ScopedGL(GLuint h, std::enable_if::value>* = nullptr) : h_(h) + { + } + + /// Constructor from handle when we need a destroyer instance. + ScopedGL(GLuint h, Destroyer d) : h_(h), d_(d) + { + } + + /// Destructor + ~ScopedGL() + { + reset(); + } + + /// Non-copyable + ScopedGL(ScopedGL const&) = delete; + + /// Non-copy-assignable + ScopedGL& operator=(ScopedGL const&) = delete; + + /// Move-constructible + ScopedGL(ScopedGL&& other) noexcept : h_(std::move(other.h_)), d_(std::move(other.d_)) + { + other.clear(); + } + + /// Move-assignable + ScopedGL& operator=(ScopedGL&& other) noexcept + { + if (&other == this) { + return *this; + } + reset(); + swap(other); + return *this; + } + + /// Is this handle valid? + constexpr bool valid() const noexcept + { + return get() != GLnull; + } + + /// Is this handle valid? + explicit operator bool() const noexcept + { + return valid(); + } + + void swap(ScopedGL& other) noexcept + { + std::swap(h_, other.h_); + std::swap(d_, other.d_); + } + + /// Destroy the owned handle, if any. + void reset() + { + if (get() != GLnull) { + get_destroyer()(get()); + clear(); + } + } + + /// Assign a new handle into this object's control, destroying the old one if applicable. + void adopt(GLuint h) + { + reset(); + h_ = h; + } + + /// Assign a new handle into this object's control, including new destroyer, destroying the old one if applicable. + void adopt(GLuint h, Destroyer&& d) + { + adopt(h); + d_ = std::move(d); + } + + /// Access the raw handle without affecting ownership or lifetime. + GLuint get() const noexcept + { + return h_; + } + + /// Access the destroyer functor + const Destroyer& get_destroyer() const noexcept + { + return d_; + } + + /// Release the handle from this object's control. + GLuint release() noexcept + { + GLuint ret = h_; + clear(); + return ret; + } + + /// Reset and return the address of the handle to be used as an outparam. + /// + /// Permissible per 2.3.1 in the OpenGL spec: "If the generating command modifies values through a pointer argument, no change is made to these values." + GLuint* resetAndPut() + { + reset(); + return &h_; + } + + private: + void clear() noexcept + { + h_ = GLnull; + } + GLuint h_ = GLnull; + Destroyer d_; + }; + + /// Swap function for scoped handles, found using ADL. + /// @relates ScopedGL + template + inline void swap(ScopedGL& a, ScopedGL& b) + { + return a.swap(b); + } + + /// Equality comparison between a scoped handle and a null handle + /// @relates ScopedGL + template + inline bool operator==(ScopedGL const& handle, std::nullptr_t const&) + { + return !handle.valid(); + } + + /// Equality comparison between a scoped handle and a null handle + /// @relates ScopedGL + template + inline bool operator==(std::nullptr_t const&, ScopedGL const& handle) + { + return !handle.valid(); + } + + /// Inequality comparison between a scoped handle and a null handle + /// @relates ScopedGL + template + inline bool operator!=(ScopedGL const& handle, std::nullptr_t const&) + { + return handle.valid(); + } + + /// Inequality comparison between a scoped handle and a null handle + /// @relates ScopedGL + template + inline bool operator!=(std::nullptr_t const&, ScopedGL const& handle) + { + return handle.valid(); + } + + /// Alias to ease use of ScopedGL with handle types whose destroy function is statically available. + /// + /// @tparam TagType A tag type to have a little bit of type safety + /// @tparam DestroyFunction The function used to destroy the handle. + /// + /// @see GLDestroyerWithFuncPointer + /// + /// @ingroup cts_handle_helpers + /// @relates ScopedGL + template + using ScopedGLWithDefaultDestroy = ScopedGL>; + + /// Alias to ease use of ScopedGL with handle types whose destroy function is a run-time function pointer (such as from an extension) + /// + /// @tparam TagType A tag type to have a little bit of type safety + /// + /// @see GLDefaultDestroyer + /// + /// @ingroup cts_handle_helpers + /// @relates ScopedGL + template + using ScopedGLWithPfn = ScopedGL; + + /// Function template to wrap a statically-known deleter function that takes a count (1) and a pointer/array of names. + /// + /// @tparam F The address of the actual delete function + /// + /// @see ScopedGLWithDefaultDestroy + /// + /// @ingroup cts_handle_helpers + /// @relates ScopedGL + template + void GL_APIENTRY destroyOne(GLuint handle) + { + F(1, &handle); + } + + /// Functor wrapping a delete function that wants just the name to delete as a parameter. + /// + /// You don't have to know the function pointer at compile time (OK for dynamically loaded OpenGL), + /// you just need to say where you will put the function pointer when you look it up upon load. + /// + /// @tparam PFN Function pointer type + /// @tparam FunctionExtern statically known address of function pointer for the deleter. + /// + /// @see GLDeleterOne if the function actually wants a count and an array/pointer + /// @see ScopedGL + /// + /// @ingroup cts_handle_helpers + template + class GLDeleter + { + public: + GLDeleter() = default; + + void operator()(GLuint handle) const + { + (*FunctionExtern)(handle); + } + }; + + /// Functor wrapping a delete function that wants a "1" as the first parameter and the address of the name as the second. + /// + /// These functions typically are designed to support deleting arrays of names. + /// + /// You don't have to know the function pointer at compile time (OK for dynamically loaded OpenGL), + /// you just need to say where you will put the function pointer when you look it up upon load. + /// + /// @tparam PFN Function pointer type + /// @tparam FunctionExtern statically known address of function pointer for the deleter. + /// + /// @see GLDeleter if the only parameter is the name to delete. + /// @see ScopedGL + /// + /// @ingroup cts_handle_helpers + template + class GLDeleterOne + { + public: + GLDeleterOne() = default; + + void operator()(GLuint handle) const + { + (*FunctionExtern)(1, &handle); + } + }; + + // TODO remove awkward hack: we load OpenGL at runtime into function pointers, + // but link directly against a library with symbols for OpenGL ES +#if defined(XR_USE_GRAPHICS_API_OPENGL) +#define XRC_GL_PFN(p) p* +#elif defined(XR_USE_GRAPHICS_API_OPENGL_ES) +#define XRC_GL_PFN(p) p +#endif + + /// GLuint wrapper for use with an OpenGL Shader Program: somewhat type-safe, RAII deletes by calling glDeleteProgram + /// @ingroup cts_handle_helpers +#if defined(OS_APPLE_MACOS) + using ScopedGLProgram = ScopedGL>; +#else + using ScopedGLProgram = ScopedGL>; +#endif + + /// GLuint wrapper for use with an OpenGL Shader: somewhat type-safe, RAII deletes by calling glDeleteShader + /// @ingroup cts_handle_helpers +#if defined(OS_APPLE_MACOS) + using ScopedGLShader = ScopedGL>; +#else + using ScopedGLShader = ScopedGL>; +#endif + + /// GLuint wrapper for use with an OpenGL Texture: somewhat type-safe, RAII deletes by calling glDeleteTextures + /// @ingroup cts_handle_helpers + using ScopedGLTexture = ScopedGLWithDefaultDestroy>; + + /// GLuint wrapper for use with an OpenGL Sampler: somewhat type-safe, RAII deletes by calling glDeleteSamplers + /// @ingroup cts_handle_helpers +#if defined(OS_APPLE_MACOS) + using ScopedGLSampler = ScopedGL>; +#else + using ScopedGLSampler = ScopedGL>; +#endif + /// GLuint wrapper for use with an OpenGL Buffer: somewhat type-safe, RAII deletes by calling glDeleteBuffers + /// @ingroup cts_handle_helpers +#if defined(OS_APPLE_MACOS) + using ScopedGLBuffer = ScopedGL>; +#else + using ScopedGLBuffer = ScopedGL>; +#endif + + /// GLuint wrapper for use with an OpenGL Vertex Array: somewhat type-safe, RAII deletes by calling glDeleteVertexArrays + /// @ingroup cts_handle_helpers +#if defined(OS_APPLE_MACOS) + using ScopedGLVertexArray = ScopedGL>; +#else + using ScopedGLVertexArray = + ScopedGL>; +#endif + +} // namespace Pbr diff --git a/src/conformance/framework/pbr/OpenGL/GLMaterial.cpp b/src/conformance/framework/pbr/OpenGL/GLMaterial.cpp new file mode 100644 index 00000000..f8771fad --- /dev/null +++ b/src/conformance/framework/pbr/OpenGL/GLMaterial.cpp @@ -0,0 +1,107 @@ +// Copyright 2022-2023, The Khronos Group, Inc. +// +// Based in part on code that is: +// Copyright (C) Microsoft Corporation. All Rights Reserved +// Licensed under the MIT License. See License.txt in the project root for license information. +// +// SPDX-License-Identifier: MIT AND Apache-2.0 + +#if defined(XR_USE_GRAPHICS_API_OPENGL) || defined(XR_USE_GRAPHICS_API_OPENGL_ES) + +#include "GLMaterial.h" + +#include "GLResources.h" +#include "GLTexture.h" + +#include "../PbrMaterial.h" + +#include "common/gfxwrapper_opengl.h" +#include "utilities/opengl_utils.h" + +#include + +namespace Pbr +{ + GLMaterial::GLMaterial(Pbr::GLResources const& /* pbrResources */) + { + XRC_CHECK_THROW_GLCMD(glGenBuffers(1, m_constantBuffer.resetAndPut())); + XRC_CHECK_THROW_GLCMD(glBindBuffer(GL_SHADER_STORAGE_BUFFER, m_constantBuffer.get())); + XRC_CHECK_THROW_GLCMD(glBufferData(GL_SHADER_STORAGE_BUFFER, sizeof(ConstantBufferData), &m_parameters, GL_STATIC_DRAW)); + } + + std::shared_ptr GLMaterial::Clone(Pbr::GLResources const& pbrResources) const + { + auto clone = std::make_shared(pbrResources); + clone->CopyFrom(*this); + clone->m_textures = m_textures; + clone->m_samplers = m_samplers; + return clone; + } + + /* static */ + std::shared_ptr GLMaterial::CreateFlat(const GLResources& pbrResources, RGBAColor baseColorFactor, + float roughnessFactor /* = 1.0f */, float metallicFactor /* = 0.0f */, + RGBColor emissiveFactor /* = XMFLOAT3(0, 0, 0) */) + { + std::shared_ptr material = std::make_shared(pbrResources); + + if (baseColorFactor.a < 1.0f) { // Alpha channel + material->SetAlphaBlended(BlendState::AlphaBlended); + } + + Pbr::GLMaterial::ConstantBufferData& parameters = material->Parameters(); + parameters.BaseColorFactor = baseColorFactor; + parameters.EmissiveFactor = emissiveFactor; + parameters.MetallicFactor = metallicFactor; + parameters.RoughnessFactor = roughnessFactor; + + auto defaultSampler = std::make_shared(Pbr::GLTexture::CreateSampler()); + material->SetTexture(ShaderSlots::BaseColor, pbrResources.CreateTypedSolidColorTexture(RGBA::White), defaultSampler); + material->SetTexture(ShaderSlots::MetallicRoughness, pbrResources.CreateTypedSolidColorTexture(RGBA::White), defaultSampler); + // No occlusion. + material->SetTexture(ShaderSlots::Occlusion, pbrResources.CreateTypedSolidColorTexture(RGBA::White), defaultSampler); + // Flat normal. + material->SetTexture(ShaderSlots::Normal, pbrResources.CreateTypedSolidColorTexture(RGBA::FlatNormal), defaultSampler); + material->SetTexture(ShaderSlots::Emissive, pbrResources.CreateTypedSolidColorTexture(RGBA::White), defaultSampler); + + return material; + } + + void GLMaterial::SetTexture(ShaderSlots::PSMaterial slot, std::shared_ptr textureView, + std::shared_ptr sampler) + { + m_textures[slot] = textureView; + + if (sampler) { + m_samplers[slot] = sampler; + } + } + + void GLMaterial::Bind(const GLResources& pbrResources) const + { + // If the parameters of the constant buffer have changed, update the constant buffer. + if (m_parametersChanged) { + m_parametersChanged = false; + XRC_CHECK_THROW_GLCMD(glBindBuffer(GL_UNIFORM_BUFFER, m_constantBuffer.get())); + XRC_CHECK_THROW_GLCMD(glBufferSubData(GL_UNIFORM_BUFFER, 0, sizeof(ConstantBufferData), &m_parameters)); + } + + pbrResources.SetBlendState(m_alphaBlended == BlendState::AlphaBlended); + pbrResources.SetDepthStencilState(m_alphaBlended == BlendState::AlphaBlended); + pbrResources.SetRasterizerState(m_doubleSided == DoubleSided::DoubleSided); + + XRC_CHECK_THROW_GLCMD(glBindBufferBase(GL_UNIFORM_BUFFER, Pbr::ShaderSlots::ConstantBuffers::Material, m_constantBuffer.get())); + + static_assert(Pbr::ShaderSlots::BaseColor == 0, "BaseColor must be the first slot"); + + std::array, TextureCount> textures; + for (uint32_t texIndex = 0; texIndex < ShaderSlots::NumMaterialSlots; ++texIndex) { + GLuint unit = ShaderSlots::GLSL::MaterialTexturesOffset + texIndex; + XRC_CHECK_THROW_GLCMD(glActiveTexture(GL_TEXTURE0 + unit)); + XRC_CHECK_THROW_GLCMD(glBindTexture(GL_TEXTURE_2D, m_textures[texIndex]->get())); + XRC_CHECK_THROW_GLCMD(glBindSampler(unit, m_samplers[texIndex]->get())); + } + } +} // namespace Pbr + +#endif diff --git a/src/conformance/framework/pbr/OpenGL/GLMaterial.h b/src/conformance/framework/pbr/OpenGL/GLMaterial.h new file mode 100644 index 00000000..8b7b6e0a --- /dev/null +++ b/src/conformance/framework/pbr/OpenGL/GLMaterial.h @@ -0,0 +1,62 @@ +// Copyright 2022-2023, The Khronos Group, Inc. +// +// Based in part on code that is: +// Copyright (C) Microsoft Corporation. All Rights Reserved +// Licensed under the MIT License. See License.txt in the project root for license information. +// +// SPDX-License-Identifier: MIT AND Apache-2.0 +#pragma once + +#include "GLCommon.h" +#include "GLResources.h" + +#include "../PbrCommon.h" +#include "../PbrMaterial.h" +#include "../PbrSharedState.h" + +#include "common/gfxwrapper_opengl.h" + +#include +#include +#include +#include +#include +#include + +namespace Pbr +{ + struct GLResources; + + // A GLMaterial contains the metallic roughness parameters and textures. + // Primitives specify which GLMaterial to use when being rendered. + struct GLMaterial final : public Material + { + // Create a uninitialized material. Textures and shader coefficients must be set. + GLMaterial(Pbr::GLResources const& pbrResources); + + // Create a clone of this material. + std::shared_ptr Clone(Pbr::GLResources const& pbrResources) const; + + // Create a flat (no texture) material. + static std::shared_ptr CreateFlat(const GLResources& pbrResources, RGBAColor baseColorFactor, + float roughnessFactor = 1.0f, float metallicFactor = 0.0f, + RGBColor emissiveFactor = RGB::Black); + + // Set a Metallic-Roughness texture. + void SetTexture(ShaderSlots::PSMaterial slot, std::shared_ptr textureView, + std::shared_ptr sampler = nullptr); + // void SetTexture(ShaderSlots::PSMaterial slot, ITexture& texture) override; + + // Bind this material to current context. + void Bind(const GLResources& pbrResources) const; + + std::string Name; + bool Hidden{false}; + + private: + static constexpr size_t TextureCount = ShaderSlots::NumMaterialSlots; + std::array, TextureCount> m_textures; + std::array, TextureCount> m_samplers; + ScopedGLBuffer m_constantBuffer; + }; +} // namespace Pbr diff --git a/src/conformance/framework/pbr/OpenGL/GLModel.cpp b/src/conformance/framework/pbr/OpenGL/GLModel.cpp new file mode 100644 index 00000000..f21c6509 --- /dev/null +++ b/src/conformance/framework/pbr/OpenGL/GLModel.cpp @@ -0,0 +1,99 @@ +// Copyright 2022-2023, The Khronos Group, Inc. +// +// Based in part on code that is: +// Copyright (C) Microsoft Corporation. All Rights Reserved +// Licensed under the MIT License. See License.txt in the project root for license information. +// +// SPDX-License-Identifier: MIT AND Apache-2.0 + +#if defined(XR_USE_GRAPHICS_API_OPENGL) || defined(XR_USE_GRAPHICS_API_OPENGL_ES) + +#include "GLModel.h" + +#include "GLCommon.h" +#include "GLMaterial.h" +#include "GLPrimitive.h" +#include "GLResources.h" + +#include "../PbrHandles.h" +#include "../PbrModel.h" +#include "../PbrSharedState.h" + +#include "common/gfxwrapper_opengl.h" +#include "utilities/opengl_utils.h" + +#include +#include +#include +#include + +namespace Pbr +{ + + void GLModel::Render(Pbr::GLResources const& pbrResources) + { + UpdateTransforms(pbrResources); + + XRC_CHECK_THROW_GLCMD(glBindBufferBase(GL_SHADER_STORAGE_BUFFER, ShaderSlots::GLSL::VSResourceViewsOffset + ShaderSlots::Transforms, + m_modelTransformsStructuredBuffer.get())); + + for (PrimitiveHandle primitiveHandle : GetPrimitives()) { + const Pbr::GLPrimitive& primitive = pbrResources.GetPrimitive(primitiveHandle); + if (primitive.GetMaterial()->Hidden) + continue; + + primitive.GetMaterial()->Bind(pbrResources); + primitive.Render(pbrResources.GetFillMode()); + } + + // Expect the caller to reset other state, but the geometry shader is cleared specially. + //context->GSSetShader(nullptr, nullptr, 0); + } + + void GLModel::UpdateTransforms(Pbr::GLResources const& /* pbrResources */) + { + const auto& nodes = GetNodes(); + const uint32_t newTotalModifyCount = std::accumulate(nodes.begin(), nodes.end(), 0, [](uint32_t sumChangeCount, const Node& node) { + return sumChangeCount + node.GetModifyCount(); + }); + + // If none of the node transforms have changed, no need to recompute/update the model transform structured buffer. + if (newTotalModifyCount != TotalModifyCount || m_modelTransformsStructuredBufferInvalid) { + if (m_modelTransformsStructuredBufferInvalid) // The structured buffer is reset when a Node is added. + { + XrMatrix4x4f identityMatrix; + XrMatrix4x4f_CreateIdentity(&identityMatrix); // or better yet poison it + m_modelTransforms.resize(nodes.size(), identityMatrix); + + size_t elemSize = sizeof(decltype(m_modelTransforms)::value_type); + XRC_CHECK_THROW_GLCMD(glGenBuffers(1, m_modelTransformsStructuredBuffer.resetAndPut())); + XRC_CHECK_THROW_GLCMD(glBindBuffer(GL_SHADER_STORAGE_BUFFER, m_modelTransformsStructuredBuffer.get())); + XRC_CHECK_THROW_GLCMD( + glBufferData(GL_SHADER_STORAGE_BUFFER, elemSize * m_modelTransforms.size(), m_modelTransforms.data(), GL_DYNAMIC_DRAW)); + + m_modelTransformsStructuredBufferInvalid = false; + } + + // Nodes are guaranteed to come after their parents, so each node transform can be multiplied by its parent transform in a single pass. + assert(nodes.size() == m_modelTransforms.size()); + XrMatrix4x4f identityMatrix; + XrMatrix4x4f_CreateIdentity(&identityMatrix); + for (const auto& node : nodes) { + assert(node.ParentNodeIndex == RootParentNodeIndex || node.ParentNodeIndex < node.Index); + const XrMatrix4x4f& parentTransform = + (node.ParentNodeIndex == RootParentNodeIndex) ? identityMatrix : m_modelTransforms[node.ParentNodeIndex]; + XrMatrix4x4f nodeTransform = node.GetTransform(); + XrMatrix4x4f_Multiply(&m_modelTransforms[node.Index], &parentTransform, &nodeTransform); + } + + // Update node transform structured buffer. + XRC_CHECK_THROW_GLCMD(glBindBuffer(GL_SHADER_STORAGE_BUFFER, m_modelTransformsStructuredBuffer.get())); + XRC_CHECK_THROW_GLCMD(glBufferSubData(GL_SHADER_STORAGE_BUFFER, 0, + sizeof(decltype(m_modelTransforms)::value_type) * m_modelTransforms.size(), + this->m_modelTransforms.data())); + TotalModifyCount = newTotalModifyCount; + } + } +} // namespace Pbr + +#endif diff --git a/src/conformance/framework/pbr/OpenGL/GLModel.h b/src/conformance/framework/pbr/OpenGL/GLModel.h new file mode 100644 index 00000000..6c5f7341 --- /dev/null +++ b/src/conformance/framework/pbr/OpenGL/GLModel.h @@ -0,0 +1,42 @@ +// Copyright 2022-2023, The Khronos Group, Inc. +// +// Based in part on code that is: +// Copyright (C) Microsoft Corporation. All Rights Reserved +// Licensed under the MIT License. See License.txt in the project root for license information. +// +// SPDX-License-Identifier: MIT AND Apache-2.0 +#pragma once +#include "GLCommon.h" +#include "GLResources.h" + +#include "../PbrHandles.h" +#include "../PbrModel.h" + +#include "common/xr_linear.h" + +#include +#include + +namespace Pbr +{ + + struct GLPrimitive; + struct GLResources; + + class GLModel final : public Model + { + public: + // Render the model. + void Render(Pbr::GLResources const& pbrResources); + + private: + // Updated the transforms used to render the model. This needs to be called any time a node transform is changed. + void UpdateTransforms(Pbr::GLResources const& pbrResources); + + // Temporary buffer holds the world transforms, computed from the node's local transforms. + mutable std::vector m_modelTransforms; + mutable ScopedGLBuffer m_modelTransformsStructuredBuffer; + + mutable uint32_t TotalModifyCount{0}; + }; +} // namespace Pbr diff --git a/src/conformance/framework/pbr/OpenGL/GLPrimitive.cpp b/src/conformance/framework/pbr/OpenGL/GLPrimitive.cpp new file mode 100644 index 00000000..7c97335e --- /dev/null +++ b/src/conformance/framework/pbr/OpenGL/GLPrimitive.cpp @@ -0,0 +1,179 @@ +// Copyright 2022-2023, The Khronos Group, Inc. +// +// Based in part on code that is: +// Copyright (C) Microsoft Corporation. All Rights Reserved +// Licensed under the MIT License. See License.txt in the project root for license information. +// +// SPDX-License-Identifier: MIT AND Apache-2.0 + +#if defined(XR_USE_GRAPHICS_API_OPENGL) || defined(XR_USE_GRAPHICS_API_OPENGL_ES) + +#include "GLPrimitive.h" + +#include "GLCommon.h" + +#include "../PbrCommon.h" + +#include "common/gfxwrapper_opengl.h" +#include "utilities/opengl_utils.h" + +#include + +namespace Pbr +{ + struct GLMaterial; +} // namespace Pbr + +namespace +{ + struct VertexInputAttributeDescription + { + GLuint index; + GLint size; + GLenum type; + bool asFloat; + GLboolean normalized; + size_t offset; + }; + + static constexpr VertexInputAttributeDescription c_attrDesc[6] = { + {0, 3, GL_FLOAT, true, GL_FALSE, offsetof(Pbr::Vertex, Position)}, + {1, 3, GL_FLOAT, true, GL_FALSE, offsetof(Pbr::Vertex, Normal)}, + {2, 4, GL_FLOAT, true, GL_FALSE, offsetof(Pbr::Vertex, Tangent)}, + {3, 4, GL_FLOAT, true, GL_FALSE, offsetof(Pbr::Vertex, Color0)}, + {4, 2, GL_FLOAT, true, GL_FALSE, offsetof(Pbr::Vertex, TexCoord0)}, + {5, 1, GL_UNSIGNED_SHORT, false, GL_FALSE, offsetof(Pbr::Vertex, ModelTransformIndex)}, + }; + + GLsizei GetPbrVertexByteSize(size_t size) + { + return (GLsizei)(sizeof(decltype(Pbr::PrimitiveBuilder::Vertices)::value_type) * size); + } + GLsizei GetPbrIndexByteSize(size_t size) + { + return (GLsizei)(sizeof(decltype(Pbr::PrimitiveBuilder::Indices)::value_type) * size); + } + + Pbr::ScopedGLBuffer CreateVertexBuffer(const Pbr::PrimitiveBuilder& primitiveBuilder) + { + // Create Vertex Buffer + auto buffer = Pbr::ScopedGLBuffer{}; + XRC_CHECK_THROW_GLCMD(glGenBuffers(1, buffer.resetAndPut())); + XRC_CHECK_THROW_GLCMD(glBindBuffer(GL_ARRAY_BUFFER, buffer.get())); + XRC_CHECK_THROW_GLCMD(glBufferData(GL_ARRAY_BUFFER, GetPbrVertexByteSize(primitiveBuilder.Vertices.size()), + primitiveBuilder.Vertices.data(), GL_STATIC_DRAW)); + return buffer; + } + + Pbr::ScopedGLBuffer CreateIndexBuffer(const Pbr::PrimitiveBuilder& primitiveBuilder) + { + // Create Index Buffer + auto buffer = Pbr::ScopedGLBuffer{}; + XRC_CHECK_THROW_GLCMD(glGenBuffers(1, buffer.resetAndPut())); + XRC_CHECK_THROW_GLCMD(glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, buffer.get())); + XRC_CHECK_THROW_GLCMD(glBufferData(GL_ELEMENT_ARRAY_BUFFER, GetPbrIndexByteSize(primitiveBuilder.Indices.size()), + primitiveBuilder.Indices.data(), GL_STATIC_DRAW)); + return buffer; + } + + Pbr::ScopedGLVertexArray CreateVAO(Pbr::ScopedGLBuffer& vertexBuffer, Pbr::ScopedGLBuffer& indexBuffer) + { + // Create Vertex Array Object + auto vao = Pbr::ScopedGLVertexArray{}; + XRC_CHECK_THROW_GLCMD(glGenVertexArrays(1, vao.resetAndPut())); + XRC_CHECK_THROW_GLCMD(glBindVertexArray(vao.get())); + for (auto attr : c_attrDesc) { + XRC_CHECK_THROW_GLCMD(glEnableVertexAttribArray(attr.index)); + } + XRC_CHECK_THROW_GLCMD(glBindBuffer(GL_ARRAY_BUFFER, vertexBuffer.get())); + XRC_CHECK_THROW_GLCMD(glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, indexBuffer.get())); + + for (auto attr : c_attrDesc) { + if (attr.asFloat) { + XRC_CHECK_THROW_GLCMD(glVertexAttribPointer(attr.index, attr.size, attr.type, attr.normalized, GetPbrVertexByteSize(1), + reinterpret_cast(attr.offset))); + } + else { + XRC_CHECK_THROW_GLCMD(glVertexAttribIPointer(attr.index, attr.size, attr.type, GetPbrVertexByteSize(1), + reinterpret_cast(attr.offset))); + } + } + return vao; + } +} // namespace + +namespace Pbr +{ + GLPrimitive::GLPrimitive(GLsizei indexCount, ScopedGLBuffer indexBuffer, ScopedGLBuffer vertexBuffer, ScopedGLVertexArray vao, + std::shared_ptr material) + : m_indexCount(indexCount) + , m_indexBuffer(std::move(indexBuffer)) + , m_vertexBuffer(std::move(vertexBuffer)) + , m_vao(std::move(vao)) + , m_material(std::move(material)) + { + } + + GLPrimitive::GLPrimitive(const Pbr::PrimitiveBuilder& primitiveBuilder, const std::shared_ptr& material) + : GLPrimitive((GLsizei)primitiveBuilder.Indices.size(), CreateIndexBuffer(primitiveBuilder), CreateVertexBuffer(primitiveBuilder), + ScopedGLVertexArray{}, std::move(material)) + { + m_vao = CreateVAO(m_vertexBuffer, m_indexBuffer); + } + + void GLPrimitive::UpdateBuffers(const Pbr::PrimitiveBuilder& primitiveBuilder) + { + bool vaoNeedsUpdate = false; + + // Update vertex buffer. + { + GLsizei requiredSize = GetPbrVertexByteSize(primitiveBuilder.Vertices.size()); + if (m_vertexCount >= requiredSize) { + XRC_CHECK_THROW_GLCMD(glBindBuffer(GL_ARRAY_BUFFER, m_vertexBuffer.get())); + XRC_CHECK_THROW_GLCMD(glBufferSubData(GL_ARRAY_BUFFER, 0, requiredSize, primitiveBuilder.Vertices.data())); + } + else { + m_vertexBuffer = CreateVertexBuffer(primitiveBuilder); + vaoNeedsUpdate = true; + } + } + + // Update index buffer. + { + GLsizei requiredSize = GetPbrIndexByteSize(primitiveBuilder.Indices.size()); + if (m_indexCount >= requiredSize) { + XRC_CHECK_THROW_GLCMD(glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, m_indexBuffer.get())); + XRC_CHECK_THROW_GLCMD(glBufferSubData(GL_ELEMENT_ARRAY_BUFFER, 0, requiredSize, primitiveBuilder.Indices.data())); + } + else { + m_indexBuffer = CreateIndexBuffer(primitiveBuilder); + vaoNeedsUpdate = true; + } + + m_indexCount = (GLsizei)primitiveBuilder.Indices.size(); + } + + if (vaoNeedsUpdate) { + m_vao = CreateVAO(m_vertexBuffer, m_indexBuffer); + } + } + + void GLPrimitive::Render(FillMode fillMode) const + { + (void)fillMode; // suppress unused warning under GL + GLenum drawMode = +#ifdef XR_USE_GRAPHICS_API_OPENGL + GL_TRIANGLES // use glPolygonMode(..., GL_LINE) +#elif XR_USE_GRAPHICS_API_OPENGL_ES + fillMode == FillMode::Wireframe ? GL_LINES : GL_TRIANGLES +#endif + ; + + XRC_CHECK_THROW_GLCMD(glBindVertexArray(m_vao.get())); + XRC_CHECK_THROW_GLCMD(glBindBuffer(GL_ARRAY_BUFFER, m_vertexBuffer.get())); + XRC_CHECK_THROW_GLCMD(glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, m_indexBuffer.get())); + XRC_CHECK_THROW_GLCMD(glDrawElements(drawMode, m_indexCount, GL_UNSIGNED_INT, nullptr)); + } +} // namespace Pbr + +#endif \ No newline at end of file diff --git a/src/conformance/framework/pbr/OpenGL/GLPrimitive.h b/src/conformance/framework/pbr/OpenGL/GLPrimitive.h new file mode 100644 index 00000000..b716ed77 --- /dev/null +++ b/src/conformance/framework/pbr/OpenGL/GLPrimitive.h @@ -0,0 +1,67 @@ +// Copyright 2022-2023, The Khronos Group, Inc. +// +// Based in part on code that is: +// Copyright (C) Microsoft Corporation. All Rights Reserved +// Licensed under the MIT License. See License.txt in the project root for license information. +// +// SPDX-License-Identifier: MIT AND Apache-2.0 +#pragma once + +#include "GLCommon.h" +#include "GLMaterial.h" + +#include "../PbrSharedState.h" + +#include "common/gfxwrapper_opengl.h" + +#include +#include +#include + +namespace Pbr +{ + struct GLMaterial; + struct PrimitiveBuilder; + + // A primitive holds a vertex buffer, index buffer, and a pointer to a PBR material. + struct GLPrimitive final + { + using Collection = std::vector; + + GLPrimitive() = delete; + GLPrimitive(GLsizei indexCount, ScopedGLBuffer indexBuffer, ScopedGLBuffer vertexBuffer, ScopedGLVertexArray vao, + std::shared_ptr material); + GLPrimitive(const Pbr::PrimitiveBuilder& primitiveBuilder, const std::shared_ptr& material); + + void UpdateBuffers(const Pbr::PrimitiveBuilder& primitiveBuilder); + + // Get the material for the primitive. + std::shared_ptr& GetMaterial() + { + return m_material; + } + const std::shared_ptr& GetMaterial() const + { + return m_material; + } + + // Replace the material for the primitive + void SetMaterial(std::shared_ptr material) + { + m_material = std::move(material); + } + + protected: + // friend class Model; + friend class GLModel; + void Render(FillMode fillMode) const; + + private: + GLsizei m_indexCount; + ScopedGLBuffer m_indexBuffer; + GLsizei m_vertexCount; + ScopedGLBuffer m_vertexBuffer; + ScopedGLVertexArray m_vao; + std::shared_ptr m_material; + }; +} // namespace Pbr diff --git a/src/conformance/framework/pbr/OpenGL/GLResources.cpp b/src/conformance/framework/pbr/OpenGL/GLResources.cpp new file mode 100644 index 00000000..a088bfed --- /dev/null +++ b/src/conformance/framework/pbr/OpenGL/GLResources.cpp @@ -0,0 +1,428 @@ +// Copyright 2022-2023, The Khronos Group, Inc. +// +// Based in part on code that is: +// Copyright (C) Microsoft Corporation. All Rights Reserved +// Licensed under the MIT License. See License.txt in the project root for license information. +// +// SPDX-License-Identifier: MIT AND Apache-2.0 + +#if defined(XR_USE_GRAPHICS_API_OPENGL) || defined(XR_USE_GRAPHICS_API_OPENGL_ES) + +#include "GLResources.h" + +#include "GLCommon.h" +#include "GLMaterial.h" +#include "GLPrimitive.h" +#include "GLTexture.h" +#include "GLTextureCache.h" +#include "GlslBuffers.h" + +#include "../../gltf/GltfHelper.h" +#include "../PbrCommon.h" +#include "../PbrHandles.h" +#include "../PbrSharedState.h" + +#include "common/gfxwrapper_opengl.h" +#include "utilities/opengl_utils.h" +#include "utilities/throw_helpers.h" + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +namespace Pbr +{ + struct ITexture; + struct Material; +} // namespace Pbr + +// IWYU pragma: begin_keep +static const char* g_PbrVertexShader = +#ifdef XR_USE_GRAPHICS_API_OPENGL +#include +#elif XR_USE_GRAPHICS_API_OPENGL_ES +#include +#endif + ; + +static const char* g_PbrPixelShader = +#ifdef XR_USE_GRAPHICS_API_OPENGL +#include +#elif XR_USE_GRAPHICS_API_OPENGL_ES +#include +#endif + ; +// IWYU pragma: end_keep + +namespace Pbr +{ + using ImageKey = std::tuple; // Item1 is a pointer to the image, Item2 is sRGB. + + class Program + { + public: + Program() = default; + Program(const char** vertexShader, const char** fragmentShader) + { + m_vertexShader.adopt(glCreateShader(GL_VERTEX_SHADER)); + XRC_CHECK_THROW_GLCMD(glShaderSource(m_vertexShader.get(), 1, vertexShader, nullptr)); + XRC_CHECK_THROW_GLCMD(glCompileShader(m_vertexShader.get())); + Conformance::CheckGLShader(m_vertexShader.get()); + + m_fragmentShader.adopt(glCreateShader(GL_FRAGMENT_SHADER)); + XRC_CHECK_THROW_GLCMD(glShaderSource(m_fragmentShader.get(), 1, fragmentShader, nullptr)); + XRC_CHECK_THROW_GLCMD(glCompileShader(m_fragmentShader.get())); + Conformance::CheckGLShader(m_fragmentShader.get()); + + m_program.adopt(glCreateProgram()); + XRC_CHECK_THROW_GLCMD(glAttachShader(m_program.get(), m_vertexShader.get())); + XRC_CHECK_THROW_GLCMD(glAttachShader(m_program.get(), m_fragmentShader.get())); + XRC_CHECK_THROW_GLCMD(glLinkProgram(m_program.get())); + Conformance::CheckGLProgram(m_program.get()); + } + + void Bind() + { + XRC_CHECK_THROW_GLCMD(glUseProgram(m_program.get())); + } + + private: + ScopedGLShader m_vertexShader{}; + ScopedGLShader m_fragmentShader{}; + ScopedGLProgram m_program{}; + }; + + struct GLResources::Impl + { + void Initialize() + { + Resources.PbrProgram = Program(&g_PbrVertexShader, &g_PbrPixelShader); + + // Set up the constant buffers. + XRC_CHECK_THROW_GLCMD(glGenBuffers(1, Resources.SceneConstantBuffer.resetAndPut())); + XRC_CHECK_THROW_GLCMD(glBindBuffer(GL_UNIFORM_BUFFER, Resources.SceneConstantBuffer.get())); + XRC_CHECK_THROW_GLCMD(glBufferData(GL_UNIFORM_BUFFER, sizeof(Glsl::SceneConstantBuffer), nullptr, GL_DYNAMIC_DRAW)); + + XRC_CHECK_THROW_GLCMD(glGenBuffers(1, Resources.ModelConstantBuffer.resetAndPut())); + XRC_CHECK_THROW_GLCMD(glBindBuffer(GL_UNIFORM_BUFFER, Resources.ModelConstantBuffer.get())); + XRC_CHECK_THROW_GLCMD(glBufferData(GL_UNIFORM_BUFFER, sizeof(Glsl::ModelConstantBuffer), nullptr, GL_DYNAMIC_DRAW)); + + // Samplers for environment map and BRDF. + Resources.BrdfSampler = GLTexture::CreateSampler(); + Resources.EnvironmentMapSampler = GLTexture::CreateSampler(); + + Resources.SolidColorTextureCache.Init(); + } + + struct DeviceResources + { + Program PbrProgram{}; + ScopedGLSampler BrdfSampler; + ScopedGLSampler EnvironmentMapSampler; + ScopedGLBuffer SceneConstantBuffer; + ScopedGLBuffer ModelConstantBuffer; + std::shared_ptr BrdfLut; + std::shared_ptr SpecularEnvironmentMap; + std::shared_ptr DiffuseEnvironmentMap; + mutable GLTextureCache SolidColorTextureCache{}; + }; + PrimitiveCollection Primitives; + + DeviceResources Resources; + Glsl::SceneConstantBuffer SceneBuffer; + Glsl::ModelConstantBuffer ModelBuffer; + + struct LoaderResources + { + // Create D3D cache for reuse of texture views and samplers when possible. + std::map> imageMap; + std::map> samplerMap; + }; + LoaderResources loaderResources; + }; + + GLResources::GLResources() : m_impl(std::make_unique()) + { + m_impl->Initialize(); + } + + GLResources::GLResources(GLResources&& resources) noexcept = default; + + GLResources::~GLResources() = default; + + // Create a GL texture from a tinygltf Image. + static ScopedGLTexture LoadGLTFImage(const tinygltf::Image& image, bool sRGB) + { + // First convert the image to RGBA if it isn't already. + std::vector tempBuffer; + const uint8_t* rgbaBuffer = GltfHelper::ReadImageAsRGBA(image, &tempBuffer); + Internal::ThrowIf(rgbaBuffer == nullptr, "Failed to read image"); + + const GLenum format = sRGB ? GL_SRGB8_ALPHA8 : GL_RGBA8; + return Pbr::GLTexture::CreateTexture(rgbaBuffer, 4, image.width, image.height, format); + } + + static GLenum ConvertMinFilter(int glMinFilter) + { + return glMinFilter == TINYGLTF_TEXTURE_FILTER_NEAREST + ? GL_NEAREST + : glMinFilter == TINYGLTF_TEXTURE_FILTER_LINEAR + ? GL_LINEAR + : glMinFilter == TINYGLTF_TEXTURE_FILTER_NEAREST_MIPMAP_NEAREST + ? GL_NEAREST_MIPMAP_NEAREST + : glMinFilter == TINYGLTF_TEXTURE_FILTER_LINEAR_MIPMAP_NEAREST + ? GL_LINEAR_MIPMAP_NEAREST + : glMinFilter == TINYGLTF_TEXTURE_FILTER_NEAREST_MIPMAP_LINEAR + ? GL_NEAREST_MIPMAP_LINEAR + : glMinFilter == TINYGLTF_TEXTURE_FILTER_LINEAR_MIPMAP_LINEAR ? GL_LINEAR_MIPMAP_LINEAR + : GL_NEAREST; + } + static GLenum ConvertMagFilter(int glMagFilter) + { + return glMagFilter == TINYGLTF_TEXTURE_FILTER_NEAREST ? GL_NEAREST + : glMagFilter == TINYGLTF_TEXTURE_FILTER_LINEAR ? GL_LINEAR : GL_NEAREST; + } + + // Create a GL sampler from a tinygltf Sampler. + static ScopedGLSampler CreateGLTFSampler(const tinygltf::Sampler& sampler) + { + ScopedGLSampler glSampler{}; + XRC_CHECK_THROW_GLCMD(glGenSamplers(1, glSampler.resetAndPut())); + + GLenum minFilter = ConvertMinFilter(sampler.minFilter); + GLenum magFilter = ConvertMagFilter(sampler.magFilter); + + XRC_CHECK_THROW_GLCMD(glSamplerParameteri(glSampler.get(), GL_TEXTURE_MIN_FILTER, minFilter)); + XRC_CHECK_THROW_GLCMD(glSamplerParameteri(glSampler.get(), GL_TEXTURE_MAG_FILTER, magFilter)); + + GLenum addressModeS = sampler.wrapS == TINYGLTF_TEXTURE_WRAP_CLAMP_TO_EDGE + ? GL_CLAMP_TO_EDGE + : sampler.wrapS == TINYGLTF_TEXTURE_WRAP_MIRRORED_REPEAT ? GL_MIRRORED_REPEAT : GL_REPEAT; + GLenum addressModeT = sampler.wrapT == TINYGLTF_TEXTURE_WRAP_CLAMP_TO_EDGE + ? GL_CLAMP_TO_EDGE + : sampler.wrapT == TINYGLTF_TEXTURE_WRAP_MIRRORED_REPEAT ? GL_MIRRORED_REPEAT : GL_REPEAT; + GLenum addressModeR = GL_REPEAT; + + XRC_CHECK_THROW_GLCMD(glSamplerParameteri(glSampler.get(), GL_TEXTURE_WRAP_S, addressModeS)); + XRC_CHECK_THROW_GLCMD(glSamplerParameteri(glSampler.get(), GL_TEXTURE_WRAP_T, addressModeT)); + XRC_CHECK_THROW_GLCMD(glSamplerParameteri(glSampler.get(), GL_TEXTURE_WRAP_R, addressModeR)); + + return glSampler; + } + + /* IResources implementations */ + std::shared_ptr GLResources::CreateFlatMaterial(RGBAColor baseColorFactor, float roughnessFactor, float metallicFactor, + RGBColor emissiveFactor) + { + return GLMaterial::CreateFlat(*this, baseColorFactor, roughnessFactor, metallicFactor, emissiveFactor); + } + std::shared_ptr GLResources::CreateMaterial() + { + return std::make_shared(*this); + } + std::shared_ptr GLResources::CreateSolidColorTexture(RGBAColor color) + { + // TODO maybe unused + auto ret = std::make_shared(); + ret->srv = CreateTypedSolidColorTexture(color); + return ret; + } + + void GLResources::LoadTexture(const std::shared_ptr& material, Pbr::ShaderSlots::PSMaterial slot, + const tinygltf::Image* image, const tinygltf::Sampler* sampler, bool sRGB, Pbr::RGBAColor defaultRGBA) + { + auto pbrMaterial = std::dynamic_pointer_cast(material); + if (!pbrMaterial) { + throw std::logic_error("Wrong type of material"); + } + // Find or load the image referenced by the texture. + const ImageKey imageKey = std::make_tuple(image, sRGB); + std::shared_ptr textureView = + image != nullptr ? m_impl->loaderResources.imageMap[imageKey] : CreateTypedSolidColorTexture(defaultRGBA); + if (!textureView) // If not cached, load the image and store it in the texture cache. + { + // TODO: Generate mipmaps if sampler's minification filter (minFilter) uses mipmapping. + // TODO: If texture is not power-of-two and (sampler has wrapping=repeat/mirrored_repeat OR minFilter uses + // mipmapping), resize to power-of-two. + textureView = std::make_shared(LoadGLTFImage(*image, sRGB)); + m_impl->loaderResources.imageMap[imageKey] = textureView; + } + + // Find or create the sampler referenced by the texture. + std::shared_ptr samplerState = m_impl->loaderResources.samplerMap[sampler]; + if (!samplerState) // If not cached, create the sampler and store it in the sampler cache. + { + samplerState = std::make_shared(sampler != nullptr ? CreateGLTFSampler(*sampler) + : Pbr::GLTexture::CreateSampler(GL_REPEAT)); + m_impl->loaderResources.samplerMap[sampler] = samplerState; + } + + pbrMaterial->SetTexture(slot, textureView, samplerState); + } + void GLResources::DropLoaderCaches() + { + m_impl->loaderResources = {}; + } + + void GLResources::SetBrdfLut(std::shared_ptr brdfLut) + { + m_impl->Resources.BrdfLut = std::move(brdfLut); + } + + void GLResources::SetLight(XrVector3f direction, RGBColor diffuseColor) + { + m_impl->SceneBuffer.LightDirection = direction; + m_impl->SceneBuffer.LightDiffuseColor = diffuseColor; + } + + void GLResources::SetModelToWorld(XrMatrix4x4f modelToWorld) const + { + m_impl->ModelBuffer.ModelToWorld = modelToWorld; + XRC_CHECK_THROW_GLCMD(glBindBuffer(GL_UNIFORM_BUFFER, m_impl->Resources.ModelConstantBuffer.get())); + XRC_CHECK_THROW_GLCMD(glBufferSubData(GL_UNIFORM_BUFFER, 0, sizeof(Glsl::ModelConstantBuffer), &m_impl->ModelBuffer)); + } + + void GLResources::SetViewProjection(XrMatrix4x4f view, XrMatrix4x4f projection) const + { + XrMatrix4x4f_Multiply(&m_impl->SceneBuffer.ViewProjection, &projection, &view); + + XrMatrix4x4f inv; + XrMatrix4x4f_Invert(&inv, &view); + m_impl->SceneBuffer.EyePosition = {inv.m[12], inv.m[13], inv.m[14]}; + } + + void GLResources::SetEnvironmentMap(std::shared_ptr specularEnvironmentMap, + std::shared_ptr diffuseEnvironmentMap) + { + // TODO: get number of mip levels + int mipLevels = 1; + m_impl->SceneBuffer.NumSpecularMipLevels = mipLevels; + m_impl->Resources.SpecularEnvironmentMap = std::move(specularEnvironmentMap); + m_impl->Resources.DiffuseEnvironmentMap = std::move(diffuseEnvironmentMap); + } + + std::shared_ptr GLResources::CreateTypedSolidColorTexture(RGBAColor color) const + { + return m_impl->Resources.SolidColorTextureCache.CreateTypedSolidColorTexture(color); + } + + void GLResources::Bind() const + { + // SetModelToWorld must always be called before this, populating the ModelConstantBuffer. + XRC_CHECK_THROW_GLCMD(glBindBuffer(GL_UNIFORM_BUFFER, m_impl->Resources.SceneConstantBuffer.get())); + XRC_CHECK_THROW_GLCMD(glBufferSubData(GL_UNIFORM_BUFFER, 0, sizeof(Glsl::SceneConstantBuffer), &m_impl->SceneBuffer)); + + m_impl->Resources.PbrProgram.Bind(); + + XRC_CHECK_THROW_GLCMD( + glBindBufferBase(GL_UNIFORM_BUFFER, ShaderSlots::ConstantBuffers::Scene, m_impl->Resources.SceneConstantBuffer.get())); + XRC_CHECK_THROW_GLCMD( + glBindBufferBase(GL_UNIFORM_BUFFER, ShaderSlots::ConstantBuffers::Model, m_impl->Resources.ModelConstantBuffer.get())); + + XRC_CHECK_THROW_GLCMD( // + glActiveTexture(GL_TEXTURE0 + ShaderSlots::GLSL::MaterialTexturesOffset + ShaderSlots::Brdf)); + XRC_CHECK_THROW_GLCMD(glBindTexture(GL_TEXTURE_2D, m_impl->Resources.BrdfLut->get())); + XRC_CHECK_THROW_GLCMD(glBindSampler(ShaderSlots::Pbr::Brdf, m_impl->Resources.BrdfSampler.get())); + + XRC_CHECK_THROW_GLCMD( // + glActiveTexture(GL_TEXTURE0 + ShaderSlots::GLSL::MaterialTexturesOffset + ShaderSlots::EnvironmentMap::DiffuseTexture)); + XRC_CHECK_THROW_GLCMD(glBindTexture(GL_TEXTURE_CUBE_MAP, m_impl->Resources.DiffuseEnvironmentMap->get())); + XRC_CHECK_THROW_GLCMD(glBindSampler(ShaderSlots::EnvironmentMap::DiffuseTexture, m_impl->Resources.EnvironmentMapSampler.get())); + + XRC_CHECK_THROW_GLCMD( // + glActiveTexture(GL_TEXTURE0 + ShaderSlots::GLSL::MaterialTexturesOffset + ShaderSlots::EnvironmentMap::SpecularTexture)); + XRC_CHECK_THROW_GLCMD(glBindTexture(GL_TEXTURE_CUBE_MAP, m_impl->Resources.SpecularEnvironmentMap->get())); + XRC_CHECK_THROW_GLCMD(glBindSampler(ShaderSlots::EnvironmentMap::SpecularTexture, m_impl->Resources.EnvironmentMapSampler.get())); + } + + PrimitiveHandle GLResources::MakePrimitive(const Pbr::PrimitiveBuilder& primitiveBuilder, + const std::shared_ptr& material) + { + auto typedMaterial = std::dynamic_pointer_cast(material); + if (!typedMaterial) { + throw std::logic_error("Got the wrong type of material"); + } + return m_impl->Primitives.emplace_back(primitiveBuilder, typedMaterial); + } + + GLPrimitive& GLResources::GetPrimitive(PrimitiveHandle p) + { + return m_impl->Primitives[p]; + } + + const GLPrimitive& GLResources::GetPrimitive(PrimitiveHandle p) const + { + return m_impl->Primitives[p]; + } + + void GLResources::SetFillMode(FillMode mode) + { + m_sharedState.SetFillMode(mode); + } + + FillMode GLResources::GetFillMode() const + { + return m_sharedState.GetFillMode(); + } + + void GLResources::SetFrontFaceWindingOrder(FrontFaceWindingOrder windingOrder) + { + m_sharedState.SetFrontFaceWindingOrder(windingOrder); + } + + FrontFaceWindingOrder GLResources::GetFrontFaceWindingOrder() const + { + return m_sharedState.GetFrontFaceWindingOrder(); + } + + void GLResources::SetDepthDirection(DepthDirection depthDirection) + { + m_sharedState.SetDepthDirection(depthDirection); + } + + void GLResources::SetBlendState(bool enabled) const + { + if (enabled) { + XRC_CHECK_THROW_GLCMD(glEnable(GL_BLEND)); + XRC_CHECK_THROW_GLCMD(glBlendFuncSeparate(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, GL_ZERO, GL_ONE)); + XRC_CHECK_THROW_GLCMD(glBlendEquationSeparate(GL_FUNC_ADD, GL_FUNC_ADD)); + } + else { + + XRC_CHECK_THROW_GLCMD(glDisable(GL_BLEND)); + } + } + + void GLResources::SetRasterizerState(bool doubleSided) const + { + if (doubleSided) { + XRC_CHECK_THROW_GLCMD(glDisable(GL_CULL_FACE)); + } + else { + + XRC_CHECK_THROW_GLCMD(glEnable(GL_CULL_FACE)); + } +#ifdef XR_USE_GRAPHICS_API_OPENGL + // This does not set double-sided rendering, it says we control both front and back + XRC_CHECK_THROW_GLCMD(glPolygonMode(GL_FRONT_AND_BACK, m_sharedState.GetFillMode() == FillMode::Wireframe ? GL_LINE : GL_FILL)); +#elif XR_USE_GRAPHICS_API_OPENGL_ES + // done during rendering using GL_LINES instead +#endif + } + + void GLResources::SetDepthStencilState(bool disableDepthWrite) const + { + XRC_CHECK_THROW_GLCMD(glDepthFunc(m_sharedState.GetDepthDirection() == DepthDirection::Reversed ? GL_GREATER : GL_LESS)); + XRC_CHECK_THROW_GLCMD(glDepthMask(disableDepthWrite ? GL_FALSE : GL_TRUE)); + } +} // namespace Pbr + +#endif diff --git a/src/conformance/framework/pbr/OpenGL/GLResources.h b/src/conformance/framework/pbr/OpenGL/GLResources.h new file mode 100644 index 00000000..f6b05802 --- /dev/null +++ b/src/conformance/framework/pbr/OpenGL/GLResources.h @@ -0,0 +1,118 @@ +// Copyright 2022-2023, The Khronos Group, Inc. +// +// Based in part on code that is: +// Copyright (C) Microsoft Corporation. All Rights Reserved +// Licensed under the MIT License. See License.txt in the project root for license information. +// +// SPDX-License-Identifier: MIT AND Apache-2.0 +#pragma once + +#include "GLCommon.h" + +#include "../IResources.h" +#include "../PbrCommon.h" +#include "../PbrHandles.h" +#include "../PbrSharedState.h" + +#include "common/gfxwrapper_opengl.h" +#include "common/xr_linear.h" + +#include + +namespace tinygltf +{ + struct Image; + struct Sampler; +} // namespace tinygltf + +#include +#include +#include +#include +#include + +namespace Pbr +{ + struct Primitive; + struct Material; + + using Duration = std::chrono::high_resolution_clock::duration; + struct GLPrimitive; + struct GLMaterial; + + struct GLTextureAndSampler : public ITexture + { + ~GLTextureAndSampler() override = default; + /// Required + std::shared_ptr srv; + + /// Optional + std::shared_ptr sampler; + }; + + // Global PBR resources required for rendering a scene. + struct GLResources final : public IResources + { + explicit GLResources(); + GLResources(GLResources&&) noexcept; + + ~GLResources() override; + + std::shared_ptr CreateFlatMaterial(RGBAColor baseColorFactor, float roughnessFactor = 1.0f, float metallicFactor = 0.0f, + RGBColor emissiveFactor = RGB::Black) override; + std::shared_ptr CreateMaterial() override; + std::shared_ptr CreateSolidColorTexture(RGBAColor color); + void LoadTexture(const std::shared_ptr& pbrMaterial, Pbr::ShaderSlots::PSMaterial slot, const tinygltf::Image* image, + const tinygltf::Sampler* sampler, bool sRGB, Pbr::RGBAColor defaultRGBA) override; + PrimitiveHandle MakePrimitive(const Pbr::PrimitiveBuilder& primitiveBuilder, + const std::shared_ptr& material) override; + void DropLoaderCaches() override; + + // Sets the Bidirectional Reflectance Distribution Function Lookup Table texture, required by the shader to compute surface + // reflectance from the IBL. + void SetBrdfLut(std::shared_ptr brdfLut); + + // Set the directional light. + void SetLight(XrVector3f direction, RGBColor diffuseColor); + + // Set the specular and diffuse image-based lighting (IBL) maps. ShaderResourceViews must be TextureCubes. + void SetEnvironmentMap(std::shared_ptr specularEnvironmentMap, + std::shared_ptr diffuseEnvironmentMap); + + // Set the current view and projection matrices. + void SetViewProjection(XrMatrix4x4f view, XrMatrix4x4f projection) const; + + // Many 1x1 pixel colored textures are used in the PBR system. This is used to create textures backed by a cache to reduce the + // number of textures created. + std::shared_ptr CreateTypedSolidColorTexture(RGBAColor color) const; + + // Bind the the PBR resources to the current context. + void Bind() const; + + // Set and update the model to world constant buffer value. + void SetModelToWorld(XrMatrix4x4f modelToWorld) const; + + GLPrimitive& GetPrimitive(PrimitiveHandle p); + const GLPrimitive& GetPrimitive(PrimitiveHandle p) const; + + // Set or get the shading and fill modes. + void SetFillMode(FillMode mode); + FillMode GetFillMode() const; + void SetFrontFaceWindingOrder(FrontFaceWindingOrder windingOrder); + FrontFaceWindingOrder GetFrontFaceWindingOrder() const; + void SetDepthDirection(DepthDirection depthDirection); + + private: + void SetBlendState(bool enabled) const; + void SetRasterizerState(bool doubleSided) const; + void SetDepthStencilState(bool disableDepthWrite) const; + + friend struct GLMaterial; + + struct Impl; + + std::unique_ptr m_impl; + + SharedState m_sharedState; + }; +} // namespace Pbr diff --git a/src/conformance/framework/pbr/OpenGL/GLTexture.cpp b/src/conformance/framework/pbr/OpenGL/GLTexture.cpp new file mode 100644 index 00000000..c76da494 --- /dev/null +++ b/src/conformance/framework/pbr/OpenGL/GLTexture.cpp @@ -0,0 +1,125 @@ +// Copyright 2022-2023, The Khronos Group, Inc. +// +// Based in part on code that is: +// Copyright (C) Microsoft Corporation. All Rights Reserved +// Licensed under the MIT License. See License.txt in the project root for license information. +// +// SPDX-License-Identifier: MIT AND Apache-2.0 + +#if defined(XR_USE_GRAPHICS_API_OPENGL) || defined(XR_USE_GRAPHICS_API_OPENGL_ES) + +#include "GLTexture.h" + +#include "GLCommon.h" +#include "stb_image.h" + +#include "../PbrCommon.h" + +#include "common/gfxwrapper_opengl.h" +#include "utilities/opengl_utils.h" + +#include +#include +#include +#include +#include + +namespace Pbr +{ + namespace GLTexture + { + std::array LoadRGBAUI4(RGBAColor color) + { + return std::array{(uint8_t)(color.r * 255.), (uint8_t)(color.g * 255.), (uint8_t)(color.b * 255.), + (uint8_t)(color.a * 255.)}; + } + + ScopedGLTexture LoadTextureImage(const uint8_t* fileData, uint32_t fileSize) + { + auto freeImageData = [](unsigned char* ptr) { ::free(ptr); }; + using stbi_unique_ptr = std::unique_ptr; + + constexpr uint32_t DesiredComponentCount = 4; + + int w, h, c; + // If c == 3, a component will be padded with 1.0f + stbi_unique_ptr rgbaData(stbi_load_from_memory(fileData, fileSize, &w, &h, &c, DesiredComponentCount), freeImageData); + if (!rgbaData) { + throw std::runtime_error("Failed to load image file data."); + } + + return CreateTexture(rgbaData.get(), DesiredComponentCount, w, h, GL_RGBA8); + } + + /// Creates a texture and fills all array members with the data in rgba + ScopedGLTexture CreateTextureOrCubemapRepeat(const uint8_t* rgba, uint32_t elemSize, uint32_t width, uint32_t height, GLenum format, + bool isCubemap) + { + assert(elemSize == 4); // non-RGBA isn't implemented + + ScopedGLTexture texture{}; + + const GLenum target = isCubemap ? GL_TEXTURE_CUBE_MAP : GL_TEXTURE_2D; + XRC_CHECK_THROW_GLCMD(glGenTextures(1, texture.resetAndPut())); + XRC_CHECK_THROW_GLCMD(glBindTexture(target, texture.get())); + XRC_CHECK_THROW_GLCMD(glTexParameteri(target, GL_TEXTURE_MAG_FILTER, GL_NEAREST)); + XRC_CHECK_THROW_GLCMD(glTexParameteri(target, GL_TEXTURE_MIN_FILTER, GL_NEAREST)); + XRC_CHECK_THROW_GLCMD(glTexParameteri(target, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE)); + XRC_CHECK_THROW_GLCMD(glTexParameteri(target, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE)); + XRC_CHECK_THROW_GLCMD(glTexParameteri(target, GL_TEXTURE_BASE_LEVEL, 0)); + XRC_CHECK_THROW_GLCMD(glTexParameteri(target, GL_TEXTURE_MAX_LEVEL, 0)); // if we add mipmaps we need to change this + if (isCubemap) { + for (unsigned int i = 0; i < 6; i++) + XRC_CHECK_THROW_GLCMD( + glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X + i, 0, format, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr)); + } + else { + XRC_CHECK_THROW_GLCMD(glTexImage2D(target, 0, format, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr)); + } + + XRC_CHECK_THROW_GLCMD(glBindTexture(target, texture.get())); + if (isCubemap) { + for (unsigned int i = 0; i < 6; i++) + for (GLuint y = 0; y < height; ++y) { + const void* pixels = &rgba[(height - 1 - y) * width * elemSize]; + XRC_CHECK_THROW_GLCMD( + glTexSubImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X + i, 0, 0, y, width, 1, GL_RGBA, GL_UNSIGNED_BYTE, pixels)); + } + } + else { + for (GLuint y = 0; y < height; ++y) { + const void* pixels = &rgba[(height - 1 - y) * width * elemSize]; + XRC_CHECK_THROW_GLCMD(glTexSubImage2D(target, 0, 0, y, width, 1, GL_RGBA, GL_UNSIGNED_BYTE, pixels)); + } + } + XRC_CHECK_THROW_GLCMD(glBindTexture(target, 0)); + + return texture; + } + + ScopedGLTexture CreateFlatCubeTexture(RGBAColor color, GLenum format) + { + const std::array rgbaColor = LoadRGBAUI4(color); + return CreateTextureOrCubemapRepeat(rgbaColor.data(), 4, 1, 1, format, true); + } + + ScopedGLTexture CreateTexture(const uint8_t* rgba, uint32_t elemSize, uint32_t width, uint32_t height, GLenum format) + { + return CreateTextureOrCubemapRepeat(rgba, elemSize, width, height, format, false); + } + + ScopedGLSampler CreateSampler(GLenum edgeSamplingMode) + { + ScopedGLSampler sampler{}; + XRC_CHECK_THROW_GLCMD(glGenSamplers(1, sampler.resetAndPut())); + + XRC_CHECK_THROW_GLCMD(glSamplerParameteri(sampler.get(), GL_TEXTURE_WRAP_S, edgeSamplingMode)); + XRC_CHECK_THROW_GLCMD(glSamplerParameteri(sampler.get(), GL_TEXTURE_WRAP_T, edgeSamplingMode)); + XRC_CHECK_THROW_GLCMD(glSamplerParameteri(sampler.get(), GL_TEXTURE_WRAP_R, edgeSamplingMode)); + + return sampler; + } + } // namespace GLTexture +} // namespace Pbr + +#endif diff --git a/src/conformance/framework/pbr/OpenGL/GLTexture.h b/src/conformance/framework/pbr/OpenGL/GLTexture.h new file mode 100644 index 00000000..3f88789f --- /dev/null +++ b/src/conformance/framework/pbr/OpenGL/GLTexture.h @@ -0,0 +1,36 @@ +// Copyright 2022-2023, The Khronos Group, Inc. +// +// Based in part on code that is: +// Copyright (C) Microsoft Corporation. All Rights Reserved +// Licensed under the MIT License. See License.txt in the project root for license information. +// +// SPDX-License-Identifier: MIT AND Apache-2.0 +// +// Shared data types and functions used throughout the Pbr rendering library. +// + +#pragma once + +#include "GLCommon.h" + +#include "../PbrCommon.h" + +#include "common/gfxwrapper_opengl.h" + +#include + +#include +#include + +namespace Pbr +{ + namespace GLTexture + { + std::array LoadRGBAUI4(RGBAColor color); + + ScopedGLTexture LoadTextureImage(const uint8_t* fileData, uint32_t fileSize); + ScopedGLTexture CreateFlatCubeTexture(RGBAColor color, GLenum format = GL_RGBA8); + ScopedGLTexture CreateTexture(const uint8_t* rgba, uint32_t elemSize, uint32_t width, uint32_t height, GLenum format); + ScopedGLSampler CreateSampler(GLenum edgeSamplingMode = GL_CLAMP_TO_EDGE); + } // namespace GLTexture +} // namespace Pbr diff --git a/src/conformance/framework/pbr/OpenGL/GLTextureCache.cpp b/src/conformance/framework/pbr/OpenGL/GLTextureCache.cpp new file mode 100644 index 00000000..75b2c83a --- /dev/null +++ b/src/conformance/framework/pbr/OpenGL/GLTextureCache.cpp @@ -0,0 +1,60 @@ +// Copyright 2023, The Khronos Group, Inc. +// +// Based in part on code that is: +// +// Copyright (C) Microsoft Corporation. All Rights Reserved +// Licensed under the MIT License. See License.txt in the project root for license information. +// +// SPDX-License-Identifier: MIT AND Apache-2.0 + +#if defined(XR_USE_GRAPHICS_API_OPENGL) || defined(XR_USE_GRAPHICS_API_OPENGL_ES) + +#include "GLTextureCache.h" + +#include "GLCommon.h" +#include "GLTexture.h" + +#include "common/gfxwrapper_opengl.h" + +#include + +#include +#include +#include +#include +#include +#include + +namespace Pbr +{ + void GLTextureCache::Init() + { + m_cacheMutex = std::make_unique(); + } + + std::shared_ptr GLTextureCache::CreateTypedSolidColorTexture(XrColor4f color) + { + if (!IsValid()) { + throw std::logic_error("GLTextureCache accessed before initialization"); + } + const std::array rgba = GLTexture::LoadRGBAUI4(color); + + // Check cache to see if this flat texture already exists. + const uint32_t colorKey = *reinterpret_cast(rgba.data()); + { + std::lock_guard guard(*m_cacheMutex); + auto textureIt = m_solidColorTextureCache.find(colorKey); + if (textureIt != m_solidColorTextureCache.end()) { + return textureIt->second; + } + } + + auto texture = std::make_shared(GLTexture::CreateTexture(rgba.data(), 4, 1, 1, GL_RGBA8)); + + std::lock_guard guard(*m_cacheMutex); + // If the key already exists then the existing texture will be returned. + return m_solidColorTextureCache.emplace(colorKey, texture).first->second; + } +} // namespace Pbr + +#endif diff --git a/src/conformance/framework/pbr/OpenGL/GLTextureCache.h b/src/conformance/framework/pbr/OpenGL/GLTextureCache.h new file mode 100644 index 00000000..8e55b0f7 --- /dev/null +++ b/src/conformance/framework/pbr/OpenGL/GLTextureCache.h @@ -0,0 +1,52 @@ +// Copyright 2023, The Khronos Group, Inc. +// +// Based in part on code that is: +// +// Copyright (C) Microsoft Corporation. All Rights Reserved +// Licensed under the MIT License. See License.txt in the project root for license information. +// +// SPDX-License-Identifier: MIT AND Apache-2.0 + +#include "GLCommon.h" + +#include "common/gfxwrapper_opengl.h" + +#include + +#include +#include +#include +#include + +namespace Pbr +{ + + /// Cache of single-color textures. + /// + /// Device-dependent, drop when device is lost or destroyed. + class GLTextureCache + { + public: + /// Default constructor makes an invalid cache. + GLTextureCache() = default; + + GLTextureCache(GLTextureCache&&) = default; + GLTextureCache& operator=(GLTextureCache&&) = default; + + void Init(); + + bool IsValid() const noexcept + { + return m_cacheMutex != nullptr; + } + + /// Find or create a single pixel texture of the given color + std::shared_ptr CreateTypedSolidColorTexture(XrColor4f color); + + private: + // in unique_ptr to make it moveable + std::unique_ptr m_cacheMutex; + std::map> m_solidColorTextureCache; + }; + +} // namespace Pbr diff --git a/src/conformance/framework/pbr/PbrCommon.cpp b/src/conformance/framework/pbr/PbrCommon.cpp new file mode 100644 index 00000000..96a7f00a --- /dev/null +++ b/src/conformance/framework/pbr/PbrCommon.cpp @@ -0,0 +1,256 @@ +// Copyright 2022-2023, The Khronos Group, Inc. +// +// Based in part on code that is: +// Copyright (C) Microsoft Corporation. All Rights Reserved +// Licensed under the MIT License. See License.txt in the project root for license information. +// +// SPDX-License-Identifier: MIT AND Apache-2.0 + +#include "PbrCommon.h" + +#include "common/xr_linear.h" + +#include + +#include +#include +#include +#include +#include +#include +#include + +namespace Pbr +{ + namespace Internal + { + // for later consolidation + void ThrowIf(bool cond, const char* msg) + { + if (cond) { + std::stringstream ss; + ss << std::hex << "Error in PBR renderer: " << msg; + throw std::runtime_error(ss.str().c_str()); + } + } + } // namespace Internal + + static inline float ChannelFromSRGB(float srgb) + { + if (srgb < 0.04045f) + return srgb / 12.92f; + return std::pow((srgb + .055f) / 1.055f, 2.4f); + } + + RGBAColor FromSRGB(XrColor4f color) + { + RGBAColor linearColor{}; + linearColor.r = ChannelFromSRGB(color.r); + linearColor.g = ChannelFromSRGB(color.g); + linearColor.b = ChannelFromSRGB(color.b); + linearColor.a = color.a; + return linearColor; + } + + // Based on code from DirectXTK + PrimitiveBuilder& PrimitiveBuilder::AddSphere(float diameter, uint32_t tessellation, Pbr::NodeIndex_t transformIndex, + RGBAColor vertexColor) + { + if (tessellation < 3) { + throw std::out_of_range("tessellation parameter out of range"); + } + + const uint32_t verticalSegments = tessellation; + const uint32_t horizontalSegments = tessellation * 2; + + const float radius = diameter / 2; + + const uint32_t startVertexIndex = (uint32_t)Vertices.size(); + + // Create rings of vertices at progressively higher latitudes. + for (uint32_t i = 0; i <= verticalSegments; i++) { + const float v = 1 - (float)i / verticalSegments; + + const float latitude = (i * MATH_PI / verticalSegments) - (MATH_PI * 0.5f); + float dy = std::sin(latitude); + float dxz = std::cos(latitude); + + // Create a single ring of vertices at this latitude. + for (uint32_t j = 0; j <= horizontalSegments; j++) { + const float longitude = j * (MATH_PI * 2.f) / horizontalSegments; + float dx = std::sin(longitude); + float dz = std::cos(longitude); + dx *= dxz; + dz *= dxz; + + // Compute tangent at 90 degrees along longitude. + // todo: is this supposed to be PI/2? + float tdx = std::sin(longitude + MATH_PI); + float tdz = std::cos(longitude + MATH_PI); + tdx *= dxz; + tdz *= dxz; + + const float u = (float)j / horizontalSegments; + + Pbr::Vertex vert; + vert.Normal = {dx, dy, dz}; + XrVector3f_Scale(&vert.Position, &vert.Normal, radius); + vert.Tangent = {tdx, 0, tdz, 0}; + vert.TexCoord0 = {u, v}; + + vert.Color0 = vertexColor; + vert.ModelTransformIndex = transformIndex; + Vertices.push_back(vert); + } + } + + // Fill the index buffer with triangles joining each pair of latitude rings. + const uint32_t stride = horizontalSegments + 1; + for (uint32_t i = 0; i < verticalSegments; i++) { + for (uint32_t j = 0; j <= horizontalSegments; j++) { + uint32_t nextI = i + 1; + uint32_t nextJ = (j + 1) % stride; + + Indices.push_back(startVertexIndex + (i * stride + j)); + Indices.push_back(startVertexIndex + (nextI * stride + j)); + Indices.push_back(startVertexIndex + (i * stride + nextJ)); + + Indices.push_back(startVertexIndex + (i * stride + nextJ)); + Indices.push_back(startVertexIndex + (nextI * stride + j)); + Indices.push_back(startVertexIndex + (nextI * stride + nextJ)); + } + } + + return *this; + } + + // Based on code from DirectXTK + PrimitiveBuilder& PrimitiveBuilder::AddCube(XrVector3f sideLengths, XrVector3f translation, Pbr::NodeIndex_t transformIndex, + RGBAColor vertexColor) + { + // A box has six faces, each one pointing in a different direction. + const int FaceCount = 6; + + static const XrVector3f faceNormals[FaceCount] = { + {0, 0, 1}, {0, 0, -1}, {1, 0, 0}, {-1, 0, 0}, {0, 1, 0}, {0, -1, 0}, + }; + + static const XrVector2f textureCoordinates[4] = { + {1, 0}, + {1, 1}, + {0, 1}, + {0, 0}, + }; + + // Create each face in turn. + const XrVector3f sideLengthHalfVector = {sideLengths.x / 2, sideLengths.y / 2, sideLengths.z / 2}; + + for (int i = 0; i < FaceCount; i++) { + XrVector3f normal = faceNormals[i]; + + // Get two vectors perpendicular both to the face normal and to each other. + XrVector3f basis = (i >= 4) ? XrVector3f{0, 0, 1} : XrVector3f{0, 1, 0}; + + XrVector3f side1; + XrVector3f_Cross(&side1, &normal, &basis); + XrVector3f side2; + XrVector3f_Cross(&side2, &normal, &side1); + + // Six indices (two triangles) per face. + size_t vbase = Vertices.size(); + Indices.push_back((uint32_t)vbase + 0); + Indices.push_back((uint32_t)vbase + 1); + Indices.push_back((uint32_t)vbase + 2); + + Indices.push_back((uint32_t)vbase + 0); + Indices.push_back((uint32_t)vbase + 2); + Indices.push_back((uint32_t)vbase + 3); + + XrVector3f positions[4]; + for (int j = 0; j < 4; j++) { + // const XrVector3f positions[4] = {{(normal - side1 - side2) * sideLengthHalfVector}, + // {(normal - side1 + side2) * sideLengthHalfVector}, + // {(normal + side1 + side2) * sideLengthHalfVector}, + // {(normal + side1 - side2) * sideLengthHalfVector}}; + XrVector3f offset; + if ((j % 2) == 0) { + XrVector3f_Add(&offset, &side1, &side2); + } + else { + XrVector3f_Sub(&offset, &side1, &side2); + } + XrVector3f offsetNormal; + if (j >= 2) { + XrVector3f_Sub(&offsetNormal, &normal, &offset); + } + else { + XrVector3f_Add(&offsetNormal, &normal, &offset); + } + positions[j].x = offsetNormal.x * sideLengthHalfVector.x; + positions[j].y = offsetNormal.y * sideLengthHalfVector.y; + positions[j].z = offsetNormal.z * sideLengthHalfVector.z; + } + + for (int j = 0; j < 4; j++) { + Pbr::Vertex vert; + XrVector3f_Add(&vert.Position, &positions[j], &translation); + vert.Normal = normal; + // 1. might be wrong, just getting it building + vert.Tangent = {side1.x, side1.y, side1.z, 1.}; // TODO arbitrarily picked side 1 + vert.TexCoord0 = textureCoordinates[j]; + vert.Color0 = vertexColor; + vert.ModelTransformIndex = transformIndex; + Vertices.push_back(vert); + } + } + + return *this; + } + + PrimitiveBuilder& PrimitiveBuilder::AddCube(XrVector3f sideLengths, Pbr::NodeIndex_t transformIndex, RGBAColor vertexColor) + { + return AddCube(sideLengths, {0, 0, 0}, transformIndex, vertexColor); + } + + PrimitiveBuilder& PrimitiveBuilder::AddCube(float sideLength, Pbr::NodeIndex_t transformIndex, RGBAColor vertexColor) + { + return AddCube(XrVector3f{sideLength, sideLength, sideLength}, transformIndex, vertexColor); + } + + PrimitiveBuilder& PrimitiveBuilder::AddQuad(XrVector2f sideLengths, XrVector2f textureCoord, Pbr::NodeIndex_t transformIndex, + RGBAColor vertexColor) + { + const XrVector2f halfSideLength = {sideLengths.x / 2, sideLengths.y / 2}; + const std::array vertices = {{{-halfSideLength.x, -halfSideLength.y, 0}, // LB + {-halfSideLength.x, halfSideLength.y, 0}, // LT + {halfSideLength.x, halfSideLength.y, 0}, // RT + {halfSideLength.x, -halfSideLength.y, 0}}}; // RB + const XrVector2f uvs[4] = { + {0, textureCoord.y}, + {0, 0}, + {textureCoord.x, 0}, + {textureCoord.x, textureCoord.y}, + }; + + // Two triangles. + auto vbase = static_cast(Vertices.size()); + Indices.push_back(vbase + 0); + Indices.push_back(vbase + 1); + Indices.push_back(vbase + 2); + Indices.push_back(vbase + 0); + Indices.push_back(vbase + 2); + Indices.push_back(vbase + 3); + + Pbr::Vertex vert; + vert.Normal = {0, 0, 1}; + vert.Tangent = {1, 0, 0, 0}; + vert.Color0 = vertexColor; + vert.ModelTransformIndex = transformIndex; + for (size_t j = 0; j < vertices.size(); j++) { + vert.Position = vertices[j]; + vert.TexCoord0 = uvs[j]; + Vertices.push_back(vert); + } + return *this; + } +} // namespace Pbr diff --git a/src/conformance/framework/pbr/PbrCommon.h b/src/conformance/framework/pbr/PbrCommon.h new file mode 100644 index 00000000..d0b416a3 --- /dev/null +++ b/src/conformance/framework/pbr/PbrCommon.h @@ -0,0 +1,107 @@ +// Copyright 2022-2023, The Khronos Group, Inc. +// +// Based in part on code that is: +// Copyright (C) Microsoft Corporation. All Rights Reserved +// Licensed under the MIT License. See License.txt in the project root for license information. +// +// SPDX-License-Identifier: MIT AND Apache-2.0 + +// +// Shared data types and functions used throughout the Pbr rendering library. +// + +#pragma once + +#include + +#include +#include +#include +#include +#include + +#ifdef _WIN32 + +#ifndef WIN32_LEAN_AND_MEAN +#define WIN32_LEAN_AND_MEAN +#endif + +#ifndef NOMINMAX +#define NOMINMAX +#endif + +#include + +#endif + +namespace tinygltf +{ + struct Image; +} +namespace Pbr +{ + namespace Internal + { +#ifdef _WIN32 + void ThrowIfFailed(HRESULT hr); +#endif + void ThrowIf(bool cond, const char* msg); + } // namespace Internal + + using NodeIndex_t = uint16_t; // This type must align with the type used in the Pbr shaders. + + // Indicates an invalid node index (similar to std::variant_npos) + static constexpr Pbr::NodeIndex_t NodeIndex_npos = static_cast(-1); + static constexpr Pbr::NodeIndex_t RootNodeIndex = 0; + + // These colors are in linear color space unless otherwise specified. + using RGBAColor = XrColor4f; + using RGBColor = XrVector3f; + + using ImageKey = std::tuple; // Item1 is a pointer to the image, Item2 is sRGB. + + // // DirectX::Colors are in sRGB color space. + // RGBAColor FromSRGB(DirectX::XMVECTOR color); + + namespace RGBA + { + constexpr RGBAColor White{1, 1, 1, 1}; + constexpr RGBAColor Black{0, 0, 0, 1}; + constexpr RGBAColor FlatNormal{0.5f, 0.5f, 1, 1}; + constexpr RGBAColor Transparent{0, 0, 0, 0}; + } // namespace RGBA + + namespace RGB + { + constexpr RGBColor White{1, 1, 1}; + constexpr RGBColor Black{0, 0, 0}; + } // namespace RGB + + // Vertex structure used by the PBR shaders. + struct Vertex + { + XrVector3f Position; + XrVector3f Normal; + XrVector4f Tangent; + XrColor4f Color0; + XrVector2f TexCoord0; + NodeIndex_t ModelTransformIndex; // Index into the node transforms + }; + + struct PrimitiveBuilder + { + std::vector Vertices; + std::vector Indices; + + PrimitiveBuilder& AddSphere(float diameter, uint32_t tessellation, Pbr::NodeIndex_t transformIndex = Pbr::RootNodeIndex, + RGBAColor vertexColor = RGBA::White); + PrimitiveBuilder& AddCube(float sideLength, Pbr::NodeIndex_t transformIndex = Pbr::RootNodeIndex, + RGBAColor vertexColor = RGBA::White); + PrimitiveBuilder& AddCube(XrVector3f sideLengths, Pbr::NodeIndex_t transformIndex = Pbr::RootNodeIndex, + RGBAColor vertexColor = RGBA::White); + PrimitiveBuilder& AddCube(XrVector3f sideLengths, XrVector3f translation, Pbr::NodeIndex_t transformIndex = Pbr::RootNodeIndex, + RGBAColor vertexColor = RGBA::White); + PrimitiveBuilder& AddQuad(XrVector2f sideLengths, XrVector2f textureCoord = {1, 1}, + Pbr::NodeIndex_t transformIndex = Pbr::RootNodeIndex, RGBAColor vertexColor = RGBA::White); + }; +} // namespace Pbr diff --git a/src/conformance/framework/pbr/PbrHandles.h b/src/conformance/framework/pbr/PbrHandles.h new file mode 100644 index 00000000..91ce9548 --- /dev/null +++ b/src/conformance/framework/pbr/PbrHandles.h @@ -0,0 +1,36 @@ +// Copyright 2019-2023, The Khronos Group, Inc. +// +// SPDX-License-Identifier: Apache-2.0 + +#include "framework/graphics_plugin_impl_helpers.h" + +#include "../graphics_plugin_impl_helpers.h" + +#include + +#include + +namespace Pbr +{ + + namespace detail + { + // Policy to default-init to max so we can tell that a "null" handle is bad. + // Otherwise, a default-init would be 0 which is often a perfectly fine index. + // using custom_default_max_uint16 = nonstd::custom_default_t::max()>; + + // using custom_default_max_uint32 = nonstd::custom_default_t::max()>; + using custom_default_max_uint64 = nonstd::custom_default_t::max()>; + } // namespace detail + + using MaterialHandle = nonstd::equality; + + template + using MaterialCollection = Conformance::VectorWithGenerationCountedHandles; + + using PrimitiveHandle = nonstd::equality; + // static_assert(sizeof(PrimitiveHandle) == sizeof(uint64_t), "The handle should be 64 bit"); + template + using PrimitiveCollection = Conformance::VectorWithGenerationCountedHandles; + +} // namespace Pbr diff --git a/src/conformance/framework/pbr/PbrMaterial.cpp b/src/conformance/framework/pbr/PbrMaterial.cpp new file mode 100644 index 00000000..3aae2974 --- /dev/null +++ b/src/conformance/framework/pbr/PbrMaterial.cpp @@ -0,0 +1,67 @@ +// Copyright 2022-2023, The Khronos Group, Inc. +// +// Based in part on code that is: +// Copyright (C) Microsoft Corporation. All Rights Reserved +// Licensed under the MIT License. See License.txt in the project root for license information. +// +// SPDX-License-Identifier: MIT AND Apache-2.0 + +#include "PbrMaterial.h" +#include + +namespace Pbr +{ + static_assert(std::is_standard_layout::value, "Type must be standard layout"); + static_assert(sizeof(RGBAColor) == 16, "RGBAColor must be 16 bytes"); + static_assert(offsetof(Material::ConstantBufferData, MetallicFactor) == 16, "Offsets must match struct in shader"); + static_assert(offsetof(Material::ConstantBufferData, RoughnessFactor) == 20, "Offsets must match struct in shader"); + static_assert(offsetof(Material::ConstantBufferData, EmissiveFactor) == 32, "Offsets must match struct in shader"); + static_assert(offsetof(Material::ConstantBufferData, NormalScale) == 48, "Offsets must match struct in shader"); + static_assert(offsetof(Material::ConstantBufferData, OcclusionStrength) == 52, "Offsets must match struct in shader"); + static_assert(offsetof(Material::ConstantBufferData, AlphaCutoff) == 56, "Offsets must match struct in shader"); + + Material::Material() + { + } + + void Material::CopyFrom(Material const& from) + { + Name = from.Name; + Hidden = from.Hidden; + m_parameters = from.m_parameters; + m_parametersChanged = true; + // m_alphaBlended = from.m_alphaBlended; + // m_doubleSided = from.m_doubleSided; + } + + void Material::SetDoubleSided(DoubleSided doubleSided) + { + m_doubleSided = doubleSided; + } + + void Material::SetAlphaBlended(BlendState alphaBlended) + { + m_alphaBlended = alphaBlended; + } + + DoubleSided Material::GetDoubleSided() + { + return m_doubleSided; + } + + BlendState Material::GetAlphaBlended() + { + return m_alphaBlended; + } + + Material::ConstantBufferData& Material::Parameters() + { + m_parametersChanged = true; + return m_parameters; + } + + const Material::ConstantBufferData& Material::Parameters() const + { + return m_parameters; + } +} // namespace Pbr diff --git a/src/conformance/framework/pbr/PbrMaterial.h b/src/conformance/framework/pbr/PbrMaterial.h new file mode 100644 index 00000000..e5796b8b --- /dev/null +++ b/src/conformance/framework/pbr/PbrMaterial.h @@ -0,0 +1,108 @@ +// Copyright 2022-2023, The Khronos Group, Inc. +// +// Based in part on code that is: +// Copyright (C) Microsoft Corporation. All Rights Reserved +// Licensed under the MIT License. See License.txt in the project root for license information. +// +// SPDX-License-Identifier: MIT AND Apache-2.0 + +#pragma once + +#include "IResources.h" +#include "PbrCommon.h" +#include "PbrSharedState.h" + +#include + +#include +#include +#include +#include +#include + +namespace tinygltf +{ + struct Image; + struct Sampler; +} // namespace tinygltf + +namespace Pbr +{ + /// A Material contains the metallic roughness parameters and textures. + /// Primitives specify which Material to use when being rendered. + struct Material + { +#ifdef _MSC_VER +#pragma warning(push) +#pragma warning(disable : 4324) +#endif + /// Coefficients used by the shader. Each texture is sampled and multiplied by these coefficients. + struct ConstantBufferData + { + // packoffset(c0) + RGBAColor BaseColorFactor{1, 1, 1, 1}; + + // packoffset(c1.x and c1.y) + float MetallicFactor{1}; + float RoughnessFactor{1}; + float _pad0[2]; + + // packoffset(c2) + RGBColor EmissiveFactor{1, 1, 1}; + // padding here must be explicit + float _pad1; + + // packoffset(c3.x, c3.y and c3.z) + float NormalScale{1}; + float OcclusionStrength{1}; + float AlphaCutoff{0}; + // needed to round out the size + float _pad2; + }; +#ifdef _MSC_VER +#pragma warning(pop) +#endif + + static_assert((sizeof(ConstantBufferData) % 16) == 0, "Constant Buffer must be divisible by 16 bytes"); + static_assert(sizeof(ConstantBufferData) == 64, "Size must be the same as known"); + static_assert(offsetof(ConstantBufferData, BaseColorFactor) == 0, "Offsets must match shader"); + static_assert(offsetof(ConstantBufferData, MetallicFactor) == 16, "Offsets must match shader"); + static_assert(offsetof(ConstantBufferData, RoughnessFactor) == 20, "Offsets must match shader"); + static_assert(offsetof(ConstantBufferData, EmissiveFactor) == 32, "Offsets must match shader"); + static_assert(offsetof(ConstantBufferData, NormalScale) == 48, "Offsets must match shader"); + static_assert(offsetof(ConstantBufferData, OcclusionStrength) == 52, "Offsets must match shader"); + static_assert(offsetof(ConstantBufferData, AlphaCutoff) == 56, "Offsets must match shader"); + + // Create a uninitialized material. Textures and shader coefficients must be set. + Material(); + + // Need at least one virtual function to trigger a vtable and things like dynamic_pointer_cast + virtual ~Material() = default; + + void SetDoubleSided(DoubleSided doubleSided); + void SetAlphaBlended(BlendState alphaBlended); + + DoubleSided GetDoubleSided(); + FillMode GetWireframe(); + BlendState GetAlphaBlended(); + + ConstantBufferData& Parameters(); + const ConstantBufferData& Parameters() const; + + std::string Name; + bool Hidden{false}; + // virtual void SetTexture(ShaderSlots::PSMaterial slot, ITexture& texture) = 0; + + protected: + // copy settings but not state, used in sub-material's Clone + void CopyFrom(Material const& from); + + mutable bool m_parametersChanged{true}; + std::aligned_storage_t m_parametersStorage; + + ConstantBufferData m_parameters; + + BlendState m_alphaBlended{BlendState::NotAlphaBlended}; + DoubleSided m_doubleSided{DoubleSided::NotDoubleSided}; + }; +} // namespace Pbr diff --git a/src/conformance/framework/pbr/PbrModel.cpp b/src/conformance/framework/pbr/PbrModel.cpp new file mode 100644 index 00000000..26487209 --- /dev/null +++ b/src/conformance/framework/pbr/PbrModel.cpp @@ -0,0 +1,105 @@ +// Copyright 2022-2023, The Khronos Group, Inc. +// +// Based in part on code that is: +// Copyright (C) Microsoft Corporation. All Rights Reserved +// Licensed under the MIT License. See License.txt in the project root for license information. +// +// SPDX-License-Identifier: MIT AND Apache-2.0 +#include "PbrModel.h" + +#include "PbrCommon.h" + +#include "common/xr_linear.h" + +#include +#include +#include + +namespace Pbr +{ + Model::Model(bool createRootNode /*= true*/) + { + if (createRootNode) { + XrMatrix4x4f identityMatrix; + XrMatrix4x4f_CreateIdentity(&identityMatrix); + AddNode(identityMatrix, RootParentNodeIndex, "root"); + } + } + + NodeIndex_t Model::AddNode(const XrMatrix4x4f& transform, Pbr::NodeIndex_t parentIndex, std::string name) + { + auto newNodeIndex = (Pbr::NodeIndex_t)m_nodes.size(); + if (newNodeIndex != RootNodeIndex && parentIndex == RootParentNodeIndex) { + throw std::runtime_error("Only the first node can be the root"); + } + + m_nodes.emplace_back(transform, std::move(name), newNodeIndex, parentIndex); + // m_modelTransformsStructuredBuffer = nullptr; // Structured buffer will need to be recreated. + InvalidateBuffer(); // Structured buffer will need to be recreated. + return m_nodes.back().Index; + } + + void Model::Clear() + { + m_primitives.clear(); + } + + bool Model::FindFirstNode(NodeIndex_t* outNodeIndex, const char* name, const NodeIndex_t* parentNodeIndex) const + { + // Children are guaranteed to come after their parents, so start looking after the parent index if one is provided. + const NodeIndex_t startIndex = parentNodeIndex ? *parentNodeIndex + 1 : Pbr::RootNodeIndex; + for (NodeIndex_t i = startIndex; i < m_nodes.size(); ++i) { + const Pbr::Node& node = m_nodes[i]; + if ((!parentNodeIndex || node.ParentNodeIndex == *parentNodeIndex) && (node.Name.compare(name) == 0)) { + *outNodeIndex = node.Index; + return true; + } + } + return false; + } + + XrMatrix4x4f Model::GetNodeToModelRootTransform(NodeIndex_t nodeIndex) const + { + const Pbr::Node& node = GetNode(nodeIndex); + + // Compute the transform recursively. + XrMatrix4x4f identityMatrix; + XrMatrix4x4f_CreateIdentity(&identityMatrix); + const XrMatrix4x4f parentTransform = + node.ParentNodeIndex == Pbr::RootNodeIndex ? identityMatrix : GetNodeToModelRootTransform(node.ParentNodeIndex); + XrMatrix4x4f nodeTransform = node.GetTransform(); + XrMatrix4x4f result; + XrMatrix4x4f_Multiply(&result, &nodeTransform, &parentTransform); + return result; + } + + void Model::AddPrimitive(PrimitiveHandle primitive) + { + m_primitives.push_back(primitive); + } + + Node::Node(Node&& other) noexcept + { + using std::swap; + swap(Name, other.Name); + swap(Index, other.Index); + swap(ParentNodeIndex, other.ParentNodeIndex); + m_modifyCount.store(other.m_modifyCount); + swap(m_localTransform, other.m_localTransform); + } + + Node& Node::operator=(Node&& other) noexcept + { + if (&other == this) { + return *this; + } + using std::swap; + swap(Name, other.Name); + swap(Index, other.Index); + swap(ParentNodeIndex, other.ParentNodeIndex); + m_modifyCount.store(other.m_modifyCount); + swap(m_localTransform, other.m_localTransform); + return *this; + } + +} // namespace Pbr diff --git a/src/conformance/framework/pbr/PbrModel.h b/src/conformance/framework/pbr/PbrModel.h new file mode 100644 index 00000000..747dbfa2 --- /dev/null +++ b/src/conformance/framework/pbr/PbrModel.h @@ -0,0 +1,145 @@ +// Copyright 2022-2023, The Khronos Group, Inc. +// +// Based in part on code that is: +// Copyright (C) Microsoft Corporation. All Rights Reserved +// Licensed under the MIT License. See License.txt in the project root for license information. +// +// SPDX-License-Identifier: MIT AND Apache-2.0 +#pragma once + +#include "PbrCommon.h" +#include "PbrHandles.h" + +#include "common/xr_linear.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +namespace Pbr +{ + // Node for creating a hierarchy of transforms. These transforms are referenced by vertices in the model's primitives. + struct Node + { + using Collection = std::vector; + + Node(const XrMatrix4x4f& localTransform, std::string name, NodeIndex_t index, NodeIndex_t parentNodeIndex) + : Name(std::move(name)), Index(index), ParentNodeIndex(parentNodeIndex) + { + SetTransform(localTransform); + } + + Node(Node&& other) noexcept; + Node& operator=(Node&& other) noexcept; + + // Set the local transform for this node. + void SetTransform(const XrMatrix4x4f& transform) + { + m_localTransform = transform; + ++m_modifyCount; + } + + // Get the local transform for this node. + XrMatrix4x4f GetTransform() const + { + return m_localTransform; + } + uint32_t GetModifyCount() const + { + return m_modifyCount; + } + + std::string Name; + NodeIndex_t Index; + NodeIndex_t ParentNodeIndex; + + private: + friend class Model; + // TODO std::atomic_uint32_t + std::atomic_uint32_t m_modifyCount{0}; + XrMatrix4x4f m_localTransform; + }; + + /// A model is a collection of primitives (which reference a material) and transforms referenced by the primitives' vertices. + class Model + { + public: + std::string Name; + + Model(bool createRootNode = true); + + // Add a node to the model. + NodeIndex_t AddNode(const XrMatrix4x4f& transform, NodeIndex_t parentIndex, std::string name = ""); + + // Add a primitive to the model. + void AddPrimitive(PrimitiveHandle primitive); + + // Remove all primitives. + void Clear(); + + NodeIndex_t GetNodeCount() const + { + return (NodeIndex_t)m_nodes.size(); + } + Node& GetNode(NodeIndex_t nodeIndex) + { + return m_nodes[nodeIndex]; + } + const Node& GetNode(NodeIndex_t nodeIndex) const + { + return m_nodes[nodeIndex]; + } + + uint32_t GetPrimitiveCount() const + { + return (uint32_t)m_primitives.size(); + } + PrimitiveHandle GetPrimitive(uint32_t index) const + { + return m_primitives[index]; + } + + // Find the first node which matches a given name. + bool FindFirstNode(NodeIndex_t* outNodeIndex, const char* name, const NodeIndex_t* parentNodeIndex = nullptr) const; + + protected: + // Invalidate buffers associated with model transforms + bool m_modelTransformsStructuredBufferInvalid{true}; + void InvalidateBuffer() + { + m_modelTransformsStructuredBufferInvalid = true; + } + + const std::vector& GetPrimitives() const + { + return m_primitives; + } + + const Node::Collection& GetNodes() const + { + return m_nodes; + } + static constexpr Pbr::NodeIndex_t RootParentNodeIndex = (Pbr::NodeIndex_t)-1; + + private: + // Compute the transform relative to the root of the model for a given node. + XrMatrix4x4f GetNodeToModelRootTransform(NodeIndex_t nodeIndex) const; + + // Updated the transforms used to render the model. This needs to be called any time a node transform is changed. + // void UpdateTransforms(Pbr::D3D11Resources const& pbrResources, std::runtime_error ID3D11DeviceContext* context) const; + + private: + // A model is made up of one or more Primitives. Each Primitive has a unique material. + // Ideally primitives with the same material should be merged to reduce draw calls. + std::vector m_primitives; + + // A model contains one or more nodes. Each vertex of a primitive references a node to have the + // node's transform applied. + Node::Collection m_nodes; + }; +} // namespace Pbr diff --git a/src/conformance/framework/pbr/PbrSharedState.cpp b/src/conformance/framework/pbr/PbrSharedState.cpp new file mode 100644 index 00000000..f1ed0cc1 --- /dev/null +++ b/src/conformance/framework/pbr/PbrSharedState.cpp @@ -0,0 +1,43 @@ +// Copyright 2023, The Khronos Group, Inc. +// +// Based in part on code that is: +// +// Copyright (C) Microsoft Corporation. All Rights Reserved +// Licensed under the MIT License. See License.txt in the project root for license information. +// +// SPDX-License-Identifier: MIT AND Apache-2.0 + +#include "PbrSharedState.h" + +namespace Pbr +{ + void SharedState::SetFillMode(FillMode mode) + { + m_fill = mode; + } + + FillMode SharedState::GetFillMode() const + { + return m_fill; + } + + void SharedState::SetFrontFaceWindingOrder(FrontFaceWindingOrder windingOrder) + { + m_windingOrder = windingOrder; + } + + FrontFaceWindingOrder SharedState::GetFrontFaceWindingOrder() const + { + return m_windingOrder; + } + + void SharedState::SetDepthDirection(DepthDirection depthDirection) + { + m_depthDirection = depthDirection; + } + + DepthDirection SharedState::GetDepthDirection() const + { + return m_depthDirection; + } +} // namespace Pbr diff --git a/src/conformance/framework/pbr/PbrSharedState.h b/src/conformance/framework/pbr/PbrSharedState.h new file mode 100644 index 00000000..c9908101 --- /dev/null +++ b/src/conformance/framework/pbr/PbrSharedState.h @@ -0,0 +1,130 @@ +// Copyright 2023, The Khronos Group, Inc. +// +// Based in part on code that is: +// +// Copyright (C) Microsoft Corporation. All Rights Reserved +// Licensed under the MIT License. See License.txt in the project root for license information. +// +// SPDX-License-Identifier: MIT AND Apache-2.0 + +#pragma once + +#include + +#include + +namespace Pbr +{ + + namespace ShaderSlots + { + /// shader resource index + enum VSResourceViews + { + Transforms = 0, + }; + + /// Sampler/texture index + enum PSMaterial + { // For both samplers and textures. + BaseColor = 0, + MetallicRoughness, + Normal, + Occlusion, + Emissive, + LastMaterialSlot = Emissive, + NumMaterialSlots = LastMaterialSlot + 1 + }; + + /// Sampler/texture index + enum Pbr + { // For both samplers and textures. + Brdf = NumMaterialSlots + }; + + /// Sampler/texture index + enum EnvironmentMap + { // For both samplers and textures. + SpecularTexture = Brdf + 1, + DiffuseTexture = SpecularTexture + 1, + EnvironmentMapSampler = Brdf + 1 + }; + + /// constant buffer index + enum ConstantBuffers + { + Scene, // Used by VS and PS + Model, // VS only + Material, // PS only + }; + + enum NumSlots + { + NumVSResourceViews = 1, + NumTextures = DiffuseTexture + 1, + NumSRVs = NumVSResourceViews + NumTextures, + NumSamplers = EnvironmentMapSampler + 1, + NumConstantBuffers = Material + 1, + }; + + namespace GLSL + { + enum BindingOffsets + { + VSResourceViewsOffset = NumConstantBuffers, + MaterialTexturesOffset = VSResourceViewsOffset + NumVSResourceViews, + GlobalTexturesOffset = MaterialTexturesOffset + NumMaterialSlots, + }; + } + } // namespace ShaderSlots + + enum class FillMode : uint32_t + { + Solid, + Wireframe, + }; + + enum class BlendState : uint32_t + { + NotAlphaBlended, + AlphaBlended, + }; + + enum class DoubleSided : uint32_t + { + DoubleSided, + NotDoubleSided, + }; + + enum class FrontFaceWindingOrder : uint32_t + { + ClockWise, + CounterClockWise, + }; + + enum class DepthDirection : uint32_t + { + Forward, + Reversed, + }; + + /// API-independent state + class SharedState + { + public: + void SetFillMode(FillMode mode); + FillMode GetFillMode() const; + + void SetFrontFaceWindingOrder(FrontFaceWindingOrder windingOrder); + FrontFaceWindingOrder GetFrontFaceWindingOrder() const; + + void SetDepthDirection(DepthDirection depthDirection); + DepthDirection GetDepthDirection() const; + + private: + FillMode m_fill = FillMode::Solid; + FrontFaceWindingOrder m_windingOrder = FrontFaceWindingOrder::ClockWise; + DepthDirection m_depthDirection = DepthDirection::Forward; + }; + +} // namespace Pbr diff --git a/src/conformance/framework/pbr/Shaders/PbrPixelShader.hlsl b/src/conformance/framework/pbr/Shaders/PbrPixelShader.hlsl new file mode 100644 index 00000000..4c60f419 --- /dev/null +++ b/src/conformance/framework/pbr/Shaders/PbrPixelShader.hlsl @@ -0,0 +1,161 @@ +//////////////////////////////////////////////////////////////////////////////// +// Copyright (C) Microsoft Corporation. All Rights Reserved +// Licensed under the MIT License. See License.txt in the project root for license information. +// +// This shader is based on the fragment shader at https://github.com/KhronosGroup/glTF-WebGL-PBR +// with modifications for HLSL and stereoscopic rendering. +// +// The MIT License +// +// Copyright(c) 2016 - 2017 Mohamad Moneimne and Contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files(the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and / or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions : +// +// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// +// SPDX-License-Identifier: MIT +// + +#include "PbrShared.hlsl" + +cbuffer MaterialConstantBuffer : register(b2) +{ + float4 BaseColorFactor : packoffset(c0); + float MetallicFactor : packoffset(c1.x); + float RoughnessFactor : packoffset(c1.y); + float3 EmissiveFactor : packoffset(c2); + float NormalScale : packoffset(c3.x); + float OcclusionStrength : packoffset(c3.y); + float AlphaCutoff : packoffset(c3.z); +}; + +// The texture registers must match the order of the MaterialTextures enum. +Texture2D BaseColorTexture : register(t0); +Texture2D MetallicRoughnessTexture : register(t1); // Green(y)=Roughness, Blue(z)=Metallic +Texture2D NormalTexture : register(t2); +Texture2D OcclusionTexture : register(t3); // Red(x) channel +Texture2D EmissiveTexture : register(t4); +Texture2D BRDFTexture : register(t5); +TextureCube SpecularTexture : register(t6); +TextureCube DiffuseTexture : register(t7); + +SamplerState BaseColorSampler : register(s0); +SamplerState MetallicRoughnessSampler : register(s1); +SamplerState NormalSampler : register(s2); +SamplerState OcclusionSampler : register(s3); +SamplerState EmissiveSampler : register(s4); +SamplerState BRDFSampler : register(s5); +SamplerState IBLSampler : register(s6); + +static const float3 f0 = float3(0.04, 0.04, 0.04); +static const float MinRoughness = 0.04; +static const float PI = 3.141592653589793; + +float3 getIBLContribution(float perceptualRoughness, float NdotV, float3 diffuseColor, float3 specularColor, float3 n, float3 reflection) +{ + const float lod = perceptualRoughness * NumSpecularMipLevels; + + const float3 brdf = BRDFTexture.Sample(BRDFSampler, float2(NdotV, 1.0 - perceptualRoughness)).rgb; + + const float3 diffuseLight = DiffuseTexture.Sample(IBLSampler, n).rgb; + const float3 specularLight = SpecularTexture.SampleLevel(IBLSampler, reflection, lod).rgb; + + const float3 diffuse = diffuseLight * diffuseColor; + const float3 specular = specularLight * (specularColor * brdf.x + brdf.y); + + return diffuse + specular; +} + +float3 diffuse(float3 diffuseColor) +{ + return diffuseColor / PI; +} + +float3 specularReflection(float3 reflectance0, float3 reflectance90, float VdotH) +{ + return reflectance0 + (reflectance90 - reflectance0) * pow(clamp(1.0 - VdotH, 0.0, 1.0), 5.0); +} + +float geometricOcclusion(float NdotL, float NdotV, float alphaRoughness) +{ + const float attenuationL = 2.0 * NdotL / (NdotL + sqrt(alphaRoughness * alphaRoughness + (1.0 - alphaRoughness * alphaRoughness) * (NdotL * NdotL))); + const float attenuationV = 2.0 * NdotV / (NdotV + sqrt(alphaRoughness * alphaRoughness + (1.0 - alphaRoughness * alphaRoughness) * (NdotV * NdotV))); + return attenuationL * attenuationV; +} + +float microfacetDistribution(float NdotH, float alphaRoughness) +{ + const float roughnessSq = alphaRoughness * alphaRoughness; + const float f = (NdotH * roughnessSq - NdotH) * NdotH + 1.0; + return roughnessSq / (PI * f * f); +} + +float4 main(PSInputPbr input, bool isFrontFace : SV_IsFrontFace) : SV_TARGET +{ + // Roughness is stored in the 'g' channel, metallic is stored in the 'b' channel. + // This layout intentionally reserves the 'r' channel for (optional) occlusion map data + const float3 mrSample = MetallicRoughnessTexture.Sample(MetallicRoughnessSampler, input.TexCoord0); + const float4 baseColor = BaseColorTexture.Sample(BaseColorSampler, input.TexCoord0) * input.Color0 * BaseColorFactor; + + // Discard if below alpha cutoff. + clip(baseColor.a - AlphaCutoff); + + const float metallic = saturate(mrSample.b * MetallicFactor); + const float perceptualRoughness = clamp(mrSample.g * RoughnessFactor, MinRoughness, 1.0); + + // Roughness is authored as perceptual roughness; as is convention, + // convert to material roughness by squaring the perceptual roughness [2]. + const float alphaRoughness = perceptualRoughness * perceptualRoughness; + + const float3 diffuseColor = (baseColor.rgb * (float3(1.0, 1.0, 1.0) - f0)) * (1.0 - metallic); + const float3 specularColor = lerp(f0, baseColor.rgb, metallic); + + // Compute reflectance. + const float reflectance = max(max(specularColor.r, specularColor.g), specularColor.b); + + // For typical incident reflectance range (between 4% to 100%) set the grazing reflectance to 100% for typical fresnel effect. + // For very low reflectance range on highly diffuse objects (below 4%), incrementally reduce grazing reflecance to 0%. + const float reflectance90 = saturate(reflectance * 25.0); + const float3 specularEnvironmentR0 = specularColor.rgb; + const float3 specularEnvironmentR90 = float3(1.0, 1.0, 1.0) * reflectance90; + + // normal at surface point + float3 n = 2.0 * NormalTexture.Sample(NormalSampler, input.TexCoord0) - 1.0; + n = isFrontFace ? n : -n; + n = normalize(mul(n * float3(NormalScale, NormalScale, 1.0), input.TBN)); + + const float3 v = normalize(EyePosition - input.PositionWorld); // Vector from surface point to camera + const float3 l = normalize(LightDirection); // Vector from surface point to light + const float3 h = normalize(l + v); // Half vector between both l and v + const float3 reflection = -normalize(reflect(v, n)); + + const float NdotL = clamp(dot(n, l), 0.001, 1.0); + const float NdotV = abs(dot(n, v)) + 0.001; + const float NdotH = saturate(dot(n, h)); + const float LdotH = saturate(dot(l, h)); + const float VdotH = saturate(dot(v, h)); + + // Calculate the shading terms for the microfacet specular shading model + const float3 F = specularReflection(specularEnvironmentR0, specularEnvironmentR90, VdotH); + const float G = geometricOcclusion(NdotL, NdotV, alphaRoughness); + const float D = microfacetDistribution(NdotH, alphaRoughness); + + // Calculation of analytical lighting contribution + const float3 diffuseContrib = (1.0 - F) * diffuse(diffuseColor); + const float3 specContrib = F * G * D / (4.0 * NdotL * NdotV); + float3 color = NdotL * LightColor * (diffuseContrib + specContrib); + + // Calculate lighting contribution from image based lighting source (IBL) + color += getIBLContribution(perceptualRoughness, NdotV, diffuseColor, specularColor, n, reflection); + + // Apply optional PBR terms for additional (optional) shading + const float ao = OcclusionTexture.Sample(OcclusionSampler, input.TexCoord0).r; + color = lerp(color, color * ao, OcclusionStrength); + + const float3 emissive = EmissiveTexture.Sample(EmissiveSampler, input.TexCoord0) * EmissiveFactor; + color += emissive; + + return float4(color, baseColor.a); +} diff --git a/src/conformance/framework/pbr/Shaders/PbrPixelShader_glsl.frag b/src/conformance/framework/pbr/Shaders/PbrPixelShader_glsl.frag new file mode 100644 index 00000000..3e2ae34a --- /dev/null +++ b/src/conformance/framework/pbr/Shaders/PbrPixelShader_glsl.frag @@ -0,0 +1,222 @@ +#version 450 +precision mediump float; +precision highp int; +// Copyright (c) 2016 - 2017 Mohamad Moneimne and Contributors +// Copyright (C) Microsoft Corporation. All Rights Reserved +// Copyright 2023, The Khronos Group, Inc. +// +// SPDX-License-Identifier: MIT + +layout(binding = 0, std140) uniform type_SceneBuffer +{ + mat4 ViewProjection; // offset 0 + vec3 EyePosition; // offset 64 + // implicit 4 bytes of padding + vec3 LightDirection; // offset 80 + // implicit 4 bytes of padding + vec3 LightColor; // offset 96 + // implicit 4 bytes of padding + uint _pad; // explicit 4 bytes of padding so this matches the d3d offsets too + lowp uint NumSpecularMipLevels; // offset 112 + // implicit 12 bytes of padding +} +SceneBuffer; + +layout(binding = 2, std140) uniform type_MaterialConstantBuffer +{ + vec4 BaseColorFactor; // offset 0 + float MetallicFactor; // offset 16 + float RoughnessFactor; // offset 20 + vec3 EmissiveFactor; // offset 32 + float _pad; // Need explicit 4 bytes of padding + float NormalScale; // offset 48 + float OcclusionStrength; // offset 52 + float AlphaCutoff; // offset 56 + // implicit 4 bytes of padding +} +MaterialConstantBuffer; + +layout(binding = 4) uniform sampler2D BaseColorSampler; +layout(binding = 5) uniform sampler2D MetallicRoughnessSampler; +layout(binding = 6) uniform sampler2D NormalSampler; +layout(binding = 7) uniform sampler2D OcclusionTextureOcclusionSampler; +layout(binding = 8) uniform sampler2D EmissiveTextureEmissiveSampler; +layout(binding = 9) uniform sampler2D BRDFSampler; +layout(binding = 10) uniform samplerCube SpecularTextureIBLSampler; +layout(binding = 11) uniform samplerCube DiffuseTextureIBLSampler; + +// input to fragment shader, output of vertex shader +layout(location = 0) in vec3 varying_POSITION1; +layout(location = 1) in mat3 varying_TANGENT; +layout(location = 4) in vec2 varying_TEXCOORD0; +layout(location = 5) in vec4 varying_COLOR0; + +layout(location = 0) out vec4 out_var_SV_TARGET; + +#define texture2D texture +#define textureCube texture +#define textureCubeLodEXT textureLod + +const float M_PI = 3.141592653589793; +const float c_MinRoughness = 0.04; +const vec3 c_f0 = vec3(0.04, 0.04, 0.04); + +vec4 SRGBtoLINEAR(vec4 srgbIn) +{ +#ifdef MANUAL_SRGB +#ifdef SRGB_FAST_APPROXIMATION + vec3 linOut = pow(srgbIn.xyz, vec3(2.2)); +#else //SRGB_FAST_APPROXIMATION + vec3 bLess = step(vec3(0.04045), srgbIn.xyz); + vec3 linOut = mix(srgbIn.xyz / vec3(12.92), pow((srgbIn.xyz + vec3(0.055)) / vec3(1.055), vec3(2.4)), bLess); +#endif //SRGB_FAST_APPROXIMATION + return vec4(linOut, srgbIn.w); + ; +#else //MANUAL_SRGB + return srgbIn; +#endif //MANUAL_SRGB +} + +// Find the normal for this fragment, pulling either from a predefined normal map +// or from the interpolated mesh normal and tangent attributes. +vec3 getNormal() +{ + // Retrieve the tangent space matrix + mat3 tbn = varying_TANGENT; // ???? + + vec3 n = texture2D(NormalSampler, varying_TEXCOORD0).rgb; + n = normalize(tbn * ((2.0 * n - 1.0) * vec3(MaterialConstantBuffer.NormalScale, MaterialConstantBuffer.NormalScale, 1.0))); + + return n; +} + +// Calculation of the lighting contribution from an optional Image Based Light source. +// Precomputed Environment Maps are required uniform inputs and are computed as outlined in [1]. +// See our README.md on Environment Maps [3] for additional discussion. +vec3 getIBLContribution(float perceptualRoughness, float NdotV, vec3 diffuseColor, vec3 specularColor, vec3 n, vec3 reflection) +{ + float lod = (perceptualRoughness * float(SceneBuffer.NumSpecularMipLevels)); + + // retrieve a scale and bias to F0. See [1], Figure 3 + vec3 brdf = SRGBtoLINEAR(texture2D(BRDFSampler, vec2(NdotV, 1.0 - perceptualRoughness))).rgb; + vec3 diffuseLight = SRGBtoLINEAR(textureCube(DiffuseTextureIBLSampler, n)).rgb; + + vec3 specularLight = SRGBtoLINEAR(textureCubeLodEXT(SpecularTextureIBLSampler, reflection, lod)).rgb; + + vec3 diffuse = diffuseLight * diffuseColor; + vec3 specular = specularLight * (specularColor * brdf.x + brdf.y); + + // For presentation, this allows us to disable IBL terms + // diffuse *= u_ScaleIBLAmbient.x; + // specular *= u_ScaleIBLAmbient.y; + + return diffuse + specular; +} + +// Basic Lambertian diffuse +// Implementation from Lambert's Photometria https://archive.org/details/lambertsphotome00lambgoog +// See also [1], Equation 1 +vec3 diffuse(vec3 diffuseColor) +{ + return diffuseColor / M_PI; +} + +// The following equation models the Fresnel reflectance term of the spec equation (aka F()) +// Implementation of fresnel from [4], Equation 15 +vec3 specularReflection(vec3 reflectance0, vec3 reflectance90, float VdotH) +{ + return reflectance0 + (reflectance90 - reflectance0) * pow(clamp(1.0 - VdotH, 0.0, 1.0), 5.0); +} + +// This calculates the specular geometric attenuation (aka G()), +// where rougher material will reflect less light back to the viewer. +// This implementation is based on [1] Equation 4, and we adopt their modifications to +// alphaRoughness as input as originally proposed in [2]. +float geometricOcclusion(float NdotL, float NdotV, float alphaRoughness) +{ + float r = alphaRoughness; + + float attenuationL = 2.0 * NdotL / (NdotL + sqrt(r * r + (1.0 - r * r) * (NdotL * NdotL))); + float attenuationV = 2.0 * NdotV / (NdotV + sqrt(r * r + (1.0 - r * r) * (NdotV * NdotV))); + return attenuationL * attenuationV; +} + +// The following equation(s) model the distribution of microfacet normals across the area being drawn (aka D()) +// Implementation from "Average Irregularity Representation of a Roughened Surface for Ray Reflection" by T. S. Trowbridge, and K. P. Reitz +// Follows the distribution function recommended in the SIGGRAPH 2013 course notes from EPIC Games [1], Equation 3. +highp float microfacetDistribution(highp float NdotH, float alphaRoughness) +{ + highp float roughnessSq = alphaRoughness * alphaRoughness; + highp float f = (NdotH * roughnessSq - NdotH) * NdotH + 1.0; + return roughnessSq / (M_PI * f * f); +} + +void main() +{ + // Roughness is stored in the 'g' channel, metallic is stored in the 'b' channel. + // This layout intentionally reserves the 'r' channel for (optional) occlusion map data + vec4 mrSample = texture(MetallicRoughnessSampler, varying_TEXCOORD0); + vec4 baseColor = (texture(BaseColorSampler, varying_TEXCOORD0) * varying_COLOR0) * MaterialConstantBuffer.BaseColorFactor; + + // Discard if below alpha cutoff. + if ((baseColor.w - MaterialConstantBuffer.AlphaCutoff) < 0.0) { + discard; + } + float metallic = clamp(mrSample.z * MaterialConstantBuffer.MetallicFactor, 0.0, 1.0); + float perceptualRoughness = clamp(mrSample.y * MaterialConstantBuffer.RoughnessFactor, c_MinRoughness, 1.0); + + // Roughness is authored as perceptual roughness; as is convention, + // convert to material roughness by squaring the perceptual roughness [2]. + float alphaRoughness = perceptualRoughness * perceptualRoughness; + vec3 diffuseColor = (baseColor.xyz * (vec3(1) - c_f0)) * (1.0 - metallic); + vec3 specularColor = mix(c_f0, baseColor.xyz, vec3(metallic)); + + // Compute reflectance. + // float _119 = specularColor.x; + // float _120 = specularColor.y; + // float _121 = isnan(_120) ? _119 : (isnan(_119) ? _120 : max(_119, _120)); + // float _122 = specularColor.z; + // float reflectance = isnan(_122) ? _121 : (isnan(_121) ? _122 : max(_121, _122)); + float reflectance = max(max(specularColor.x, specularColor.y), specularColor.z); + + // For typical incident reflectance range (between 4% to 100%) set the grazing reflectance to 100% for typical fresnel effect. + // For very low reflectance range on highly diffuse objects (below 4%), incrementally reduce grazing reflecance to 0%. + float reflectance90 = clamp((reflectance)*25.0, 0.0, 1.0); + vec3 specularEnvironmentR0 = specularColor.xyz; + vec3 specularEnvironmentR90 = vec3(1.0, 1.0, 1.0) * reflectance90; + + // normal at surface point + highp vec3 n_orig = (texture(NormalSampler, varying_TEXCOORD0).xyz * 2.0) - vec3(1.0); + n_orig = gl_FrontFacing ? n_orig : -n_orig; + vec3 n = normalize(varying_TANGENT * (n_orig * vec3(MaterialConstantBuffer.NormalScale, MaterialConstantBuffer.NormalScale, 1.0))); + highp vec3 v = normalize(SceneBuffer.EyePosition - varying_POSITION1); + vec3 l = normalize(SceneBuffer.LightDirection); + highp vec3 h = normalize(l + v); + vec3 reflection = -normalize(reflect(v, n)); + + float NdotL = clamp(dot(n, l), 0.001, 1.0); + float NdotV = abs(dot(n, v)) + 0.001; + float NdotH = clamp(dot(n, h), 0.0, 1.0); + float LdotH = clamp(dot(l, h), 0.0, 1.0); + float VdotH = clamp(dot(v, h), 0.0, 1.0); + + // Calculate the shading terms for the microfacet specular shading model + vec3 F = specularReflection(specularEnvironmentR0, specularEnvironmentR90, VdotH); + float G = geometricOcclusion(NdotL, NdotV, alphaRoughness); + highp float D = microfacetDistribution(NdotH, alphaRoughness); + + // Calculation of analytical lighting contribution + vec3 diffuseContrib = (1.0 - F) * diffuse(diffuseColor); + vec3 specContrib = F * G * D / (4.0 * NdotL * NdotV); + + vec3 color = SceneBuffer.LightColor * NdotL * (diffuseContrib + specContrib); + + vec3 colorWithIBL = color + getIBLContribution(perceptualRoughness, NdotV, diffuseColor, specularColor, n, reflection); + + float ao = texture(OcclusionTextureOcclusionSampler, varying_TEXCOORD0).x; + vec3 colorWithIBLandAO = mix(colorWithIBL, colorWithIBL * ao, vec3(MaterialConstantBuffer.OcclusionStrength)); + + vec3 emissive = texture(EmissiveTextureEmissiveSampler, varying_TEXCOORD0).xyz * MaterialConstantBuffer.EmissiveFactor; + + out_var_SV_TARGET = vec4(colorWithIBLandAO + emissive, baseColor.w); +} diff --git a/src/conformance/framework/pbr/Shaders/PbrPixelShader_glsl.spv b/src/conformance/framework/pbr/Shaders/PbrPixelShader_glsl.spv new file mode 100644 index 00000000..039be44c --- /dev/null +++ b/src/conformance/framework/pbr/Shaders/PbrPixelShader_glsl.spv @@ -0,0 +1,440 @@ +{0x07230203,0x00010000,0x000d000b,0x00000297, +0x00000000,0x00020011,0x00000001,0x0006000b, +0x00000001,0x4c534c47,0x6474732e,0x3035342e, +0x00000000,0x0003000e,0x00000000,0x00000001, +0x000b000f,0x00000004,0x00000004,0x6e69616d, +0x00000000,0x000000d4,0x000000dd,0x00000138, +0x00000145,0x00000155,0x000001e2,0x00030010, +0x00000004,0x00000007,0x00040048,0x00000035, +0x00000000,0x00000000,0x00050048,0x00000035, +0x00000000,0x00000023,0x00000040,0x00040048, +0x00000035,0x00000001,0x00000000,0x00050048, +0x00000035,0x00000001,0x00000023,0x00000050, +0x00040048,0x00000035,0x00000002,0x00000000, +0x00050048,0x00000035,0x00000002,0x00000023, +0x00000060,0x00040048,0x00000035,0x00000003, +0x00000000,0x00050048,0x00000035,0x00000003, +0x00000023,0x00000070,0x00030047,0x00000035, +0x00000002,0x00040047,0x00000037,0x00000022, +0x00000000,0x00040047,0x00000037,0x00000021, +0x00000000,0x00040047,0x00000043,0x00000022, +0x00000000,0x00040047,0x00000043,0x00000021, +0x00000009,0x00040047,0x00000053,0x00000022, +0x00000000,0x00040047,0x00000053,0x00000021, +0x0000000b,0x00040047,0x0000005b,0x00000022, +0x00000000,0x00040047,0x0000005b,0x00000021, +0x0000000a,0x00040047,0x000000d1,0x00000022, +0x00000000,0x00040047,0x000000d1,0x00000021, +0x00000005,0x00030047,0x000000d4,0x00000000, +0x00040047,0x000000d4,0x0000001e,0x00000004, +0x00030047,0x000000d5,0x00000000,0x00040047, +0x000000d8,0x00000022,0x00000000,0x00040047, +0x000000d8,0x00000021,0x00000004,0x00030047, +0x000000dd,0x00000000,0x00040047,0x000000dd, +0x0000001e,0x00000005,0x00030047,0x000000de, +0x00000000,0x00040048,0x000000e0,0x00000000, +0x00000000,0x00050048,0x000000e0,0x00000000, +0x00000023,0x00000000,0x00040048,0x000000e0, +0x00000001,0x00000000,0x00050048,0x000000e0, +0x00000001,0x00000023,0x00000010,0x00040048, +0x000000e0,0x00000002,0x00000000,0x00050048, +0x000000e0,0x00000002,0x00000023,0x00000014, +0x00040048,0x000000e0,0x00000003,0x00000000, +0x00050048,0x000000e0,0x00000003,0x00000023, +0x00000020,0x00040048,0x000000e0,0x00000004, +0x00000000,0x00050048,0x000000e0,0x00000004, +0x00000023,0x00000030,0x00040048,0x000000e0, +0x00000005,0x00000000,0x00050048,0x000000e0, +0x00000005,0x00000023,0x00000034,0x00040048, +0x000000e0,0x00000006,0x00000000,0x00050048, +0x000000e0,0x00000006,0x00000023,0x00000038, +0x00030047,0x000000e0,0x00000002,0x00040047, +0x000000e2,0x00000022,0x00000000,0x00040047, +0x000000e2,0x00000021,0x00000002,0x00030047, +0x000000e6,0x00000000,0x00030047,0x000000ea, +0x00000000,0x00030047,0x000000ee,0x00000000, +0x00030047,0x000000ef,0x00000000,0x00030047, +0x000000f8,0x00000000,0x00030047,0x000000fb, +0x00000000,0x00030047,0x000000fc,0x00000000, +0x00030047,0x000000fd,0x00000000,0x00030047, +0x00000100,0x00000000,0x00030047,0x00000103, +0x00000000,0x00030047,0x00000104,0x00000000, +0x00030047,0x00000106,0x00000000,0x00030047, +0x0000010a,0x00000000,0x00030047,0x0000010d, +0x00000000,0x00030047,0x00000110,0x00000000, +0x00030047,0x00000112,0x00000000,0x00030047, +0x00000113,0x00000000,0x00030047,0x00000119, +0x00000000,0x00030047,0x0000011a,0x00000000, +0x00030047,0x0000011d,0x00000000,0x00030047, +0x0000011f,0x00000000,0x00030047,0x00000120, +0x00000000,0x00030047,0x00000122,0x00000000, +0x00030047,0x00000123,0x00000000,0x00030047, +0x00000127,0x00000000,0x00030047,0x00000128, +0x00000000,0x00030047,0x0000012e,0x00000000, +0x00040047,0x00000130,0x00000022,0x00000000, +0x00040047,0x00000130,0x00000021,0x00000006, +0x00040047,0x00000138,0x0000000b,0x00000011, +0x00030047,0x00000145,0x00000000,0x00040047, +0x00000145,0x0000001e,0x00000001,0x00030047, +0x00000146,0x00000000,0x00030047,0x00000149, +0x00000000,0x00030047,0x0000014c,0x00000000, +0x00030047,0x00000153,0x00000000,0x00030047, +0x00000155,0x00000000,0x00040047,0x00000155, +0x0000001e,0x00000000,0x00030047,0x00000156, +0x00000000,0x00030047,0x00000157,0x00000000, +0x00030047,0x00000158,0x00000000,0x00030047, +0x0000015b,0x00000000,0x00030047,0x0000015c, +0x00000000,0x00030047,0x0000016b,0x00000000, +0x00030047,0x0000016d,0x00000000,0x00030047, +0x0000019c,0x00000000,0x00030047,0x000001a4, +0x00000000,0x00030047,0x000001a9,0x00000000, +0x00030047,0x000001ab,0x00000000,0x00030047, +0x000001b1,0x00000000,0x00030047,0x000001b3, +0x00000000,0x00030047,0x000001b6,0x00000000, +0x00030047,0x000001c7,0x00000000,0x00040047, +0x000001c9,0x00000022,0x00000000,0x00040047, +0x000001c9,0x00000021,0x00000007,0x00030047, +0x000001d2,0x00000000,0x00030047,0x000001d5, +0x00000000,0x00030047,0x000001d6,0x00000000, +0x00030047,0x000001d7,0x00000000,0x00040047, +0x000001d9,0x00000022,0x00000000,0x00040047, +0x000001d9,0x00000021,0x00000008,0x00030047, +0x000001df,0x00000000,0x00030047,0x000001e2, +0x00000000,0x00040047,0x000001e2,0x0000001e, +0x00000000,0x00030047,0x000001e5,0x00000000, +0x00030047,0x000001e8,0x00000000,0x00030047, +0x000001e9,0x00000000,0x00030047,0x000001ea, +0x00000000,0x00030047,0x000001eb,0x00000000, +0x00030047,0x0000007a,0x00000000,0x00030047, +0x00000105,0x00000000,0x00030047,0x00000115, +0x00000000,0x00030047,0x000001f1,0x00000000, +0x00030047,0x000001f3,0x00000000,0x00030047, +0x000001f4,0x00000000,0x00030047,0x000001f5, +0x00000000,0x00030047,0x000001f6,0x00000000, +0x00030047,0x000001f7,0x00000000,0x00030047, +0x000001ff,0x00000000,0x00030047,0x00000207, +0x00000000,0x00030047,0x0000020a,0x00000000, +0x00030047,0x0000020b,0x00000000,0x00030047, +0x0000020c,0x00000000,0x00030047,0x0000020d, +0x00000000,0x00030047,0x0000020e,0x00000000, +0x00030047,0x0000020f,0x00000000,0x00030047, +0x00000211,0x00000000,0x00030047,0x0000021c, +0x00000000,0x00030047,0x0000021d,0x00000000, +0x00030047,0x0000021e,0x00000000,0x00030047, +0x0000021f,0x00000000,0x00030047,0x00000220, +0x00000000,0x00030047,0x00000221,0x00000000, +0x00030047,0x00000224,0x00000000,0x00030047, +0x0000022b,0x00000000,0x00030047,0x0000023e, +0x00000000,0x00030047,0x0000024c,0x00000000, +0x00030047,0x0000024d,0x00000000,0x00030047, +0x0000024e,0x00000000,0x00030047,0x00000252, +0x00000000,0x00030047,0x00000253,0x00000000, +0x00030047,0x0000025b,0x00000000,0x00030047, +0x00000261,0x00000000,0x00030047,0x00000268, +0x00000000,0x00030047,0x00000269,0x00000000, +0x00030047,0x0000026b,0x00000000,0x00030047, +0x0000026c,0x00000000,0x00030047,0x0000026d, +0x00000000,0x00030047,0x0000026e,0x00000000, +0x00030047,0x00000271,0x00000000,0x00020013, +0x00000002,0x00030021,0x00000003,0x00000002, +0x00030016,0x00000006,0x00000020,0x00040017, +0x00000007,0x00000006,0x00000004,0x00040017, +0x0000000e,0x00000006,0x00000003,0x00040015, +0x00000034,0x00000020,0x00000000,0x0006001e, +0x00000035,0x0000000e,0x0000000e,0x0000000e, +0x00000034,0x00040020,0x00000036,0x00000002, +0x00000035,0x0004003b,0x00000036,0x00000037, +0x00000002,0x00040015,0x00000038,0x00000020, +0x00000001,0x00040020,0x0000003a,0x00000002, +0x00000034,0x00090019,0x00000040,0x00000006, +0x00000001,0x00000000,0x00000000,0x00000000, +0x00000001,0x00000000,0x0003001b,0x00000041, +0x00000040,0x00040020,0x00000042,0x00000000, +0x00000041,0x0004003b,0x00000042,0x00000043, +0x00000000,0x0004002b,0x00000006,0x00000046, +0x3f800000,0x00040017,0x00000049,0x00000006, +0x00000002,0x00090019,0x00000050,0x00000006, +0x00000003,0x00000000,0x00000000,0x00000000, +0x00000001,0x00000000,0x0003001b,0x00000051, +0x00000050,0x00040020,0x00000052,0x00000000, +0x00000051,0x0004003b,0x00000052,0x00000053, +0x00000000,0x0004003b,0x00000052,0x0000005b, +0x00000000,0x0004002b,0x00000006,0x0000007a, +0x40490fdb,0x0004002b,0x00000006,0x00000085, +0x00000000,0x0004002b,0x00000006,0x00000087, +0x40a00000,0x0004002b,0x00000006,0x00000090, +0x40000000,0x0004003b,0x00000042,0x000000d1, +0x00000000,0x00040020,0x000000d3,0x00000001, +0x00000049,0x0004003b,0x000000d3,0x000000d4, +0x00000001,0x0004003b,0x00000042,0x000000d8, +0x00000000,0x00040020,0x000000dc,0x00000001, +0x00000007,0x0004003b,0x000000dc,0x000000dd, +0x00000001,0x0009001e,0x000000e0,0x00000007, +0x00000006,0x00000006,0x0000000e,0x00000006, +0x00000006,0x00000006,0x00040020,0x000000e1, +0x00000002,0x000000e0,0x0004003b,0x000000e1, +0x000000e2,0x00000002,0x0004002b,0x00000038, +0x000000e3,0x00000000,0x00040020,0x000000e4, +0x00000002,0x00000007,0x00040020,0x000000ec, +0x00000002,0x00000006,0x00020014,0x000000f0, +0x0004002b,0x00000038,0x000000f9,0x00000001, +0x0004002b,0x00000038,0x00000101,0x00000002, +0x0004002b,0x00000006,0x00000105,0x3d23d70a, +0x0004002b,0x00000006,0x0000010e,0x3f75c28f, +0x0006002c,0x0000000e,0x0000010f,0x0000010e, +0x0000010e,0x0000010e,0x0006002c,0x0000000e, +0x00000115,0x00000105,0x00000105,0x00000105, +0x0004002b,0x00000006,0x00000126,0x41c80000, +0x0006002c,0x0000000e,0x0000012c,0x00000046, +0x00000046,0x00000046,0x0004003b,0x00000042, +0x00000130,0x00000000,0x00040020,0x00000137, +0x00000001,0x000000f0,0x0004003b,0x00000137, +0x00000138,0x00000001,0x00040018,0x00000143, +0x0000000e,0x00000003,0x00040020,0x00000144, +0x00000001,0x00000143,0x0004003b,0x00000144, +0x00000145,0x00000001,0x00040020,0x00000151, +0x00000002,0x0000000e,0x00040020,0x00000154, +0x00000001,0x0000000e,0x0004003b,0x00000154, +0x00000155,0x00000001,0x0004002b,0x00000006, +0x0000016c,0x3a83126f,0x0004002b,0x00000006, +0x000001a7,0x40800000,0x0004002b,0x00000038, +0x000001af,0x00000003,0x0004003b,0x00000042, +0x000001c9,0x00000000,0x0004003b,0x00000042, +0x000001d9,0x00000000,0x00040020,0x000001e1, +0x00000003,0x00000007,0x0004003b,0x000001e1, +0x000001e2,0x00000003,0x0004002b,0x00000006, +0x00000285,0x3ea2f983,0x0006002c,0x0000000e, +0x00000286,0x00000285,0x00000285,0x00000285, +0x0004002b,0x00000034,0x00000290,0x00000006, +0x0004002b,0x00000034,0x00000291,0x00000004, +0x0004002b,0x00000034,0x00000292,0x00000000, +0x0004002b,0x00000034,0x00000293,0x00000001, +0x0004002b,0x00000034,0x00000294,0x00000002, +0x0004002b,0x00000034,0x00000295,0x00000003, +0x0004002b,0x00000034,0x00000296,0x00000005, +0x00050036,0x00000002,0x00000004,0x00000000, +0x00000003,0x000200f8,0x00000005,0x0004003d, +0x00000041,0x000000d2,0x000000d1,0x0004003d, +0x00000049,0x000000d5,0x000000d4,0x00050057, +0x00000007,0x000000d6,0x000000d2,0x000000d5, +0x0004003d,0x00000041,0x000000d9,0x000000d8, +0x00050057,0x00000007,0x000000db,0x000000d9, +0x000000d5,0x0004003d,0x00000007,0x000000de, +0x000000dd,0x00050085,0x00000007,0x000000df, +0x000000db,0x000000de,0x00050041,0x000000e4, +0x000000e5,0x000000e2,0x000000e3,0x0004003d, +0x00000007,0x000000e6,0x000000e5,0x00050085, +0x00000007,0x000000e7,0x000000df,0x000000e6, +0x00050051,0x00000006,0x000000ea,0x000000e7, +0x00000003,0x00050041,0x000000ec,0x000000ed, +0x000000e2,0x00000290,0x0004003d,0x00000006, +0x000000ee,0x000000ed,0x00050083,0x00000006, +0x000000ef,0x000000ea,0x000000ee,0x000500b8, +0x000000f0,0x000000f1,0x000000ef,0x00000085, +0x000300f7,0x000000f3,0x00000000,0x000400fa, +0x000000f1,0x000000f2,0x000000f3,0x000200f8, +0x000000f2,0x000100fc,0x000200f8,0x000000f3, +0x00050051,0x00000006,0x000000f8,0x000000d6, +0x00000002,0x00050041,0x000000ec,0x000000fa, +0x000000e2,0x000000f9,0x0004003d,0x00000006, +0x000000fb,0x000000fa,0x00050085,0x00000006, +0x000000fc,0x000000f8,0x000000fb,0x0008000c, +0x00000006,0x000000fd,0x00000001,0x0000002b, +0x000000fc,0x00000085,0x00000046,0x00050051, +0x00000006,0x00000100,0x000000d6,0x00000001, +0x00050041,0x000000ec,0x00000102,0x000000e2, +0x00000101,0x0004003d,0x00000006,0x00000103, +0x00000102,0x00050085,0x00000006,0x00000104, +0x00000100,0x00000103,0x0008000c,0x00000006, +0x00000106,0x00000001,0x0000002b,0x00000104, +0x00000105,0x00000046,0x00050085,0x00000006, +0x0000010a,0x00000106,0x00000106,0x0008004f, +0x0000000e,0x0000010d,0x000000e7,0x000000e7, +0x00000000,0x00000001,0x00000002,0x00050085, +0x0000000e,0x00000110,0x0000010d,0x0000010f, +0x00050083,0x00000006,0x00000112,0x00000046, +0x000000fd,0x0005008e,0x0000000e,0x00000113, +0x00000110,0x00000112,0x00060050,0x0000000e, +0x00000119,0x000000fd,0x000000fd,0x000000fd, +0x0008000c,0x0000000e,0x0000011a,0x00000001, +0x0000002e,0x00000115,0x0000010d,0x00000119, +0x00050051,0x00000006,0x0000011d,0x0000011a, +0x00000000,0x00050051,0x00000006,0x0000011f, +0x0000011a,0x00000001,0x0007000c,0x00000006, +0x00000120,0x00000001,0x00000028,0x0000011d, +0x0000011f,0x00050051,0x00000006,0x00000122, +0x0000011a,0x00000002,0x0007000c,0x00000006, +0x00000123,0x00000001,0x00000028,0x00000120, +0x00000122,0x00050085,0x00000006,0x00000127, +0x00000123,0x00000126,0x0008000c,0x00000006, +0x00000128,0x00000001,0x0000002b,0x00000127, +0x00000085,0x00000046,0x0005008e,0x0000000e, +0x0000012e,0x0000012c,0x00000128,0x0004003d, +0x00000041,0x00000131,0x00000130,0x00050057, +0x00000007,0x00000133,0x00000131,0x000000d5, +0x0008004f,0x0000000e,0x00000134,0x00000133, +0x00000133,0x00000000,0x00000001,0x00000002, +0x0005008e,0x0000000e,0x00000135,0x00000134, +0x00000090,0x00050083,0x0000000e,0x00000136, +0x00000135,0x0000012c,0x0004003d,0x000000f0, +0x00000139,0x00000138,0x000300f7,0x0000013c, +0x00000000,0x000400fa,0x00000139,0x0000013b, +0x0000013e,0x000200f8,0x0000013b,0x000200f9, +0x0000013c,0x000200f8,0x0000013e,0x00050083, +0x0000000e,0x00000140,0x0000012c,0x00000135, +0x000200f9,0x0000013c,0x000200f8,0x0000013c, +0x000700f5,0x0000000e,0x0000027b,0x00000136, +0x0000013b,0x00000140,0x0000013e,0x0004003d, +0x00000143,0x00000146,0x00000145,0x00050041, +0x000000ec,0x00000148,0x000000e2,0x00000291, +0x0004003d,0x00000006,0x00000149,0x00000148, +0x00060050,0x0000000e,0x0000014c,0x00000149, +0x00000149,0x00000046,0x00050085,0x0000000e, +0x0000014d,0x0000027b,0x0000014c,0x00050091, +0x0000000e,0x0000014e,0x00000146,0x0000014d, +0x0006000c,0x0000000e,0x0000014f,0x00000001, +0x00000045,0x0000014e,0x00050041,0x00000151, +0x00000152,0x00000037,0x00000292,0x0004003d, +0x0000000e,0x00000153,0x00000152,0x0004003d, +0x0000000e,0x00000156,0x00000155,0x00050083, +0x0000000e,0x00000157,0x00000153,0x00000156, +0x0006000c,0x0000000e,0x00000158,0x00000001, +0x00000045,0x00000157,0x00050041,0x00000151, +0x0000015a,0x00000037,0x00000293,0x0004003d, +0x0000000e,0x0000015b,0x0000015a,0x0006000c, +0x0000000e,0x0000015c,0x00000001,0x00000045, +0x0000015b,0x00050081,0x0000000e,0x00000160, +0x0000015c,0x00000158,0x0006000c,0x0000000e, +0x00000161,0x00000001,0x00000045,0x00000160, +0x0007000c,0x0000000e,0x00000165,0x00000001, +0x00000047,0x00000158,0x0000014f,0x0006000c, +0x0000000e,0x00000166,0x00000001,0x00000045, +0x00000165,0x0004007f,0x0000000e,0x00000167, +0x00000166,0x00050094,0x00000006,0x0000016b, +0x0000014f,0x0000015c,0x0008000c,0x00000006, +0x0000016d,0x00000001,0x0000002b,0x0000016b, +0x0000016c,0x00000046,0x00050094,0x00000006, +0x00000171,0x0000014f,0x00000158,0x0006000c, +0x00000006,0x00000172,0x00000001,0x00000004, +0x00000171,0x00050081,0x00000006,0x00000173, +0x00000172,0x0000016c,0x00050094,0x00000006, +0x00000177,0x0000014f,0x00000161,0x0008000c, +0x00000006,0x00000178,0x00000001,0x0000002b, +0x00000177,0x00000085,0x00000046,0x00050094, +0x00000006,0x00000181,0x00000158,0x00000161, +0x0008000c,0x00000006,0x00000182,0x00000001, +0x0000002b,0x00000181,0x00000085,0x00000046, +0x00050083,0x0000000e,0x000001f1,0x0000012e, +0x0000011a,0x00050083,0x00000006,0x000001f3, +0x00000046,0x00000182,0x0008000c,0x00000006, +0x000001f4,0x00000001,0x0000002b,0x000001f3, +0x00000085,0x00000046,0x0007000c,0x00000006, +0x000001f5,0x00000001,0x0000001a,0x000001f4, +0x00000087,0x0005008e,0x0000000e,0x000001f6, +0x000001f1,0x000001f5,0x00050081,0x0000000e, +0x000001f7,0x0000011a,0x000001f6,0x00050085, +0x00000006,0x000001ff,0x00000090,0x0000016d, +0x0004007f,0x00000006,0x00000282,0x0000010a, +0x0008000c,0x00000006,0x00000207,0x00000001, +0x00000032,0x00000282,0x0000010a,0x00000046, +0x00050085,0x00000006,0x0000020a,0x0000016d, +0x0000016d,0x00050085,0x00000006,0x0000020b, +0x00000207,0x0000020a,0x0008000c,0x00000006, +0x0000020c,0x00000001,0x00000032,0x0000010a, +0x0000010a,0x0000020b,0x0006000c,0x00000006, +0x0000020d,0x00000001,0x0000001f,0x0000020c, +0x00050081,0x00000006,0x0000020e,0x0000016d, +0x0000020d,0x00050088,0x00000006,0x0000020f, +0x000001ff,0x0000020e,0x00050085,0x00000006, +0x00000211,0x00000090,0x00000173,0x00050085, +0x00000006,0x0000021c,0x00000173,0x00000173, +0x00050085,0x00000006,0x0000021d,0x00000207, +0x0000021c,0x0008000c,0x00000006,0x0000021e, +0x00000001,0x00000032,0x0000010a,0x0000010a, +0x0000021d,0x0006000c,0x00000006,0x0000021f, +0x00000001,0x0000001f,0x0000021e,0x00050081, +0x00000006,0x00000220,0x00000173,0x0000021f, +0x00050088,0x00000006,0x00000221,0x00000211, +0x00000220,0x00050085,0x00000006,0x00000224, +0x0000020f,0x00000221,0x00050085,0x00000006, +0x0000022b,0x0000010a,0x0000010a,0x0004007f, +0x00000006,0x00000284,0x00000178,0x0008000c, +0x00000006,0x00000230,0x00000001,0x00000032, +0x00000178,0x0000022b,0x00000284,0x0008000c, +0x00000006,0x00000233,0x00000001,0x00000032, +0x00000230,0x00000178,0x00000046,0x00050085, +0x00000006,0x00000236,0x0000007a,0x00000233, +0x00050085,0x00000006,0x00000238,0x00000236, +0x00000233,0x00050088,0x00000006,0x00000239, +0x0000022b,0x00000238,0x00050083,0x0000000e, +0x0000019c,0x0000012c,0x000001f7,0x00050085, +0x0000000e,0x0000023e,0x00000113,0x00000286, +0x0005008e,0x0000000e,0x000001a4,0x000001f7, +0x00000224,0x0005008e,0x0000000e,0x000001a6, +0x000001a4,0x00000239,0x00050085,0x00000006, +0x000001a9,0x000001a7,0x0000016d,0x00050085, +0x00000006,0x000001ab,0x000001a9,0x00000173, +0x00060050,0x0000000e,0x000001ac,0x000001ab, +0x000001ab,0x000001ab,0x00050088,0x0000000e, +0x000001ad,0x000001a6,0x000001ac,0x00050041, +0x00000151,0x000001b0,0x00000037,0x00000294, +0x0004003d,0x0000000e,0x000001b1,0x000001b0, +0x0005008e,0x0000000e,0x000001b3,0x000001b1, +0x0000016d,0x0008000c,0x0000000e,0x000001b6, +0x00000001,0x00000032,0x0000019c,0x0000023e, +0x000001ad,0x00050041,0x0000003a,0x0000024b, +0x00000037,0x00000295,0x0004003d,0x00000034, +0x0000024c,0x0000024b,0x00040070,0x00000006, +0x0000024d,0x0000024c,0x00050085,0x00000006, +0x0000024e,0x00000106,0x0000024d,0x0004003d, +0x00000041,0x0000024f,0x00000043,0x00050083, +0x00000006,0x00000252,0x00000046,0x00000106, +0x00050050,0x00000049,0x00000253,0x00000173, +0x00000252,0x00050057,0x00000007,0x00000254, +0x0000024f,0x00000253,0x0004003d,0x00000051, +0x00000257,0x00000053,0x00050057,0x00000007, +0x00000259,0x00000257,0x0000014f,0x0008004f, +0x0000000e,0x0000025b,0x00000259,0x00000259, +0x00000000,0x00000001,0x00000002,0x0004003d, +0x00000051,0x0000025c,0x0000005b,0x00070058, +0x00000007,0x0000025f,0x0000025c,0x00000167, +0x00000002,0x0000024e,0x0008004f,0x0000000e, +0x00000261,0x0000025f,0x0000025f,0x00000000, +0x00000001,0x00000002,0x00050051,0x00000006, +0x00000268,0x00000254,0x00000000,0x0005008e, +0x0000000e,0x00000269,0x0000011a,0x00000268, +0x00050051,0x00000006,0x0000026b,0x00000254, +0x00000001,0x00060050,0x0000000e,0x0000026c, +0x0000026b,0x0000026b,0x0000026b,0x00050081, +0x0000000e,0x0000026d,0x00000269,0x0000026c, +0x00050085,0x0000000e,0x0000026e,0x00000261, +0x0000026d,0x0008000c,0x0000000e,0x00000271, +0x00000001,0x00000032,0x0000025b,0x00000113, +0x0000026e,0x0008000c,0x0000000e,0x000001c7, +0x00000001,0x00000032,0x000001b3,0x000001b6, +0x00000271,0x0004003d,0x00000041,0x000001ca, +0x000001c9,0x00050057,0x00000007,0x000001cc, +0x000001ca,0x000000d5,0x00050051,0x00000006, +0x000001cd,0x000001cc,0x00000000,0x0005008e, +0x0000000e,0x000001d2,0x000001c7,0x000001cd, +0x00050041,0x000000ec,0x000001d4,0x000000e2, +0x00000296,0x0004003d,0x00000006,0x000001d5, +0x000001d4,0x00060050,0x0000000e,0x000001d6, +0x000001d5,0x000001d5,0x000001d5,0x0008000c, +0x0000000e,0x000001d7,0x00000001,0x0000002e, +0x000001c7,0x000001d2,0x000001d6,0x0004003d, +0x00000041,0x000001da,0x000001d9,0x00050057, +0x00000007,0x000001dc,0x000001da,0x000000d5, +0x0008004f,0x0000000e,0x000001dd,0x000001dc, +0x000001dc,0x00000000,0x00000001,0x00000002, +0x00050041,0x00000151,0x000001de,0x000000e2, +0x000001af,0x0004003d,0x0000000e,0x000001df, +0x000001de,0x0008000c,0x0000000e,0x000001e5, +0x00000001,0x00000032,0x000001dd,0x000001df, +0x000001d7,0x00050051,0x00000006,0x000001e8, +0x000001e5,0x00000000,0x00050051,0x00000006, +0x000001e9,0x000001e5,0x00000001,0x00050051, +0x00000006,0x000001ea,0x000001e5,0x00000002, +0x00070050,0x00000007,0x000001eb,0x000001e8, +0x000001e9,0x000001ea,0x000000ea,0x0003003e, +0x000001e2,0x000001eb,0x000100fd,0x00010038} diff --git a/src/conformance/framework/pbr/Shaders/PbrPixelShader_glsl.spv.license b/src/conformance/framework/pbr/Shaders/PbrPixelShader_glsl.spv.license new file mode 100644 index 00000000..0721500c --- /dev/null +++ b/src/conformance/framework/pbr/Shaders/PbrPixelShader_glsl.spv.license @@ -0,0 +1,5 @@ +Copyright (c) 2016 - 2017 Mohamad Moneimne and Contributors +Copyright (C) Microsoft Corporation. All Rights Reserved +Copyright 2023, The Khronos Group, Inc. + +SPDX-License-Identifier: MIT diff --git a/src/conformance/framework/pbr/Shaders/PbrShared.hlsl b/src/conformance/framework/pbr/Shaders/PbrShared.hlsl new file mode 100644 index 00000000..f586e2a8 --- /dev/null +++ b/src/conformance/framework/pbr/Shaders/PbrShared.hlsl @@ -0,0 +1,16 @@ +//////////////////////////////////////////////////////////////////////////////// +// Copyright (C) Microsoft Corporation. All Rights Reserved +// Licensed under the MIT License. See License.txt in the project root for license information. +// +// SPDX-License-Identifier: MIT + +#include "Shared.hlsl" + +struct PSInputPbr +{ + float4 PositionProj : SV_POSITION; + float3 PositionWorld: POSITION1; + float3x3 TBN : TANGENT; + float2 TexCoord0 : TEXCOORD0; + float4 Color0 : COLOR0; +}; diff --git a/src/conformance/framework/pbr/Shaders/PbrVertexShader.hlsl b/src/conformance/framework/pbr/Shaders/PbrVertexShader.hlsl new file mode 100644 index 00000000..5f1f8b92 --- /dev/null +++ b/src/conformance/framework/pbr/Shaders/PbrVertexShader.hlsl @@ -0,0 +1,60 @@ +//////////////////////////////////////////////////////////////////////////////// +// Copyright (C) Microsoft Corporation. All Rights Reserved +// Licensed under the MIT License. See License.txt in the project root for license information. +// +// This shader is based on the vertex shader at https://github.com/KhronosGroup/glTF-WebGL-PBR +// with modifications for HLSL and stereoscopic rendering. +// +// The MIT License +// +// Copyright(c) 2016 - 2017 Mohamad Moneimne and Contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files(the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and / or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions : +// +// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// +// SPDX-License-Identifier: MIT +// + +#include "PbrShared.hlsl" + +StructuredBuffer Transforms : register(t0); + +cbuffer ModelConstantBuffer : register(b1) +{ + float4x4 ModelToWorld : packoffset(c0); + +}; + +struct VSInputPbr +{ + float4 Position : POSITION; + float3 Normal : NORMAL; + float4 Tangent : TANGENT; + float4 Color0 : COLOR0; + float2 TexCoord0 : TEXCOORD0; + min16uint ModelTransformIndex : TRANSFORMINDEX; +}; + +#define VSOutputPbr PSInputPbr +VSOutputPbr main(VSInputPbr input) +{ + VSOutputPbr output; + + const float4x4 modelTransform = mul(Transforms[input.ModelTransformIndex], ModelToWorld); + const float4 transformedPosWorld = mul(input.Position, modelTransform); + output.PositionProj = mul(transformedPosWorld, ViewProjection); + output.PositionWorld = transformedPosWorld.xyz / transformedPosWorld.w; + + const float3 normalW = normalize(mul(float4(input.Normal, 0.0), modelTransform).xyz); + const float3 tangentW = normalize(mul(float4(input.Tangent.xyz, 0.0), modelTransform).xyz); + const float3 bitangentW = cross(normalW, tangentW) * input.Tangent.w; + output.TBN = float3x3(tangentW, bitangentW, normalW); + + output.TexCoord0 = input.TexCoord0; + output.Color0 = input.Color0; + + return output; +} diff --git a/src/conformance/framework/pbr/Shaders/PbrVertexShader_glsl.spv b/src/conformance/framework/pbr/Shaders/PbrVertexShader_glsl.spv new file mode 100644 index 00000000..29a0a63b --- /dev/null +++ b/src/conformance/framework/pbr/Shaders/PbrVertexShader_glsl.spv @@ -0,0 +1,178 @@ +{0x07230203,0x00010000,0x000d000b,0x0000007b, +0x00000000,0x00020011,0x00000001,0x0006000b, +0x00000001,0x4c534c47,0x6474732e,0x3035342e, +0x00000000,0x0003000e,0x00000000,0x00000001, +0x0010000f,0x00000000,0x00000004,0x6e69616d, +0x00000000,0x00000019,0x00000022,0x0000002a, +0x00000036,0x00000042,0x0000004d,0x00000060, +0x00000074,0x00000076,0x00000078,0x00000079, +0x00040048,0x0000000b,0x00000000,0x00000005, +0x00040048,0x0000000b,0x00000000,0x00000000, +0x00050048,0x0000000b,0x00000000,0x00000023, +0x00000000,0x00050048,0x0000000b,0x00000000, +0x00000007,0x00000010,0x00030047,0x0000000b, +0x00000002,0x00040047,0x0000000d,0x00000022, +0x00000000,0x00040047,0x0000000d,0x00000021, +0x00000001,0x00030047,0x00000012,0x00000000, +0x00040047,0x00000013,0x00000006,0x00000040, +0x00040048,0x00000014,0x00000000,0x00000005, +0x00040048,0x00000014,0x00000000,0x00000000, +0x00040048,0x00000014,0x00000000,0x00000018, +0x00050048,0x00000014,0x00000000,0x00000023, +0x00000000,0x00050048,0x00000014,0x00000000, +0x00000007,0x00000010,0x00030047,0x00000014, +0x00000003,0x00040047,0x00000016,0x00000022, +0x00000000,0x00040047,0x00000016,0x00000021, +0x00000003,0x00030047,0x00000019,0x00000000, +0x00040047,0x00000019,0x0000001e,0x00000005, +0x00030047,0x0000001a,0x00000000,0x00030047, +0x0000001c,0x00000000,0x00030047,0x0000001d, +0x00000000,0x00030047,0x00000022,0x00000000, +0x00040047,0x00000022,0x0000001e,0x00000000, +0x00030047,0x00000023,0x00000000,0x00030047, +0x00000024,0x00000000,0x00030047,0x0000002a, +0x00000000,0x00040047,0x0000002a,0x0000001e, +0x00000001,0x00030047,0x0000002b,0x00000000, +0x00030047,0x0000002d,0x00000000,0x00030047, +0x0000002e,0x00000000,0x00030047,0x0000002f, +0x00000000,0x00030047,0x00000030,0x00000000, +0x00030047,0x00000031,0x00000000,0x00030047, +0x00000032,0x00000000,0x00030047,0x00000033, +0x00000000,0x00030047,0x00000036,0x00000000, +0x00040047,0x00000036,0x0000001e,0x00000002, +0x00030047,0x00000037,0x00000000,0x00030047, +0x00000039,0x00000000,0x00030047,0x0000003a, +0x00000000,0x00030047,0x0000003b,0x00000000, +0x00030047,0x0000003c,0x00000000,0x00030047, +0x0000003d,0x00000000,0x00030047,0x0000003e, +0x00000000,0x00030047,0x0000003f,0x00000000, +0x00050048,0x00000040,0x00000000,0x0000000b, +0x00000000,0x00030047,0x00000040,0x00000002, +0x00040048,0x00000043,0x00000000,0x00000005, +0x00040048,0x00000043,0x00000000,0x00000000, +0x00050048,0x00000043,0x00000000,0x00000023, +0x00000000,0x00050048,0x00000043,0x00000000, +0x00000007,0x00000010,0x00030047,0x00000043, +0x00000002,0x00040047,0x00000045,0x00000022, +0x00000000,0x00040047,0x00000045,0x00000021, +0x00000000,0x00030047,0x00000047,0x00000000, +0x00030047,0x00000049,0x00000000,0x00030047, +0x0000004d,0x00000000,0x00040047,0x0000004d, +0x0000001e,0x00000000,0x00030047,0x0000004f, +0x00000000,0x00030047,0x00000053,0x00000000, +0x00030047,0x00000054,0x00000000,0x00030047, +0x00000055,0x00000000,0x00030047,0x00000059, +0x00000000,0x00030047,0x0000005c,0x00000000, +0x00030047,0x0000005d,0x00000000,0x00030047, +0x00000060,0x00000000,0x00040047,0x00000060, +0x0000001e,0x00000001,0x00030047,0x00000071, +0x00000000,0x00030047,0x00000074,0x00000000, +0x00040047,0x00000074,0x0000001e,0x00000004, +0x00030047,0x00000076,0x00000000,0x00040047, +0x00000076,0x0000001e,0x00000004,0x00030047, +0x00000077,0x00000000,0x00030047,0x00000078, +0x00000000,0x00040047,0x00000078,0x0000001e, +0x00000005,0x00030047,0x00000079,0x00000000, +0x00040047,0x00000079,0x0000001e,0x00000003, +0x00030047,0x0000007a,0x00000000,0x00020013, +0x00000002,0x00030021,0x00000003,0x00000002, +0x00030016,0x00000006,0x00000020,0x00040017, +0x00000007,0x00000006,0x00000004,0x00040018, +0x00000008,0x00000007,0x00000004,0x0003001e, +0x0000000b,0x00000008,0x00040020,0x0000000c, +0x00000002,0x0000000b,0x0004003b,0x0000000c, +0x0000000d,0x00000002,0x00040015,0x0000000e, +0x00000020,0x00000001,0x0004002b,0x0000000e, +0x0000000f,0x00000000,0x00040020,0x00000010, +0x00000002,0x00000008,0x0003001d,0x00000013, +0x00000008,0x0003001e,0x00000014,0x00000013, +0x00040020,0x00000015,0x00000002,0x00000014, +0x0004003b,0x00000015,0x00000016,0x00000002, +0x00040015,0x00000017,0x00000020,0x00000000, +0x00040020,0x00000018,0x00000001,0x00000017, +0x0004003b,0x00000018,0x00000019,0x00000001, +0x00040020,0x00000021,0x00000001,0x00000007, +0x0004003b,0x00000021,0x00000022,0x00000001, +0x00040017,0x00000025,0x00000006,0x00000003, +0x00040020,0x00000029,0x00000001,0x00000025, +0x0004003b,0x00000029,0x0000002a,0x00000001, +0x0004002b,0x00000006,0x0000002c,0x00000000, +0x0004003b,0x00000021,0x00000036,0x00000001, +0x0003001e,0x00000040,0x00000007,0x00040020, +0x00000041,0x00000003,0x00000040,0x0004003b, +0x00000041,0x00000042,0x00000003,0x0003001e, +0x00000043,0x00000008,0x00040020,0x00000044, +0x00000002,0x00000043,0x0004003b,0x00000044, +0x00000045,0x00000002,0x00040020,0x0000004a, +0x00000003,0x00000007,0x00040020,0x0000004c, +0x00000003,0x00000025,0x0004003b,0x0000004c, +0x0000004d,0x00000003,0x0004002b,0x00000017, +0x00000050,0x00000003,0x00040020,0x0000005a, +0x00000001,0x00000006,0x00040018,0x0000005e, +0x00000025,0x00000003,0x00040020,0x0000005f, +0x00000003,0x0000005e,0x0004003b,0x0000005f, +0x00000060,0x00000003,0x00040017,0x00000072, +0x00000006,0x00000002,0x00040020,0x00000073, +0x00000003,0x00000072,0x0004003b,0x00000073, +0x00000074,0x00000003,0x00040020,0x00000075, +0x00000001,0x00000072,0x0004003b,0x00000075, +0x00000076,0x00000001,0x0004003b,0x0000004a, +0x00000078,0x00000003,0x0004003b,0x00000021, +0x00000079,0x00000001,0x00050036,0x00000002, +0x00000004,0x00000000,0x00000003,0x000200f8, +0x00000005,0x00050041,0x00000010,0x00000011, +0x0000000d,0x0000000f,0x0004003d,0x00000008, +0x00000012,0x00000011,0x0004003d,0x00000017, +0x0000001a,0x00000019,0x00060041,0x00000010, +0x0000001b,0x00000016,0x0000000f,0x0000001a, +0x0004003d,0x00000008,0x0000001c,0x0000001b, +0x00050092,0x00000008,0x0000001d,0x00000012, +0x0000001c,0x0004003d,0x00000007,0x00000023, +0x00000022,0x00050091,0x00000007,0x00000024, +0x0000001d,0x00000023,0x0004003d,0x00000025, +0x0000002b,0x0000002a,0x00050051,0x00000006, +0x0000002d,0x0000002b,0x00000000,0x00050051, +0x00000006,0x0000002e,0x0000002b,0x00000001, +0x00050051,0x00000006,0x0000002f,0x0000002b, +0x00000002,0x00070050,0x00000007,0x00000030, +0x0000002d,0x0000002e,0x0000002f,0x0000002c, +0x00050091,0x00000007,0x00000031,0x0000001d, +0x00000030,0x0008004f,0x00000025,0x00000032, +0x00000031,0x00000031,0x00000000,0x00000001, +0x00000002,0x0006000c,0x00000025,0x00000033, +0x00000001,0x00000045,0x00000032,0x0004003d, +0x00000007,0x00000037,0x00000036,0x00050051, +0x00000006,0x00000039,0x00000037,0x00000000, +0x00050051,0x00000006,0x0000003a,0x00000037, +0x00000001,0x00050051,0x00000006,0x0000003b, +0x00000037,0x00000002,0x00070050,0x00000007, +0x0000003c,0x00000039,0x0000003a,0x0000003b, +0x0000002c,0x00050091,0x00000007,0x0000003d, +0x0000001d,0x0000003c,0x0008004f,0x00000025, +0x0000003e,0x0000003d,0x0000003d,0x00000000, +0x00000001,0x00000002,0x0006000c,0x00000025, +0x0000003f,0x00000001,0x00000045,0x0000003e, +0x00050041,0x00000010,0x00000046,0x00000045, +0x0000000f,0x0004003d,0x00000008,0x00000047, +0x00000046,0x00050091,0x00000007,0x00000049, +0x00000047,0x00000024,0x00050041,0x0000004a, +0x0000004b,0x00000042,0x0000000f,0x0003003e, +0x0000004b,0x00000049,0x0008004f,0x00000025, +0x0000004f,0x00000024,0x00000024,0x00000000, +0x00000001,0x00000002,0x00050051,0x00000006, +0x00000053,0x00000024,0x00000003,0x00060050, +0x00000025,0x00000054,0x00000053,0x00000053, +0x00000053,0x00050088,0x00000025,0x00000055, +0x0000004f,0x00000054,0x0003003e,0x0000004d, +0x00000055,0x0007000c,0x00000025,0x00000059, +0x00000001,0x00000044,0x00000033,0x0000003f, +0x00050041,0x0000005a,0x0000005b,0x00000036, +0x00000050,0x0004003d,0x00000006,0x0000005c, +0x0000005b,0x0005008e,0x00000025,0x0000005d, +0x00000059,0x0000005c,0x00060050,0x0000005e, +0x00000071,0x0000003f,0x0000005d,0x00000033, +0x0003003e,0x00000060,0x00000071,0x0004003d, +0x00000072,0x00000077,0x00000076,0x0003003e, +0x00000074,0x00000077,0x0004003d,0x00000007, +0x0000007a,0x00000079,0x0003003e,0x00000078, +0x0000007a,0x000100fd,0x00010038} diff --git a/src/conformance/framework/pbr/Shaders/PbrVertexShader_glsl.spv.license b/src/conformance/framework/pbr/Shaders/PbrVertexShader_glsl.spv.license new file mode 100644 index 00000000..0721500c --- /dev/null +++ b/src/conformance/framework/pbr/Shaders/PbrVertexShader_glsl.spv.license @@ -0,0 +1,5 @@ +Copyright (c) 2016 - 2017 Mohamad Moneimne and Contributors +Copyright (C) Microsoft Corporation. All Rights Reserved +Copyright 2023, The Khronos Group, Inc. + +SPDX-License-Identifier: MIT diff --git a/src/conformance/framework/pbr/Shaders/PbrVertexShader_glsl.vert b/src/conformance/framework/pbr/Shaders/PbrVertexShader_glsl.vert new file mode 100644 index 00000000..c6171340 --- /dev/null +++ b/src/conformance/framework/pbr/Shaders/PbrVertexShader_glsl.vert @@ -0,0 +1,77 @@ +#version 450 +precision mediump float; +precision highp int; +// Copyright (c) 2016 - 2017 Mohamad Moneimne and Contributors +// Copyright (C) Microsoft Corporation. All Rights Reserved +// Copyright 2023, The Khronos Group, Inc. +// +// SPDX-License-Identifier: MIT + +layout(binding = 0, std140) uniform type_SceneBuffer +{ + mat4 ViewProjection; // offset 0 + vec3 EyePosition; // offset 64 + // implicit 4 bytes of padding + vec3 LightDirection; // offset 80 + // implicit 4 bytes of padding + vec3 LightColor; // offset 96 + // implicit 4 bytes of padding + uint _pad; // explicit 4 bytes of padding so this matches the d3d offsets too + lowp uint NumSpecularMipLevels; // offset 112 + // implicit 12 bytes of padding +} +SceneBuffer; + + +layout(binding = 1, std140) uniform type_ModelConstantBuffer +{ + mat4 ModelToWorld; +} +ModelConstantBuffer; + +layout(binding = 3, std430) readonly buffer type_StructuredBuffer_mat4v4float +{ + mat4 _m0[]; +} +Transforms; + + +layout(location = 0) in vec4 in_var_POSITION; +layout(location = 1) in vec3 in_var_NORMAL; +layout(location = 2) in vec4 in_var_TANGENT; +layout(location = 3) in vec4 in_var_COLOR0; +layout(location = 4) in vec2 in_var_TEXCOORD0; +layout(location = 5) in mediump uint in_var_TRANSFORMINDEX; + +// output of vertex shader, input to fragment shader +layout(location = 0) out vec3 varying_POSITION1; +layout(location = 1) out mat3 varying_TANGENT; +layout(location = 4) out vec2 varying_TEXCOORD0; +layout(location = 5) out vec4 varying_COLOR0; + +out gl_PerVertex +{ + vec4 gl_Position; +}; + +void main() +{ + mat4 modelTransform = ModelConstantBuffer.ModelToWorld * Transforms._m0[(in_var_TRANSFORMINDEX)]; + vec4 transformedPosWorld = modelTransform * in_var_POSITION; + vec3 normalW = normalize((modelTransform * vec4(in_var_NORMAL, 0.0)).xyz); + vec3 tangentW = normalize((modelTransform * vec4(in_var_TANGENT.xyz, 0.0)).xyz); + // aka output.PositionProj + gl_Position = SceneBuffer.ViewProjection * transformedPosWorld; + // aka output.PositionWorld + varying_POSITION1 = transformedPosWorld.xyz / vec3(transformedPosWorld.w); + + vec3 bitangentW = cross(normalW, tangentW) * in_var_TANGENT.w; + varying_TANGENT = mat3(tangentW, bitangentW, normalW); + +#ifdef VULKAN + varying_TEXCOORD0 = in_var_TEXCOORD0; +#else + varying_TEXCOORD0 = vec2(in_var_TEXCOORD0.x, (1.0 - in_var_TEXCOORD0.y)); +#endif + varying_COLOR0 = in_var_COLOR0; +} diff --git a/src/conformance/framework/pbr/Shaders/Shared.hlsl b/src/conformance/framework/pbr/Shaders/Shared.hlsl new file mode 100644 index 00000000..6e4b0321 --- /dev/null +++ b/src/conformance/framework/pbr/Shaders/Shared.hlsl @@ -0,0 +1,14 @@ +//////////////////////////////////////////////////////////////////////////////// +// Copyright (C) Microsoft Corporation. All Rights Reserved +// Licensed under the MIT License. See License.txt in the project root for license information. +// +// SPDX-License-Identifier: MIT + +cbuffer SceneBuffer : register(b0) +{ + float4x4 ViewProjection : packoffset(c0); + float3 EyePosition : packoffset(c4); + float3 LightDirection : packoffset(c5); + float3 LightColor : packoffset(c6); + uint NumSpecularMipLevels : packoffset(c7); +}; diff --git a/src/conformance/framework/pbr/Vulkan/VkCommon.h b/src/conformance/framework/pbr/Vulkan/VkCommon.h new file mode 100644 index 00000000..e766be90 --- /dev/null +++ b/src/conformance/framework/pbr/Vulkan/VkCommon.h @@ -0,0 +1,26 @@ +// Copyright 2023, The Khronos Group, Inc. +// +// Based in part on code that is: +// +// Copyright (C) Microsoft Corporation. All Rights Reserved +// Licensed under the MIT License. See License.txt in the project root for license information. +// +// SPDX-License-Identifier: MIT AND Apache-2.0 + +#pragma once + +#include "utilities/vulkan_scoped_handle.h" + +namespace Pbr +{ + struct VulkanTextureBundle + { + uint32_t width, height; + uint32_t mipLevels; + uint32_t layerCount; + Conformance::ScopedVkImage image{}; + VkImageLayout imageLayout{}; + Conformance::ScopedVkImageView view{}; + Conformance::ScopedVkDeviceMemory deviceMemory{}; + }; +} // namespace Pbr diff --git a/src/conformance/framework/pbr/Vulkan/VkMaterial.cpp b/src/conformance/framework/pbr/Vulkan/VkMaterial.cpp new file mode 100644 index 00000000..fe5b551a --- /dev/null +++ b/src/conformance/framework/pbr/Vulkan/VkMaterial.cpp @@ -0,0 +1,110 @@ +// Copyright 2023, The Khronos Group, Inc. +// +// Based in part on code that is: +// +// Copyright (C) Microsoft Corporation. All Rights Reserved +// Licensed under the MIT License. See License.txt in the project root for license information. +// +// SPDX-License-Identifier: MIT AND Apache-2.0 +#include "VkMaterial.h" + +#include "VkCommon.h" +#include "VkResources.h" +#include "VkTexture.h" + +#include "../PbrMaterial.h" + +#include "utilities/vulkan_scoped_handle.h" +#include "utilities/vulkan_utils.h" + +namespace Pbr +{ + VulkanMaterial::VulkanMaterial(Pbr::VulkanResources const& pbrResources) + { + static_assert((sizeof(ConstantBufferData) % 16) == 0, "Constant Buffer must be divisible by 16 bytes"); + m_constantBuffer.Init(pbrResources.GetDevice(), pbrResources.GetMemoryAllocator()); + m_constantBuffer.Create(1, VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT); + } + + std::shared_ptr VulkanMaterial::Clone(Pbr::VulkanResources const& pbrResources) const + { + auto clone = std::make_shared(pbrResources); + clone->CopyFrom(*this); + clone->m_textures = m_textures; + clone->m_samplers = m_samplers; + return clone; + } + + /* static */ + std::shared_ptr VulkanMaterial::CreateFlat(VulkanResources& pbrResources, RGBAColor baseColorFactor, + float roughnessFactor /* = 1.0f */, float metallicFactor /* = 0.0f */, + RGBColor emissiveFactor /* = XMFLOAT3(0, 0, 0) */) + { + std::shared_ptr material = std::make_shared(pbrResources); + + if (baseColorFactor.a < 1.0f) { // Alpha channel + material->SetAlphaBlended(BlendState::AlphaBlended); + } + + Pbr::VulkanMaterial::ConstantBufferData& parameters = material->Parameters(); + parameters.BaseColorFactor = baseColorFactor; + parameters.EmissiveFactor = emissiveFactor; + parameters.MetallicFactor = metallicFactor; + parameters.RoughnessFactor = roughnessFactor; + + auto defaultSampler = std::make_shared(Pbr::VulkanTexture::CreateSampler(pbrResources.GetDevice()), + pbrResources.GetDevice()); + auto setDefaultTexture = [&](Pbr::ShaderSlots::PSMaterial slot, Pbr::RGBAColor defaultRGBA) { + material->SetTexture(slot, pbrResources.CreateTypedSolidColorTexture(defaultRGBA), defaultSampler); + }; + + setDefaultTexture(ShaderSlots::BaseColor, RGBA::White); + setDefaultTexture(ShaderSlots::MetallicRoughness, RGBA::White); + // No occlusion. + setDefaultTexture(ShaderSlots::Occlusion, RGBA::White); + // Flat normal. + setDefaultTexture(ShaderSlots::Normal, RGBA::FlatNormal); + setDefaultTexture(ShaderSlots::Emissive, RGBA::White); + + return material; + } + + void VulkanMaterial::SetTexture(ShaderSlots::PSMaterial slot, std::shared_ptr textureView, + std::shared_ptr sampler) + { + m_textures[slot] = textureView; + + if (sampler) { + m_samplers[slot] = sampler; + } + } + + // Get the material constant buffer for binding + VkDescriptorBufferInfo VulkanMaterial::GetMaterialConstantBuffer() + { + return m_constantBuffer.MakeDescriptor(); + } + + // Get the combined image sampler descriptors for binding + std::vector VulkanMaterial::GetTextureDescriptors() + { + std::vector ret(TextureCount); + + for (size_t i = 0; i < TextureCount; i++) { + ret[i].sampler = m_samplers[i]->get(); + ret[i].imageView = m_textures[i]->view.get(); + ret[i].imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL; + } + + return ret; + } + + void VulkanMaterial::UpdateBuffer() + { + // If the parameters of the constant buffer have changed, update the constant buffer. + if (m_parametersChanged) { + m_parametersChanged = false; + m_constantBuffer.Update({&m_parameters, 1}); + } + } +} // namespace Pbr diff --git a/src/conformance/framework/pbr/Vulkan/VkMaterial.h b/src/conformance/framework/pbr/Vulkan/VkMaterial.h new file mode 100644 index 00000000..ac7b83b8 --- /dev/null +++ b/src/conformance/framework/pbr/Vulkan/VkMaterial.h @@ -0,0 +1,72 @@ +// Copyright 2023, The Khronos Group, Inc. +// +// Based in part on code that is: +// +// Copyright (C) Microsoft Corporation. All Rights Reserved +// Licensed under the MIT License. See License.txt in the project root for license information. +// +// SPDX-License-Identifier: MIT AND Apache-2.0 +#pragma once + +#include "VkResources.h" +#include "VkTexture.h" + +#include "../PbrCommon.h" +#include "../PbrMaterial.h" +#include "../PbrSharedState.h" + +#include "utilities/vulkan_scoped_handle.h" +#include "utilities/vulkan_utils.h" + +#include + +#include +#include +#include +#include +#include +#include + +namespace Pbr +{ + struct VulkanResources; + struct VulkanTextureBundle; + + // A VulkanMaterial contains the metallic roughness parameters and textures. + // Primitives specify which VulkanMaterial to use when being rendered. + struct VulkanMaterial final : public Material + { + // Create a uninitialized material. Textures and shader coefficients must be set. + VulkanMaterial(Pbr::VulkanResources const& pbrResources); + ~VulkanMaterial() override = default; + + // Create a clone of this material. Shares the texture and sampler heap with this material. + std::shared_ptr Clone(Pbr::VulkanResources const& pbrResources) const; + + // Create a flat (no texture) material. + static std::shared_ptr CreateFlat(VulkanResources& pbrResources, RGBAColor baseColorFactor, + float roughnessFactor = 1.0f, float metallicFactor = 0.0f, + RGBColor emissiveFactor = RGB::Black); + + // Set a Metallic-Roughness texture. + void SetTexture(ShaderSlots::PSMaterial slot, std::shared_ptr textureView, + std::shared_ptr sampler); + + // Get the material constant buffer for binding + VkDescriptorBufferInfo GetMaterialConstantBuffer(); + // Get the combined image sampler descriptors for binding + std::vector GetTextureDescriptors(); + + // Update the material constant buffer + void UpdateBuffer(); + + std::string Name; + bool Hidden{false}; + + private: + static constexpr size_t TextureCount = ShaderSlots::NumMaterialSlots; + std::array, TextureCount> m_textures; + std::array, TextureCount> m_samplers; + Conformance::StructuredBuffer m_constantBuffer; + }; +} // namespace Pbr diff --git a/src/conformance/framework/pbr/Vulkan/VkModel.cpp b/src/conformance/framework/pbr/Vulkan/VkModel.cpp new file mode 100644 index 00000000..d92221e2 --- /dev/null +++ b/src/conformance/framework/pbr/Vulkan/VkModel.cpp @@ -0,0 +1,116 @@ +// Copyright 2022-2023, The Khronos Group, Inc. +// +// Based in part on code that is: +// Copyright (C) Microsoft Corporation. All Rights Reserved +// Licensed under the MIT License. See License.txt in the project root for license information. +// +// SPDX-License-Identifier: MIT AND Apache-2.0 + +#include "VkModel.h" + +#include "VkMaterial.h" +#include "VkPrimitive.h" +#include "VkResources.h" + +#include "../PbrHandles.h" +#include "../PbrModel.h" + +#include "utilities/vulkan_scoped_handle.h" +#include "utilities/vulkan_utils.h" + +#include +#include +#include +#include +#include + +namespace Pbr +{ + + void VulkanModel::Render(Pbr::VulkanResources& pbrResources, Conformance::CmdBuffer& directCommandBuffer, VkRenderPass renderPass, + VkSampleCountFlagBits sampleCount) + { + pbrResources.UpdateBuffer(); + UpdateTransforms(pbrResources); + + auto primitives = GetPrimitives(); + if (m_descriptorSets.size() < primitives.size()) { + AllocateDescriptorSets(pbrResources, (uint32_t)primitives.size()); + } + + for (size_t i = 0; i < primitives.size(); i++) { + PrimitiveHandle primitiveHandle = primitives[i]; + VkDescriptorSet descriptorSet = m_descriptorSets[i]; + const Pbr::VulkanPrimitive& primitive = pbrResources.GetPrimitive(primitiveHandle); + if (primitive.GetMaterial()->Hidden) + continue; + + primitive.Render(directCommandBuffer, pbrResources, descriptorSet, renderPass, sampleCount, + m_modelTransformsStructuredBuffer.MakeDescriptor()); + } + + // Expect the caller to reset other state, but the geometry shader is cleared specially. + //context->GSSetShader(nullptr, nullptr, 0); + } + + void VulkanModel::AllocateDescriptorSets(Pbr::VulkanResources& pbrResources, uint32_t numSets) + { + m_descriptorPool.adopt(pbrResources.MakeDescriptorPool(numSets), pbrResources.GetDevice()); + + auto descriptorSetLayout = pbrResources.GetDescriptorSetLayout(); + + std::vector layouts(numSets, descriptorSetLayout); + VkDescriptorSetAllocateInfo allocInfo{}; + allocInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO; + allocInfo.descriptorPool = m_descriptorPool.get(); + allocInfo.descriptorSetCount = numSets; + allocInfo.pSetLayouts = layouts.data(); + + m_descriptorSets.resize(numSets); + XRC_CHECK_THROW_VKCMD(vkAllocateDescriptorSets(pbrResources.GetDevice(), &allocInfo, m_descriptorSets.data())); + } + + void VulkanModel::UpdateTransforms(Pbr::VulkanResources& pbrResources) + { + const auto& nodes = GetNodes(); + const uint32_t newTotalModifyCount = std::accumulate(nodes.begin(), nodes.end(), 0, [](uint32_t sumChangeCount, const Node& node) { + return sumChangeCount + node.GetModifyCount(); + }); + + // If none of the node transforms have changed, no need to recompute/update the model transform structured buffer. + if (newTotalModifyCount != TotalModifyCount || m_modelTransformsStructuredBufferInvalid) { + if (m_modelTransformsStructuredBufferInvalid) // The structured buffer is reset when a Node is added. + { + XrMatrix4x4f identityMatrix; + XrMatrix4x4f_CreateIdentity(&identityMatrix); // or better yet poison it + m_modelTransforms.resize(nodes.size(), identityMatrix); + + // Create/recreate the structured buffer and SRV which holds the node transforms. + size_t elemSize = sizeof(decltype(m_modelTransforms)::value_type); + uint32_t count = (uint32_t)(m_modelTransforms.size()); + uint32_t size = (uint32_t)(count * elemSize); + + m_modelTransformsStructuredBuffer.Init(pbrResources.GetDevice(), pbrResources.GetMemoryAllocator()); + m_modelTransformsStructuredBuffer.Create(size, VK_BUFFER_USAGE_STORAGE_BUFFER_BIT); + + m_modelTransformsStructuredBufferInvalid = false; + } + + // Nodes are guaranteed to come after their parents, so each node transform can be multiplied by its parent transform in a single pass. + assert(nodes.size() == m_modelTransforms.size()); + XrMatrix4x4f identityMatrix; + XrMatrix4x4f_CreateIdentity(&identityMatrix); + for (const auto& node : nodes) { + assert(node.ParentNodeIndex == RootParentNodeIndex || node.ParentNodeIndex < node.Index); + const XrMatrix4x4f& parentTransform = + (node.ParentNodeIndex == RootParentNodeIndex) ? identityMatrix : m_modelTransforms[node.ParentNodeIndex]; + XrMatrix4x4f nodeTransform = node.GetTransform(); + XrMatrix4x4f_Multiply(&m_modelTransforms[node.Index], &parentTransform, &nodeTransform); + } + + // Update node transform structured buffer. + m_modelTransformsStructuredBuffer.Update(this->m_modelTransforms); + TotalModifyCount = newTotalModifyCount; + } + } +} // namespace Pbr diff --git a/src/conformance/framework/pbr/Vulkan/VkModel.h b/src/conformance/framework/pbr/Vulkan/VkModel.h new file mode 100644 index 00000000..33cc9128 --- /dev/null +++ b/src/conformance/framework/pbr/Vulkan/VkModel.h @@ -0,0 +1,51 @@ +// Copyright 2022-2023, The Khronos Group, Inc. +// +// Based in part on code that is: +// Copyright (C) Microsoft Corporation. All Rights Reserved +// Licensed under the MIT License. See License.txt in the project root for license information. +// +// SPDX-License-Identifier: MIT AND Apache-2.0 + +#pragma once +#include "VkResources.h" + +#include "../PbrHandles.h" +#include "../PbrModel.h" + +#include "common/xr_linear.h" +#include "utilities/vulkan_scoped_handle.h" +#include "utilities/vulkan_utils.h" + +#include + +#include +#include + +namespace Pbr +{ + + struct VulkanPrimitive; + struct VulkanResources; + + class VulkanModel final : public Model + { + public: + // Render the model. + void Render(Pbr::VulkanResources& pbrResources, Conformance::CmdBuffer& directCommandBuffer, VkRenderPass renderPass, + VkSampleCountFlagBits sampleCount); + + private: + void AllocateDescriptorSets(Pbr::VulkanResources& pbrResources, uint32_t numSets); + // Updated the transforms used to render the model. This needs to be called any time a node transform is changed. + void UpdateTransforms(Pbr::VulkanResources& pbrResources); + + // Temporary buffer holds the world transforms, computed from the node's local transforms. + mutable std::vector m_modelTransforms; + mutable Conformance::StructuredBuffer m_modelTransformsStructuredBuffer; + // mutable Microsoft::WRL::ComPtr m_modelTransformsResourceViewHeap; + Conformance::ScopedVkDescriptorPool m_descriptorPool; + std::vector m_descriptorSets; + + mutable uint32_t TotalModifyCount{0}; + }; +} // namespace Pbr diff --git a/src/conformance/framework/pbr/Vulkan/VkPipelineStates.cpp b/src/conformance/framework/pbr/Vulkan/VkPipelineStates.cpp new file mode 100644 index 00000000..a9867319 --- /dev/null +++ b/src/conformance/framework/pbr/Vulkan/VkPipelineStates.cpp @@ -0,0 +1,152 @@ +// Copyright 2023, The Khronos Group, Inc. +// +// Based in part on code that is: +// +// Copyright (C) Microsoft Corporation. All Rights Reserved +// Licensed under the MIT License. See License.txt in the project root for license information. +// +// SPDX-License-Identifier: MIT AND Apache-2.0 + +#include "VkPipelineStates.h" + +#include "../PbrSharedState.h" + +#include "utilities/array_size.h" +#include "utilities/vulkan_scoped_handle.h" +#include "utilities/vulkan_utils.h" + +#include + +#include +#include +#include + +namespace Pbr +{ + Conformance::Pipeline& VulkanPipelines::GetOrCreatePipeline(VkRenderPass renderPass, VkSampleCountFlagBits sampleCount, + FillMode fillMode, FrontFaceWindingOrder frontFaceWindingOrder, + BlendState blendState, DoubleSided doubleSided, + DepthDirection depthDirection) + { + const PipelineStateKey state{renderPass, sampleCount, fillMode, frontFaceWindingOrder, blendState, doubleSided, depthDirection}; + auto iter = m_pipelines.find(state); + if (iter != m_pipelines.end()) { + return iter->second; + } + + static_assert(std::is_same>::value, + "This function copies all fields to the desc and must be updated if the fieldset is changed"); + + VkDynamicState dynamicStates[] = {VK_DYNAMIC_STATE_SCISSOR, VK_DYNAMIC_STATE_VIEWPORT}; + VkPipelineDynamicStateCreateInfo dynamicState{VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO}; + dynamicState.dynamicStateCount = (uint32_t)Conformance::ArraySize(dynamicStates); + dynamicState.pDynamicStates = dynamicStates; + + VkPipelineVertexInputStateCreateInfo vi{VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO}; + vi.vertexBindingDescriptionCount = (uint32_t)m_vertexInputBindDesc.size(); + vi.pVertexBindingDescriptions = m_vertexInputBindDesc.data(); + vi.vertexAttributeDescriptionCount = (uint32_t)m_vertexAttrDesc.size(); + vi.pVertexAttributeDescriptions = m_vertexAttrDesc.data(); + + VkPipelineInputAssemblyStateCreateInfo ia{VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO}; + ia.primitiveRestartEnable = VK_FALSE; + ia.topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST; + + VkPipelineRasterizationStateCreateInfo rs{VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO}; + rs.polygonMode = (fillMode == FillMode::Wireframe) ? VK_POLYGON_MODE_LINE : VK_POLYGON_MODE_FILL; + rs.cullMode = (doubleSided == DoubleSided::DoubleSided) ? VK_CULL_MODE_NONE : VK_CULL_MODE_BACK_BIT; + rs.frontFace = VK_FRONT_FACE_CLOCKWISE; + rs.frontFace = + (frontFaceWindingOrder == FrontFaceWindingOrder::CounterClockWise) ? VK_FRONT_FACE_COUNTER_CLOCKWISE : VK_FRONT_FACE_CLOCKWISE; + rs.depthClampEnable = VK_FALSE; + rs.rasterizerDiscardEnable = VK_FALSE; + rs.depthBiasEnable = VK_FALSE; + rs.depthBiasConstantFactor = 0; + rs.depthBiasClamp = 0; + rs.depthBiasSlopeFactor = 0; + rs.lineWidth = 1.0f; + + VkPipelineColorBlendAttachmentState attachState{}; + + if (blendState == BlendState::AlphaBlended) { + attachState.blendEnable = 1; + attachState.srcColorBlendFactor = VK_BLEND_FACTOR_SRC_ALPHA; + attachState.dstColorBlendFactor = VK_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA; + attachState.colorBlendOp = VK_BLEND_OP_ADD; + attachState.srcAlphaBlendFactor = VK_BLEND_FACTOR_ZERO; + attachState.dstAlphaBlendFactor = VK_BLEND_FACTOR_ONE; + attachState.alphaBlendOp = VK_BLEND_OP_ADD; + attachState.colorWriteMask = + VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT | VK_COLOR_COMPONENT_B_BIT | VK_COLOR_COMPONENT_A_BIT; + } + else { + attachState.blendEnable = 0; + attachState.srcColorBlendFactor = VK_BLEND_FACTOR_ONE; + attachState.dstColorBlendFactor = VK_BLEND_FACTOR_ZERO; + attachState.colorBlendOp = VK_BLEND_OP_ADD; + attachState.srcAlphaBlendFactor = VK_BLEND_FACTOR_ONE; + attachState.dstAlphaBlendFactor = VK_BLEND_FACTOR_ZERO; + attachState.alphaBlendOp = VK_BLEND_OP_ADD; + attachState.colorWriteMask = + VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT | VK_COLOR_COMPONENT_B_BIT | VK_COLOR_COMPONENT_A_BIT; + } + + VkPipelineColorBlendStateCreateInfo cb{VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO}; + cb.attachmentCount = 1; + cb.pAttachments = &attachState; + cb.logicOpEnable = VK_FALSE; + cb.logicOp = VK_LOGIC_OP_NO_OP; + cb.blendConstants[0] = 1.0f; + cb.blendConstants[1] = 1.0f; + cb.blendConstants[2] = 1.0f; + cb.blendConstants[3] = 1.0f; + + // Use dynamic scissor and viewport + VkPipelineViewportStateCreateInfo vp{VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO}; + vp.viewportCount = 1; + vp.scissorCount = 1; + + VkPipelineDepthStencilStateCreateInfo ds{VK_STRUCTURE_TYPE_PIPELINE_DEPTH_STENCIL_STATE_CREATE_INFO}; + ds.depthTestEnable = VK_TRUE; + ds.depthWriteEnable = blendState != BlendState::AlphaBlended; + ds.depthCompareOp = (depthDirection == DepthDirection::Reversed) ? VK_COMPARE_OP_GREATER : VK_COMPARE_OP_LESS; + ds.depthBoundsTestEnable = VK_FALSE; + ds.stencilTestEnable = VK_FALSE; + ds.front.failOp = VK_STENCIL_OP_KEEP; + ds.front.passOp = VK_STENCIL_OP_KEEP; + ds.front.depthFailOp = VK_STENCIL_OP_KEEP; + ds.front.compareOp = VK_COMPARE_OP_ALWAYS; + ds.back = ds.front; + ds.minDepthBounds = 0.0f; + ds.maxDepthBounds = 1.0f; + + VkPipelineMultisampleStateCreateInfo ms{VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO}; + ms.rasterizationSamples = sampleCount; + + VkGraphicsPipelineCreateInfo pipeInfo{VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO}; + + pipeInfo.stageCount = (uint32_t)m_pbrShader.shaderInfo.size(); + pipeInfo.pStages = m_pbrShader.shaderInfo.data(); + + pipeInfo.pVertexInputState = &vi; + pipeInfo.pInputAssemblyState = &ia; + pipeInfo.pTessellationState = nullptr; + pipeInfo.pViewportState = &vp; + pipeInfo.pRasterizationState = &rs; + pipeInfo.pMultisampleState = &ms; + pipeInfo.pDepthStencilState = &ds; + pipeInfo.pColorBlendState = &cb; + if (dynamicState.dynamicStateCount > 0) { + pipeInfo.pDynamicState = &dynamicState; + } + pipeInfo.layout = m_layout->get(); + pipeInfo.renderPass = renderPass; + pipeInfo.subpass = 0; + + Conformance::Pipeline& pipeline = m_pipelines.emplace(state, Conformance::Pipeline()).first->second; + pipeline.Create(m_device, pipeInfo); + + return pipeline; + } +} // namespace Pbr diff --git a/src/conformance/framework/pbr/Vulkan/VkPipelineStates.h b/src/conformance/framework/pbr/Vulkan/VkPipelineStates.h new file mode 100644 index 00000000..c87b925a --- /dev/null +++ b/src/conformance/framework/pbr/Vulkan/VkPipelineStates.h @@ -0,0 +1,65 @@ +// Copyright 2023, The Khronos Group, Inc. +// +// Based in part on code that is: +// +// Copyright (C) Microsoft Corporation. All Rights Reserved +// Licensed under the MIT License. See License.txt in the project root for license information. +// +// SPDX-License-Identifier: MIT AND Apache-2.0 + +#pragma once + +#include "../PbrSharedState.h" + +#include "utilities/vulkan_scoped_handle.h" +#include "utilities/vulkan_utils.h" + +#include +#include + +#include +#include +#include +#include + +namespace Pbr +{ + using nonstd::span; + + /// A factory/cache for pipeline state objects that differ in a few dimensions. + class VulkanPipelines + { + public: + /// Note: Make sure your shaders are global/static! + VulkanPipelines(VkDevice device, std::shared_ptr layout, + span vertexAttrDesc, + span vertexInputBindDesc, span pbrVS, + span pbrPS) + : m_device(device), m_layout(layout), m_vertexAttrDesc(vertexAttrDesc), m_vertexInputBindDesc(vertexInputBindDesc) + { + m_pbrShader.Init(m_device); + m_pbrShader.LoadVertexShader(pbrVS); + m_pbrShader.LoadFragmentShader(pbrPS); + } + + Conformance::Pipeline& GetOrCreatePipeline(VkRenderPass renderPass, VkSampleCountFlagBits sampleCount, FillMode fillMode, + FrontFaceWindingOrder frontFaceWindingOrder, BlendState blendState, + DoubleSided doubleSided, DepthDirection depthDirection); + + void DropStates() + { + m_pipelines.clear(); + } + + private: + VkDevice m_device; + using PipelineStateKey = + std::tuple; + std::shared_ptr m_layout; + span m_vertexAttrDesc; + span m_vertexInputBindDesc; + Conformance::ShaderProgram m_pbrShader; + + std::map m_pipelines; + }; +} // namespace Pbr diff --git a/src/conformance/framework/pbr/Vulkan/VkPrimitive.cpp b/src/conformance/framework/pbr/Vulkan/VkPrimitive.cpp new file mode 100644 index 00000000..35fa1729 --- /dev/null +++ b/src/conformance/framework/pbr/Vulkan/VkPrimitive.cpp @@ -0,0 +1,93 @@ +// Copyright 2023, The Khronos Group, Inc. +// +// Based in part on code that is: +// +// Copyright (C) Microsoft Corporation. All Rights Reserved +// Licensed under the MIT License. See License.txt in the project root for license information. +// +// SPDX-License-Identifier: MIT AND Apache-2.0 + +#include "VkPrimitive.h" + +#include "VkMaterial.h" +#include "VkResources.h" + +#include "../PbrCommon.h" +#include "../PbrSharedState.h" + +#include "utilities/vulkan_utils.h" + +#include +#include +#include + +namespace +{ + Conformance::VertexBuffer CreateVertexBuffer(VkDevice device, + const Conformance::MemoryAllocator& memoryAllocator, + const Pbr::PrimitiveBuilder& primitiveBuilder) + { + // Create Vertex Buffer + Conformance::VertexBuffer buffer; + std::vector attr{}; + buffer.Init(device, &memoryAllocator, attr); + buffer.Create((uint32_t)primitiveBuilder.Indices.size(), (uint32_t)primitiveBuilder.Vertices.size()); + buffer.UpdateIndices(primitiveBuilder.Indices); + buffer.UpdateVertices(primitiveBuilder.Vertices); + return buffer; + } +} // namespace + +namespace Pbr +{ + VulkanPrimitive::VulkanPrimitive(Conformance::VertexBuffer&& vertexAndIndexBuffer, + std::shared_ptr material) + : m_vertexAndIndexBuffer(std::move(vertexAndIndexBuffer)), m_material(std::move(material)) + { + } + + VulkanPrimitive::VulkanPrimitive(Pbr::VulkanResources const& pbrResources, const Pbr::PrimitiveBuilder& primitiveBuilder, + const std::shared_ptr& material) + : VulkanPrimitive(CreateVertexBuffer(pbrResources.GetDevice(), pbrResources.GetMemoryAllocator(), primitiveBuilder), material) + { + } + + void VulkanPrimitive::Render(Conformance::CmdBuffer& directCommandBuffer, VulkanResources& pbrResources, VkDescriptorSet descriptorSet, + VkRenderPass renderPass, VkSampleCountFlagBits sampleCount, VkDescriptorBufferInfo transformBuffer) const + { + GetMaterial()->UpdateBuffer(); + + auto materialConstantBuffer = GetMaterial()->GetMaterialConstantBuffer(); + auto materialTextures = GetMaterial()->GetTextureDescriptors(); + std::unique_ptr wds = + pbrResources.BuildWriteDescriptorSets(materialConstantBuffer, transformBuffer, materialTextures, descriptorSet); + + vkUpdateDescriptorSets(pbrResources.GetDevice(), static_cast(wds->writeDescriptorSets.size()), + wds->writeDescriptorSets.data(), 0, NULL); + + BlendState blendState = GetMaterial()->GetAlphaBlended(); + DoubleSided doubleSided = GetMaterial()->GetDoubleSided(); + + Conformance::Pipeline& pipeline = pbrResources.GetOrCreatePipeline(renderPass, sampleCount, blendState, doubleSided); + + vkCmdBindDescriptorSets(directCommandBuffer.buf, VK_PIPELINE_BIND_POINT_GRAPHICS, pbrResources.GetPipelineLayout(), 0, 1, + &descriptorSet, 0, nullptr); + vkCmdBindPipeline(directCommandBuffer.buf, VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline.pipe); + + const VkDeviceSize vertexOffset = 0; + const VkDeviceSize indexOffset = 0; + + // Bind index and vertex buffers + vkCmdBindIndexBuffer(directCommandBuffer.buf, m_vertexAndIndexBuffer.idx.buf, indexOffset, VK_INDEX_TYPE_UINT32); + + CHECKPOINT(); + + vkCmdBindVertexBuffers(directCommandBuffer.buf, 0, 1, &m_vertexAndIndexBuffer.vtx.buf, &vertexOffset); + + CHECKPOINT(); + + vkCmdDrawIndexed(directCommandBuffer.buf, m_vertexAndIndexBuffer.count.idx, 1, 0, 0, 0); + + CHECKPOINT(); + } +} // namespace Pbr diff --git a/src/conformance/framework/pbr/Vulkan/VkPrimitive.h b/src/conformance/framework/pbr/Vulkan/VkPrimitive.h new file mode 100644 index 00000000..93338e75 --- /dev/null +++ b/src/conformance/framework/pbr/Vulkan/VkPrimitive.h @@ -0,0 +1,73 @@ +// Copyright 2023, The Khronos Group, Inc. +// +// Based in part on code that is: +// +// Copyright (C) Microsoft Corporation. All Rights Reserved +// Licensed under the MIT License. See License.txt in the project root for license information. +// +// SPDX-License-Identifier: MIT AND Apache-2.0 + +#pragma once + +#include "VkMaterial.h" + +#include "../PbrCommon.h" + +#include "utilities/vulkan_scoped_handle.h" +#include "utilities/vulkan_utils.h" + +#include +#include + +#include +#include +#include +#include + +namespace Pbr +{ + struct VulkanMaterial; + struct VulkanResources; + + // A primitive holds a vertex buffer, index buffer, and a pointer to a PBR material. + struct VulkanPrimitive final + { + using Collection = std::vector; + + VulkanPrimitive() = delete; + VulkanPrimitive(Conformance::VertexBuffer&& vertexAndIndexBuffer, std::shared_ptr material); + VulkanPrimitive(Pbr::VulkanResources const& pbrResources, const Pbr::PrimitiveBuilder& primitiveBuilder, + const std::shared_ptr& material); + + // void UpdateBuffers(Pbr::VulkanResources& pbrResources, const Pbr::PrimitiveBuilder& primitiveBuilder); + + /// Get the material for the primitive. + std::shared_ptr& GetMaterial() + { + return m_material; + } + const std::shared_ptr& GetMaterial() const + { + return m_material; + } + + /// Replace the material for the primitive + void SetMaterial(std::shared_ptr material) + { + m_material = std::move(material); + } + + protected: + // friend class Model; + friend class VulkanModel; + void Render(Conformance::CmdBuffer& directCommandBuffer, VulkanResources& pbrResources, VkDescriptorSet descriptorSet, + VkRenderPass renderPass, VkSampleCountFlagBits sampleCount, VkDescriptorBufferInfo transformBuffer) const; + + /// The clone shares the vertex and index buffers - they are not cloned + VulkanPrimitive Clone(Pbr::VulkanResources const& pbrResources) const; + + private: + Conformance::VertexBuffer m_vertexAndIndexBuffer; + std::shared_ptr m_material; + }; +} // namespace Pbr diff --git a/src/conformance/framework/pbr/Vulkan/VkResources.cpp b/src/conformance/framework/pbr/Vulkan/VkResources.cpp new file mode 100644 index 00000000..adbf0da0 --- /dev/null +++ b/src/conformance/framework/pbr/Vulkan/VkResources.cpp @@ -0,0 +1,706 @@ +// Copyright 2023, The Khronos Group, Inc. +// +// Based in part on code that is: +// +// Copyright (C) Microsoft Corporation. All Rights Reserved +// Licensed under the MIT License. See License.txt in the project root for license information. +// +// SPDX-License-Identifier: MIT AND Apache-2.0 + +#include "VkResources.h" + +#include "GlslBuffers.h" +#include "VkCommon.h" +#include "VkMaterial.h" +#include "VkPipelineStates.h" +#include "VkPrimitive.h" +#include "VkTexture.h" +#include "VkTextureCache.h" + +#include "../../gltf/GltfHelper.h" +#include "../PbrCommon.h" +#include "../PbrHandles.h" +#include "../PbrSharedState.h" + +#include "common/vulkan_debug_object_namer.hpp" +#include "utilities/vulkan_scoped_handle.h" +#include "utilities/vulkan_utils.h" + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +namespace Pbr +{ + struct Material; +} // namespace Pbr + +// IWYU pragma: begin_keep +const uint32_t g_PbrVertexShader[] = SPV_PREFIX +#include + SPV_SUFFIX; + +const uint32_t g_PbrPixelShader[] = SPV_PREFIX +#include + SPV_SUFFIX; +// IWYU pragma: end_keep + +namespace +{ + + // TODO why are these here instead of in VkPipelines or something? + static constexpr VkVertexInputAttributeDescription c_attrDesc[6] = { + {0, 0, VK_FORMAT_R32G32B32_SFLOAT, offsetof(Pbr::Vertex, Position)}, + {1, 0, VK_FORMAT_R32G32B32_SFLOAT, offsetof(Pbr::Vertex, Normal)}, + {2, 0, VK_FORMAT_R32G32B32A32_SFLOAT, offsetof(Pbr::Vertex, Tangent)}, + {3, 0, VK_FORMAT_R32G32B32A32_SFLOAT, offsetof(Pbr::Vertex, Color0)}, + {4, 0, VK_FORMAT_R32G32_SFLOAT, offsetof(Pbr::Vertex, TexCoord0)}, + {5, 0, VK_FORMAT_R16_UINT, offsetof(Pbr::Vertex, ModelTransformIndex)}, + }; + + static constexpr VkVertexInputBindingDescription c_bindingDesc[1] = { + {0, sizeof(Pbr::Vertex), VK_VERTEX_INPUT_RATE_VERTEX}, + }; +} // namespace + +namespace Pbr +{ + using ImageKey = std::tuple; // Item1 is a pointer to the image, Item2 is sRGB. + + namespace PipelineLayout + { + enum BindingSection + { + SceneConstantBuffer, + ModelConstantBuffer, + MaterialConstantBuffer, + TransformsBuffer, + MaterialTextures, + GlobalTextures, + BindingSectionCount, + }; + + class VulkanDescriptorSetLayout + { + public: + VulkanDescriptorSetLayout() = default; + std::array m_bindings = {}; + std::array m_poolSizes = {}; + std::array m_writtenBindings = {}; + std::array m_sectionOffsets = {}; + std::array m_sectionSizes = {}; + + void AssertFullyInitialized() + { + for (int i = 0; i < BindingSectionCount; i++) { + if (m_sectionSizes[i] == 0) { + throw std::logic_error("VulkanDescriptorSetLayout: Not all layout sections were written"); + } + } + for (auto writtenBinding : m_writtenBindings) { + if (!writtenBinding) { + // this is legal but we aren't intentionally doing sparse bindings + throw std::logic_error("VulkanDescriptorSetLayout: Not all bindings were written"); + } + } + }; + + void SetBindings(BindingSection section, uint32_t bindIndex, VkDescriptorType descriptorType, VkShaderStageFlags stageFlags, + uint32_t count = 1) + { + // section ranges for indexing + m_sectionOffsets[section] = bindIndex; + m_sectionSizes[section] = count; + + for (uint32_t i = 0; i < count; i++) { + // descriptor pool layout + m_bindings[bindIndex + i].binding = bindIndex + i; + m_bindings[bindIndex + i].descriptorType = descriptorType; + m_bindings[bindIndex + i].descriptorCount = 1; + m_bindings[bindIndex + i].stageFlags = stageFlags; + m_bindings[bindIndex + i].pImmutableSamplers = nullptr; + + // pool sizes for allocation + m_poolSizes[bindIndex + i].type = descriptorType; + m_poolSizes[bindIndex + i].descriptorCount = 1; + + m_writtenBindings[bindIndex + i] = true; + } + }; + + VkDescriptorSetLayout CreateDescriptorSetLayout(VkDevice device) + { + AssertFullyInitialized(); + + VkDescriptorSetLayoutCreateInfo layoutInfo{}; + layoutInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO; + layoutInfo.bindingCount = (uint32_t)m_bindings.size(); + layoutInfo.pBindings = m_bindings.data(); + + VkDescriptorSetLayout descriptorSetLayout; + XRC_CHECK_THROW_VKCMD(vkCreateDescriptorSetLayout(device, &layoutInfo, nullptr, &descriptorSetLayout)); + + return descriptorSetLayout; + } + + VkDescriptorPool CreateDescriptorPool(VkDevice device, uint32_t maxSets) + { + AssertFullyInitialized(); + + VkDescriptorPoolCreateInfo descriptorPoolInfo{VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO}; + descriptorPoolInfo.maxSets = maxSets; + // maxSets is not a multiplier on pool sizes, so we need to scale them too + std::array poolSizesScaled = m_poolSizes; + for (VkDescriptorPoolSize& poolSize : poolSizesScaled) { + poolSize.descriptorCount *= maxSets; + } + descriptorPoolInfo.poolSizeCount = (uint32_t)poolSizesScaled.size(); + descriptorPoolInfo.pPoolSizes = poolSizesScaled.data(); + + VkDescriptorPool descriptorPool; + XRC_CHECK_THROW_VKCMD(vkCreateDescriptorPool(device, &descriptorPoolInfo, nullptr, &descriptorPool)); + return descriptorPool; + }; + }; + + class VulkanWriteDescriptorSetsBuilder + { + private: + VulkanDescriptorSetLayout m_layout; + std::unique_ptr m_wds{}; + std::array m_boundBindings{}; + + public: + VulkanWriteDescriptorSetsBuilder(VulkanDescriptorSetLayout layout, VkDescriptorSet dstSet) : m_layout(layout) + { + m_layout.AssertFullyInitialized(); + m_wds = std::make_unique(); + + for (size_t i = 0; i < m_layout.m_bindings.size(); ++i) { + m_wds->writeDescriptorSets[i] = VkWriteDescriptorSet{VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET}; + m_wds->writeDescriptorSets[i].dstSet = dstSet; + m_wds->writeDescriptorSets[i].dstBinding = m_layout.m_bindings[i].binding; + m_wds->writeDescriptorSets[i].descriptorCount = m_layout.m_bindings[i].descriptorCount; + m_wds->writeDescriptorSets[i].descriptorType = m_layout.m_bindings[i].descriptorType; + } + } + + void BindBuffers(BindingSection section, nonstd::span bufferInfos) + { + const size_t sectionOffset = m_layout.m_sectionOffsets[section]; + const size_t sectionSize = m_layout.m_sectionSizes[section]; + assert(bufferInfos.size() <= sectionSize); + (void)sectionSize; + + for (size_t indexInSection = 0; indexInSection < bufferInfos.size(); ++indexInSection) { + size_t bindingIndex = sectionOffset + indexInSection; + assert((m_wds->writeDescriptorSets[bindingIndex].descriptorType == VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER) || + (m_wds->writeDescriptorSets[bindingIndex].descriptorType == VK_DESCRIPTOR_TYPE_STORAGE_BUFFER) || + (m_wds->writeDescriptorSets[bindingIndex].descriptorType == VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC) || + (m_wds->writeDescriptorSets[bindingIndex].descriptorType == VK_DESCRIPTOR_TYPE_STORAGE_BUFFER_DYNAMIC)); + assert(bufferInfos[indexInSection].buffer != VK_NULL_HANDLE); + m_wds->BindBuffer(bindingIndex, bufferInfos[indexInSection]); + m_boundBindings[bindingIndex] = true; + } + } + + void BindImages(BindingSection section, nonstd::span imageInfos) + { + const size_t sectionOffset = m_layout.m_sectionOffsets[section]; + const size_t sectionSize = m_layout.m_sectionSizes[section]; + assert(sectionSize == imageInfos.size()); + (void)sectionSize; + + for (size_t indexInSection = 0; indexInSection < imageInfos.size(); ++indexInSection) { + size_t bindingIndex = sectionOffset + indexInSection; + assert((m_wds->writeDescriptorSets[bindingIndex].descriptorType == VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER) || + (m_wds->writeDescriptorSets[bindingIndex].descriptorType == VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE) || + (m_wds->writeDescriptorSets[bindingIndex].descriptorType == VK_DESCRIPTOR_TYPE_STORAGE_IMAGE)); + assert(imageInfos[indexInSection].imageView != VK_NULL_HANDLE); + assert(imageInfos[indexInSection].imageLayout != VK_IMAGE_LAYOUT_UNDEFINED); + assert(imageInfos[indexInSection].sampler != VK_NULL_HANDLE); + m_wds->BindImage(bindingIndex, imageInfos[indexInSection]); + m_boundBindings[bindingIndex] = true; + } + } + + std::unique_ptr Build() + { + for (auto boundBinding : m_boundBindings) { + if (!boundBinding) { + // this is legal but we aren't intentionally doing sparse bindings + throw std::logic_error("VulkanDescriptorSetLayout: Not all bindings were bound"); + } + } + return std::move(m_wds); + } + }; + + void SetupBindings(VulkanDescriptorSetLayout& layoutBuilder) + { + // constant buffers + layoutBuilder.SetBindings(SceneConstantBuffer, ShaderSlots::ConstantBuffers::Scene, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, + VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_FRAGMENT_BIT); + layoutBuilder.SetBindings(ModelConstantBuffer, ShaderSlots::ConstantBuffers::Model, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, + VK_SHADER_STAGE_VERTEX_BIT); + layoutBuilder.SetBindings(MaterialConstantBuffer, ShaderSlots::ConstantBuffers::Material, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, + VK_SHADER_STAGE_FRAGMENT_BIT); + + // transform buffer + layoutBuilder.SetBindings(TransformsBuffer, ShaderSlots::GLSL::VSResourceViewsOffset + ShaderSlots::Transforms, + VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, VK_SHADER_STAGE_VERTEX_BIT, // + ShaderSlots::NumVSResourceViews); + + // combined textures and samplers + layoutBuilder.SetBindings(MaterialTextures, ShaderSlots::GLSL::MaterialTexturesOffset, // + VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, VK_SHADER_STAGE_FRAGMENT_BIT, // + ShaderSlots::NumMaterialSlots); + layoutBuilder.SetBindings(GlobalTextures, ShaderSlots::GLSL::GlobalTexturesOffset, // + VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, VK_SHADER_STAGE_FRAGMENT_BIT, // + ShaderSlots::NumTextures - ShaderSlots::NumMaterialSlots); + } + + // very basic for now, can grow if needed + VkPipelineLayout CreatePipelineLayout(VkDevice device, const VkDescriptorSetLayout& descriptorSetLayout) + { + + VkPipelineLayoutCreateInfo pipelineLayoutCreateInfo{VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO}; + pipelineLayoutCreateInfo.setLayoutCount = 1; + pipelineLayoutCreateInfo.pSetLayouts = &descriptorSetLayout; + + VkPipelineLayout pipelineLayout; + XRC_CHECK_THROW_VKCMD(vkCreatePipelineLayout(device, &pipelineLayoutCreateInfo, nullptr, &pipelineLayout)); + + return pipelineLayout; + } + } // namespace PipelineLayout + + struct VulkanResources::Impl + { + void Initialize(const VulkanDebugObjectNamer& objnamer, VkPhysicalDevice physicalDevice_, VkDevice device_, + uint32_t queueFamilyIndex) + { + device = device_; + allocator.Init(physicalDevice_, device); + + Internal::ThrowIf(!copyCmdBuffer.Init(objnamer, device_, queueFamilyIndex), "Failed to create command buffer"); + copyCmdBuffer.Begin(); + + PipelineLayout::SetupBindings(VulkanLayout); + + Resources.DescriptorSetLayout = + std::make_shared(VulkanLayout.CreateDescriptorSetLayout(device), device); + Resources.PipelineLayout = std::make_shared( + PipelineLayout::CreatePipelineLayout(device, Resources.DescriptorSetLayout->get()), device); + + Resources.Pipelines = std::make_unique(device, Resources.PipelineLayout, c_attrDesc, c_bindingDesc, + g_PbrVertexShader, g_PbrPixelShader); + + // Set up the constant buffers. + + Resources.SceneBuffer.Init(device, allocator); + Resources.SceneBuffer.Create(1, VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT); + + Resources.ModelBuffer.Init(device, allocator); + Resources.ModelBuffer.Create(1, VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT); + + Resources.BrdfSampler.adopt(VulkanTexture::CreateSampler(device), device); + Resources.EnvironmentMapSampler.adopt(VulkanTexture::CreateSampler(device), device); + + Resources.SolidColorTextureCache = VulkanTextureCache{device}; + } + + void Reset() + { + allocator.Reset(); + + Resources.BrdfSampler.reset(); + Resources.EnvironmentMapSampler.reset(); + + device = VK_NULL_HANDLE; + } + + struct DeviceResources + { + VkDevice device; + + std::shared_ptr BrdfLut; + std::shared_ptr SpecularEnvironmentMap; + std::shared_ptr DiffuseEnvironmentMap; + mutable VulkanTextureCache SolidColorTextureCache; + + Conformance::StructuredBuffer SceneBuffer; + Conformance::StructuredBuffer ModelBuffer; + Conformance::ScopedVkSampler BrdfSampler; + Conformance::ScopedVkSampler EnvironmentMapSampler; + std::shared_ptr DescriptorSetLayout; + std::shared_ptr PipelineLayout; + std::unique_ptr Pipelines{}; + + std::vector StagingBuffers; + }; + + VulkanDebugObjectNamer namer; + VkDevice device{VK_NULL_HANDLE}; + Conformance::MemoryAllocator allocator{}; + Conformance::CmdBuffer copyCmdBuffer{}; + + PrimitiveCollection Primitives; + + DeviceResources Resources; + Glsl::SceneConstantBuffer SceneBuffer; + Glsl::ModelConstantBuffer ModelBuffer; + PipelineLayout::VulkanDescriptorSetLayout VulkanLayout{}; + + struct LoaderResources + { + // Create cache for reuse of textures (images, samplers, etc) when possible. + std::map> imageMap; + std::map> samplerMap; + }; + LoaderResources loaderResources; + }; + + VulkanResources::VulkanResources(const VulkanDebugObjectNamer& namer, VkPhysicalDevice physicalDevice, VkDevice device, + uint32_t queueFamilyIndex) + : m_impl(std::make_unique()) + { + m_impl->Initialize(namer, physicalDevice, device, queueFamilyIndex); + } + + VulkanResources::VulkanResources(VulkanResources&& resources) noexcept = default; + + VulkanResources::~VulkanResources() = default; + + /* IResources implementations */ + std::shared_ptr VulkanResources::CreateFlatMaterial(RGBAColor baseColorFactor, float roughnessFactor, float metallicFactor, + RGBColor emissiveFactor) + { + return VulkanMaterial::CreateFlat(*this, baseColorFactor, roughnessFactor, metallicFactor, emissiveFactor); + } + std::shared_ptr VulkanResources::CreateMaterial() + { + return std::make_shared(*this); + } + + // Create a Vulkan texture from a tinygltf Image. + static VulkanTextureBundle LoadGLTFImage(VulkanResources& pbrResources, const tinygltf::Image& image, bool sRGB) + { + // First convert the image to RGBA if it isn't already. + std::vector tempBuffer; + const uint8_t* rgbaBuffer = GltfHelper::ReadImageAsRGBA(image, &tempBuffer); + Internal::ThrowIf(rgbaBuffer == nullptr, "Failed to read image"); + + const VkFormat format = sRGB ? VK_FORMAT_R8G8B8A8_SRGB : VK_FORMAT_R8G8B8A8_UNORM; + return VulkanTexture::CreateTexture(pbrResources, rgbaBuffer, 4, image.width, image.height, format); + } + + static VkFilter ConvertMinFilter(int glMinFilter) + { + return glMinFilter == TINYGLTF_TEXTURE_FILTER_NEAREST + ? VK_FILTER_NEAREST + : glMinFilter == TINYGLTF_TEXTURE_FILTER_LINEAR + ? VK_FILTER_LINEAR + : glMinFilter == TINYGLTF_TEXTURE_FILTER_NEAREST_MIPMAP_NEAREST + ? VK_FILTER_NEAREST + : glMinFilter == TINYGLTF_TEXTURE_FILTER_LINEAR_MIPMAP_NEAREST + ? VK_FILTER_LINEAR + : glMinFilter == TINYGLTF_TEXTURE_FILTER_NEAREST_MIPMAP_LINEAR + ? VK_FILTER_NEAREST + : glMinFilter == TINYGLTF_TEXTURE_FILTER_LINEAR_MIPMAP_LINEAR ? VK_FILTER_LINEAR + : VK_FILTER_NEAREST; + } + + static VkSamplerMipmapMode ConvertMipFilter(int glMinFilter) + { + return glMinFilter == TINYGLTF_TEXTURE_FILTER_NEAREST + ? VK_SAMPLER_MIPMAP_MODE_NEAREST + : glMinFilter == TINYGLTF_TEXTURE_FILTER_LINEAR + ? VK_SAMPLER_MIPMAP_MODE_NEAREST + : glMinFilter == TINYGLTF_TEXTURE_FILTER_NEAREST_MIPMAP_NEAREST + ? VK_SAMPLER_MIPMAP_MODE_NEAREST + : glMinFilter == TINYGLTF_TEXTURE_FILTER_LINEAR_MIPMAP_NEAREST + ? VK_SAMPLER_MIPMAP_MODE_NEAREST + : glMinFilter == TINYGLTF_TEXTURE_FILTER_NEAREST_MIPMAP_LINEAR + ? VK_SAMPLER_MIPMAP_MODE_LINEAR + : glMinFilter == TINYGLTF_TEXTURE_FILTER_LINEAR_MIPMAP_LINEAR ? VK_SAMPLER_MIPMAP_MODE_LINEAR + : VK_SAMPLER_MIPMAP_MODE_NEAREST; + } + + static VkFilter ConvertMagFilter(int glMagFilter) + { + return glMagFilter == TINYGLTF_TEXTURE_FILTER_NEAREST + ? VK_FILTER_NEAREST + : glMagFilter == TINYGLTF_TEXTURE_FILTER_LINEAR ? VK_FILTER_LINEAR : VK_FILTER_NEAREST; + } + + // Create a Vulkan sampler from a tinygltf Sampler. + static VkSampler CreateGLTFSampler(VkDevice device, const tinygltf::Sampler& sampler) + { + VkSamplerCreateInfo info{VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO}; + + info.minFilter = ConvertMinFilter(sampler.minFilter); + info.mipmapMode = ConvertMipFilter(sampler.minFilter); + info.magFilter = ConvertMagFilter(sampler.magFilter); + info.addressModeU = sampler.wrapS == TINYGLTF_TEXTURE_WRAP_CLAMP_TO_EDGE + ? VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE + : sampler.wrapS == TINYGLTF_TEXTURE_WRAP_MIRRORED_REPEAT ? VK_SAMPLER_ADDRESS_MODE_MIRRORED_REPEAT + : VK_SAMPLER_ADDRESS_MODE_REPEAT; + info.addressModeV = sampler.wrapT == TINYGLTF_TEXTURE_WRAP_CLAMP_TO_EDGE + ? VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE + : sampler.wrapT == TINYGLTF_TEXTURE_WRAP_MIRRORED_REPEAT ? VK_SAMPLER_ADDRESS_MODE_MIRRORED_REPEAT + : VK_SAMPLER_ADDRESS_MODE_REPEAT; + info.addressModeW = VK_SAMPLER_ADDRESS_MODE_REPEAT; + info.maxAnisotropy = 1; + info.compareOp = VK_COMPARE_OP_ALWAYS; + info.minLod = 0; + info.maxLod = VK_LOD_CLAMP_NONE; + + VkSampler destSampler; + XRC_CHECK_THROW_VKCMD(vkCreateSampler(device, &info, nullptr, &destSampler)); + return destSampler; + } + + void VulkanResources::LoadTexture(const std::shared_ptr& material, Pbr::ShaderSlots::PSMaterial slot, + const tinygltf::Image* image, const tinygltf::Sampler* sampler, bool sRGB, Pbr::RGBAColor defaultRGBA) + { + auto pbrMaterial = std::dynamic_pointer_cast(material); + if (!pbrMaterial) { + throw std::logic_error("Wrong type of material"); + } + // Find or load the image referenced by the texture. + const ImageKey imageKey = std::make_tuple(image, sRGB); + std::shared_ptr textureView = + image != nullptr ? m_impl->loaderResources.imageMap[imageKey] : CreateTypedSolidColorTexture(defaultRGBA); + if (!textureView) // If not cached, load the image and store it in the texture cache. + { + // TODO: Generate mipmaps if sampler's minification filter (minFilter) uses mipmapping. + // TODO: If texture is not power-of-two and (sampler has wrapping=repeat/mirrored_repeat OR minFilter uses + // mipmapping), resize to power-of-two. + textureView = std::make_shared(LoadGLTFImage(*this, *image, sRGB)); + m_impl->loaderResources.imageMap[imageKey] = textureView; + } + + // Find or create the sampler referenced by the texture. + std::shared_ptr vkSampler = m_impl->loaderResources.samplerMap[sampler]; + if (!vkSampler) // If not cached, create the sampler and store it in the sampler cache. + { + + VkSampler raw = sampler != nullptr ? CreateGLTFSampler(GetDevice(), *sampler) : VulkanTexture::CreateSampler(GetDevice()); + vkSampler = std::make_shared(Conformance::ScopedVkSampler(raw, GetDevice())); + m_impl->loaderResources.samplerMap[sampler] = vkSampler; + } + + pbrMaterial->SetTexture(slot, textureView, vkSampler); + } + + void VulkanResources::DropLoaderCaches() + { + m_impl->loaderResources = {}; + } + + void VulkanResources::SetBrdfLut(std::shared_ptr brdfLut) + { + m_impl->Resources.BrdfLut = std::move(brdfLut); + } + + std::unique_ptr + VulkanResources::BuildWriteDescriptorSets(VkDescriptorBufferInfo materialConstantBuffer, VkDescriptorBufferInfo transformBuffer, + nonstd::span materialCombinedImageSamplers, VkDescriptorSet dstSet) + { + PipelineLayout::VulkanWriteDescriptorSetsBuilder builder(m_impl->VulkanLayout, dstSet); + + // SceneConstantBuffer + VkDescriptorBufferInfo sceneConstantBuffer[] = { + m_impl->Resources.SceneBuffer.MakeDescriptor(), + }; + builder.BindBuffers(PipelineLayout::SceneConstantBuffer, sceneConstantBuffer); + + // ModelConstantBuffer + VkDescriptorBufferInfo modelConstantBuffer[] = { + m_impl->Resources.ModelBuffer.MakeDescriptor(), + }; + builder.BindBuffers(PipelineLayout::ModelConstantBuffer, modelConstantBuffer); + + // MaterialConstantBuffer + builder.BindBuffers(PipelineLayout::MaterialConstantBuffer, {&materialConstantBuffer, 1}); + + // TransformsBuffer + builder.BindBuffers(PipelineLayout::TransformsBuffer, {&transformBuffer, 1}); + + // MaterialTextures + builder.BindImages(PipelineLayout::MaterialTextures, materialCombinedImageSamplers); + + // GlobalTextures + VkDescriptorImageInfo globalTextures[] = { + {m_impl->Resources.BrdfSampler.get(), m_impl->Resources.BrdfLut->view.get(), // + VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL}, + {m_impl->Resources.EnvironmentMapSampler.get(), m_impl->Resources.DiffuseEnvironmentMap->view.get(), // + VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL}, + {m_impl->Resources.EnvironmentMapSampler.get(), m_impl->Resources.SpecularEnvironmentMap->view.get(), // + VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL}, + }; + builder.BindImages(PipelineLayout::GlobalTextures, globalTextures); + + return builder.Build(); + } + + Conformance::Pipeline& VulkanResources::GetOrCreatePipeline(VkRenderPass renderPass, VkSampleCountFlagBits sampleCount, + BlendState blendState, DoubleSided doubleSided) + { + return m_impl->Resources.Pipelines->GetOrCreatePipeline(renderPass, sampleCount, m_sharedState.GetFillMode(), + m_sharedState.GetFrontFaceWindingOrder(), blendState, doubleSided, + m_sharedState.GetDepthDirection()); + } + + void VulkanResources::SetLight(XrVector3f direction, RGBColor diffuseColor) + { + m_impl->SceneBuffer.LightDirection = direction; + m_impl->SceneBuffer.LightDiffuseColor = diffuseColor; + } + + void VulkanResources::SetModelToWorld(XrMatrix4x4f modelToWorld) const + { + m_impl->ModelBuffer.ModelToWorld = modelToWorld; + m_impl->Resources.ModelBuffer.Update({&m_impl->ModelBuffer, 1}); + } + + void VulkanResources::SetViewProjection(XrMatrix4x4f view, XrMatrix4x4f projection) const + { + XrMatrix4x4f_Multiply(&m_impl->SceneBuffer.ViewProjection, &projection, &view); + + XrMatrix4x4f inv; + XrMatrix4x4f_Invert(&inv, &view); + m_impl->SceneBuffer.EyePosition = {inv.m[12], inv.m[13], inv.m[14]}; + } + + void VulkanResources::SetEnvironmentMap(std::shared_ptr specularEnvironmentMap, + std::shared_ptr diffuseEnvironmentMap) + { + m_impl->SceneBuffer.NumSpecularMipLevels = specularEnvironmentMap->mipLevels; + m_impl->Resources.SpecularEnvironmentMap = std::move(specularEnvironmentMap); + m_impl->Resources.DiffuseEnvironmentMap = std::move(diffuseEnvironmentMap); + } + + std::shared_ptr VulkanResources::CreateTypedSolidColorTexture(RGBAColor color) + { + return m_impl->Resources.SolidColorTextureCache.CreateTypedSolidColorTexture(*this, color); + } + + void VulkanResources::UpdateBuffer() const + { + m_impl->Resources.SceneBuffer.Update({&m_impl->SceneBuffer, 1}); + } + + PrimitiveHandle VulkanResources::MakePrimitive(const Pbr::PrimitiveBuilder& primitiveBuilder, + const std::shared_ptr& material) + { + auto typedMaterial = std::dynamic_pointer_cast(material); + if (!typedMaterial) { + throw std::logic_error("Got the wrong type of material"); + } + return m_impl->Primitives.emplace_back(*this, primitiveBuilder, typedMaterial); + } + + VulkanPrimitive& VulkanResources::GetPrimitive(PrimitiveHandle p) + { + return m_impl->Primitives[p]; + } + + const VulkanPrimitive& VulkanResources::GetPrimitive(PrimitiveHandle p) const + { + return m_impl->Primitives[p]; + } + + void VulkanResources::SetFillMode(FillMode mode) + { + m_sharedState.SetFillMode(mode); + } + + FillMode VulkanResources::GetFillMode() const + { + return m_sharedState.GetFillMode(); + } + + void VulkanResources::SetFrontFaceWindingOrder(FrontFaceWindingOrder windingOrder) + { + m_sharedState.SetFrontFaceWindingOrder(windingOrder); + } + + FrontFaceWindingOrder VulkanResources::GetFrontFaceWindingOrder() const + { + return m_sharedState.GetFrontFaceWindingOrder(); + } + + void VulkanResources::SetDepthDirection(DepthDirection depthDirection) + { + m_sharedState.SetDepthDirection(depthDirection); + } + + VkDevice VulkanResources::GetDevice() const + { + return m_impl->device; + } + + const Conformance::MemoryAllocator& VulkanResources::GetMemoryAllocator() const + { + return m_impl->allocator; + } + + const Conformance::CmdBuffer& VulkanResources::GetCopyCommandBuffer() const + { + return m_impl->copyCmdBuffer; + } + + VkPipelineLayout VulkanResources::GetPipelineLayout() const + { + return m_impl->Resources.PipelineLayout->get(); + } + + void VulkanResources::SubmitFrameResources(VkQueue queue) const + { + m_impl->copyCmdBuffer.End(); + m_impl->copyCmdBuffer.Exec(queue); + } + + void VulkanResources::Wait() const + { + m_impl->copyCmdBuffer.Wait(); + m_impl->copyCmdBuffer.Clear(); + m_impl->copyCmdBuffer.Begin(); + + for (auto stagingBuffer : m_impl->Resources.StagingBuffers) { + stagingBuffer.Reset(GetDevice()); + } + m_impl->Resources.StagingBuffers.clear(); + } + + const VulkanDebugObjectNamer& VulkanResources::GetDebugNamer() const + { + return m_impl->namer; + } + + VkDescriptorSetLayout VulkanResources::GetDescriptorSetLayout() const + { + return m_impl->Resources.DescriptorSetLayout->get(); + } + + VkDescriptorPool VulkanResources::MakeDescriptorPool(uint32_t maxSets) const + { + return m_impl->VulkanLayout.CreateDescriptorPool(GetDevice(), maxSets); + } + + void VulkanResources::DestroyAfterRender(Conformance::BufferAndMemory buffer) const + { + m_impl->Resources.StagingBuffers.push_back(buffer); + } + +} // namespace Pbr diff --git a/src/conformance/framework/pbr/Vulkan/VkResources.h b/src/conformance/framework/pbr/Vulkan/VkResources.h new file mode 100644 index 00000000..4391d2d8 --- /dev/null +++ b/src/conformance/framework/pbr/Vulkan/VkResources.h @@ -0,0 +1,190 @@ +// Copyright 2023, The Khronos Group, Inc. +// +// Based in part on code that is: +// +// Copyright (C) Microsoft Corporation. All Rights Reserved +// Licensed under the MIT License. See License.txt in the project root for license information. +// +// SPDX-License-Identifier: MIT AND Apache-2.0 +#pragma once + +#include "VkCommon.h" + +#include "../IResources.h" +#include "../PbrCommon.h" +#include "../PbrHandles.h" +#include "../PbrSharedState.h" + +#include "common/vulkan_debug_object_namer.hpp" +#include "common/xr_linear.h" +#include "utilities/vulkan_utils.h" + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +class VulkanDebugObjectNamer; + +namespace Conformance +{ + struct BufferAndMemory; + struct CmdBuffer; + struct MemoryAllocator; + struct Pipeline; +} // namespace Conformance +namespace tinygltf +{ + struct Image; + struct Sampler; +} // namespace tinygltf + +// namespace Conformance +// { +// struct MemoryAllocator; +// } +namespace Pbr +{ + struct Primitive; + struct Material; + struct VulkanTextureBundle; + + using Duration = std::chrono::high_resolution_clock::duration; + struct VulkanPrimitive; + struct VulkanMaterial; + + struct VulkanTextureAndSampler : public ITexture + { + ~VulkanTextureAndSampler() override = default; + /// Required + // Microsoft::WRL::ComPtr texture; + + /// Optional + // Vulkan_SAMPLER_DESC sampler; + bool samplerSet; + }; + + static constexpr size_t BindingCount = ShaderSlots::NumConstantBuffers + ShaderSlots::NumVSResourceViews + ShaderSlots::NumTextures; + class VulkanWriteDescriptorSets + { + public: + VulkanWriteDescriptorSets() = default; + std::array writeDescriptorSets{}; + + private: + std::array m_bufferInfos{}; + std::array m_imageInfos{}; + + public: + void BindBuffer(size_t bindingIndex, const VkDescriptorBufferInfo& bufferInfo) + { + m_bufferInfos[bindingIndex] = bufferInfo; + writeDescriptorSets[bindingIndex].pBufferInfo = &m_bufferInfos[bindingIndex]; + } + + void BindImage(size_t bindingIndex, const VkDescriptorImageInfo& imageInfo) + { + m_imageInfos[bindingIndex] = imageInfo; + writeDescriptorSets[bindingIndex].pImageInfo = &m_imageInfos[bindingIndex]; + } + + // self-referential + VulkanWriteDescriptorSets(VulkanWriteDescriptorSets const&) = delete; + VulkanWriteDescriptorSets(VulkanWriteDescriptorSets&&) = delete; + VulkanWriteDescriptorSets& operator=(VulkanWriteDescriptorSets const&) = delete; + VulkanWriteDescriptorSets& operator=(VulkanWriteDescriptorSets&&) = delete; + }; + + // Global PBR resources required for rendering a scene. + struct VulkanResources final : public IResources + { + VulkanResources(const VulkanDebugObjectNamer& namer, VkPhysicalDevice physicalDevice, VkDevice device, uint32_t queueFamilyIndex); + VulkanResources(VulkanResources&&) noexcept; + + ~VulkanResources() override; + + std::shared_ptr CreateFlatMaterial(RGBAColor baseColorFactor, float roughnessFactor = 1.0f, float metallicFactor = 0.0f, + RGBColor emissiveFactor = RGB::Black) override; + std::shared_ptr CreateMaterial() override; + std::shared_ptr CreateSolidColorTexture(RGBAColor color); + + void LoadTexture(const std::shared_ptr& pbrMaterial, Pbr::ShaderSlots::PSMaterial slot, const tinygltf::Image* image, + const tinygltf::Sampler* sampler, bool sRGB, Pbr::RGBAColor defaultRGBA) override; + PrimitiveHandle MakePrimitive(const Pbr::PrimitiveBuilder& primitiveBuilder, + const std::shared_ptr& material) override; + void DropLoaderCaches() override; + + // Sets the Bidirectional Reflectance Distribution Function Lookup Table texture, required by the shader to compute surface + // reflectance from the IBL. + void SetBrdfLut(std::shared_ptr brdfLut); + + // Get a pipeline state matching some parameters as well as the current settings inside VkResources + Conformance::Pipeline& GetOrCreatePipeline(VkRenderPass renderPass, VkSampleCountFlagBits sampleCount, BlendState blendState, + DoubleSided doubleSided); + + // Set the directional light. + void SetLight(XrVector3f direction, RGBColor diffuseColor); + + // Set the specular and diffuse image-based lighting (IBL) maps. ShaderResourceViews must be TextureCubes. + void SetEnvironmentMap(std::shared_ptr specularEnvironmentMap, + std::shared_ptr diffuseEnvironmentMap); + + // Set the current view and projection matrices. + void SetViewProjection(XrMatrix4x4f view, XrMatrix4x4f projection) const; + + // Many 1x1 pixel colored textures are used in the PBR system. This is used to create textures backed by a cache to reduce the + // number of textures created. + std::shared_ptr CreateTypedSolidColorTexture(RGBAColor color); + + // Update the scene buffer in GPU memory. + void UpdateBuffer() const; + + // Get the fence to wait on before executing any command list built on this Resources. + // std::pair GetFenceAndValue() const; + + // Set and update the model to world constant buffer value. + void SetModelToWorld(XrMatrix4x4f modelToWorld) const; + + VulkanPrimitive& GetPrimitive(PrimitiveHandle p); + const VulkanPrimitive& GetPrimitive(PrimitiveHandle p) const; + + // Set or get the shading and fill modes. + void SetFillMode(FillMode mode); + FillMode GetFillMode() const; + void SetFrontFaceWindingOrder(FrontFaceWindingOrder windingOrder); + FrontFaceWindingOrder GetFrontFaceWindingOrder() const; + void SetDepthDirection(DepthDirection depthDirection); + + VkDevice GetDevice() const; + const Conformance::MemoryAllocator& GetMemoryAllocator() const; + const Conformance::CmdBuffer& GetCopyCommandBuffer() const; + VkPipelineLayout GetPipelineLayout() const; + void SubmitFrameResources(VkQueue queue) const; + void Wait() const; + const VulkanDebugObjectNamer& GetDebugNamer() const; + VkDescriptorSetLayout GetDescriptorSetLayout() const; + VkDescriptorPool MakeDescriptorPool(uint32_t maxSets) const; + void DestroyAfterRender(Conformance::BufferAndMemory buffer) const; + + private: + std::unique_ptr + BuildWriteDescriptorSets(VkDescriptorBufferInfo materialConstantBuffer, VkDescriptorBufferInfo transformBuffer, + nonstd::span materialCombinedImageSamplers, VkDescriptorSet dstSet); + friend struct VulkanMaterial; + friend struct VulkanPrimitive; + + struct Impl; + + std::unique_ptr m_impl; + + SharedState m_sharedState; + }; +} // namespace Pbr diff --git a/src/conformance/framework/pbr/Vulkan/VkTexture.cpp b/src/conformance/framework/pbr/Vulkan/VkTexture.cpp new file mode 100644 index 00000000..bc334f07 --- /dev/null +++ b/src/conformance/framework/pbr/Vulkan/VkTexture.cpp @@ -0,0 +1,315 @@ +// Copyright 2023, The Khronos Group, Inc. +// +// Based in part on code that is: +// +// Copyright (C) Microsoft Corporation. All Rights Reserved +// Licensed under the MIT License. See License.txt in the project root for license information. +// +// SPDX-License-Identifier: MIT AND Apache-2.0 + +#include "VkTexture.h" + +#include "VkCommon.h" +#include "VkResources.h" +#include "stb_image.h" + +#include "../PbrCommon.h" + +#include "common/vulkan_debug_object_namer.hpp" +#include "utilities/throw_helpers.h" +#include "utilities/vulkan_scoped_handle.h" +#include "utilities/vulkan_utils.h" + +#include +#include +#include +#include +#include + +namespace Pbr +{ + namespace VulkanTexture + { + std::array LoadRGBAUI4(RGBAColor color) + { + return std::array{(uint8_t)(color.r * 255.), (uint8_t)(color.g * 255.), (uint8_t)(color.b * 255.), + (uint8_t)(color.a * 255.)}; + } + + VulkanTextureBundle LoadTextureImage(Pbr::VulkanResources& pbrResources, const uint8_t* fileData, uint32_t fileSize) + { + auto freeImageData = [](unsigned char* ptr) { ::free(ptr); }; + using stbi_unique_ptr = std::unique_ptr; + + constexpr uint32_t DesiredComponentCount = 4; + + int w, h, c; + // If c == 3, a component will be padded with 1.0f + stbi_unique_ptr rgbaData(stbi_load_from_memory(fileData, fileSize, &w, &h, &c, DesiredComponentCount), freeImageData); + if (!rgbaData) { + throw std::runtime_error("Failed to load image file data."); + } + + return CreateTexture(pbrResources, rgbaData.get(), DesiredComponentCount, w, h, VK_FORMAT_R8G8B8A8_UNORM); + } + + /// Creates a texture and fills all array members with the data in rgba + VulkanTextureBundle CreateTextureArrayRepeat(VulkanResources& pbrResources, const VulkanDebugObjectNamer& namer, const char* name, + const uint8_t* rgba, uint32_t elemSize, uint32_t width, uint32_t height, bool cubemap, + VkFormat format) + { + // Microsoft::WRL::ComPtr device = pbrResources.GetDevice(); + + // Microsoft::WRL::ComPtr cmdList = pbrResources.CreateCopyCommandList(); + + // std::vector> imageUploadBuffers; + // Microsoft::WRL::ComPtr image = + // Conformance::VulkanCreateImage(device.get(), width, height, arraySize, format, Vulkan_HEAP_TYPE_DEFAULT); + + // Vulkan_RESOURCE_DESC imageDesc = image->GetDesc(); + // assert(imageDesc.DepthOrArraySize == arraySize); + // imageUploadBuffers.reserve(arraySize); + // // TODO: maybe call GetCopyableFootprints only once, as all out fields accept arrays + // // TODO: put the upload buffer in a staging resources vector and make async + // for (int arrayIndex = 0; arrayIndex < arraySize; arrayIndex++) { + // UINT subresourceIndex = VulkanCalcSubresource(0, arrayIndex, 0, imageDesc.MipLevels, arraySize); + + // Vulkan_PLACED_SUBRESOURCE_FOOTPRINT footprint; + // UINT rowCount; + // UINT64 rowSize; + // UINT64 uploadBufferSize; + // device->GetCopyableFootprints(&imageDesc, subresourceIndex, 1, 0, &footprint, &rowCount, &rowSize, &uploadBufferSize); + + // assert( + // rowCount == + // height); // doesn't hold for compressed textures, see: https://www.gamedev.net/forums/topic/677932-getcopyablefootprints-question/ + // assert(rowSize == width * elemSize); // assert this for now, probably doesn't hold for e.g. compressed textures + + // Microsoft::WRL::ComPtr imageUpload = + // Conformance::VulkanCreateBuffer(device.get(), (uint32_t)uploadBufferSize, Vulkan_HEAP_TYPE_UPLOAD); + // imageUploadBuffers.push_back(imageUpload); + + // Vulkan_SUBRESOURCE_DATA initData{}; + // initData.pData = rgba; + // initData.RowPitch = elemSize * width; + // initData.SlicePitch = elemSize * width * height; + + // // this does a row-by-row memcpy internally or we would have used our own CopyWithStride + // Internal::ThrowIf(!UpdateSubresources(cmdList.get(), image.get(), imageUpload.get(), 0, 1, uploadBufferSize, &footprint, + // &rowCount, &rowSize, &initData), + // "Call to UpdateSubresources helper failed"); + // } + + // XRC_CHECK_THROW_HRCMD(cmdList->Close()); + // pbrResources.ExecuteCopyCommandList(cmdList.get(), std::move(imageUploadBuffers)); + + // return image; + + VkDevice device = pbrResources.GetDevice(); + const Conformance::MemoryAllocator& memAllocator = pbrResources.GetMemoryAllocator(); + const Conformance::CmdBuffer& copyCmdBuffer = pbrResources.GetCopyCommandBuffer(); + + VulkanTextureBundle bundle{}; + + uint32_t layerCount = cubemap ? 6 : 1; + + bundle.width = width; + bundle.height = height; + bundle.mipLevels = 1; + bundle.layerCount = layerCount; + + // Create a staging buffer + VkBufferCreateInfo bufferCreateInfo{VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO}; + bufferCreateInfo.usage = VK_BUFFER_USAGE_TRANSFER_SRC_BIT; + bufferCreateInfo.size = width * height * elemSize; + + Conformance::BufferAndMemory stagingBuffer; + stagingBuffer.Create(device, memAllocator, bufferCreateInfo); + stagingBuffer.Update(device, {rgba, width * height * elemSize}, 0); + + // create image + VkImage image{VK_NULL_HANDLE}; + VkImageCreateInfo imageInfo{VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO}; + imageInfo.flags = cubemap ? VK_IMAGE_CREATE_CUBE_COMPATIBLE_BIT : 0; + imageInfo.imageType = VK_IMAGE_TYPE_2D; + imageInfo.extent.width = width; + imageInfo.extent.height = height; + imageInfo.extent.depth = 1; + imageInfo.mipLevels = 1; + imageInfo.arrayLayers = layerCount; + imageInfo.format = format; + imageInfo.tiling = VK_IMAGE_TILING_OPTIMAL; + imageInfo.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; + imageInfo.usage = VK_IMAGE_USAGE_SAMPLED_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT; + imageInfo.samples = VK_SAMPLE_COUNT_1_BIT; + imageInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE; + XRC_CHECK_THROW_VKCMD(vkCreateImage(device, &imageInfo, nullptr, &image)); + XRC_CHECK_THROW_VKCMD(namer.SetName(VK_OBJECT_TYPE_IMAGE, (uint64_t)image, name)); + + bundle.image = Conformance::ScopedVkImage(image, device); + + VkDeviceMemory imageMemory; + VkMemoryRequirements memRequirements{}; + vkGetImageMemoryRequirements(device, bundle.image.get(), &memRequirements); + memAllocator.Allocate(memRequirements, &imageMemory, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT); + XRC_CHECK_THROW_VKCMD(namer.SetName(VK_OBJECT_TYPE_DEVICE_MEMORY, (uint64_t)imageMemory, name)); + XRC_CHECK_THROW_VKCMD(vkBindImageMemory(device, bundle.image.get(), imageMemory, 0)); + + bundle.deviceMemory = Conformance::ScopedVkDeviceMemory(imageMemory, device); + + // // Switch the source buffer to TRANSFER_DST_OPTIMAL + // VkBufferMemoryBarrier bufferBarrier{VK_STRUCTURE_TYPE_BUFFER_MEMORY_BARRIER}; + // // VkAccessFlags srcAccessMask; + // // VkAccessFlags dstAccessMask; + // // uint32_t srcQueueFamilyIndex; + // // uint32_t dstQueueFamilyIndex; + // // VkBuffer buffer; + // // VkDeviceSize offset; + // // VkDeviceSize size; + // bufferBarrier.srcAccessMask = 0; + // bufferBarrier.dstAccessMask = VK_ACCESS_TRANSFER_READ_BIT; + // bufferBarrier.oldLayout = VK_BUFFER_; + // bufferBarrier.newLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL; + // bufferBarrier.= stagingBuffer.buf; + // bufferBarrier.subresourceRange = {VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, layerCount}; + // vkCmdPipelineBarrier(copyCmdBuffer.buf, VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, 0, 0, nullptr, 0, + // nullptr, 1, &bufferBarrier); + + // Switch the destination image to TRANSFER_DST_OPTIMAL + VkImageMemoryBarrier imgBarrier{VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER}; + imgBarrier.srcAccessMask = 0; + imgBarrier.dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT; + imgBarrier.oldLayout = VK_IMAGE_LAYOUT_UNDEFINED; + imgBarrier.newLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL; + imgBarrier.image = bundle.image.get(); + imgBarrier.subresourceRange = {VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, layerCount}; + vkCmdPipelineBarrier(copyCmdBuffer.buf, VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, 0, 0, nullptr, 0, + nullptr, 1, &imgBarrier); + + for (uint32_t layer = 0; layer < layerCount; ++layer) { + VkBufferImageCopy region; + region.bufferOffset = 0; + region.bufferRowLength = 0; + region.bufferImageHeight = 0; + region.imageSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + region.imageSubresource.mipLevel = 0; + region.imageSubresource.baseArrayLayer = layer; + region.imageSubresource.layerCount = 1; + region.imageOffset = {0, 0, 0}; + region.imageExtent = {width, height, 1}; + + vkCmdCopyBufferToImage(copyCmdBuffer.buf, stagingBuffer.buf, bundle.image.get(), VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, + ®ion); + } + + // Switch the destination image to SHADER_READ_ONLY_OPTIMAL + imgBarrier.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT; + imgBarrier.dstAccessMask = VK_ACCESS_SHADER_READ_BIT; + imgBarrier.oldLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL; + imgBarrier.newLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL; + imgBarrier.image = bundle.image.get(); + imgBarrier.subresourceRange = {VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, layerCount}; + vkCmdPipelineBarrier(copyCmdBuffer.buf, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT, 0, 0, nullptr, 0, + nullptr, 1, &imgBarrier); + + pbrResources.DestroyAfterRender(stagingBuffer); + + return bundle; + } + + VulkanTextureBundle CreateFlatCubeTexture(VulkanResources& pbrResources, RGBAColor color, VkFormat format) + { + // Each side is a 1x1 pixel (RGBA) image. + const std::array rgbaColor = LoadRGBAUI4(color); + const VulkanDebugObjectNamer& namer = pbrResources.GetDebugNamer(); + VulkanTextureBundle textureBundle = + CreateTextureArrayRepeat(pbrResources, namer, "CTS PBR 2D color image", rgbaColor.data(), 4, 1, 1, true, format); + assert(textureBundle.image != VK_NULL_HANDLE); + + VkImageViewCreateInfo viewInfo{VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO}; + viewInfo.image = textureBundle.image.get(); + viewInfo.viewType = VK_IMAGE_VIEW_TYPE_CUBE; + viewInfo.format = format; + viewInfo.components.r = VK_COMPONENT_SWIZZLE_R; + viewInfo.components.g = VK_COMPONENT_SWIZZLE_G; + viewInfo.components.b = VK_COMPONENT_SWIZZLE_B; + viewInfo.components.a = VK_COMPONENT_SWIZZLE_A; + viewInfo.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + viewInfo.subresourceRange.baseMipLevel = 0; + viewInfo.subresourceRange.levelCount = 1; + viewInfo.subresourceRange.baseArrayLayer = 0; + viewInfo.subresourceRange.layerCount = 6; + VkImageView view; + XRC_CHECK_THROW_VKCMD(vkCreateImageView(pbrResources.GetDevice(), &viewInfo, nullptr, &view)); + XRC_CHECK_THROW_VKCMD(namer.SetName(VK_OBJECT_TYPE_IMAGE_VIEW, (uint64_t)view, "CTS PBR 2D color image view")); + + textureBundle.view.adopt(view, pbrResources.GetDevice()); + + return textureBundle; + } + + VulkanTextureBundle CreateTexture(VulkanResources& pbrResources, const uint8_t* rgba, int elemSize, int width, int height, + VkFormat format) + { + const VulkanDebugObjectNamer& namer = pbrResources.GetDebugNamer(); + VulkanTextureBundle textureBundle = + CreateTextureArrayRepeat(pbrResources, namer, "CTS PBR 2D color image", rgba, elemSize, width, height, false, format); + assert(textureBundle.image != VK_NULL_HANDLE); + + VkImageViewCreateInfo viewInfo{VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO}; + viewInfo.image = textureBundle.image.get(); + viewInfo.viewType = VK_IMAGE_VIEW_TYPE_2D; + viewInfo.format = format; + viewInfo.components.r = VK_COMPONENT_SWIZZLE_R; + viewInfo.components.g = VK_COMPONENT_SWIZZLE_G; + viewInfo.components.b = VK_COMPONENT_SWIZZLE_B; + viewInfo.components.a = VK_COMPONENT_SWIZZLE_A; + viewInfo.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + viewInfo.subresourceRange.baseMipLevel = 0; + viewInfo.subresourceRange.levelCount = 1; + viewInfo.subresourceRange.baseArrayLayer = 0; + viewInfo.subresourceRange.layerCount = 1; + VkImageView view; + XRC_CHECK_THROW_VKCMD(vkCreateImageView(pbrResources.GetDevice(), &viewInfo, nullptr, &view)); + XRC_CHECK_THROW_VKCMD(namer.SetName(VK_OBJECT_TYPE_IMAGE_VIEW, (uint64_t)view, "CTS PBR 2D color image view")); + + textureBundle.view.adopt(view, pbrResources.GetDevice()); + + return textureBundle; + } + + VkSamplerCreateInfo DefaultSamplerCreateInfo() + { + VkSamplerCreateInfo info{}; + + info.sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO; + info.magFilter = VK_FILTER_LINEAR; + info.minFilter = VK_FILTER_LINEAR; + info.mipmapMode = VK_SAMPLER_MIPMAP_MODE_LINEAR; + info.addressModeU = VK_SAMPLER_ADDRESS_MODE_REPEAT; + info.addressModeV = VK_SAMPLER_ADDRESS_MODE_REPEAT; + info.addressModeW = VK_SAMPLER_ADDRESS_MODE_REPEAT; + info.mipLodBias = 0.0f; + info.anisotropyEnable = false; + info.maxAnisotropy = 16.0f; + info.compareEnable = false; + info.minLod = 0.0f; + info.maxLod = VK_LOD_CLAMP_NONE; + info.borderColor = VK_BORDER_COLOR_FLOAT_TRANSPARENT_BLACK; + info.unnormalizedCoordinates = false; + + return info; + } + + VkSampler CreateSampler(VkDevice device, VkSamplerAddressMode addressMode) + { + VkSamplerCreateInfo info = DefaultSamplerCreateInfo(); + + info.addressModeU = info.addressModeV = info.addressModeW = addressMode; + + VkSampler destSampler; + XRC_CHECK_THROW_VKCMD(vkCreateSampler(device, &info, NULL, &destSampler)); + return destSampler; + } + } // namespace VulkanTexture +} // namespace Pbr diff --git a/src/conformance/framework/pbr/Vulkan/VkTexture.h b/src/conformance/framework/pbr/Vulkan/VkTexture.h new file mode 100644 index 00000000..e03fda93 --- /dev/null +++ b/src/conformance/framework/pbr/Vulkan/VkTexture.h @@ -0,0 +1,44 @@ +// Copyright 2023, The Khronos Group, Inc. +// +// Based in part on code that is: +// +// Copyright (C) Microsoft Corporation. All Rights Reserved +// Licensed under the MIT License. See License.txt in the project root for license information. +// +// SPDX-License-Identifier: MIT AND Apache-2.0 + +#pragma once + +#include "VkCommon.h" +#include "VkResources.h" + +#include "../PbrCommon.h" + +#include "utilities/vulkan_scoped_handle.h" + +#include +#include + +#include +#include +#include + +namespace Pbr +{ + struct VulkanResources; + + namespace VulkanTexture + { + std::array LoadRGBAUI4(RGBAColor color); + + VulkanTextureBundle LoadTextureImage(VulkanResources& pbrResources, const uint8_t* fileData, uint32_t fileSize); + + VulkanTextureBundle CreateFlatCubeTexture(VulkanResources& pbrResources, RGBAColor color, VkFormat format); + + VulkanTextureBundle CreateTexture(VulkanResources& pbrResources, const uint8_t* rgba, int elemSize, int width, int height, + VkFormat format); + + VkSamplerCreateInfo DefaultSamplerCreateInfo(); + VkSampler CreateSampler(VkDevice device, VkSamplerAddressMode addressMode = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE); + } // namespace VulkanTexture +} // namespace Pbr diff --git a/src/conformance/framework/pbr/Vulkan/VkTextureCache.cpp b/src/conformance/framework/pbr/Vulkan/VkTextureCache.cpp new file mode 100644 index 00000000..40ccf2b5 --- /dev/null +++ b/src/conformance/framework/pbr/Vulkan/VkTextureCache.cpp @@ -0,0 +1,58 @@ +// Copyright 2023, The Khronos Group, Inc. +// +// Based in part on code that is: +// +// Copyright (C) Microsoft Corporation. All Rights Reserved +// Licensed under the MIT License. See License.txt in the project root for license information. +// +// SPDX-License-Identifier: MIT AND Apache-2.0 + +#include "VkTextureCache.h" + +#include "VkCommon.h" +#include "VkTexture.h" + +#include + +#include +#include +#include +#include +#include +#include + +namespace Pbr +{ + struct VulkanResources; + + VulkanTextureCache::VulkanTextureCache(VkDevice device) : m_device(device), m_cacheMutex(std::make_unique()) + { + m_device = device; + } + + std::shared_ptr VulkanTextureCache::CreateTypedSolidColorTexture(Pbr::VulkanResources& pbrResources, + XrColor4f color) + { + if (!IsValid()) { + throw std::logic_error("VulkanTextureCache accessed before initialization"); + } + const std::array rgba = VulkanTexture::LoadRGBAUI4(color); + + // Check cache to see if this flat texture already exists. + const uint32_t colorKey = *reinterpret_cast(rgba.data()); + { + std::lock_guard guard(*m_cacheMutex); + auto textureIt = m_solidColorTextureCache.find(colorKey); + if (textureIt != m_solidColorTextureCache.end()) { + return textureIt->second; + } + } + + auto texture = std::make_shared( + VulkanTexture::CreateTexture(pbrResources, rgba.data(), 4, 1, 1, VK_FORMAT_R8G8B8A8_UNORM)); + + std::lock_guard guard(*m_cacheMutex); + // If the key already exists then the existing texture will be returned. + return m_solidColorTextureCache.emplace(colorKey, texture).first->second; + } +} // namespace Pbr diff --git a/src/conformance/framework/pbr/Vulkan/VkTextureCache.h b/src/conformance/framework/pbr/Vulkan/VkTextureCache.h new file mode 100644 index 00000000..dce69665 --- /dev/null +++ b/src/conformance/framework/pbr/Vulkan/VkTextureCache.h @@ -0,0 +1,60 @@ +// Copyright 2023, The Khronos Group, Inc. +// +// Based in part on code that is: +// +// Copyright (C) Microsoft Corporation. All Rights Reserved +// Licensed under the MIT License. See License.txt in the project root for license information. +// +// SPDX-License-Identifier: MIT AND Apache-2.0 + +#include "VkCommon.h" +#include "VkResources.h" + +#include "utilities/vulkan_utils.h" + +#include +#include + +#include +#include +#include +#include + +namespace Pbr +{ + struct VulkanResources; + struct VulkanTextureBundle; + + /// Cache of single-color textures. + /// + /// Device-dependent, drop when device is lost or destroyed. + class VulkanTextureCache + { + public: + /// Default constructor makes an invalid cache. + VulkanTextureCache() = default; + + // VulkanTextureCache(const VulkanTextureCache&) = default; + // VulkanTextureCache& operator=(const VulkanTextureCache&) = default; + + VulkanTextureCache(VulkanTextureCache&&) = default; + VulkanTextureCache& operator=(VulkanTextureCache&&) = default; + + explicit VulkanTextureCache(VkDevice device); + + bool IsValid() const noexcept + { + return m_device != nullptr; + } + + /// Find or create a single pixel texture of the given color + std::shared_ptr CreateTypedSolidColorTexture(Pbr::VulkanResources& pbrResources, XrColor4f color); + + private: + VkDevice m_device; + // in unique_ptr to make it moveable + std::unique_ptr m_cacheMutex; + std::map> m_solidColorTextureCache; + }; + +} // namespace Pbr diff --git a/src/conformance/framework/platform_plugin_android.cpp b/src/conformance/framework/platform_plugin_android.cpp index ec094582..614463a0 100644 --- a/src/conformance/framework/platform_plugin_android.cpp +++ b/src/conformance/framework/platform_plugin_android.cpp @@ -19,7 +19,7 @@ #ifdef XR_USE_PLATFORM_ANDROID #include "conformance_framework.h" -#include "xr_dependencies.h" +#include "common/xr_dependencies.h" #include #include diff --git a/src/conformance/framework/platform_plugin_win32.cpp b/src/conformance/framework/platform_plugin_win32.cpp index ca521433..e5e050fb 100644 --- a/src/conformance/framework/platform_plugin_win32.cpp +++ b/src/conformance/framework/platform_plugin_win32.cpp @@ -30,7 +30,7 @@ namespace Conformance ~PlatformPluginWin32() override { - Shutdown(); + PlatformPluginWin32::Shutdown(); } virtual bool Initialize() override diff --git a/src/conformance/framework/swapchain_image_data.h b/src/conformance/framework/swapchain_image_data.h index acb6449d..0211a5b2 100644 --- a/src/conformance/framework/swapchain_image_data.h +++ b/src/conformance/framework/swapchain_image_data.h @@ -89,12 +89,15 @@ namespace Conformance /// This is a base class template, extended by each graphics plugin to add API-specific functionality. /// /// It implements the generic interface @ref ISwapchainImageData + /// + /// @tparam SwapchainImageDerivedType The per-API OpenXR structure type based on XrSwapchainImageBaseHeader template class SwapchainImageDataBase : public ISwapchainImageData { - public: - /// Constructor - /// @param derivedTypeConstant The `XrStructureType` for your specialized, API-specific swapchain image struct. + protected: + /// Constructor with no explicit depth swapchain: must call from a subclass + /// + /// @param derivedTypeConstant The `XrStructureType` for your specialized, API-specific swapchain image struct @p SwapchainImageDerivedType /// @param capacity The number of swapchain image structs to allocate. /// @param colorSwapchainCreateInfo The info used to create your color swapchain. SwapchainImageDataBase(XrStructureType derivedTypeConstant, uint32_t capacity, @@ -106,8 +109,9 @@ namespace Conformance { } - /// Constructor - /// @param derivedTypeConstant The `XrStructureType` for your specialized, API-specific swapchain image struct. + /// Constructor with explicit depth swapchain: must call from a subclass + /// + /// @param derivedTypeConstant The `XrStructureType` for your specialized, API-specific swapchain image struct @p SwapchainImageDerivedType /// @param capacity The number of swapchain image structs to allocate. /// @param colorSwapchainCreateInfo The info used to create your color swapchain. /// @param depthSwapchain The handle to your depth swapchain: while we won't own it, we will acquire, wait, and release images on it @@ -124,6 +128,7 @@ namespace Conformance { } + public: /// Get a pointer to the first color swapchain image in the array, as a base pointer, /// for use in passing to `xrEnumerateSwapchainImages`. /// diff --git a/src/conformance/framework/two_call_struct.h b/src/conformance/framework/two_call_struct.h index 46b71963..5ae1d9db 100644 --- a/src/conformance/framework/two_call_struct.h +++ b/src/conformance/framework/two_call_struct.h @@ -19,7 +19,7 @@ #include "type_utils.h" #include "utilities/throw_helpers.h" -#include "hex_and_handles.h" +#include "common/hex_and_handles.h" #include diff --git a/src/conformance/framework/two_call_struct_tests.h b/src/conformance/framework/two_call_struct_tests.h index ffba6810..8f214560 100644 --- a/src/conformance/framework/two_call_struct_tests.h +++ b/src/conformance/framework/two_call_struct_tests.h @@ -19,7 +19,7 @@ #include "two_call_struct.h" #include "utilities/throw_helpers.h" -#include "hex_and_handles.h" +#include "common/hex_and_handles.h" #include diff --git a/src/conformance/framework/xml_test_environment.h b/src/conformance/framework/xml_test_environment.h index b55e49dd..8049d1f8 100644 --- a/src/conformance/framework/xml_test_environment.h +++ b/src/conformance/framework/xml_test_environment.h @@ -4,8 +4,8 @@ #pragma once -#include "openxr/openxr.h" -#include "nonstd/span.hpp" +#include +#include namespace Catch { diff --git a/src/conformance/utilities/CMakeLists.txt b/src/conformance/utilities/CMakeLists.txt index 99128825..9914fc69 100644 --- a/src/conformance/utilities/CMakeLists.txt +++ b/src/conformance/utilities/CMakeLists.txt @@ -7,10 +7,14 @@ add_library( bitmask_generator.cpp bitmask_to_string.cpp d3d_common.cpp + d3d12_queue_wrapper.cpp + d3d12_utils.cpp event_reader.cpp Geometry.cpp + opengl_utils.cpp stringification.cpp swapchain_format_data.cpp + throw_helpers.cpp types_and_constants.cpp utils.cpp) @@ -19,12 +23,18 @@ target_include_directories( PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/.. # Backport of std::span functionality to pre-C++17 ${PROJECT_SOURCE_DIR}/src/external/span-lite/include + # for GL: + ${PROJECT_SOURCE_DIR}/external/include # for openxr.h: ${PROJECT_BINARY_DIR}/include) add_dependencies(conformance_utilities generate_openxr_header) -if(GLSLANG_VALIDATOR AND NOT GLSLC_COMMAND) +if(TARGET openxr-gfxwrapper) + target_link_libraries(conformance_utilities PRIVATE openxr-gfxwrapper) +endif() + +if(GLSLANG_VALIDATOR AND NOT GLSL_COMPILER) target_compile_definitions(conformance_utilities PUBLIC USE_GLSLANGVALIDATOR) endif() diff --git a/src/conformance/utilities/android_declarations.h b/src/conformance/utilities/android_declarations.h new file mode 100644 index 00000000..31017913 --- /dev/null +++ b/src/conformance/utilities/android_declarations.h @@ -0,0 +1,16 @@ +// Copyright (c) 2019-2023, The Khronos Group Inc. +// +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +#if defined(XR_USE_PLATFORM_ANDROID) +// For Android, we require the following functions to be implemented +// in our library for accessing Android specific information. +void* Conformance_Android_Get_Application_VM(); +void* Conformance_Android_Get_Application_Context(); +void* Conformance_Android_Get_Application_Activity(); +void* Conformance_Android_Get_Asset_Manager(); +void Conformance_Android_Attach_Current_Thread(); +void Conformance_Android_Detach_Current_Thread(); +#endif // defined(XR_USE_PLATFORM_ANDROID) diff --git a/src/conformance/utilities/colors.h b/src/conformance/utilities/colors.h new file mode 100644 index 00000000..a363171f --- /dev/null +++ b/src/conformance/utilities/colors.h @@ -0,0 +1,30 @@ +// Copyright (c) 2019-2023, The Khronos Group Inc. +// +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +#include + +#include + +namespace Conformance +{ + namespace Colors + { + + constexpr XrColor4f Red = {1, 0, 0, 1}; + constexpr XrColor4f Green = {0, 1, 0, 1}; + constexpr XrColor4f GreenZeroAlpha = {0, 1, 0, 0}; + 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}; + + } // namespace Colors +} // namespace Conformance diff --git a/src/conformance/utilities/d3d12_queue_wrapper.cpp b/src/conformance/utilities/d3d12_queue_wrapper.cpp new file mode 100644 index 00000000..b1e8cb98 --- /dev/null +++ b/src/conformance/utilities/d3d12_queue_wrapper.cpp @@ -0,0 +1,97 @@ +// 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. + +#if defined(XR_USE_GRAPHICS_API_D3D12) + +#include "d3d12_queue_wrapper.h" + +#include "throw_helpers.h" + +#include + +namespace Conformance +{ + D3D12QueueWrapper::D3D12QueueWrapper(Microsoft::WRL::ComPtr d3d12Device, D3D12_COMMAND_LIST_TYPE type) + : m_device(d3d12Device) + { + + D3D12_COMMAND_QUEUE_DESC queueDesc = {}; + queueDesc.Flags = D3D12_COMMAND_QUEUE_FLAG_NONE; + queueDesc.Type = type; + XRC_CHECK_THROW_HRCMD(d3d12Device->CreateCommandQueue(&queueDesc, __uuidof(ID3D12CommandQueue), + reinterpret_cast(m_cmdQueue.ReleaseAndGetAddressOf()))); + + XRC_CHECK_THROW_HRCMD(d3d12Device->CreateFence(m_fenceValue, D3D12_FENCE_FLAG_NONE, __uuidof(ID3D12Fence), + reinterpret_cast(m_fence.ReleaseAndGetAddressOf()))); + m_fenceEvent = ::CreateEvent(nullptr, FALSE, FALSE, nullptr); + XRC_CHECK_THROW(m_fenceEvent != nullptr); + } + + D3D12QueueWrapper::~D3D12QueueWrapper() + { + CPUWaitOnFence(); + m_cmdQueue.Reset(); + m_fence.Reset(); + if (m_fenceEvent != INVALID_HANDLE_VALUE) { + ::CloseHandle(m_fenceEvent); + m_fenceEvent = INVALID_HANDLE_VALUE; + } + } + + bool D3D12QueueWrapper::ExecuteCommandList(ID3D12CommandList* commandList) const + { + + bool success; + // weird structured exception handling stuff that windows does. + __try { + std::array cmdLists = {{commandList}}; + m_cmdQueue->ExecuteCommandLists((UINT)cmdLists.size(), cmdLists.data()); + success = true; + } + __except (EXCEPTION_EXECUTE_HANDLER) { + success = false; + } + + ++m_fenceValue; + XRC_CHECK_THROW_HRCMD(m_cmdQueue->Signal(m_fence.Get(), m_fenceValue)); + m_cpuWaited = false; + + return success; + } + + void D3D12QueueWrapper::CPUWaitOnFence() + { + if (m_cpuWaited) { + return; + } + if (m_fence->GetCompletedValue() < m_fenceValue) { + if (m_fenceEvent == INVALID_HANDLE_VALUE) { + m_fenceEvent = CreateEventEx(nullptr, nullptr, 0, EVENT_ALL_ACCESS); + } + m_fence->SetEventOnCompletion(m_fenceValue, m_fenceEvent); + WaitForSingleObject(m_fenceEvent, INFINITE); + } + m_cpuWaited = true; + } + + void D3D12QueueWrapper::GPUWaitOnOtherFence(ID3D12Fence* otherFence, uint64_t otherFenceValue) + { + XRC_CHECK_THROW_HRCMD(m_cmdQueue->Wait(otherFence, otherFenceValue)); + } + +} // namespace Conformance + +#endif // XR_USE_GRAPHICS_API_D3D12 diff --git a/src/conformance/utilities/d3d12_queue_wrapper.h b/src/conformance/utilities/d3d12_queue_wrapper.h new file mode 100644 index 00000000..6a30b96c --- /dev/null +++ b/src/conformance/utilities/d3d12_queue_wrapper.h @@ -0,0 +1,83 @@ +// 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. + +#if defined(XR_USE_GRAPHICS_API_D3D12) + +#include +#include // For Microsoft::WRL::ComPtr + +#include +#include + +namespace Conformance +{ + /// Wraps a command queue, a fence, and the value last signaled for the fence + class D3D12QueueWrapper + { + public: + D3D12QueueWrapper(Microsoft::WRL::ComPtr d3d12Device, D3D12_COMMAND_LIST_TYPE type); + ~D3D12QueueWrapper(); + + /// Execute a command list, increment the fence value, and signal the fence. + /// @return false on failure + bool ExecuteCommandList(ID3D12CommandList* commandList) const; + + /// CPU wait on the most recently-signaled fence value + void CPUWaitOnFence(); + + /// GPU wait in this queue on some other fence + void GPUWaitOnOtherFence(ID3D12Fence* otherFence, uint64_t otherFenceValue); + + void GPUWaitOnOtherFence(std::pair otherFenceAndValue) + { + GPUWaitOnOtherFence(otherFenceAndValue.first, otherFenceAndValue.second); + } + + /// Get the internal fence + Microsoft::WRL::ComPtr GetFence() const + { + return m_fence; + } + + /// Get the completed fence value (not the most recently signaled) + uint64_t GetCompletedFenceValue() const + { + return m_fence->GetCompletedValue(); + } + + /// Get the most recently signaled fence value + uint64_t GetSignaledFenceValue() const + { + return m_fenceValue; + } + + /// Get the command queue for passing in to OpenXR or similar + Microsoft::WRL::ComPtr GetCommandQueue() const + { + return m_cmdQueue; + } + + private: + Microsoft::WRL::ComPtr m_device; + Microsoft::WRL::ComPtr m_cmdQueue; + Microsoft::WRL::ComPtr m_fence; + mutable uint64_t m_fenceValue = 0; + mutable bool m_cpuWaited = true; + HANDLE m_fenceEvent = INVALID_HANDLE_VALUE; + }; +} // namespace Conformance + +#endif // XR_USE_GRAPHICS_API_D3D12 diff --git a/src/conformance/utilities/d3d12_utils.cpp b/src/conformance/utilities/d3d12_utils.cpp new file mode 100644 index 00000000..904a9ad5 --- /dev/null +++ b/src/conformance/utilities/d3d12_utils.cpp @@ -0,0 +1,70 @@ +// Copyright (c) 2019-2023, The Khronos Group Inc. +// +// SPDX-License-Identifier: Apache-2.0 + +#if defined(XR_USE_GRAPHICS_API_D3D12) + +#include "d3d12_utils.h" + +#include "align_to.h" +#include "throw_helpers.h" + +#include +#include // For Microsoft::WRL::ComPtr + +using Microsoft::WRL::ComPtr; + +namespace Conformance +{ + ComPtr D3D12CreateResource(ID3D12Device* d3d12Device, uint32_t width, uint32_t height, uint16_t depth, + D3D12_RESOURCE_DIMENSION dimension, DXGI_FORMAT format, D3D12_TEXTURE_LAYOUT layout, + D3D12_HEAP_TYPE heapType) + { + D3D12_RESOURCE_STATES d3d12ResourceState; + if (heapType == D3D12_HEAP_TYPE_UPLOAD) { + d3d12ResourceState = D3D12_RESOURCE_STATE_GENERIC_READ; + width = AlignTo(width); + } + else { + d3d12ResourceState = D3D12_RESOURCE_STATE_COMMON; + } + + D3D12_HEAP_PROPERTIES heapProp{}; + heapProp.Type = heapType; + heapProp.CPUPageProperty = D3D12_CPU_PAGE_PROPERTY_UNKNOWN; + heapProp.MemoryPoolPreference = D3D12_MEMORY_POOL_UNKNOWN; + + D3D12_RESOURCE_DESC buffDesc{}; + buffDesc.Dimension = dimension; + buffDesc.Alignment = 0; + buffDesc.Width = width; + buffDesc.Height = height; + buffDesc.DepthOrArraySize = depth; + buffDesc.MipLevels = 1; + buffDesc.Format = format; + buffDesc.SampleDesc.Count = 1; + buffDesc.SampleDesc.Quality = 0; + buffDesc.Layout = layout; + buffDesc.Flags = D3D12_RESOURCE_FLAG_NONE; + + ComPtr buffer; + XRC_CHECK_THROW_HRCMD(d3d12Device->CreateCommittedResource(&heapProp, D3D12_HEAP_FLAG_NONE, &buffDesc, d3d12ResourceState, nullptr, + __uuidof(ID3D12Resource), + reinterpret_cast(buffer.ReleaseAndGetAddressOf()))); + return buffer; + } + + ComPtr D3D12CreateBuffer(ID3D12Device* d3d12Device, uint32_t size, D3D12_HEAP_TYPE heapType) + { + return D3D12CreateResource(d3d12Device, size, 1, 1, D3D12_RESOURCE_DIMENSION_BUFFER, DXGI_FORMAT_UNKNOWN, + D3D12_TEXTURE_LAYOUT_ROW_MAJOR, heapType); + } + + ComPtr D3D12CreateImage(ID3D12Device* d3d12Device, uint32_t width, uint32_t height, uint16_t arraySize, + DXGI_FORMAT format, D3D12_HEAP_TYPE heapType) + { + return D3D12CreateResource(d3d12Device, width, height, arraySize, D3D12_RESOURCE_DIMENSION_TEXTURE2D, format, + D3D12_TEXTURE_LAYOUT_UNKNOWN, heapType); + } +} // namespace Conformance +#endif diff --git a/src/conformance/utilities/d3d12_utils.h b/src/conformance/utilities/d3d12_utils.h new file mode 100644 index 00000000..1d67756f --- /dev/null +++ b/src/conformance/utilities/d3d12_utils.h @@ -0,0 +1,107 @@ +// Copyright (c) 2019-2023, The Khronos Group Inc. +// +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +#if defined(XR_USE_GRAPHICS_API_D3D12) + +#include "utilities/throw_helpers.h" + +#include +#include // For Microsoft::WRL::ComPtr + +#include + +namespace Conformance +{ + Microsoft::WRL::ComPtr D3D12CreateResource(ID3D12Device* d3d12Device, uint32_t width, uint32_t height, uint16_t depth, + D3D12_RESOURCE_DIMENSION dimension, DXGI_FORMAT format, + D3D12_TEXTURE_LAYOUT layout, D3D12_HEAP_TYPE heapType); + + Microsoft::WRL::ComPtr D3D12CreateBuffer(ID3D12Device* d3d12Device, uint32_t size, D3D12_HEAP_TYPE heapType); + + Microsoft::WRL::ComPtr D3D12CreateImage(ID3D12Device* d3d12Device, uint32_t width, uint32_t height, uint16_t arraySize, + DXGI_FORMAT format, D3D12_HEAP_TYPE heapType); + + template + void D3D12BasicUpload(_In_ ID3D12Resource* buffer, _In_reads_(count) const T* data, size_t count) + { + void* pData; + XRC_CHECK_THROW_HRCMD(buffer->Map(0, nullptr, &pData)); + + size_t writeBytes = count * sizeof(T); + memcpy(pData, data, writeBytes); + + D3D12_RANGE writtenRange{0, writeBytes}; + buffer->Unmap(0, &writtenRange); + } + + struct D3D12ResourceWithSRVDesc + { + Microsoft::WRL::ComPtr resource; + D3D12_SHADER_RESOURCE_VIEW_DESC srvDesc; + }; + + /// Wrap a normal GPU buffer (for an array or single structure) + /// and a corresponding upload buffer to be able to update the contents of the buffer asynchronously. + template + class D3D12BufferWithUpload + { + public: + D3D12BufferWithUpload() = default; + + /// Construct and allocate buffers suitable for an array of @p maxCapacity elements (defaulting to a single element) + explicit D3D12BufferWithUpload(_In_ ID3D12Device* device, size_t maxCapacity = 1) + : resource(D3D12CreateBuffer(device, (uint32_t)RequiredBytesFor(maxCapacity), D3D12_HEAP_TYPE_DEFAULT)) + , uploadBuffer(D3D12CreateBuffer(device, (uint32_t)RequiredBytesFor(maxCapacity), D3D12_HEAP_TYPE_UPLOAD)) + { + } + + /// Allocate (or discard and re-allocate) buffers suitable for an array of @p maxCapacity elements (defaulting to a single element) + void Allocate(_In_ ID3D12Device* device, size_t maxCapacity = 1) + { + *this = D3D12BufferWithUpload(device, maxCapacity); + } + + /// Would an array of @p count elements of this type fit in the resource? + bool Fits(size_t count) const + { + if (!resource) { + throw std::logic_error("Resources not allocated before calling Fits()"); + } + D3D12_RESOURCE_DESC bufferDesc = resource->GetDesc(); + return bufferDesc.Width >= RequiredBytesFor(count); + } + + /// Copy the supplied array @p data with @p count elements to an upload buffer, + /// then add a copy to the "real" buffer to the supplied command list + void AsyncUpload(ID3D12GraphicsCommandList* copyCommandList, _In_reads_(count) const T* data, size_t count = 1) const + { + if (!resource) { + throw std::logic_error("Resources not allocated before calling AsyncUpload()"); + } + assert(Fits(count)); // caller is responsible for making a larger buffer if count changes + + D3D12BasicUpload(uploadBuffer.Get(), data, count); + copyCommandList->CopyBufferRegion(resource.Get(), 0, uploadBuffer.Get(), 0, RequiredBytesFor(count)); + } + + /// Get the resource on the GPU, without affecting the reference count. + ID3D12Resource* GetResource() const noexcept + { + return resource.Get(); + } + + private: + Microsoft::WRL::ComPtr resource; + Microsoft::WRL::ComPtr uploadBuffer; + + static size_t RequiredBytesFor(size_t count) + { + return count * sizeof(T); + } + }; +} // namespace Conformance + +#endif diff --git a/src/conformance/utilities/destruction_queue.h b/src/conformance/utilities/destruction_queue.h new file mode 100644 index 00000000..222c8d2f --- /dev/null +++ b/src/conformance/utilities/destruction_queue.h @@ -0,0 +1,99 @@ +// 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. +#pragma once + +#include +#include + +namespace Conformance +{ + /// Tracks some kind of owned resource and the corresponding fence value at which it can be released. + template + class DestructionQueue + { + public: + /// Push some thing you can de-allocate following a fence value. + /// + /// Move your ownership into this method, and the container will release it at some future @ref ReleaseForFenceValue call + /// + /// @param fenceValue the fence value you signaled after finishing use of the resources + /// @param resource a resource owner + void PushResource(uint64_t fenceValue, OwnedResource&& resource) + { + resourcesAwaitingDestruction.emplace(fenceValue, std::move(resource)); + } + + // /// Push more than one thing to de-allocate after a fence value. + // /// + // /// @param fenceValue the fence value you signaled after finishing use of the resources + // /// @param begin the begin iterator + // /// @param end the past-the-end iterator + // /// + // /// The iterator arguments match the conventional usage of std::begin/std::end, std::vector::begin/std::vector::end, etc. + // template + // void PushResources(uint64_t fenceValue, It begin, It end) + // { + // for (; begin != end; ++begin) { + // PushResource(fenceValue, std::move(*begin)); + // } + // } + + /// Push more than one thing to de-allocate after a fence value. + /// + /// @param fenceValue the fence value you signaled after finishing use of the resources + /// @param resources the resources in a vector you move in + void PushResources(uint64_t fenceValue, std::vector&& resources) + { + for (auto res : resources) { + PushResource(fenceValue, std::move(res)); + } + } + + /// Release all resources associated with a fence value less than or equal to the parameter. + /// + /// @param completedFenceValue the completed fence value from the fence. + void ReleaseForFenceValue(uint64_t completedFenceValue) + { + while (!resourcesAwaitingDestruction.empty() && resourcesAwaitingDestruction.top().fenceValue <= completedFenceValue) { + resourcesAwaitingDestruction.pop(); + } + } + + private: + struct QueueEntry + { + uint64_t fenceValue; + OwnedResource resource; + + QueueEntry(uint64_t fenceValue, OwnedResource res) : fenceValue(fenceValue), resource(std::move(res)) + { + } + }; + + struct QueueLater + { + /// true if lhs will be ready later than rhs + bool operator()(const QueueEntry& lhs, const QueueEntry& rhs) const noexcept + { + return lhs.fenceValue > rhs.fenceValue; + } + }; + + // Priority queue is a "max queue" that uses std::less by default: the comparison functor returns true if LHS is "lower priority" (later in array, etc) than RHS + // todo: consider wrapping deque here instead of vector + std::priority_queue, QueueLater /* std::greater */> resourcesAwaitingDestruction; + }; +} // namespace Conformance diff --git a/src/conformance/utilities/opengl_utils.cpp b/src/conformance/utilities/opengl_utils.cpp new file mode 100644 index 00000000..38225fbf --- /dev/null +++ b/src/conformance/utilities/opengl_utils.cpp @@ -0,0 +1,62 @@ +// Copyright (c) 2019-2023, The Khronos Group Inc. +// +// SPDX-License-Identifier: Apache-2.0 + +#if defined(XR_USE_GRAPHICS_API_OPENGL) || defined(XR_USE_GRAPHICS_API_OPENGL_ES) +#include "opengl_utils.h" + +#include "common/gfxwrapper_opengl.h" + +#include + +namespace Conformance +{ + std::string glResultString(GLenum err) + { + switch (err) { + case GL_NO_ERROR: + return "GL_NO_ERROR"; + case GL_INVALID_ENUM: + return "GL_INVALID_ENUM"; + case GL_INVALID_VALUE: + return "GL_INVALID_VALUE"; + case GL_INVALID_OPERATION: + return "GL_INVALID_OPERATION"; + case GL_INVALID_FRAMEBUFFER_OPERATION: + return "GL_INVALID_FRAMEBUFFER_OPERATION"; + case GL_OUT_OF_MEMORY: + return "GL_OUT_OF_MEMORY"; + case GL_STACK_UNDERFLOW: + return "GL_STACK_UNDERFLOW"; + case GL_STACK_OVERFLOW: + return "GL_STACK_OVERFLOW"; + } + return ""; + } + + void CheckGLShader(GLuint shader) + { + GLint r = 0; + XRC_CHECK_THROW_GLCMD(glGetShaderiv(shader, GL_COMPILE_STATUS, &r)); + if (r == GL_FALSE) { + GLchar msg[4096] = {}; + GLsizei length; + XRC_CHECK_THROW_GLCMD(glGetShaderInfoLog(shader, sizeof(msg), &length, msg)); + XRC_CHECK_THROW_MSG(r, msg); + } + } + + void CheckGLProgram(GLuint prog) + { + GLint r = 0; + XRC_CHECK_THROW_GLCMD(glGetProgramiv(prog, GL_LINK_STATUS, &r)); + if (r == GL_FALSE) { + GLchar msg[4096] = {}; + GLsizei length; + XRC_CHECK_THROW_GLCMD(glGetProgramInfoLog(prog, sizeof(msg), &length, msg)); + XRC_CHECK_THROW_MSG(r, msg); + } + } +} // namespace Conformance + +#endif // defined(XR_USE_GRAPHICS_API_OPENGL) || defined(XR_USE_GRAPHICS_API_OPENGL_ES) diff --git a/src/conformance/utilities/opengl_utils.h b/src/conformance/utilities/opengl_utils.h new file mode 100644 index 00000000..c50004ac --- /dev/null +++ b/src/conformance/utilities/opengl_utils.h @@ -0,0 +1,59 @@ +// Copyright (c) 2019-2023, The Khronos Group Inc. +// +// SPDX-License-Identifier: Apache-2.0 +#pragma once + +#if defined(XR_USE_GRAPHICS_API_OPENGL) || defined(XR_USE_GRAPHICS_API_OPENGL_ES) + +#include "common/xr_dependencies.h" + +#include "common/gfxwrapper_opengl.h" +#include "utilities/stringification.h" +#include "utilities/throw_helpers.h" + +#include + +namespace Conformance +{ + std::string glResultString(GLenum err); + + [[noreturn]] inline void ThrowGLResult(GLenum res, const char* originator = nullptr, const char* sourceLocation = nullptr) + { + Throw("GL failure " + glResultString(res), originator, sourceLocation); + } + + inline GLenum CheckThrowGLResult(GLenum res, const char* originator = nullptr, const char* sourceLocation = nullptr) + { + if ((res) != GL_NO_ERROR) { + ThrowGLResult(res, originator, sourceLocation); + } + + return res; + } + +#define XRC_THROW_GL(res, cmd) ::Conformance::ThrowGLResult(res, #cmd, XRC_FILE_AND_LINE) +#define XRC_CHECK_THROW_GLCMD(cmd) ::Conformance::CheckThrowGLResult(((cmd), glGetError()), #cmd, XRC_FILE_AND_LINE) +#define XRC_CHECK_THROW_GLRESULT(res, cmdStr) ::Conformance::CheckThrowGLResult(res, cmdStr, XRC_FILE_AND_LINE) + + inline GLenum TexTarget(bool isArray, bool isMultisample) + { + if (isArray && isMultisample) { + return GL_TEXTURE_2D_MULTISAMPLE_ARRAY; + } + else if (isMultisample) { + return GL_TEXTURE_2D_MULTISAMPLE; + } + else if (isArray) { + return GL_TEXTURE_2D_ARRAY; + } + else { + return GL_TEXTURE_2D; + } + } + + void CheckGLShader(GLuint shader); + void CheckGLProgram(GLuint prog); + +} // namespace Conformance + +#endif // defined(XR_USE_GRAPHICS_API_OPENGL) || defined(XR_USE_GRAPHICS_API_OPENGL_ES) diff --git a/src/conformance/utilities/throw_helpers.cpp b/src/conformance/utilities/throw_helpers.cpp new file mode 100644 index 00000000..b9a0227a --- /dev/null +++ b/src/conformance/utilities/throw_helpers.cpp @@ -0,0 +1,37 @@ +// 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 "throw_helpers.h" +#include "stringification.h" +#include "utils.h" + +namespace Conformance +{ + + void ThrowXrResult(XrResult res, const char* originator, const char* sourceLocation) noexcept(false) + { + Throw(StringSprintf("XrResult failure [%s]", ResultToString(res)), originator, sourceLocation); + } + + XrResult CheckThrowXrResultSuccessOrLimitReached(XrResult res, const char* originator, const char* sourceLocation) noexcept(false) + { + if (XR_FAILED(res) && res != XR_ERROR_LIMIT_REACHED) { + Throw(StringSprintf("XrResult failure (and not XR_ERROR_LIMIT_REACHED) [%s]", ResultToString(res)), originator, sourceLocation); + } + return res; + } + +} // namespace Conformance diff --git a/src/conformance/utilities/throw_helpers.h b/src/conformance/utilities/throw_helpers.h index 56571578..3378114a 100644 --- a/src/conformance/utilities/throw_helpers.h +++ b/src/conformance/utilities/throw_helpers.h @@ -68,30 +68,26 @@ namespace Conformance #define XRC_THROW(msg) ::Conformance::Throw(msg, nullptr, XRC_FILE_AND_LINE); -#define XRC_CHECK_THROW(exp) \ - { \ - if (!(exp)) { \ - Throw("Check failed", #exp, XRC_FILE_AND_LINE); \ - } \ +#define XRC_CHECK_THROW(exp) \ + { \ + if (!(exp)) { \ + ::Conformance::Throw("Check failed", #exp, XRC_FILE_AND_LINE); \ + } \ } -#define XRC_CHECK_THROW_MSG(exp, msg) \ - { \ - if (!(exp)) { \ - Throw(msg, #exp, XRC_FILE_AND_LINE); \ - } \ +#define XRC_CHECK_THROW_MSG(exp, msg) \ + { \ + if (!(exp)) { \ + ::Conformance::Throw(msg, #exp, XRC_FILE_AND_LINE); \ + } \ } - [[noreturn]] inline void ThrowXrResult(XrResult res, const char* originator = nullptr, - const char* sourceLocation = nullptr) noexcept(false) - { - Throw(StringSprintf("XrResult failure [%s]", ResultToString(res)), originator, sourceLocation); - } + [[noreturn]] void ThrowXrResult(XrResult res, const char* originator = nullptr, const char* sourceLocation = nullptr) noexcept(false); inline XrResult CheckThrowXrResult(XrResult res, const char* originator = nullptr, const char* sourceLocation = nullptr) noexcept(false) { if (XR_FAILED(res)) { - ThrowXrResult(res, originator, sourceLocation); + ::Conformance::ThrowXrResult(res, originator, sourceLocation); } return res; @@ -101,20 +97,14 @@ namespace Conformance const char* sourceLocation = nullptr) noexcept(false) { if (!XR_UNQUALIFIED_SUCCESS(res)) { - ThrowXrResult(res, originator, sourceLocation); + ::Conformance::ThrowXrResult(res, originator, sourceLocation); } return res; } - inline XrResult CheckThrowXrResultSuccessOrLimitReached(XrResult res, const char* originator = nullptr, - const char* sourceLocation = nullptr) noexcept(false) - { - if (XR_FAILED(res) && res != XR_ERROR_LIMIT_REACHED) { - Throw(StringSprintf("XrResult failure (and not XR_ERROR_LIMIT_REACHED) [%s]", ResultToString(res)), originator, sourceLocation); - } - return res; - } + XrResult CheckThrowXrResultSuccessOrLimitReached(XrResult res, const char* originator = nullptr, + const char* sourceLocation = nullptr) noexcept(false); #define XRC_THROW_XRRESULT(xr, cmd) ::Conformance::ThrowXrResult(xr, #cmd, XRC_FILE_AND_LINE); #define XRC_CHECK_THROW_XRCMD(cmd) ::Conformance::CheckThrowXrResult(cmd, #cmd, XRC_FILE_AND_LINE); diff --git a/src/conformance/utilities/utils.cpp b/src/conformance/utilities/utils.cpp index 1f506054..51ecacae 100644 --- a/src/conformance/utilities/utils.cpp +++ b/src/conformance/utilities/utils.cpp @@ -25,6 +25,7 @@ #include #include #include +#include #include #include #include @@ -33,6 +34,12 @@ #include #endif +#ifdef XR_USE_PLATFORM_ANDROID +#include "common/unique_asset.h" + +#include "utilities/android_declarations.h" +#endif + namespace Conformance { @@ -87,6 +94,44 @@ namespace Conformance return str; } + std::vector ReadFileBytes(const char* path, const char* description) + { + auto space = (description[0] == '\0') ? "" : " "; + std::vector data; +#ifdef XR_USE_PLATFORM_ANDROID + AAssetManager* assetManager = (AAssetManager*)Conformance_Android_Get_Asset_Manager(); + UniqueAsset asset(AAssetManager_open(assetManager, path, AASSET_MODE_BUFFER)); + + if (!asset) { + throw std::runtime_error((std::string("Unable to load ") + description + space + "asset " + path).c_str()); + } + + size_t length = AAsset_getLength(asset.get()); + data.resize(length); + + auto buf = AAsset_getBuffer(asset.get()); + + if (!buf) { + throw std::runtime_error((std::string("Unable to load ") + description + space + "asset " + path).c_str()); + } + + memcpy(data.data(), buf, data.size()); +#else + std::ifstream file; + file.open(path, std::ios::in | std::ios::binary); + if (!file) { + throw std::runtime_error((std::string("Unable to open ") + description + space + "file " + path).c_str()); + } + + file.seekg(0, std::ios::end); + data.resize((uint32_t)file.tellg()); + file.seekg(0, std::ios::beg); + + file.read(reinterpret_cast(data.data()), data.size()); +#endif + return data; + } + // Provides a managed set of random number generators. Currently the usage of these generators // is imperfect because modulus (%) operations are done against their results, which introduces // a slight skew in the distribution for most ranges. C++ random number generation requires diff --git a/src/conformance/utilities/utils.h b/src/conformance/utilities/utils.h index e92e3d79..ef572faa 100644 --- a/src/conformance/utilities/utils.h +++ b/src/conformance/utilities/utils.h @@ -245,6 +245,10 @@ namespace Conformance /// Returns a reference to the input str. std::string& FlipCase(std::string& str); + /// Reads the file at path @p path to a vector. @p description can be used for more informative + /// errors in case this fails, e.g. "texture". + std::vector ReadFileBytes(const char* path, const char* description = ""); + /// SleepMs /// /// Sleeps the current thread for at least the given milliseconds. Attempt is made to return diff --git a/src/conformance/utilities/vulkan_scoped_handle.h b/src/conformance/utilities/vulkan_scoped_handle.h new file mode 100644 index 00000000..ba40c074 --- /dev/null +++ b/src/conformance/utilities/vulkan_scoped_handle.h @@ -0,0 +1,294 @@ +// Copyright (c) 2019-2023, The Khronos Group Inc. +// +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +#ifdef XR_USE_GRAPHICS_API_VULKAN + +#include + +#include +#include +#include + +namespace Conformance +{ + /// A stateless destroyer for Vulkan handles that have a destroy function we can refer to statically. + /// + /// @tparam HandleType The handle type to wrap + /// @tparam ParentHandle The parent handle type needed by the destroy function. + /// @tparam DestroyFunction The function used to destroy the handle. + /// + /// @see ScopedVkWithDefaultDestroy + /// @see ScopedVk + /// @see VkDestroyerWithFuncPointer + /// + /// @ingroup cts_handle_helpers + template + class VkDefaultDestroyer + { + public: + void operator()(ParentHandle parent, HandleType handle) const noexcept + { + DestroyFunction(parent, handle, nullptr); + } + }; + + /// A destroyer for Vulkan handles that holds state at runtime to contain a function pointer. + /// + /// This is mainly for things from extensions. + /// + /// @tparam HandleType The handle type to wrap + /// @tparam ParentHandle The parent handle type needed by the destroy function. + /// + /// @see ScopedVkWithPfn + /// @see VkDefaultDestroyer + /// @see ScopedVk + /// + /// @ingroup cts_handle_helpers + template + class VkDestroyerWithFuncPointer + { + public: + using DestroyFunction = VKAPI_ATTR void (*)(ParentHandle, HandleType, const VkAllocationCallbacks*); + + VkDestroyerWithFuncPointer(DestroyFunction pfn) : pfn_(pfn) + { + } + + void operator()(ParentHandle parent, HandleType handle) const noexcept + { + pfn_(parent, handle, nullptr); + } + + private: + DestroyFunction pfn_; + }; + + /// A unique-ownership RAII helper for Vulkan handles. + /// + /// @tparam HandleType The handle type to wrap + /// @tparam ParentHandle The parent handle type needed by the destroy function. + /// @tparam Destroyer a functor type that destroys the handle - may be stateless or have state + /// + /// @see VkDefaultDestroyer + /// @see VkDestroyerWithFuncPointer + /// + /// @ingroup cts_handle_helpers + template + class ScopedVk + { + public: + /// Default (empty) constructor + ScopedVk() = default; + + /// Empty constructor when we need a destroyer instance. + explicit ScopedVk(Destroyer d) : d_(d) + { + } + + /// Explicit constructor from handle and parent, if we don't need a destroyer instance. + /// + /// The parent handle is not owned, just observed. + explicit ScopedVk(HandleType h, ParentHandle parent, std::enable_if::value>* = nullptr) + : h_(h), parent_(parent) + { + } + + /// Constructor from handle and parent when we need a destroyer instance. + /// + /// The parent handle is not owned, just observed. + ScopedVk(HandleType h, ParentHandle parent, Destroyer d) : h_(h), parent_(parent), d_(d) + { + } + + /// Destructor + ~ScopedVk() + { + reset(); + } + + /// Non-copyable + ScopedVk(ScopedVk const&) = delete; + + /// Non-copy-assignable + ScopedVk& operator=(ScopedVk const&) = delete; + + /// Move-constructible + ScopedVk(ScopedVk&& other) noexcept : h_(std::move(other.h_)), parent_(std::move(other.parent_)), d_(std::move(other.d_)) + { + other.clear(); + } + + /// Move-assignable + ScopedVk& operator=(ScopedVk&& other) noexcept + { + if (&other == this) { + return *this; + } + reset(); + swap(other); + return *this; + } + + /// Is this handle valid? + constexpr bool valid() const noexcept + { + return get() != VK_NULL_HANDLE; + } + + /// Is this handle valid? + explicit operator bool() const noexcept + { + return valid(); + } + + void swap(ScopedVk& other) noexcept + { + std::swap(h_, other.h_); + std::swap(parent_, other.parent_); + std::swap(d_, other.d_); + } + + /// Destroy the owned handle, if any. + void reset() + { + if (get() != VK_NULL_HANDLE) { + get_destroyer()(get_parent(), get()); + clear(); + } + } + + /// Assign a new handle into this object's control, destroying the old one if applicable. + /// The parent handle is not owned, just observed. + void adopt(HandleType h, ParentHandle parent) + { + reset(); + h_ = h; + parent_ = parent; + } + + /// Assign a new handle into this object's control, including new destroyer, destroying the old one if applicable. + /// The parent handle is not owned, just observed. + void adopt(HandleType h, ParentHandle parent, Destroyer&& d) + { + adopt(h, parent); + d_ = std::move(d); + } + + /// Access the raw handle without affecting ownership or lifetime. + HandleType get() const noexcept + { + return h_; + } + + /// Access the raw handle of the parent + ParentHandle get_parent() const noexcept + { + return parent_; + } + + /// Access the destroyer functor + const Destroyer& get_destroyer() const noexcept + { + return d_; + } + + /// Release the handle from this object's control. + HandleType release() noexcept + { + HandleType ret = h_; + clear(); + return ret; + } + + private: + void clear() noexcept + { + h_ = VK_NULL_HANDLE; + parent_ = VK_NULL_HANDLE; + } + HandleType h_ = VK_NULL_HANDLE; + ParentHandle parent_ = VK_NULL_HANDLE; + Destroyer d_; + }; + + /// Swap function for scoped handles, found using ADL. + /// @relates ScopedVk + template + inline void swap(ScopedVk& a, ScopedVk& b) + { + return a.swap(b); + } + + /// Equality comparison between a scoped handle and a null handle + /// @relates ScopedVk + template + inline bool operator==(ScopedVk const& handle, std::nullptr_t const&) + { + return !handle.valid(); + } + + /// Equality comparison between a scoped handle and a null handle + /// @relates ScopedVk + template + inline bool operator==(std::nullptr_t const&, ScopedVk const& handle) + { + return !handle.valid(); + } + + /// Inequality comparison between a scoped handle and a null handle + /// @relates ScopedVk + template + inline bool operator!=(ScopedVk const& handle, std::nullptr_t const&) + { + return handle.valid(); + } + + /// Inequality comparison between a scoped handle and a null handle + /// @relates ScopedVk + template + inline bool operator!=(std::nullptr_t const&, ScopedVk const& handle) + { + return handle.valid(); + } + + /// Alias to ease use of ScopedVk with handle types whose destroy function is statically available. + /// + /// @tparam HandleType The handle type to wrap + /// @tparam ParentHandle The parent handle type needed by the destroy function. + /// @tparam DestroyFunction The function used to destroy the handle. + /// + /// @see VkDestroyerWithFuncPointer + /// + /// @ingroup cts_handle_helpers + /// @relates ScopedVk + template + using ScopedVkWithDefaultDestroy = ScopedVk>; + + /// Alias to ease use of ScopedVk with handle types whose destroy function is a run-time function pointer (such as from an extension) + /// + /// @tparam HandleType The handle type to wrap + /// @tparam ParentHandle The parent handle type needed by the destroy function. + /// + /// @see VkDefaultDestroyer + /// + /// @ingroup cts_handle_helpers + /// @relates ScopedVk + template + using ScopedVkWithPfn = ScopedVk>; + + using ScopedVkDeviceMemory = ScopedVkWithDefaultDestroy; + using ScopedVkPipeline = ScopedVkWithDefaultDestroy; + using ScopedVkPipelineLayout = ScopedVkWithDefaultDestroy; + using ScopedVkDescriptorSetLayout = ScopedVkWithDefaultDestroy; + using ScopedVkDescriptorPool = ScopedVkWithDefaultDestroy; + using ScopedVkImage = ScopedVkWithDefaultDestroy; + using ScopedVkImageView = ScopedVkWithDefaultDestroy; + using ScopedVkSampler = ScopedVkWithDefaultDestroy; +} // namespace Conformance + +#endif diff --git a/src/conformance/utilities/vulkan_utils.h b/src/conformance/utilities/vulkan_utils.h index 2a4acceb..5128519b 100644 --- a/src/conformance/utilities/vulkan_utils.h +++ b/src/conformance/utilities/vulkan_utils.h @@ -7,11 +7,15 @@ #ifdef XR_USE_GRAPHICS_API_VULKAN #include "throw_helpers.h" -#include "xr_dependencies.h" -#include +#include "common/xr_dependencies.h" +#include "common/vulkan_debug_object_namer.hpp" #include +#include + #include +#include +#include //#define USE_ONLINE_VULKAN_SHADERC #ifdef USE_ONLINE_VULKAN_SHADERC @@ -37,6 +41,7 @@ namespace Conformance { + using nonstd::span; inline std::string vkResultString(VkResult res) { @@ -156,9 +161,9 @@ namespace Conformance } // XXX These really shouldn't have trailing ';'s -#define XRC_THROW_VK(res, cmd) ThrowVkResult(res, #cmd, XRC_FILE_AND_LINE); -#define XRC_CHECK_THROW_VKCMD(cmd) CheckThrowVkResult(cmd, #cmd, XRC_FILE_AND_LINE); -#define XRC_CHECK_THROW_VKRESULT(res, cmdStr) CheckThrowVkResult(res, cmdStr, XRC_FILE_AND_LINE); +#define XRC_THROW_VK(res, cmd) ::Conformance::ThrowVkResult(res, #cmd, XRC_FILE_AND_LINE); +#define XRC_CHECK_THROW_VKCMD(cmd) ::Conformance::CheckThrowVkResult(cmd, #cmd, XRC_FILE_AND_LINE); +#define XRC_CHECK_THROW_VKRESULT(res, cmdStr) ::Conformance::CheckThrowVkResult(res, cmdStr, XRC_FILE_AND_LINE); struct MemoryAllocator { @@ -187,12 +192,12 @@ namespace Conformance VkMemoryAllocateInfo memAlloc{VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO, pNext}; memAlloc.allocationSize = memReqs.size; memAlloc.memoryTypeIndex = i; - XRC_CHECK_THROW_VKCMD(vkAllocateMemory(m_vkDevice, &memAlloc, nullptr, mem)); + XRC_CHECK_THROW_VKCMD(vkAllocateMemory(m_vkDevice, &memAlloc, nullptr, mem)) return; } } } - XRC_THROW("Memory format not supported"); + XRC_THROW("Memory format not supported") } private: @@ -386,12 +391,12 @@ namespace Conformance ShaderProgram(ShaderProgram&&) = delete; ShaderProgram& operator=(ShaderProgram&&) = delete; - void LoadVertexShader(const std::vector& code) + void LoadVertexShader(const span code) { Load(0, code); } - void LoadFragmentShader(const std::vector& code) + void LoadFragmentShader(const span code) { Load(1, code); } @@ -404,7 +409,7 @@ namespace Conformance private: VkDevice m_vkDevice{VK_NULL_HANDLE}; - void Load(uint32_t index, const std::vector& code) + void Load(uint32_t index, const span code) { VkShaderModuleCreateInfo modInfo{VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO}; @@ -435,13 +440,195 @@ namespace Conformance } }; + /// A wrapper for VkBuffer and VkDeviceMemory. + /// DOES NOT clean up itself on destruction because it does not carry a VkDevice pointer - the containing/derived class's destructor + /// must call Reset(device) + struct BufferAndMemory + { + VkBuffer buf{VK_NULL_HANDLE}; + VkDeviceMemory mem{VK_NULL_HANDLE}; + + /// Destroy the buffer and free the memory, if applicable. + void Reset(VkDevice device) + { + if (device != nullptr) { + if (buf != VK_NULL_HANDLE) { + vkDestroyBuffer(device, buf, nullptr); + } + if (mem != VK_NULL_HANDLE) { + vkFreeMemory(device, mem, nullptr); + } + } + buf = VK_NULL_HANDLE; + mem = VK_NULL_HANDLE; + } + + /// Swap the internals with another object. + /// Used by subclasses to provide move construction/assignment. + void Swap(BufferAndMemory& other) + { + using std::swap; + swap(buf, other.buf); + swap(mem, other.mem); + } + + /// Create the buffer handle (using the specified VkBufferCreateInfo), + /// allocate the memory, and bind the buffer to the memory. + void Create(VkDevice device, const MemoryAllocator& memAllocator, const VkBufferCreateInfo& bufInfo) + { + XRC_CHECK_THROW_VKCMD(vkCreateBuffer(device, &bufInfo, nullptr, &this->buf)); + VkMemoryRequirements memReq = {}; + vkGetBufferMemoryRequirements(device, buf, &memReq); + memAllocator.Allocate(memReq, &mem); + XRC_CHECK_THROW_VKCMD(vkBindBufferMemory(device, this->buf, this->mem, 0)); + } + + /// Create the buffer handle (using the specified array count, usage, and element type `T`), + /// allocate the memory, and bind the buffer to the memory. + /// Function template to allow easy, generic access: caller must make sure the type used here matches the type used elsewhere! + template + void Create(VkDevice device, const MemoryAllocator& memAllocator, uint32_t count, VkBufferUsageFlags usage) + { + + VkBufferCreateInfo bufInfo{VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO}; + bufInfo.usage = usage; + bufInfo.size = sizeof(T) * count; + Create(device, memAllocator, bufInfo); + } + + /// Update the elements of the buffer using vkMapMemory. + /// + /// Function template to allow easy, generic access: caller must make sure the type used here matches the type used elsewhere! + /// + /// @param device The VkDevice associated with this buffer + /// @param data Your data span (contiguous - pointer and size) + /// @param offsetElements A zero-based **element** offset from the beginning of the memory object. + template + void Update(VkDevice device, span data, uint32_t offsetElements = 0) + { + const size_t elements = data.size(); + T* map = nullptr; + XRC_CHECK_THROW_VKCMD(vkMapMemory(device, this->mem, sizeof(T) * offsetElements, sizeof(T) * elements, 0, (void**)&map)); + for (size_t i = 0; i < elements; ++i) { + map[i] = data[i]; + } + vkUnmapMemory(device, this->mem); + } + }; + + /// Type-generic base class for what d3d12 calls a "structured buffer" - an array of arbitrary things. + /// Unlike @ref UntypedBuffer this *does* carry the VkDevice + struct StructuredBufferBase : BufferAndMemory + { + /// Default constructible + StructuredBufferBase() noexcept = default; + + /// Delete the buffer, free the memory, and reset the count to 0. + void Reset() + { + BufferAndMemory::Reset(m_vkDevice); + m_vkDevice = nullptr; + m_memAllocator = nullptr; + m_count = 0; + } + + /// Destructor - frees resources + ~StructuredBufferBase() + { + Reset(); + } + + StructuredBufferBase(StructuredBufferBase&&) noexcept = delete; + StructuredBufferBase& operator=(StructuredBufferBase&& other) noexcept = delete; + StructuredBufferBase(const StructuredBufferBase&) = delete; + StructuredBufferBase& operator=(const StructuredBufferBase&) = delete; + + /// Initialize with a device and a memory allocator + void Init(VkDevice device, const MemoryAllocator& memAllocator) + { + m_vkDevice = device; + m_memAllocator = &memAllocator; + } + + protected: + /// Swap the internals with another object. + /// Used by subclasses to provide move construction/assignment. + void Swap(StructuredBufferBase& other) + { + using std::swap; + BufferAndMemory::Swap(other); + swap(m_vkDevice, other.m_vkDevice); + swap(m_count, other.m_count); + swap(m_memAllocator, other.m_memAllocator); + } + VkDevice m_vkDevice{VK_NULL_HANDLE}; + uint32_t m_count{}; + const MemoryAllocator* m_memAllocator{nullptr}; + }; + + /// Class template for a "Structured Buffer" - an array of some arbitrary type. + /// + /// Most of the functionality is in StructuredBufferBase, only the few things that are type-specific are on this most-derived class. + template + struct StructuredBuffer : StructuredBufferBase + { + + /// Default constructible + StructuredBuffer() noexcept = default; + + /// Move-constructor + StructuredBuffer(StructuredBuffer&& other) noexcept : StructuredBuffer() + { + Swap(other); + } + + /// Move-assignment + StructuredBuffer& operator=(StructuredBuffer&& other) noexcept + { + if (this == &other) { + return *this; + } + Reset(); + Swap(other); + return *this; + } + + StructuredBuffer(const StructuredBuffer&) = delete; + StructuredBuffer& operator=(const StructuredBuffer&) = delete; + + /// Create the buffer handle (using the specified array count and usage), allocate the memory, + /// and bind the buffer to the memory. + bool Create(uint32_t count, VkBufferUsageFlags usage) + { + BufferAndMemory::Create(m_vkDevice, *m_memAllocator, count, usage); + + m_count = count; + + return true; + } + + /// Update the elements of the buffer using vkMapMemory + /// + /// @param device The VkDevice associated with this buffer + /// @param data Your data (contiguous) + /// @param offsetElements A zero-based **element** offset from the beginning of the memory object. + void Update(span data, uint32_t offsetElements = 0) + { + BufferAndMemory::Update(m_vkDevice, data, offsetElements); + } + + /// Create a VkDescriptorBufferInfo covering the entire buffer + VkDescriptorBufferInfo MakeDescriptor() + { + return {buf, 0, sizeof(T) * m_count}; + } + }; + // VertexBuffer base class struct VertexBufferBase { - VkBuffer idxBuf{VK_NULL_HANDLE}; - VkDeviceMemory idxMem{VK_NULL_HANDLE}; - VkBuffer vtxBuf{VK_NULL_HANDLE}; - VkDeviceMemory vtxMem{VK_NULL_HANDLE}; + BufferAndMemory idx; + BufferAndMemory vtx; VkVertexInputBindingDescription bindDesc{}; std::vector attrDesc{}; struct @@ -454,24 +641,8 @@ namespace Conformance void Reset() { - if (m_vkDevice != nullptr) { - if (idxBuf != VK_NULL_HANDLE) { - vkDestroyBuffer(m_vkDevice, idxBuf, nullptr); - } - if (idxMem != VK_NULL_HANDLE) { - vkFreeMemory(m_vkDevice, idxMem, nullptr); - } - if (vtxBuf != VK_NULL_HANDLE) { - vkDestroyBuffer(m_vkDevice, vtxBuf, nullptr); - } - if (vtxMem != VK_NULL_HANDLE) { - vkFreeMemory(m_vkDevice, vtxMem, nullptr); - } - } - idxBuf = VK_NULL_HANDLE; - idxMem = VK_NULL_HANDLE; - vtxBuf = VK_NULL_HANDLE; - vtxMem = VK_NULL_HANDLE; + idx.Reset(m_vkDevice); + vtx.Reset(m_vkDevice); bindDesc = {}; attrDesc.clear(); count = {0, 0}; @@ -487,6 +658,13 @@ namespace Conformance VertexBufferBase& operator=(const VertexBufferBase&) = delete; VertexBufferBase(VertexBufferBase&&) = delete; VertexBufferBase& operator=(VertexBufferBase&&) = delete; + void Init(VkDevice device, const MemoryAllocator* memAllocator, std::vector&& attr) + { + m_vkDevice = device; + m_memAllocator = memAllocator; + attrDesc = std::move(attr); + } + void Init(VkDevice device, const MemoryAllocator* memAllocator, const std::vector& attr) { m_vkDevice = device; @@ -496,11 +674,18 @@ namespace Conformance protected: VkDevice m_vkDevice{VK_NULL_HANDLE}; - void AllocateBufferMemory(VkBuffer buf, VkDeviceMemory* mem) const + + /// Create and bind the index and vertex buffers. + /// @pre Call Init() + template + bool Create(uint32_t idxCount, uint32_t vtxCount) { - VkMemoryRequirements memReq = {}; - vkGetBufferMemoryRequirements(m_vkDevice, buf, &memReq); - m_memAllocator->Allocate(memReq, mem); + idx.Create(m_vkDevice, *m_memAllocator, idxCount, VK_BUFFER_USAGE_INDEX_BUFFER_BIT); + vtx.Create(m_vkDevice, *m_memAllocator, vtxCount, VK_BUFFER_USAGE_VERTEX_BUFFER_BIT); + + count = {idxCount, vtxCount}; + + return true; } /// Swap the internals with another object. @@ -508,10 +693,8 @@ namespace Conformance void Swap(VertexBufferBase& other) { using std::swap; - swap(idxBuf, other.idxBuf); - swap(idxMem, other.idxMem); - swap(vtxBuf, other.vtxBuf); - swap(vtxMem, other.vtxMem); + swap(idx, other.idx); + swap(vtx, other.vtx); swap(bindDesc, other.bindDesc); swap(attrDesc, other.attrDesc); swap(count, other.count); @@ -524,22 +707,22 @@ namespace Conformance }; // VertexBuffer template to wrap the indices and vertices - template + template struct VertexBuffer : public VertexBufferBase { - static constexpr VkVertexInputBindingDescription c_bindingDesc = {0, sizeof(T), VK_VERTEX_INPUT_RATE_VERTEX}; + static constexpr VkVertexInputBindingDescription c_bindingDesc = {0, sizeof(VertexType), VK_VERTEX_INPUT_RATE_VERTEX}; /// Default constructible VertexBuffer() noexcept = default; /// Move-constructor - VertexBuffer(VertexBuffer&& other) noexcept : VertexBuffer() + VertexBuffer(VertexBuffer&& other) noexcept : VertexBuffer() { Swap(other); } /// Move-assignment - VertexBuffer& operator=(VertexBuffer&& other) noexcept + VertexBuffer& operator=(VertexBuffer&& other) noexcept { if (this == &other) { return *this; @@ -550,54 +733,36 @@ namespace Conformance } // no copy construct - VertexBuffer(const VertexBuffer&) = delete; + VertexBuffer(const VertexBuffer&) = delete; // no copy assign - VertexBuffer& operator=(const VertexBuffer&) = delete; + VertexBuffer& operator=(const VertexBuffer&) = delete; + /// Create and bind the index and vertex buffers. + /// @pre Call Init() bool Create(uint32_t idxCount, uint32_t vtxCount) { - VkBufferCreateInfo bufInfo{VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO}; - bufInfo.usage = VK_BUFFER_USAGE_INDEX_BUFFER_BIT; - bufInfo.size = sizeof(uint16_t) * idxCount; - XRC_CHECK_THROW_VKCMD(vkCreateBuffer(m_vkDevice, &bufInfo, nullptr, &idxBuf)); - AllocateBufferMemory(idxBuf, &idxMem); - XRC_CHECK_THROW_VKCMD(vkBindBufferMemory(m_vkDevice, idxBuf, idxMem, 0)); - - bufInfo.usage = VK_BUFFER_USAGE_VERTEX_BUFFER_BIT; - bufInfo.size = sizeof(T) * vtxCount; - XRC_CHECK_THROW_VKCMD(vkCreateBuffer(m_vkDevice, &bufInfo, nullptr, &vtxBuf)); - AllocateBufferMemory(vtxBuf, &vtxMem); - XRC_CHECK_THROW_VKCMD(vkBindBufferMemory(m_vkDevice, vtxBuf, vtxMem, 0)); - bindDesc = c_bindingDesc; - - count = {idxCount, vtxCount}; - - return true; + return VertexBufferBase::Create(idxCount, vtxCount); } - void UpdateIndices(const uint16_t* data, uint32_t elements, uint32_t offset = 0) + /// Update the elements of the index buffer using vkMapMemory + /// + /// @param device The VkDevice associated with this buffer + /// @param data Your index data (contiguous) + /// @param offsetElements A zero-based **element** offset from the beginning of the memory object. + void UpdateIndices(span data, uint32_t offsetElements = 0) { - uint16_t* map = nullptr; - XRC_CHECK_THROW_VKCMD(vkMapMemory(m_vkDevice, idxMem, sizeof(map[0]) * offset, sizeof(map[0]) * elements, 0, (void**)&map)); - for (size_t i = 0; i < elements; ++i) { - map[i] = data[i]; - } - vkUnmapMemory(m_vkDevice, idxMem); + idx.Update(m_vkDevice, data, offsetElements); } - void UpdateVertices(const T* data, uint32_t elements, uint32_t offset = 0) + void UpdateVertices(span data, uint32_t offsetElements = 0) { - T* map = nullptr; - XRC_CHECK_THROW_VKCMD(vkMapMemory(m_vkDevice, vtxMem, sizeof(map[0]) * offset, sizeof(map[0]) * elements, 0, (void**)&map)); - for (size_t i = 0; i < elements; ++i) { - map[i] = data[i]; - } - vkUnmapMemory(m_vkDevice, vtxMem); + vtx.Update(m_vkDevice, data, offsetElements); } }; - template - constexpr VkVertexInputBindingDescription VertexBuffer::c_bindingDesc; + + template + constexpr VkVertexInputBindingDescription VertexBuffer::c_bindingDesc; // RenderPass wrapper struct RenderPass @@ -886,8 +1051,6 @@ namespace Conformance struct Pipeline { VkPipeline pipe{VK_NULL_HANDLE}; - VkPrimitiveTopology topology{VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST}; - std::vector dynamicStateEnables; Pipeline() = default; ~Pipeline() @@ -895,19 +1058,15 @@ namespace Conformance Reset(); } - void Dynamic(VkDynamicState state) - { - dynamicStateEnables.emplace_back(state); - } - void Create(VkDevice device, VkExtent2D /*size*/, const PipelineLayout& layout, const RenderPass& rp, const ShaderProgram& sp, - const VkVertexInputBindingDescription& bindDesc, span attrDesc) + const VkVertexInputBindingDescription& bindDesc, span attrDesc, + span dynamicStates) { m_vkDevice = device; VkPipelineDynamicStateCreateInfo dynamicState{VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO}; - dynamicState.dynamicStateCount = (uint32_t)dynamicStateEnables.size(); - dynamicState.pDynamicStates = dynamicStateEnables.data(); + dynamicState.dynamicStateCount = (uint32_t)dynamicStates.size(); + dynamicState.pDynamicStates = dynamicStates.data(); VkPipelineVertexInputStateCreateInfo vi{VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO}; vi.vertexBindingDescriptionCount = 1; @@ -917,7 +1076,7 @@ namespace Conformance VkPipelineInputAssemblyStateCreateInfo ia{VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO}; ia.primitiveRestartEnable = VK_FALSE; - ia.topology = topology; + ia.topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST; VkPipelineRasterizationStateCreateInfo rs{VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO}; rs.polygonMode = VK_POLYGON_MODE_FILL; @@ -991,7 +1150,15 @@ namespace Conformance pipeInfo.layout = layout.layout; pipeInfo.renderPass = rp.pass; pipeInfo.subpass = 0; - XRC_CHECK_THROW_VKCMD(vkCreateGraphicsPipelines(m_vkDevice, VK_NULL_HANDLE, 1, &pipeInfo, nullptr, &pipe)); + + Create(device, pipeInfo); + } + + void Create(VkDevice device, const VkGraphicsPipelineCreateInfo& info) + { + m_vkDevice = device; + + XRC_CHECK_THROW_VKCMD(vkCreateGraphicsPipelines(m_vkDevice, VK_NULL_HANDLE, 1, &info, nullptr, &pipe)); } void Reset() diff --git a/src/external/jnipp/jnipp.cpp b/src/external/jnipp/jnipp.cpp index 8309e54c..20a91d9a 100644 --- a/src/external/jnipp/jnipp.cpp +++ b/src/external/jnipp/jnipp.cpp @@ -16,6 +16,7 @@ // Standard Dependencies #include #include +#include // Local Dependencies #include "jnipp.h" @@ -129,9 +130,9 @@ namespace jni /** Convert from a UTF-32 string to a UTF-16 Java string. */ - std::basic_string toJString(const wchar_t* str, size_t length) + std::vector toJString(const wchar_t* str, size_t length) { - std::basic_string result; + std::vector result; result.reserve(length * 2); // Worst case scenario. @@ -144,11 +145,11 @@ namespace jni ch -= uint32_t(0x10000); // Add the first of the two-segment character. - result += jchar(0xD800 + (ch >> 10)); + result.push_back(jchar(0xD800 + (ch >> 10))); ch = wchar_t(0xDC00) + (ch & 0x03FF); } - result += jchar(ch); + result.push_back(jchar(ch)); } return result; @@ -574,7 +575,7 @@ namespace jni jobject handle = env->NewString((const jchar*) value.c_str(), jsize(value.length())); #else auto jstr = toJString(value.c_str(), value.length()); - jobject handle = env->NewString(jstr.c_str(), jsize(jstr.length())); + jobject handle = env->NewString(jstr.data(), jsize(jstr.size())); #endif env->SetObjectField(_handle, field, handle); env->DeleteLocalRef(handle); @@ -587,7 +588,7 @@ namespace jni jobject handle = env->NewString((const jchar*) value, jsize(std::wcslen(value))); #else auto jstr = toJString(value, std::wcslen(value)); - jobject handle = env->NewString(jstr.c_str(), jsize(jstr.length())); + jobject handle = env->NewString(jstr.data(), jsize(jstr.size())); #endif env->SetObjectField(_handle, field, handle); env->DeleteLocalRef(handle); @@ -871,7 +872,7 @@ namespace jni jobject handle = env->NewString((const jchar*) value.c_str(), jsize(value.length())); #else auto jstr = toJString(value.c_str(), value.length()); - jobject handle = env->NewString(jstr.c_str(), jsize(jstr.length())); + jobject handle = env->NewString(jstr.data(), jsize(jstr.size())); #endif env->SetStaticObjectField(getHandle(), field, handle); env->DeleteLocalRef(handle); @@ -1277,7 +1278,7 @@ namespace jni jobject jvalue = env->NewString((const jchar*) value.c_str(), jsize(value.length())); #else auto jstr = toJString(value.c_str(), value.length()); - jobject jvalue = env->NewString(jstr.c_str(), jsize(jstr.length())); + jobject jvalue = env->NewString(jstr.data(), jsize(jstr.size())); #endif env->SetObjectArrayElement(jobjectArray(getHandle()), index, jvalue); env->DeleteLocalRef(jvalue); @@ -1522,7 +1523,7 @@ namespace jni JNIEnv* env(); #ifndef _WIN32 - extern std::basic_string toJString(const wchar_t* str, size_t length); + extern std::vector toJString(const wchar_t* str, size_t length); #endif namespace internal @@ -1603,13 +1604,13 @@ namespace jni void valueArg(value_t* v, const std::wstring& a) { auto jstr = toJString(a.c_str(), a.length()); - ((jvalue*) v)->l = env()->NewString(jstr.c_str(), jsize(jstr.length())); + ((jvalue*) v)->l = env()->NewString(jstr.data(), jsize(jstr.size())); } void valueArg(value_t* v, const wchar_t* a) { auto jstr = toJString(a, std::wcslen(a)); - ((jvalue*) v)->l = env()->NewString(jstr.c_str(), jsize(jstr.length())); + ((jvalue*) v)->l = env()->NewString(jstr.data(), jsize(jstr.size())); } #endif diff --git a/src/external/jsoncpp/.travis.yml b/src/external/jsoncpp/.travis.yml deleted file mode 100644 index 23acd4e5..00000000 --- a/src/external/jsoncpp/.travis.yml +++ /dev/null @@ -1,71 +0,0 @@ -# Build matrix / environment variables are explained on: -# http://about.travis-ci.com/docs/user/build-configuration/ -# This file can be validated on: http://www.yamllint.com/ -# Or using the Ruby based travel command line tool: -# gem install travis --no-rdoc --no-ri -# travis lint .travis.yml -language: cpp -sudo: false -addons: - homebrew: - packages: - - clang-format - - meson - - ninja - update: false # do not update homebrew by default - apt: - sources: - - ubuntu-toolchain-r-test - - llvm-toolchain-xenial-8 - packages: - - clang-format-8 - - clang-8 - - valgrind -matrix: - include: - - name: Mac clang meson static release testing - os: osx - osx_image: xcode11 - compiler: clang - env: - CXX="clang++" - CC="clang" - LIB_TYPE=static - BUILD_TYPE=release - script: ./.travis_scripts/meson_builder.sh - - name: Linux xenial clang meson static release testing - os: linux - dist: xenial - compiler: clang - env: - CXX="clang++" - CC="clang" - LIB_TYPE=static - BUILD_TYPE=release - PYTHONUSERBASE="$(pwd)/LOCAL" - PATH="$PYTHONUSERBASE/bin:$PATH" - # before_install and install steps only needed for linux meson builds - before_install: - - source ./.travis_scripts/travis.before_install.${TRAVIS_OS_NAME}.sh - install: - - source ./.travis_scripts/travis.install.${TRAVIS_OS_NAME}.sh - script: ./.travis_scripts/meson_builder.sh - - name: Linux xenial gcc cmake coverage - os: linux - dist: xenial - compiler: gcc - env: - CXX=g++ - CC=gcc - DO_Coverage=ON - BUILD_TOOL="Unix Makefiles" - BUILD_TYPE=Debug - LIB_TYPE=shared - DESTDIR=/tmp/cmake_json_cpp - before_install: - - pip install --user cpp-coveralls - script: ./.travis_scripts/cmake_builder.sh - after_success: - - coveralls --include src/lib_json --include include -notifications: - email: false diff --git a/src/scripts/automatic_source_generator.py b/src/scripts/automatic_source_generator.py index 3866fada..2d6b6b39 100755 --- a/src/scripts/automatic_source_generator.py +++ b/src/scripts/automatic_source_generator.py @@ -41,7 +41,7 @@ def undecorate(name): """Undecorate a name by removing the leading Xr and making it lowercase.""" lower = name.lower() - assert(lower.startswith('xr')) + assert lower.startswith('xr') return lower[2:] @@ -977,7 +977,7 @@ def genStructUnion(self, type_info, type_category, type_name, alias): if lengths is not None: is_array = True is_null_terminated = any(elt.null_terminated for elt in lengths) - assert(('null-terminated' in member.get('len')) == is_null_terminated) + assert ('null-terminated' in member.get('len')) == is_null_terminated # Get the name of the (first) variable to use for the count. for length in lengths: @@ -1047,8 +1047,8 @@ def genStructUnion(self, type_info, type_category, type_name, alias): frame = currentframe() frameinfo = getframeinfo(frame) if frame is not None else None self.printCodeGenWarningMessage( - frameinfo.filename if frameinfo is not None else None, - (frameinfo.lineno + 1) if frameinfo is not None else None, + frameinfo.filename if frame is not None else None, + (frameinfo.lineno + 1) if frame is not None else None, 'Struct \"%s\" has different children than possible parent struct \"%s\".' % ( type_name, generic_struct_name)) if is_union: @@ -1166,7 +1166,7 @@ def addCommandToDispatchList(self, ext_name, ext_type, name, cmd_info): if lengths: is_array = any(not elt.null_terminated for elt in lengths) is_null_terminated = any(elt.null_terminated for elt in lengths) - # assert(is_null_terminated == new_is_null_terminated) + # assert is_null_terminated == new_is_null_terminated # Get the name of the (first) variable to use for the count. length_params = tuple(length.other_param_name for length in lengths @@ -1534,7 +1534,7 @@ def getFirstHandleName(self, param): if not self.isHandle(param.type): return None name = param.name - assert(param.pointer_count <= 1) + assert param.pointer_count <= 1 if param.pointer_count == 1: if param.pointer_count_var is None: # Just a pointer diff --git a/src/scripts/loader_source_generator.py b/src/scripts/loader_source_generator.py index 188865c0..0134c813 100755 --- a/src/scripts/loader_source_generator.py +++ b/src/scripts/loader_source_generator.py @@ -235,7 +235,7 @@ def outputLoaderGeneratedFuncs(self): tramp_variable_defines += ' if (XR_SUCCEEDED(result)) {\n' # These should be mutually exclusive - verify it. - assert((not cur_cmd.is_destroy_disconnect) or + assert ((not cur_cmd.is_destroy_disconnect) or (pointer_count == 0)) else: tramp_variable_defines += self.printCodeGenErrorMessage( diff --git a/src/scripts/utility_source_generator.py b/src/scripts/utility_source_generator.py index 9ecb0c6b..836e6d65 100755 --- a/src/scripts/utility_source_generator.py +++ b/src/scripts/utility_source_generator.py @@ -48,22 +48,30 @@ def outputGeneratedHeaderWarning(self): # gen_opts the UtilitySourceGeneratorOptions object def beginFile(self, genOpts): AutomaticSourceOutputGenerator.beginFile(self, genOpts) + assert self.genOpts + assert self.genOpts.filename + preamble = '' - if self.genOpts.filename == 'xr_generated_dispatch_table_core.h': + + if self.genOpts.filename.endswith('.h'): + # All .h start the same preamble += '#pragma once\n\n' + + elif self.genOpts.filename.endswith('.c'): + # All .c files start the same + header = self.genOpts.filename.replace('.c', '.h') + preamble += f'#include "{header}"\n\n' + else: + raise RuntimeError("Unknown filename extension! " + self.genOpts.filename) + + # The different .h files have different includes + if self.genOpts.filename == 'xr_generated_dispatch_table_core.h': preamble += '#include \n' + elif self.genOpts.filename == 'xr_generated_dispatch_table.h': - preamble += '#pragma once\n\n' preamble += '#include "xr_dependencies.h"\n' preamble += '#include \n' preamble += '#include \n' - elif self.genOpts.filename == 'xr_generated_dispatch_table_core.c': - preamble += '#include "xr_generated_dispatch_table_core.h"\n' - elif self.genOpts.filename == 'xr_generated_dispatch_table.c': - preamble += '#include \n' - preamble += '#include "xr_generated_dispatch_table.h"\n' - else: - raise RuntimeError("Unknown filename! " + self.genOpts.filename) preamble += '\n' @@ -73,24 +81,24 @@ def beginFile(self, genOpts): # and then call down to the base class to wrap everything up. # self the UtilitySourceOutputGenerator object def endFile(self): + assert self.genOpts + assert self.genOpts.filename + file_data = '' file_data += '#ifdef __cplusplus\n' file_data += 'extern "C" { \n' file_data += '#endif\n' - if self.genOpts.filename == 'xr_generated_dispatch_table_core.h': + if self.genOpts.filename.endswith('.h'): file_data += self.outputDispatchTable() file_data += self.outputDispatchPrototypes() - elif self.genOpts.filename == 'xr_generated_dispatch_table.h': - file_data += self.outputDispatchTable() - file_data += self.outputDispatchPrototypes() - elif self.genOpts.filename == 'xr_generated_dispatch_table_core.c': - file_data += self.outputDispatchTableHelper() - elif self.genOpts.filename == 'xr_generated_dispatch_table.c': + + elif self.genOpts.filename.endswith('.c'): file_data += self.outputDispatchTableHelper() + else: - raise RuntimeError("Unknown filename! " + self.genOpts.filename) + raise RuntimeError("Unknown filename extension! " + self.genOpts.filename) file_data += '\n' file_data += '#ifdef __cplusplus\n' @@ -115,6 +123,7 @@ def outputDispatchPrototypes(self): # Write out a C-style structure used to store the Dispatch table information # self the ApiDumpOutputGenerator object def outputDispatchTable(self): + assert self.genOpts commands = [] table = '' cur_extension_name = '' @@ -171,6 +180,7 @@ def outputDispatchTable(self): # an instance handle and a corresponding xrGetInstanceProcAddr command. # self the ApiDumpOutputGenerator object def outputDispatchTableHelper(self): + assert self.genOpts commands = [] table_helper = '' cur_extension_name = '' diff --git a/src/scripts/validation_layer_generator.py b/src/scripts/validation_layer_generator.py index edd8384d..5c295c82 100644 --- a/src/scripts/validation_layer_generator.py +++ b/src/scripts/validation_layer_generator.py @@ -65,6 +65,7 @@ def outputGeneratedHeaderWarning(self): # gen_opts the ValidationSourceGeneratorOptions object def beginFile(self, genOpts): AutomaticSourceOutputGenerator.beginFile(self, genOpts) + assert self.genOpts preamble = '' if self.genOpts.filename == 'xr_generated_core_validation.hpp': preamble += '#pragma once\n' @@ -115,6 +116,7 @@ def beginFile(self, genOpts): # and then call down to the base class to wrap everything up. # self the ValidationSourceOutputGenerator object def endFile(self): + assert self.genOpts file_data = '' if self.genOpts.filename == 'xr_generated_core_validation.hpp': file_data += self.outputValidationHeaderInfo() @@ -128,6 +130,7 @@ def endFile(self): def makeInfoName(self, handle_type=None, handle_type_name=None): if not handle_type_name: + assert handle_type handle_type_name = handle_type.name base_handle_name = undecorate(handle_type_name) return 'g_%s_info' % base_handle_name @@ -170,9 +173,9 @@ def outputValidationStateCheckStructs(self): active_structures = dict() for cur_state in self.api_states: type_name = '%s' % cur_state.type - cur_list = [] - if active_structures.get(type_name) is not None: - cur_list = active_structures.get(type_name) + cur_list = active_structures.get(type_name) + if cur_list is None: + cur_list = [] cur_list.append(cur_state.variable) active_structures[type_name] = cur_list for type_name, variable_list in active_structures.items(): @@ -498,13 +501,13 @@ def outputValidationSourceNextChainFunc(self): avoid_dupe = None if cur_value.alias: aliased_value = [x for x in enum_tuple.values if x.name == cur_value.alias][0] - if aliased_value.protect_value and aliased_value.protect_value != cur_value.protect_value and aliased_value.protect_value != struct_tuple.protect_value: + if struct_tuple and aliased_value.protect_value and aliased_value.protect_value != cur_value.protect_value and aliased_value.protect_value != struct_tuple.protect_value: avoid_dupe = aliased_value.protect_string next_chain_info += '#if !(%s)\n' % avoid_dupe else: # This would unconditionally cause a duplicate case continue - if struct_tuple.protect_value: + if struct_tuple and struct_tuple.protect_value: next_chain_info += '#if %s\n' % struct_tuple.protect_string next_chain_info += self.writeIndent(2) @@ -519,7 +522,7 @@ def outputValidationSourceNextChainFunc(self): next_chain_info += '}\n' next_chain_info += self.writeIndent(3) next_chain_info += 'break;\n' - if struct_tuple.protect_value: + if struct_tuple and struct_tuple.protect_value: next_chain_info += '#endif // %s\n' % struct_tuple.protect_string if avoid_dupe: next_chain_info += '#endif // !(%s)\n' % avoid_dupe @@ -1417,7 +1420,7 @@ def outputParamMemberContents(self, is_command, struct_command_name, param_membe if is_array: long_count_name = param_member_prefix if param_member.is_static_array: - assert(param_member.static_array_sizes is not None) + assert param_member.static_array_sizes is not None short_count_var = param_member.static_array_sizes[0] long_count_name = param_member.static_array_sizes[0] elif param_member.array_count_var: @@ -1576,7 +1579,7 @@ def outputParamMemberContents(self, is_command, struct_command_name, param_membe if is_relation_group: for child in relation_group.child_struct_names: child_struct = self.getStruct(child) - if child_struct.protect_value: + if child_struct and child_struct.protect_value: param_member_contents += '#if %s\n' % child_struct.protect_string param_member_contents += self.writeIndent(indent) @@ -1710,7 +1713,7 @@ def outputParamMemberContents(self, is_command, struct_command_name, param_membe param_member_contents += self.writeIndent(indent) param_member_contents += '}\n' - if child_struct.protect_value: + if child_struct and child_struct.protect_value: param_member_contents += '#endif // %s\n' % child_struct.protect_string param_member_contents += self.writeIndent(indent) @@ -1894,6 +1897,7 @@ def writeValidateStructFuncs(self): # If this struct is the base of a relation group, check to see if this call really should go to any one of # it's children instead of itself. if is_base_type: + assert relation_group for member in xr_struct.members: if member.name == 'next': struct_check += self.writeIndent(indent) @@ -1904,7 +1908,7 @@ def writeValidateStructFuncs(self): xr_struct.name, member.name) for child in relation_group.child_struct_names: child_struct = self.getStruct(child) - if child_struct.protect_value: + if child_struct and child_struct.protect_value: struct_check += '#if %s\n' % child_struct.protect_string struct_check += self.writeIndent(indent) struct_check += 'if (value->type == %s) {\n' % self.genXrStructureType( @@ -1913,7 +1917,7 @@ def writeValidateStructFuncs(self): struct_check += self.writeIndent(indent) struct_check += 'const %s* new_value = reinterpret_cast(value);\n' % ( child, child) - if child_struct.ext_name and not self.isCoreExtensionName(child_struct.ext_name): + if child_struct and child_struct.ext_name and not self.isCoreExtensionName(child_struct.ext_name): struct_check += self.writeIndent(indent) struct_check += 'if (nullptr != instance_info && !ExtensionEnabled(instance_info->enabled_extensions, "%s")) {\n' % child_struct.ext_name indent += 1 @@ -1941,7 +1945,7 @@ def writeValidateStructFuncs(self): indent -= 1 struct_check += self.writeIndent(indent) struct_check += '}\n' - if child_struct.protect_value: + if child_struct and child_struct.protect_value: struct_check += '#endif // %s\n' % child_struct.protect_string struct_check += self.writeIndent(indent) @@ -2435,6 +2439,7 @@ def genNextValidateFunc(self, cur_command, has_return, is_create, is_destroy, is if first_param.is_handle: first_handle_tuple = self.getHandle(first_param.type) first_handle_name = self.getFirstHandleName(first_param) + assert first_handle_tuple if first_handle_tuple.name == 'XrInstance': next_validate_func += ' GenValidUsageXrInstanceInfo *gen_instance_info = g_instance_info.get(%s);\n' % first_handle_name else: @@ -2469,7 +2474,8 @@ def genNextValidateFunc(self, cur_command, has_return, is_create, is_destroy, is last_handle_tuple = self.getHandle(last_param.type) last_handle_name = last_param.name if is_create: - assert(last_handle_tuple.name != 'XrInstance') + assert last_handle_tuple + assert last_handle_tuple.name != 'XrInstance' next_validate_func += ' if (XR_SUCCESS == result && nullptr != %s) {\n' % last_handle_name next_validate_func += ' std::unique_ptr handle_info(new GenValidUsageXrHandleInfo());\n' @@ -2508,6 +2514,7 @@ def genNextValidateFunc(self, cur_command, has_return, is_create, is_destroy, is # If this object contains a state that needs tracking, free it valid_type_list = [] for cur_state in self.api_states: + assert last_handle_tuple if last_handle_tuple.name == cur_state.type and cur_state.type not in valid_type_list: valid_type_list.append(cur_state.type) next_validate_func += self.writeIndent(3)