diff --git a/.azure-pipelines/openxr-cts.yml b/.azure-pipelines/openxr-cts.yml deleted file mode 100644 index ad1f82d4..00000000 --- a/.azure-pipelines/openxr-cts.yml +++ /dev/null @@ -1,22 +0,0 @@ -# Copyright (c) 2019-2023, The Khronos Group Inc. -# SPDX-License-Identifier: Apache-2.0 - -# Main azure-pipelines configuration for the OpenXR-CTS repo. -trigger: - branches: - include: - - "*" -variables: - VULKAN_SDK_VERSION: "1.1.114.0" - -stages: - - stage: Check - jobs: - - template: shared/check_clang_format.yml - # - template: shared/check_file_format.yml - - - stage: Build - jobs: - - template: shared/build_jobs.yml - parameters: - isSdkSourceRepo: false diff --git a/.azure-pipelines/shared/build_jobs.yml b/.azure-pipelines/shared/build_jobs.yml deleted file mode 100644 index 213a40a8..00000000 --- a/.azure-pipelines/shared/build_jobs.yml +++ /dev/null @@ -1,173 +0,0 @@ -# Copyright (c) 2019-2023, The Khronos Group Inc. -# SPDX-License-Identifier: Apache-2.0 - -parameters: - - name: sourceDir - type: string - default: "$(System.DefaultWorkingDirectory)" - - name: isSdkSourceRepo - type: boolean - default: true - -jobs: - # Build the loader, API layers, and samples on Linux - - job: linux_build - displayName: "Linux" - strategy: - matrix: - xlib: - buildType: RelWithDebInfo - presentationBackend: xlib - xcb: - buildType: RelWithDebInfo - presentationBackend: xcb - wayland: - buildType: RelWithDebInfo - presentationBackend: wayland - pool: - vmImage: "ubuntu-latest" - container: khronosgroup/docker-images:openxr-sdk.20230323 - # container: khronosgroup/docker-images@sha256:20edadbaa6cdec4fed7417c24b18dfb4b93eec940fdf1a27b5f97272dec47032 - steps: - # First build as debug - - template: build_linux.yml - parameters: - sourceDir: ${{parameters.sourceDir}} - buildType: Debug - cmakeArgs: "-DPRESENTATION_BACKEND=$(PresentationBackend)" - - # Then build release - - template: build_linux.yml - parameters: - sourceDir: ${{parameters.sourceDir}} - buildType: RelWithDebInfo - cmakeArgs: "-DPRESENTATION_BACKEND=$(PresentationBackend)" - - # This job computes the product of the config dimensions - - job: generator - pool: - vmImage: "ubuntu-latest" - steps: - - task: PythonScript@0 - name: winmatrix - inputs: - scriptPath: $(System.DefaultWorkingDirectory)/.azure-pipelines/shared/generate_windows_matrix_build.py - # argument sets the variable name defined by python script - arguments: winbuild - pythonInterpreter: /usr/bin/python3 - - # Build the loader, API layers, and samples on Windows - - job: windows_build - dependsOn: generator - displayName: "Windows MSVC" - variables: - VULKAN_SDK: "$(System.DefaultWorkingDirectory)\\vulkan_sdk\\$(VULKAN_SDK_VERSION)" - pool: - vmImage: "windows-latest" - # Use the json emitted by the generator job to set up this matrix - strategy: - matrix: $[ dependencies.generator.outputs['winmatrix.winbuild'] ] - steps: - - template: build_msvc.yml - parameters: - sourceDir: ${{parameters.sourceDir}} - buildType: $(buildType) - generator: "$(generator)" - cmakeArgs: $(cmakeArgs) -DBUILD_ALL_EXTENSIONS=ON -DINSTALL_TO_ARCHITECTURE_PREFIXES=ON - useVulkan: "true" - - - task: PublishPipelineArtifact@1 - displayName: Publish loader - condition: and(succeeded(), eq(variables.buildType, 'RelWithDebInfo')) - inputs: - path: ${{parameters.sourceDir}}/install - artifact: $(artifactName) - - # Build the loader, API layers, and samples on Windows with MinGW - # - job: mingw_build - # displayName: 'Windows MinGW' - # variables: - # VULKAN_SDK: "$(System.DefaultWorkingDirectory)\\vulkan_sdk\\$(VULKAN_SDK_VERSION)" - # pool: - # vmImage: 'windows-latest' - # steps: - # - template: build_mingw.yml - # parameters: - # sourceDir: ${{parameters.sourceDir}} - # buildType: RelWithDebInfo - # cmakeArgs: -DBUILD_ALL_EXTENSIONS=ON - # useVulkan: 'true' - - - job: combine_artifacts - dependsOn: - - generator - - windows_build - condition: ${{ eq(parameters.isSdkSourceRepo, true) }} - displayName: "Organize artifacts" - pool: - vmImage: "windows-latest" - steps: - - download: current - patterns: "**/*.dll" - displayName: Download dynamic libraries - - download: current - patterns: "**/*.pdb" - displayName: Download dynamic library symbols - - download: current - patterns: "**/*.lib" - displayName: Download link import libraries - - download: current - patterns: "**/*.h" - displayName: Download headers - - download: current - patterns: "**/*.cmake" - displayName: Download CMake scripts - - download: current - patterns: "**/*.exe" - displayName: Download executables - - download: current - patterns: "**/*.json" - displayName: Download manifests - # Use the specified version of Python from the tool cache - - task: UsePythonVersion@0 - inputs: - versionSpec: "3.10" - - task: PythonScript@0 - displayName: Move artifact contents - inputs: - scriptPath: $(System.DefaultWorkingDirectory)/.azure-pipelines/shared/organize_windows_artifacts.py - arguments: $(Pipeline.Workspace) $(System.DefaultWorkingDirectory)/openxr_loader - - task: PublishPipelineArtifact@1 - displayName: Publish combined artifact - condition: succeeded() - inputs: - path: $(System.DefaultWorkingDirectory)/openxr_loader - artifact: openxr_loader_windows - - # NuGet stuff - # See: - # https://learn.microsoft.com/en-us/azure/devops/pipelines/tasks/tool/nuget?view=azure-devops - # https://learn.microsoft.com/en-us/azure/devops/pipelines/tasks/package/nuget?view=azure-devops - - - task: PowerShell@2 - displayName: Stage loader and headers for NuGet - inputs: - filePath: $(System.DefaultWorkingDirectory)/.azure-pipelines/nuget/stage_nuget.ps1 - arguments: $(System.DefaultWorkingDirectory)/openxr_loader ` - $(Build.SourcesDirectory)/specification/Makefile ` - $(System.DefaultWorkingDirectory)/openxr_loader_staging - - task: NuGetToolInstaller@1 - inputs: - versionSpec: ">=5.0.0" - - task: NuGetCommand@2 - displayName: Package for NuGet - inputs: - command: pack - packagesToPack: $(System.DefaultWorkingDirectory)/openxr_loader_staging/OpenXR.Loader.nuspec - packDestination: $(System.DefaultWorkingDirectory)/nuget - - task: PublishPipelineArtifact@1 - displayName: Publish NuGet Package - condition: succeeded() - inputs: - path: $(System.DefaultWorkingDirectory)/nuget - artifact: NuGet diff --git a/.azure-pipelines/shared/build_linux.yml b/.azure-pipelines/shared/build_linux.yml deleted file mode 100644 index 49854766..00000000 --- a/.azure-pipelines/shared/build_linux.yml +++ /dev/null @@ -1,30 +0,0 @@ -# Copyright (c) 2019-2023, The Khronos Group Inc. -# SPDX-License-Identifier: Apache-2.0 - -parameters: - - name: buildType - type: string - default: "RelWithDebInfo" - - name: cmakeArgs - type: string - default: "" - - name: sourceDir - type: string - default: "$(System.DefaultWorkingDirectory)" - - name: buildDir - type: string - default: build - -steps: - - script: | - rm -rf ${{ parameters.sourceDir }}/${{ parameters.buildDir }} - mkdir -p ${{ parameters.sourceDir }}/${{ parameters.buildDir }} - displayName: "Clean up and create new build directory" - - - script: cmake -G Ninja .. -DCMAKE_BUILD_TYPE=${{ parameters.buildType }} ${{ parameters.cmakeArgs }} - workingDirectory: ${{ parameters.sourceDir }}/${{ parameters.buildDir }} - displayName: "Generate build system" - - - script: ninja - workingDirectory: ${{ parameters.sourceDir }}/${{ parameters.buildDir }} - displayName: "Compile" diff --git a/.azure-pipelines/shared/build_mingw.yml b/.azure-pipelines/shared/build_mingw.yml deleted file mode 100644 index 13c41b17..00000000 --- a/.azure-pipelines/shared/build_mingw.yml +++ /dev/null @@ -1,45 +0,0 @@ -# Copyright (c) 2019-2023, The Khronos Group Inc. -# SPDX-License-Identifier: Apache-2.0 - -parameters: - - name: buildType - type: string - default: "Debug" - - name: cmakeArgs - type: string - default: "" - - name: sourceDir - type: string - default: "$(System.DefaultWorkingDirectory)" - # TODO should be boolean - - name: useVulkan - type: string - default: "true" - -steps: - # - script: choco install -y ninja - # displayName: 'Install Ninja' - - - script: mkdir $(System.DefaultWorkingDirectory)\\vulkan_sdk - displayName: "Make Vulkan SDK dir" - - - powershell: ./.azure-pipelines/shared/install_vulkan.ps1 - displayName: Install Vulkan SDK - workingDirectory: "${{ parameters.sourceDir }}" - condition: eq('${{ parameters.useVulkan}}', 'true') - - - script: mkdir build - displayName: "Create build directory" - workingDirectory: "${{ parameters.sourceDir }}" - - - script: | - set VULKAN_SDK=$(System.DefaultWorkingDirectory)\\vulkan_sdk\\$(VULKAN_SDK_VERSION) - cmake .. -G "MinGW Makefiles" ${{ parameters.cmakeArgs }} -DCMAKE_BUILD_TYPE=${{ parameters.buildType }} -DCMAKE_INSTALL_PREFIX=${{ parameters.sourceDir }}/install - displayName: "Generate build system" - workingDirectory: "${{ parameters.sourceDir }}/build" - - - script: mingw32-make -C build -j - displayName: Build all targets - - - script: mingw32-make -C build install - displayName: Install build diff --git a/.azure-pipelines/shared/build_msvc.yml b/.azure-pipelines/shared/build_msvc.yml deleted file mode 100644 index 667c8b30..00000000 --- a/.azure-pipelines/shared/build_msvc.yml +++ /dev/null @@ -1,50 +0,0 @@ -# Copyright (c) 2019-2023, The Khronos Group Inc. -# SPDX-License-Identifier: Apache-2.0 - -parameters: - - name: buildType - type: string - default: "Debug" - - name: generator - type: string - default: "Visual Studio 17 2022" - - name: cmakeArgs - type: string - default: "" - - name: sourceDir - type: string - default: "$(System.DefaultWorkingDirectory)" - # TODO should be boolean - - name: useVulkan - type: string - default: "true" - -steps: - - powershell: ./.azure-pipelines/shared/install_vulkan.ps1 - displayName: Install Vulkan SDK - workingDirectory: "${{ parameters.sourceDir }}" - condition: eq('${{ parameters.useVulkan}}', 'true') - - - script: mkdir build - displayName: "Create build directory" - workingDirectory: "${{ parameters.sourceDir }}" - - - script: | - set VULKAN_SDK=$(System.DefaultWorkingDirectory)\\vulkan_sdk\\$(VULKAN_SDK_VERSION) - cmake .. -G "${{ parameters.generator }}" ${{ parameters.cmakeArgs }} -DCMAKE_INSTALL_PREFIX=${{ parameters.sourceDir }}/install - displayName: "Generate build system" - workingDirectory: "${{ parameters.sourceDir }}/build" - - - task: MSBuild@1 - displayName: Build all targets - inputs: - solution: "${{ parameters.sourceDir }}/build/ALL_BUILD.vcxproj" - maximumCpuCount: true - configuration: ${{ parameters.buildType }} - - - task: MSBuild@1 - displayName: Install build - inputs: - solution: "${{ parameters.sourceDir }}/build/INSTALL.vcxproj" - maximumCpuCount: true - configuration: ${{ parameters.buildType }} diff --git a/.azure-pipelines/shared/check_clang_format.yml b/.azure-pipelines/shared/check_clang_format.yml deleted file mode 100644 index d6d1cadb..00000000 --- a/.azure-pipelines/shared/check_clang_format.yml +++ /dev/null @@ -1,26 +0,0 @@ -# Copyright (c) 2019-2023, The Khronos Group Inc. -# SPDX-License-Identifier: Apache-2.0 - -jobs: - - job: check_clang_format - displayName: "clang-format" - pool: - vmImage: "ubuntu-latest" - container: khronosgroup/docker-images:openxr-sdk.20230323 - # container: khronosgroup/docker-images@sha256:20edadbaa6cdec4fed7417c24b18dfb4b93eec940fdf1a27b5f97272dec47032 - steps: - - script: ./runClangFormat.sh - displayName: Run clang-format - - - script: git diff --patch --exit-code > clang-format.patch - displayName: Save changes as diff - - # In case of failure (clang-format changes needed) do these two things - - script: echo "The following files need clang-formatting:"; sed -n -e "s/^diff.* b\///p" clang-format.patch - condition: failed() - - task: PublishPipelineArtifact@1 - displayName: Publish diff - condition: failed() - inputs: - path: $(System.DefaultWorkingDirectory)/clang-format.patch - artifact: clang-format-changes diff --git a/.azure-pipelines/shared/check_file_format.yml b/.azure-pipelines/shared/check_file_format.yml deleted file mode 100644 index 2c14cbc4..00000000 --- a/.azure-pipelines/shared/check_file_format.yml +++ /dev/null @@ -1,23 +0,0 @@ -# Copyright (c) 2020-2023, The Khronos Group Inc. -# SPDX-License-Identifier: Apache-2.0 -jobs: -- job: check_file_format - displayName: 'Check file formatting' - pool: - vmImage: 'ubuntu-latest' - container: khronosgroup/docker-images:openxr-sdk.20230323 - # container: khronosgroup/docker-images@sha256:20edadbaa6cdec4fed7417c24b18dfb4b93eec940fdf1a27b5f97272dec47032 - steps: - - script: ./file_format.sh - displayName: File formatting checks (file_format.sh) - - - script: git diff --patch --exit-code > file_format.patch - displayName: Save changes as diff - - script: echo "The following files need file formatting:"; sed -n -e "s/^diff.* b\///p" file_format.patch - condition: failed() - - task: PublishPipelineArtifact@1 - displayName: Publish diff - condition: failed() - inputs: - path: $(System.DefaultWorkingDirectory)/file_format.patch - artifact: file_format_changes diff --git a/.azure-pipelines/shared/codespell.yml b/.azure-pipelines/shared/codespell.yml deleted file mode 100644 index d582e521..00000000 --- a/.azure-pipelines/shared/codespell.yml +++ /dev/null @@ -1,13 +0,0 @@ -# Copyright (c) 2019-2023, The Khronos Group Inc. -# SPDX-License-Identifier: Apache-2.0 - -jobs: - - job: check_codespell - displayName: "codespell" - pool: - vmImage: "ubuntu-latest" - container: khronosgroup/docker-images:openxr-sdk.20230323 - # container: khronosgroup/docker-images@sha256:20edadbaa6cdec4fed7417c24b18dfb4b93eec940fdf1a27b5f97272dec47032 - steps: - - script: ./checkCodespell - displayName: Run Codespell script diff --git a/.azure-pipelines/shared/generate_windows_matrix_build.py b/.azure-pipelines/shared/generate_windows_matrix_build.py deleted file mode 100644 index 0f462201..00000000 --- a/.azure-pipelines/shared/generate_windows_matrix_build.py +++ /dev/null @@ -1,38 +0,0 @@ -#!/usr/bin/env python3 -# Copyright (c) 2019-2023, The Khronos Group Inc. -# SPDX-License-Identifier: Apache-2.0 - -from itertools import product - -from shared import (PLATFORMS, TRUE_FALSE, VS_VERSION, make_win_artifact_name, - output_json) - -if __name__ == "__main__": - - configs = {} - for platform, debug, uwp in product(PLATFORMS, (False,), TRUE_FALSE): - # No need to support ARM/ARM64 except for UWP. - if not uwp and (platform.lower() == 'arm' or platform.lower() == 'arm64'): - continue - - label = [platform] - config = [] - generator = VS_VERSION - config.append('-A ' + platform) - config.append('-DDYNAMIC_LOADER=ON') - if debug: - label.append('debug') - if uwp: - label.append('UWP') - config.append('-DCMAKE_SYSTEM_NAME=WindowsStore -DCMAKE_SYSTEM_VERSION=10.0') - name = '_'.join(label) - configs[name] = { - 'generator': generator, - 'buildType': 'Debug' if debug else 'RelWithDebInfo', - 'cmakeArgs': ' '.join(config) - } - if not debug: - configs[name]['artifactName'] = make_win_artifact_name( - platform, uwp) - - output_json(configs) diff --git a/.azure-pipelines/shared/install_vulkan.ps1 b/.azure-pipelines/shared/install_vulkan.ps1 deleted file mode 100644 index c1f2ab59..00000000 --- a/.azure-pipelines/shared/install_vulkan.ps1 +++ /dev/null @@ -1,30 +0,0 @@ -# Copyright (c) 2019-2023, The Khronos Group Inc. -# SPDX-License-Identifier: Apache-2.0 - -if (-not $env:VULKAN_SDK_VERSION) { - $env:VULKAN_SDK_VERSION = "1.1.114.0" -} - -$SDK_VER = $env:VULKAN_SDK_VERSION - -if (-not (Test-Path env:VULKAN_SDK)) { - if ($env:SYSTEM_DEFAULTWORKINGDIRECTORY) { - $env:VULKAN_SDK = "$env:SYSTEM_DEFAULTWORKINGDIRECTORY\vulkan_sdk\$SDK_VER" - } else { - $env:VULKAN_SDK = "c:\VulkanSDK\$SDK_VER" - } -} -$parent = Split-Path -path $env:VULKAN_SDK -Write-Output "Trying for Vulkan SDK $SDK_VER" -$FN = "vksdk-$SDK_VER-lite.7z" -$URL = "https://people.collabora.com/~rpavlik/ci_resources/$FN" -if (-not (Test-Path "$env:VULKAN_SDK/Include/vulkan/vulkan.h")) { - Write-Output "Downloading $URL" - $wc = New-Object System.Net.WebClient - $wc.DownloadFile($URL, "$(pwd)\$FN") - - Write-Output "Extracting $FN in silent, blocking mode to $env:VULKAN_SDK" - Start-Process "c:\Program Files\7-Zip\7z" -ArgumentList "x", $FN, "-o$parent" -Wait -} else { - Write-Output "$env:VULKAN_SDK found and contains header" -} diff --git a/.azure-pipelines/shared/organize_windows_artifacts.py b/.azure-pipelines/shared/organize_windows_artifacts.py deleted file mode 100644 index d59c53be..00000000 --- a/.azure-pipelines/shared/organize_windows_artifacts.py +++ /dev/null @@ -1,48 +0,0 @@ -#!/usr/bin/env python3 -# Copyright (c) 2019-2023, The Khronos Group Inc. -# SPDX-License-Identifier: Apache-2.0 - -from itertools import product -from pathlib import Path -import shutil -import sys - -from shared import PLATFORMS, TRUE_FALSE, VS_VERSION, make_win_artifact_name - -CWD = Path.cwd() - - -def move(src, dest): - - print(str(src), '->', str(dest)) - src.replace(dest) - - -if __name__ == "__main__": - - configs = {} - workspace = Path(sys.argv[1]) - outbase = Path(sys.argv[2]) - - common_copied = False - - for platform, uwp in product(PLATFORMS, TRUE_FALSE): - # ARM/ARM64 is only built for the UWP platform. - if not uwp and (platform.lower() == 'arm' or platform.lower() == 'arm64'): - continue - - platform_dirname = '{}{}'.format(platform, - '_uwp' if uwp else '') - - name = make_win_artifact_name(platform, uwp) - - artifact = workspace / name - - if not common_copied: - # Start by copying the full tree over. - shutil.copytree(artifact, outbase, dirs_exist_ok=True) - common_copied = True - continue - - # lib files - shutil.copytree(artifact / platform_dirname, outbase / platform_dirname, dirs_exist_ok=True) diff --git a/.azure-pipelines/shared/print_windows_artifact_names.py b/.azure-pipelines/shared/print_windows_artifact_names.py deleted file mode 100644 index d58bfdfc..00000000 --- a/.azure-pipelines/shared/print_windows_artifact_names.py +++ /dev/null @@ -1,12 +0,0 @@ -#!/usr/bin/env python3 -# Copyright (c) 2019-2023, The Khronos Group Inc. -# SPDX-License-Identifier: Apache-2.0 - -from itertools import product - -from shared import PLATFORMS, TRUE_FALSE, VS_VERSION, make_win_artifact_name - -if __name__ == "__main__": - - for platform, uwp in product(PLATFORMS, TRUE_FALSE): - print(make_win_artifact_name(platform, uwp)) diff --git a/.azure-pipelines/shared/shared.py b/.azure-pipelines/shared/shared.py deleted file mode 100644 index 4276880c..00000000 --- a/.azure-pipelines/shared/shared.py +++ /dev/null @@ -1,26 +0,0 @@ -# Copyright (c) 2019-2023, The Khronos Group Inc. -# SPDX-License-Identifier: Apache-2.0 - -import json -import sys - -VS_VERSION = 'Visual Studio 17 2022' - -PLATFORMS = ('Win32', 'x64', 'ARM', 'ARM64') - -TRUE_FALSE = (True, False) - - -def make_win_artifact_name(platform, uwp): - return 'loader_{}{}'.format( - platform.lower(), - '_uwp' if uwp else '', - ) - - -def output_json(data): - if len(sys.argv) == 2: - print( - "##vso[task.setVariable variable={};isOutput=true]{}".format(sys.argv[1], json.dumps(data))) - else: - print(json.dumps(data, indent=4)) diff --git a/.env b/.env new file mode 100644 index 00000000..e2773527 --- /dev/null +++ b/.env @@ -0,0 +1,6 @@ +# Copyright (c) 2022-2023, The Khronos Group Inc. +# +# SPDX-License-Identifier: Apache-2.0 + +# Improves the Python editing experience with vscode. +PYTHONPATH=specification/scripts:src/scripts:external/python diff --git a/.github/workflows/android-cts-build.yml b/.github/workflows/android-cts-build.yml index 8d8c1796..ec922b37 100644 --- a/.github/workflows/android-cts-build.yml +++ b/.github/workflows/android-cts-build.yml @@ -1,4 +1,4 @@ -# Copyright 2021-2022, Collabora, Ltd. +# Copyright 2021-2023, Collabora, Ltd. # SPDX-License-Identifier: CC0-1.0 name: Android OpenXR-CTS - Android Build @@ -19,10 +19,12 @@ jobs: build-android-cts: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 + with: + lfs: true - 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 @@ -31,7 +33,7 @@ jobs: distribution: "temurin" cache: gradle - - uses: gradle/gradle-build-action@v2 + - uses: gradle/gradle-build-action@v2.10.0 with: arguments: ${{ inputs.gradleTask }} build-root-directory: src/conformance diff --git a/.github/workflows/android-cts-pr.yml b/.github/workflows/android-cts-pr.yml index 42f53207..f46c32fd 100644 --- a/.github/workflows/android-cts-pr.yml +++ b/.github/workflows/android-cts-pr.yml @@ -1,4 +1,4 @@ -# Copyright 2021-2022, Collabora, Ltd. +# Copyright 2021-2023, Collabora, Ltd. # SPDX-License-Identifier: CC0-1.0 name: Android OpenXR-CTS @@ -14,10 +14,12 @@ jobs: build-loader: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 + with: + lfs: true - 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 @@ -26,7 +28,7 @@ jobs: distribution: "temurin" cache: gradle - - uses: gradle/gradle-build-action@v2 + - uses: gradle/gradle-build-action@v2.10.0 with: arguments: assembleDebug build-root-directory: src/conformance diff --git a/.github/workflows/android-cts-release.yml b/.github/workflows/android-cts-release.yml index 737540e8..e6f13193 100644 --- a/.github/workflows/android-cts-release.yml +++ b/.github/workflows/android-cts-release.yml @@ -1,4 +1,4 @@ -# Copyright 2021-2022, Collabora, Ltd. +# Copyright 2021-2023, Collabora, Ltd. # SPDX-License-Identifier: CC0-1.0 name: Android OpenXR-CTS - Release diff --git a/.github/workflows/check_clang_format_and_codespell.yml b/.github/workflows/check_clang_format_and_codespell.yml index b9f22d64..d807b660 100644 --- a/.github/workflows/check_clang_format_and_codespell.yml +++ b/.github/workflows/check_clang_format_and_codespell.yml @@ -10,9 +10,14 @@ jobs: clang-format: runs-on: ubuntu-latest container: - image: khronosgroup/docker-images:openxr-sdk.20230209 + # 20230614 + image: khronosgroup/docker-images:openxr-sdk@sha256:fbc5fe29a0787cccc8f66bd9bd03c9dbddf591c7d1aea673108c38c908b280f5 + steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 + with: + lfs: true + - 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..f7579a77 100644 --- a/.github/workflows/gradle-wrapper-validation.yml +++ b/.github/workflows/gradle-wrapper-validation.yml @@ -1,4 +1,4 @@ -# Copyright 2022, Collabora, Ltd. +# Copyright 2022-2023, Collabora, Ltd. # SPDX-License-Identifier: CC0-1.0 name: Validate Gradle Wrapper @@ -15,5 +15,7 @@ jobs: name: Validation runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 + with: + lfs: true - uses: gradle/wrapper-validation-action@v1 diff --git a/.github/workflows/msvc-build-preset.yml b/.github/workflows/msvc-build-preset.yml index c427dca1..4ac2b583 100644 --- a/.github/workflows/msvc-build-preset.yml +++ b/.github/workflows/msvc-build-preset.yml @@ -29,10 +29,12 @@ jobs: VULKAN_SDK_VERSION: "1.1.114.0" INSTALL_DIR: "${{ github.workspace }}/install" steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 + with: + lfs: true - 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..3ea0a5d5 100644 --- a/.gitignore +++ b/.gitignore @@ -15,6 +15,8 @@ CMakeFiles *.tar.gz *.log pregen/ +sources-jar/ +docs-jar/ generated-includes cmake_install.cmake cmake_uninstall.cmake @@ -60,7 +62,7 @@ local.properties !.azure-pipelines/nuget/NugetTemplate/build !.clang-format !.clang-tidy -!.cmake-format.json +!.cmake-format.py !.editorconfig !.git-blame-ignore-revs !.git-keep @@ -73,7 +75,13 @@ local.properties !.markdownlint.yaml !.proclamation.json !.reuse +!/.env # Output artifact *.aar *.pom +clang-format-patches/ +/*.jar + +# Key stores +*.jks diff --git a/.proclamation.json b/.proclamation.json index bb012540..3514b224 100644 --- a/.proclamation.json +++ b/.proclamation.json @@ -1,6 +1,6 @@ { - "#": "This is a config file for proclamation, the changelog combiner: https://gitlab.com/ryanpavlik/proclamation", - "$schema": "https://ryanpavlik.gitlab.io/proclamation/proclamation.schema.json", + "#": "This is a config file for proclamation, the changelog combiner: https://gitlab.com/proclamation/proclamation", + "$schema": "https://proclamation.gitlab.io/proclamation/proclamation.schema.json", "projects": [ { "project_name": "OpenXR SDK", diff --git a/CHANGELOG.CTS.md b/CHANGELOG.CTS.md index f0856a21..a0c65b64 100644 --- a/CHANGELOG.CTS.md +++ b/CHANGELOG.CTS.md @@ -17,6 +17,64 @@ particular, since it is primarily software, pull requests may be integrated as they are accepted even between periodic updates. However, versions that are not signed tags on the `approved` branch are not valid for conformance submission. +## OpenXR CTS 1.0.32.1 (2023-12-14) + +A notable change in this release, is that the build system now checks for git +commit/tag information at configure time and reports this information in the CTS +logs. If you have taken any porting steps that involve changing the build +system, be sure to update your changes accordingly. See the README for more +information. + +There is one known issue with the new PBR rendering subsystem, but it only +affects running a self test under Vulkan, which is not required for conformance +submissions. It will be fixed in the next release. + +- Conformance Tests + - Fix: Handle the loader passing `xrInitializeLoaderKHR` calls to enabled API + layers if `XR_KHR_loader_init` is enabled, per ratified update to that + extension. + ([internal MR 2703](https://gitlab.khronos.org/openxr/openxr/merge_requests/2703), + [internal MR 3033](https://gitlab.khronos.org/openxr/openxr/merge_requests/3033)) + - Fix: comment typo in environment source. + ([internal MR 2991](https://gitlab.khronos.org/openxr/openxr/merge_requests/2991)) + - Fix: Correct linking to GLX when glvnd is not found on the system. + ([internal MR 3000](https://gitlab.khronos.org/openxr/openxr/merge_requests/3000)) + - Fix: Warning/build fix + ([internal MR 3008](https://gitlab.khronos.org/openxr/openxr/merge_requests/3008)) + - Fix: Correct the object naming of command lists on D3D12. + ([internal MR 3066](https://gitlab.khronos.org/openxr/openxr/merge_requests/3066)) + - Improvement: Add PBR rendering subsystem for loading and rendering of glTF + assets. + ([internal MR 2501](https://gitlab.khronos.org/openxr/openxr/merge_requests/2501), + [internal issue 1726](https://gitlab.khronos.org/openxr/openxr/issues/1726), + [internal MR 2758](https://gitlab.khronos.org/openxr/openxr/merge_requests/2758), + [internal MR 3038](https://gitlab.khronos.org/openxr/openxr/merge_requests/3038), + [internal MR 3081](https://gitlab.khronos.org/openxr/openxr/merge_requests/3081)) + - Improvement: Clean up our CMake build substantially, correcting dependencies + and narrowing the scope of includes. + ([internal MR 2886](https://gitlab.khronos.org/openxr/openxr/merge_requests/2886)) + - Improvement: Include git revision information in all reports, and generate test + warnings in case of not matching a release tag, etc. + ([internal MR 2964](https://gitlab.khronos.org/openxr/openxr/merge_requests/2964), + [internal issue 2041](https://gitlab.khronos.org/openxr/openxr/issues/2041)) + - Improvement: Build system cleanup. + ([internal MR 2987](https://gitlab.khronos.org/openxr/openxr/merge_requests/2987)) + - Improvement: Update configuration for Doxygen source-code documentation + generator/extractor. + ([internal MR 2988](https://gitlab.khronos.org/openxr/openxr/merge_requests/2988)) + - Improvement: Use "matchers" rather than STL algorithms to verify that supported + environment blend modes do not include an invalid value. + ([internal MR 2994](https://gitlab.khronos.org/openxr/openxr/merge_requests/2994)) + - New test: Interactive (rendering) test of `XR_MSFT_controller_model` as an + initial usage of the glTF/PBR rendering. + ([internal MR 2501](https://gitlab.khronos.org/openxr/openxr/merge_requests/2501), + [internal issue 1726](https://gitlab.khronos.org/openxr/openxr/issues/1726), + [internal MR 2758](https://gitlab.khronos.org/openxr/openxr/merge_requests/2758), + [internal MR 3038](https://gitlab.khronos.org/openxr/openxr/merge_requests/3038), + [internal MR 3081](https://gitlab.khronos.org/openxr/openxr/merge_requests/3081)) + - New test: Try zero XrTime values in hand tracking joints test. + ([internal MR 2951](https://gitlab.khronos.org/openxr/openxr/merge_requests/2951)) + ## OpenXR CTS 1.0.30.0 (2023-10-12) - Conformance Tests diff --git a/CMakeLists.txt b/CMakeLists.txt index 03df11be..50dbd254 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -13,15 +13,12 @@ # 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. -# -# Author: -# # Note: This is the top-level CMake file for the OpenXR project. # 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.16) project(OPENXR) find_package(PythonInterp 3) @@ -29,7 +26,9 @@ include(CTest) # Enable IDE GUI folders. "Helper targets" that don't have interesting source code should set their FOLDER property to this set_property(GLOBAL PROPERTY USE_FOLDERS ON) -set_property(GLOBAL PROPERTY PREDEFINED_TARGETS_FOLDER "CMake predefined targets") +set_property( + GLOBAL PROPERTY PREDEFINED_TARGETS_FOLDER "CMake predefined targets" +) set(LOADER_FOLDER "Loader") set(HELPER_FOLDER "Helpers") set(CODEGEN_FOLDER "Generated") @@ -39,7 +38,11 @@ set(LOADER_TESTS_FOLDER "Loader Tests") set(API_LAYERS_FOLDER "Layers") set(SAMPLES_FOLDER "Samples") -option(BUILD_FORCE_GENERATION "Force re-generation of files even in the presence of pre-generated copies, replacing those copies." OFF) +option( + BUILD_FORCE_GENERATION + "Force re-generation of files even in the presence of pre-generated copies, replacing those copies." + OFF +) if(BUILD_FORCE_GENERATION AND NOT PYTHON_EXECUTABLE) message(FATAL_ERROR "BUILD_FORCE_GENERATION requires Python") @@ -49,7 +52,11 @@ string(TOUPPER "${CMAKE_GENERATOR_PLATFORM}" CMAKE_GENERATOR_PLATFORM_UPPER) # Artifact organization if(WIN32 OR ANDROID) - option(INSTALL_TO_ARCHITECTURE_PREFIXES "Install platform-specific files to architecture-specific directories, for packaging" OFF) + option( + INSTALL_TO_ARCHITECTURE_PREFIXES + "Install platform-specific files to architecture-specific directories, for packaging" + OFF + ) endif() if(WIN32 AND INSTALL_TO_ARCHITECTURE_PREFIXES) @@ -78,16 +85,26 @@ if(WIN32 AND INSTALL_TO_ARCHITECTURE_PREFIXES) elseif(ANDROID AND INSTALL_TO_ARCHITECTURE_PREFIXES) # This organizes things like a prefab module set(PREFAB_INSTALL_DIR prefab) - set(PREFAB_MODULE_INSTALL_DIR ${PREFAB_INSTALL_DIR}/modules/openxr_loader) - set(CMAKE_INSTALL_LIBDIR ${PREFAB_MODULE_INSTALL_DIR}/libs/android.${ANDROID_ABI} CACHE STRING "Where to install libraries") + set(PREFAB_HEADER_MODULE_INSTALL_DIR + "${PREFAB_INSTALL_DIR}/modules/headers" + ) + set(PREFAB_LOADER_MODULE_INSTALL_DIR + "${PREFAB_INSTALL_DIR}/modules/openxr_loader" + ) + set(CMAKE_INSTALL_LIBDIR + "${PREFAB_LOADER_MODULE_INSTALL_DIR}/libs/android.${ANDROID_ABI}" + CACHE STRING "Where to install libraries" + ) set(CMAKE_INSTALL_BINDIR ${CMAKE_INSTALL_LIBDIR}) - set(CMAKE_INSTALL_INCLUDEDIR ${PREFAB_MODULE_INSTALL_DIR}/include) + set(CMAKE_INSTALL_INCLUDEDIR ${PREFAB_HEADER_MODULE_INSTALL_DIR}/include) unset(NDK_MAJOR_VERSION) if(CMAKE_ANDROID_NDK) file(STRINGS "${CMAKE_ANDROID_NDK}/source.properties" NDK_PROPERTIES) foreach(_line ${NDK_PROPERTIES}) - if("${_line}" MATCHES "Pkg.Revision = ([0-9]+)[.]([0-9]+)[.]([0-9]+)") + if("${_line}" MATCHES + "Pkg.Revision = ([0-9]+)[.]([0-9]+)[.]([0-9]+)" + ) set(NDK_MAJOR_VERSION ${CMAKE_MATCH_1}) endif() endforeach() @@ -97,41 +114,78 @@ elseif(ANDROID AND INSTALL_TO_ARCHITECTURE_PREFIXES) if(NDK_MAJOR_VERSION) message(STATUS "Building using NDK major version ${NDK_MAJOR_VERSION}") else() - message(FATAL_ERROR "Could not parse the major version from ${CMAKE_ANDROID_NDK}/source.properties") + message( + FATAL_ERROR + "Could not parse the major version from ${CMAKE_ANDROID_NDK}/source.properties" + ) endif() elseif(NOT ANDROID) include(GNUInstallDirs) endif() +# Path separators ( : or ; ) are not handled well in CMake. +# This seems like a reasonable approach. +if(CMAKE_HOST_SYSTEM_NAME STREQUAL Windows) + set(CODEGEN_PYTHON_PATH + "${PROJECT_SOURCE_DIR}/specification/scripts;${PROJECT_SOURCE_DIR}/src/scripts;$ENV{PYTHONPATH}" + ) +else() + set(CODEGEN_PYTHON_PATH + "${PROJECT_SOURCE_DIR}/specification/scripts:${PROJECT_SOURCE_DIR}/src/scripts:$ENV{PYTHONPATH}" + ) +endif() + add_subdirectory(include) add_subdirectory(src) # uninstall target if(NOT TARGET uninstall) - configure_file("${CMAKE_CURRENT_SOURCE_DIR}/src/cmake/cmake_uninstall.cmake.in" - "${CMAKE_CURRENT_BINARY_DIR}/cmake_uninstall.cmake" - IMMEDIATE - @ONLY) - add_custom_target(uninstall COMMAND ${CMAKE_COMMAND} -P ${CMAKE_CURRENT_BINARY_DIR}/cmake_uninstall.cmake) + configure_file( + src/cmake/cmake_uninstall.cmake.in + "${CMAKE_CURRENT_BINARY_DIR}/cmake_uninstall.cmake" + IMMEDIATE + @ONLY + ) + add_custom_target( + uninstall + COMMAND + ${CMAKE_COMMAND} -P + ${CMAKE_CURRENT_BINARY_DIR}/cmake_uninstall.cmake + ) set_target_properties(uninstall PROPERTIES FOLDER ${HELPER_FOLDER}) endif() find_program(BASH_COMMAND NAMES bash) if(BASH_COMMAND AND EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/specification/Makefile") - option(BUILD_SPECIFICATION "Run './makeAllExts all' in the specification directory as part of the build - intended for one-step checking of spec changes" OFF) + option( + BUILD_SPECIFICATION + "Run './makeAllExts all' in the specification directory as part of the build - intended for one-step checking of spec changes" + OFF + ) if(BUILD_SPECIFICATION) - add_custom_target(spec-all ALL - ${BASH_COMMAND} ./makeAllExts all + add_custom_target( + spec-all ALL + "${BASH_COMMAND}" ./makeAllExts all WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/specification" VERBATIM COMMENT "Running './makeAllExts all' in the specification directory" - USES_TERMINAL) + USES_TERMINAL + ) endif() endif() if(ANDROID AND INSTALL_TO_ARCHITECTURE_PREFIXES) - install(FILES "${CMAKE_CURRENT_SOURCE_DIR}/LICENSE" DESTINATION META-INF COMPONENT License) + install( + FILES "${CMAKE_CURRENT_SOURCE_DIR}/LICENSE" + DESTINATION META-INF + COMPONENT License + ) + else() - install(FILES "${CMAKE_CURRENT_SOURCE_DIR}/LICENSE" DESTINATION share/doc/openxr COMPONENT License) + install( + FILES "${CMAKE_CURRENT_SOURCE_DIR}/LICENSE" + DESTINATION share/doc/openxr + COMPONENT License + ) endif() diff --git a/HOTFIX b/HOTFIX new file mode 100644 index 00000000..d00491fd --- /dev/null +++ b/HOTFIX @@ -0,0 +1 @@ +1 diff --git a/README.md b/README.md index f31a052d..1884334d 100644 --- a/README.md +++ b/README.md @@ -24,6 +24,22 @@ does not support a command-line interface, a host application must be built for the device which the OpenXR runtime will run on. The conformance host must invoke `conformance_test`, the test suite shared library. +When you plan to submit for conformance, you must observe a few considerations +to ensure that the build system has accurate source code revision information +available to embed in the test suite and output reports. You must build from a +git repo (forked from either the internal Gitlab or public GitHub) with tags +available (a full clone, not shallow). You also must either perform a clean +build, from an empty binary tree, or at least run `cmake` immediately before +building to pick up current source tree status. If your "porting" process (as +described by the conformance process documents) involves replacing the build +system, you must populate the revision data constants in +`utilities/git_revision.cpp.in` accurately. The contents of that file affect all +"ctsxml" format outputs, as well as an automated "SourceCodeRevision" test that +warns if it cannot identify an approved release. (It only checks for the +presence of an appropriately-named tag: it does not check for a signature on the +tag, so if you have added tags to your repo it may not warn if you are not on a +release.) + Running CTS ----------- @@ -164,6 +180,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/README.md b/changes/README.md index fb31fb3c..c87e7075 100644 --- a/changes/README.md +++ b/changes/README.md @@ -8,10 +8,10 @@ If you want to preview the changelog or perform a release, you can run a command like the following to install it locally: ```sh -pip3 install proclamation +pipx install proclamation ``` -See for more details on *proclamation*. +See for more details on *proclamation*. ## Fragments diff --git a/external/include/CMakeLists.txt b/external/include/CMakeLists.txt index dd0a905b..83816147 100644 --- a/external/include/CMakeLists.txt +++ b/external/include/CMakeLists.txt @@ -2,10 +2,9 @@ # # SPDX-License-Identifier: Apache-2.0 -set( INCLUDE_DIR "${CMAKE_CURRENT_SOURCE_DIR}" ) +set(INCLUDE_DIR "${CMAKE_CURRENT_SOURCE_DIR}") -set( GL_HEADERS - ${INCLUDE_DIR}/GL/gl_format.h ) +set(GL_HEADERS ${INCLUDE_DIR}/GL/gl_format.h) # These can be dropped by some subsetted source distributions since # they ought to exist, if required, in your OpenGL SDK package. @@ -15,5 +14,5 @@ foreach(FN ${INCLUDE_DIR}/GL/glext.h ${INCLUDE_DIR}/GL/wglext.h) endif() endforeach() -source_group( GL FILES ${GL_HEADERS} ) -set_property( TARGET include PROPERTY FOLDER external/include ) +source_group(GL FILES ${GL_HEADERS}) +set_property(TARGET include PROPERTY FOLDER external/include) diff --git a/include/CMakeLists.txt b/include/CMakeLists.txt index 46f615ac..0977a0bf 100644 --- a/include/CMakeLists.txt +++ b/include/CMakeLists.txt @@ -1,4 +1,4 @@ -# Copyright (c) 2017 The Khronos Group Inc. +# Copyright (c) 2017-2023, The Khronos Group Inc. # # SPDX-License-Identifier: Apache-2.0 # @@ -13,8 +13,5 @@ # 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. -# -# Author: -# add_subdirectory(openxr) diff --git a/include/openxr/CMakeLists.txt b/include/openxr/CMakeLists.txt index b1718f60..c814df48 100644 --- a/include/openxr/CMakeLists.txt +++ b/include/openxr/CMakeLists.txt @@ -13,93 +13,116 @@ # 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. -# -# Author: -# - -# Copy the openxr_platform_defines.h file and place it in the binary (build) directory. -configure_file(openxr_platform_defines.h ${CMAKE_CURRENT_BINARY_DIR}/openxr_platform_defines.h COPYONLY) # Generate OpenXR header files. # Get the list of generated headers from the common single-point-of-truth -file(STRINGS ${CMAKE_CURRENT_SOURCE_DIR}/../generated_header_list.txt HEADERS LENGTH_MINIMUM 8) +file(STRINGS "../generated_header_list.txt" HEADERS LENGTH_MINIMUM 8) set(HAVE_PREGENERATED TRUE) set(SOURCE_HEADERS) foreach(output ${HEADERS}) - list(APPEND SOURCE_HEADERS ${CMAKE_CURRENT_SOURCE_DIR}/${output}) - if(NOT EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/${output}) + list(APPEND SOURCE_HEADERS "${CMAKE_CURRENT_SOURCE_DIR}/${output}") + if(NOT EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/${output}") set(HAVE_PREGENERATED FALSE) endif() endforeach() -set(XR_ROOT ${CMAKE_CURRENT_SOURCE_DIR}/../..) +add_library(headers INTERFACE) +# Create alias so that it can be used the same whether vendored as source or found with CMake. +# Other targets that need the OpenXR headers should specify OpenXR::headers in their target_link_libraries. +add_library(OpenXR::headers ALIAS headers) +target_include_directories( + headers INTERFACE $ +) + if(HAVE_PREGENERATED AND NOT BUILD_FORCE_GENERATION) - add_custom_target(generate_openxr_header - COMMENT "Using found pre-generated OpenXR headers.") + add_custom_target( + generate_openxr_header + COMMENT "Using found pre-generated OpenXR headers." + ) + + set(INSTALL_HEADERS "${CMAKE_CURRENT_SOURCE_DIR}/openxr_platform_defines.h" + ${SOURCE_HEADERS} + ) - set(INSTALL_HEADERS - ${CMAKE_CURRENT_SOURCE_DIR}/openxr_platform_defines.h - ${SOURCE_HEADERS}) + # All headers are in the source dir + target_include_directories( + headers INTERFACE $ + ) else() set(GENERATED_HEADERS) set(OUTPUT_STAMPS) # Copy the openxr_platform_defines.h file and place it in the binary (build) directory. - configure_file(${CMAKE_CURRENT_SOURCE_DIR}/openxr_platform_defines.h - ${CMAKE_CURRENT_BINARY_DIR}/openxr_platform_defines.h - COPYONLY) + configure_file( + openxr_platform_defines.h + "${CMAKE_CURRENT_BINARY_DIR}/openxr_platform_defines.h" COPYONLY + ) # Generate the header files and place it in the binary (build) directory. - file(GLOB _templates LIST_DIRECTORIES false CONFIGURE_DEPENDS ${XR_ROOT}/specification/scripts/template_*) + file( + GLOB _templates + LIST_DIRECTORIES false + CONFIGURE_DEPENDS + "${PROJECT_SOURCE_DIR}/specification/scripts/template_*" + ) foreach(output ${HEADERS}) if("${output}" MATCHES "reflection") - set(_extra_deps ${XR_ROOT}/specification/scripts/jinja_helpers.py ${_templates}) + set(_extra_deps + "${PROJECT_SOURCE_DIR}/specification/scripts/jinja_helpers.py" + ${_templates} + ) else() unset(_extra_deps) endif() - add_custom_command(OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/${output} - COMMAND ${PYTHON_EXECUTABLE} ${XR_ROOT}/specification/scripts/genxr.py - -registry ${XR_ROOT}/specification/registry/xr.xml - -o ${CMAKE_CURRENT_BINARY_DIR} ${output} + add_custom_command( + OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/${output}" + COMMAND + "${CMAKE_COMMAND}" -E env "PYTHONPATH=${CODEGEN_PYTHON_PATH}" + "${PYTHON_EXECUTABLE}" + "${PROJECT_SOURCE_DIR}/specification/scripts/genxr.py" -registry + "${PROJECT_SOURCE_DIR}/specification/registry/xr.xml" -o + "${CMAKE_CURRENT_BINARY_DIR}" ${output} DEPENDS - ${XR_ROOT}/specification/scripts/genxr.py - ${XR_ROOT}/specification/scripts/cgenerator.py - ${XR_ROOT}/specification/scripts/creflectiongenerator.py - ${XR_ROOT}/specification/scripts/generator.py - ${XR_ROOT}/specification/scripts/reg.py - ${XR_ROOT}/specification/registry/xr.xml + "${PROJECT_SOURCE_DIR}/specification/scripts/genxr.py" + "${PROJECT_SOURCE_DIR}/specification/scripts/cgenerator.py" + "${PROJECT_SOURCE_DIR}/specification/scripts/creflectiongenerator.py" + "${PROJECT_SOURCE_DIR}/specification/scripts/generator.py" + "${PROJECT_SOURCE_DIR}/specification/scripts/reg.py" + "${PROJECT_SOURCE_DIR}/specification/registry/xr.xml" ${_extra_deps} COMMENT "Generating ${CMAKE_CURRENT_BINARY_DIR}/${output}" + VERBATIM ) list(APPEND GENERATED_HEADERS "${CMAKE_CURRENT_BINARY_DIR}/${output}") endforeach() - set_source_files_properties( - ${GENERATED_HEADERS} - PROPERTIES GENERATED TRUE - ) - - set(INSTALL_HEADERS - ${CMAKE_CURRENT_BINARY_DIR}/openxr_platform_defines.h - ${GENERATED_HEADERS}) + set_source_files_properties(${GENERATED_HEADERS} PROPERTIES GENERATED TRUE) + set(INSTALL_HEADERS ${CMAKE_CURRENT_BINARY_DIR}/openxr_platform_defines.h + ${GENERATED_HEADERS} + ) # Define generate_openxr_header target to generate the OpenXR header files. - # Other targets that need the OpenXR headers should use generate_openxr_header as a dependency. - add_custom_target(generate_openxr_header - SOURCES ${XR_ROOT}/specification/registry/xr.xml - DEPENDS - ${GENERATED_HEADERS} - ${OUTPUT_STAMPS} + # Not used directly - depended on by OpenXR::headers + add_custom_target( + generate_openxr_header + SOURCES "${PROJECT_SOURCE_DIR}/specification/registry/xr.xml" + DEPENDS ${GENERATED_HEADERS} ${OUTPUT_STAMPS} + ) + # Headers are all in the binary dir + target_include_directories( + headers INTERFACE $ ) endif() -set_target_properties(generate_openxr_header PROPERTIES FOLDER ${CODEGEN_FOLDER}) +set_target_properties( + generate_openxr_header PROPERTIES FOLDER ${CODEGEN_FOLDER} +) -# make cache variables for install destinations -include(GNUInstallDirs) +add_dependencies(headers generate_openxr_header) -INSTALL(FILES ${INSTALL_HEADERS} +install( + FILES ${INSTALL_HEADERS} DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/openxr COMPONENT Headers ) diff --git a/maintainer-scripts/common.sh b/maintainer-scripts/common.sh index 540578be..cce5a6f1 100644 --- a/maintainer-scripts/common.sh +++ b/maintainer-scripts/common.sh @@ -110,6 +110,7 @@ getDocsFilenames() { # TODO omitting style guide VUID chapter for now git ls-files \ $COMMON_FILES \ + .env \ .proclamation.json \ .proclamation.json.license \ CHANGELOG.Docs.md \ @@ -145,6 +146,7 @@ getSDKSourceFilenames() { git ls-files \ $COMMON_FILES \ .appveyor.yml \ + .env \ .proclamation.json \ .proclamation.json.license \ BUILDING.md \ @@ -258,9 +260,9 @@ getSDKFilenames() { getConformanceFilenames() { set -e # The src/conformance directory, plus the minimum subset of the other files required. - # TODO need a mention in the spec folder about how it's not under the same license (?) git ls-files \ $COMMON_FILES \ + .env \ .proclamation.json \ .proclamation.json.license \ BUILDING.md \ @@ -272,8 +274,6 @@ getConformanceFilenames() { openxr-codespell.exclude \ runClangFormat.sh \ tox.ini \ - .azure-pipelines/shared \ - .azure-pipelines/openxr-cts.yml \ .github/dependabot.yml \ .github/scripts \ .github/workflows/android-cts-build.yml \ diff --git a/specification/Makefile b/specification/Makefile index c9f16147..136324f2 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.32 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..5b574cdd 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, 32) XR_DEFINE_ATOM(XrRenderModelKeyFB) + + XR_DEFINE_ATOM(XrMarkerML) @@ -378,6 +380,9 @@ maintained in the default branch of the Khronos OpenXR GitHub project. typedef XrFlags64 XrVirtualKeyboardInputStateFlagsMETA; + + typedef XrFlags64 XrLocalizationMapErrorFlagsML; + XR_DEFINE_HANDLE(XrInstance) @@ -430,6 +435,12 @@ maintained in the default branch of the Khronos OpenXR GitHub project. XR_DEFINE_HANDLE(XrVirtualKeyboardMETA) + + XR_DEFINE_HANDLE(XrExportedLocalizationMapML) + + + XR_DEFINE_HANDLE(XrMarkerDetectorML) + @@ -602,6 +613,28 @@ maintained in the default branch of the Khronos OpenXR GitHub project. + + + + + + + + + + + + + + + + + + + + + + float x @@ -1707,16 +1740,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 +2011,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 +2099,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 +2136,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 +2350,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 +2372,6 @@ maintained in the default branch of the Khronos OpenXR GitHub project. void* next XrBool32 supportsRenderModelLoading - - XrStructureType type - void* next - XrRenderModelFlagsFB flags - @@ -2737,6 +2802,23 @@ maintained in the default branch of the Khronos OpenXR GitHub project. XrPassthroughColorHTC color + + + XrStructureType type + const void* next + XrSpace space + XrPosef poseInSpace + XrSpatialAnchorNameHTC name + + + char name[XR_MAX_SPATIAL_ANCHOR_NAME_SIZE_HTC] + + + XrStructureType type + void* next + XrBool32 supportsAnchor + + XrStructureType type @@ -3206,8 +3288,136 @@ 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 + + + + + XrStructureType type + void* next + char name[XR_MAX_LOCALIZATION_MAP_NAME_LENGTH_ML] + XrUuidEXT mapUuid + XrLocalizationMapTypeML mapType + + + + XrStructureType type + const void* next + XrBool32 enabled + + + + XrStructureType type + const void* next + XrSession session + XrLocalizationMapStateML state + XrLocalizationMapML map + XrLocalizationMapConfidenceML confidence + XrLocalizationMapErrorFlagsML errorFlags + + + + XrStructureType type + const void* next + + + + XrStructureType type + const void* next + XrUuidEXT mapUuid + + + + XrStructureType type + const void* next + uint32_t size + char* data + + + + + + XrStructureType type + void* next + XrBool32 supportsMarkerUnderstanding + + + + XrStructureType type + const void* next + XrMarkerDetectorProfileML profile + XrMarkerTypeML markerType + + + + XrStructureType type + const void* next + XrMarkerArucoDictML arucoDict + + + + XrStructureType type + const void* next + float markerLength + + + + XrStructureType type + const void* next + XrMarkerAprilTagDictML aprilTagDict + + + + XrStructureType type + const void* next + XrMarkerDetectorFpsML fpsHint + XrMarkerDetectorResolutionML resolutionHint + XrMarkerDetectorCameraML cameraHint + XrMarkerDetectorCornerRefineMethodML cornerRefineMethod + XrBool32 useEdgeRefinement + XrMarkerDetectorFullAnalysisIntervalML fullAnalysisIntervalHint + + + + XrStructureType type + const void* next + + + + XrStructureType type + void* next + XrMarkerDetectorStatusML state + + + + XrStructureType type + const void* next + XrMarkerDetectorML markerDetector + XrMarkerML marker + XrPosef poseInMarkerSpace + + + @@ -3716,6 +3926,80 @@ maintained in the default branch of the Khronos OpenXR GitHub project. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -3846,6 +4130,15 @@ maintained in the default branch of the Khronos OpenXR GitHub project. + + + + + + + + + @@ -4249,6 +4542,51 @@ maintained in the default branch of the Khronos OpenXR GitHub project. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -5044,6 +5382,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 +5803,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 @@ -5496,6 +5852,19 @@ maintained in the default branch of the Khronos OpenXR GitHub project. XrPassthroughHTC passthrough + + + XrResult xrCreateSpatialAnchorHTC + XrSession session + const XrSpatialAnchorCreateInfoHTC* createInfo + XrSpace* anchor + + + XrResult xrGetSpatialAnchorNameHTC + XrSpace anchor + XrSpatialAnchorNameHTC* name + + XrResult xrEnumerateViveTrackerPathsHTCX @@ -5713,7 +6082,6 @@ maintained in the default branch of the Khronos OpenXR GitHub project. XrPlaneDetectorPolygonBufferEXT* polygonBuffer - XrResult xrCreateVirtualKeyboardMETA @@ -5786,6 +6154,124 @@ maintained in the default branch of the Khronos OpenXR GitHub project. XrVirtualKeyboardMETA keyboard const XrVirtualKeyboardTextContextChangeInfoMETA* changeInfo + + + + XrResult xrEnableUserCalibrationEventsML + XrInstance instance + const XrUserCalibrationEnableEventsInfoML* enableInfo + + + + + XrResult xrEnableLocalizationEventsML + XrSession session + const XrLocalizationEnableEventsInfoML * info + + + + XrResult xrQueryLocalizationMapsML + XrSession session + const XrLocalizationMapQueryInfoBaseHeaderML* queryInfo + uint32_t mapCapacityInput + uint32_t * mapCountOutput + XrLocalizationMapML* maps + + + + XrResult xrRequestMapLocalizationML + XrSession session + const XrMapLocalizationRequestInfoML* requestInfo + + + + XrResult xrImportLocalizationMapML + XrSession session + const XrLocalizationMapImportInfoML* importInfo + XrUuidEXT* mapUuid + + + + XrResult xrCreateExportedLocalizationMapML + XrSession session + const XrUuidEXT* mapUuid + XrExportedLocalizationMapML* map + + + + XrResult xrDestroyExportedLocalizationMapML + XrExportedLocalizationMapML map + + + + XrResult xrGetExportedLocalizationMapDataML + XrExportedLocalizationMapML map + uint32_t bufferCapacityInput + uint32_t* bufferCountOutput + char* buffer + + + + + XrResult xrCreateMarkerDetectorML + XrSession session + const XrMarkerDetectorCreateInfoML* createInfo + XrMarkerDetectorML* markerDetector + + + XrResult xrDestroyMarkerDetectorML + XrMarkerDetectorML markerDetector + + + XrResult xrSnapshotMarkerDetectorML + XrMarkerDetectorML markerDetector + XrMarkerDetectorSnapshotInfoML* snapshotInfo + + + XrResult xrGetMarkerDetectorStateML + XrMarkerDetectorML markerDetector + XrMarkerDetectorStateML* state + + + XrResult xrGetMarkersML + XrMarkerDetectorML markerDetector + uint32_t markerCapacityInput + uint32_t* markerCountOutput + XrMarkerML* markers + + + XrResult xrGetMarkerReprojectionErrorML + XrMarkerDetectorML markerDetector + XrMarkerML marker + float* reprojectionErrorMeters + + + XrResult xrGetMarkerLengthML + XrMarkerDetectorML markerDetector + XrMarkerML marker + float* meters + + + XrResult xrGetMarkerNumberML + XrMarkerDetectorML markerDetector + XrMarkerML marker + uint64_t* number + + + XrResult xrGetMarkerStringML + XrMarkerDetectorML markerDetector + XrMarkerML marker + uint32_t bufferCapacityInput + uint32_t* bufferCountOutput + char* buffer + + + XrResult xrCreateMarkerSpaceML + XrSession session + const XrMarkerSpaceCreateInfoML* createInfo + XrSpace* space + + @@ -6213,6 +6699,39 @@ maintained in the default branch of the Khronos OpenXR GitHub project. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -7497,7 +8016,7 @@ maintained in the default branch of the Khronos OpenXR GitHub project. - + @@ -8363,17 +8882,114 @@ maintained in the default branch of the Khronos OpenXR GitHub project. - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -8391,6 +9007,29 @@ maintained in the default branch of the Khronos OpenXR GitHub project. + + + + + + + + + + + + + + + + + + + + + + + @@ -8448,10 +9087,25 @@ maintained in the default branch of the Khronos OpenXR GitHub project. - + - - + + + + + + + + + + + + + + + + + @@ -9750,10 +10404,11 @@ maintained in the default branch of the Khronos OpenXR GitHub project. - + - - + + + @@ -10077,10 +10732,19 @@ maintained in the default branch of the Khronos OpenXR GitHub project. - + - - + + + + + + + + + + + @@ -11257,13 +11921,6 @@ maintained in the default branch of the Khronos OpenXR GitHub project. - - - - - - - @@ -11637,6 +12294,1329 @@ 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..d055b7a7 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -22,34 +22,50 @@ endif() # Entire project uses C++14 set(CMAKE_CXX_STANDARD 14) set(CMAKE_POSITION_INDEPENDENT_CODE ON) -set(CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake") +list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake") 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) # CMake will detect OpenGL/Vulkan which are not compatible with UWP and ARM/ARM64 on Windows so skip it in these cases. -if(CMAKE_SYSTEM_NAME STREQUAL "WindowsStore" OR (WIN32 AND CMAKE_GENERATOR_PLATFORM_UPPER MATCHES "ARM.*")) +if(CMAKE_SYSTEM_NAME STREQUAL "WindowsStore" + OR (WIN32 AND CMAKE_GENERATOR_PLATFORM_UPPER MATCHES "ARM.*") +) set(OPENGL_INCOMPATIBLE TRUE) set(VULKAN_INCOMPATIBLE TRUE) message(STATUS "OpenGL/Vulkan disabled due to incompatibility") + elseif(CMAKE_SYSTEM_NAME STREQUAL "Darwin") set(OPENGL_INCOMPATIBLE TRUE) message(STATUS "OpenGL disabled as no bindings exist for OpenGL on Darwin") + elseif(ANDROID) set(OPENGL_INCOMPATIBLE TRUE) - find_path(ANDROID_NATIVE_APP_GLUE android_native_app_glue.h PATHS ${ANDROID_NDK}/sources/android/native_app_glue) + find_path( + ANDROID_NATIVE_APP_GLUE android_native_app_glue.h + PATHS "${ANDROID_NDK}/sources/android/native_app_glue" + ) if(ANDROID_NATIVE_APP_GLUE) # Needed by gfxwrapper set(OPENGLES_INCOMPATIBLE FALSE) endif() if(ANDROID_PLATFORM_LEVEL LESS 24) set(VULKAN_INCOMPATIBLE TRUE) - message(STATUS "Vulkan disabled due to incompatibility: need to target at least API 24") + message( + STATUS + "Vulkan disabled due to incompatibility: need to target at least API 24" + ) endif() endif() @@ -58,6 +74,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 +86,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) @@ -77,11 +95,10 @@ if(NOT OPENGLES_INCOMPATIBLE) endif() if(NOT VULKAN_INCOMPATIBLE) - if(NOT CMAKE_VERSION VERSION_LESS 3.7.0) - # Find the Vulkan headers - find_package(Vulkan) - endif() + # Find the Vulkan headers + find_package(Vulkan) 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) @@ -102,9 +119,15 @@ option( ) if(WIN32) - set(OPENXR_DEBUG_POSTFIX d CACHE STRING "OpenXR loader debug postfix.") + set(OPENXR_DEBUG_POSTFIX + d + CACHE STRING "OpenXR loader debug postfix." + ) else() - set(OPENXR_DEBUG_POSTFIX "" CACHE STRING "OpenXR loader debug postfix.") + set(OPENXR_DEBUG_POSTFIX + "" + CACHE STRING "OpenXR loader debug postfix." + ) endif() if(CMAKE_SYSTEM_NAME STREQUAL "Windows") @@ -124,10 +147,16 @@ endif() include(CMakeDependentOption) cmake_dependent_option( - BUILD_WITH_SYSTEM_JSONCPP "Use system jsoncpp instead of vendored source" ON "JSONCPP_FOUND" OFF + BUILD_WITH_SYSTEM_JSONCPP + "Use system jsoncpp instead of vendored source" + ON + "JSONCPP_FOUND" + OFF ) cmake_dependent_option( - BUILD_WITH_STD_FILESYSTEM "Use std::[experimental::]filesystem." ON + BUILD_WITH_STD_FILESYSTEM + "Use std::[experimental::]filesystem." + ON "HAVE_FILESYSTEM_WITHOUT_LIB OR HAVE_FILESYSTEM_NEEDING_LIBSTDCXXFS OR HAVE_FILESYSTEM_NEEDING_LIBCXXFS" OFF ) @@ -150,9 +179,16 @@ if(MSVC) endif() # This is a little helper library for setting up OpenGL -if((OPENGL_FOUND OR OpenGLES_FOUND) AND EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/common/gfxwrapper_opengl.c") - add_library(openxr-gfxwrapper STATIC common/gfxwrapper_opengl.c common/gfxwrapper_opengl.h) - target_include_directories(openxr-gfxwrapper PUBLIC ${PROJECT_SOURCE_DIR}/external/include) +if((OPENGL_FOUND OR OpenGLES_FOUND) + AND EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/common/gfxwrapper_opengl.c" +) + add_library( + openxr-gfxwrapper STATIC common/gfxwrapper_opengl.c + common/gfxwrapper_opengl.h + ) + target_include_directories( + openxr-gfxwrapper PUBLIC "${PROJECT_SOURCE_DIR}/external/include" + ) if(OPENGL_FOUND) if(TARGET OpenGL::OpenGL) target_link_libraries(openxr-gfxwrapper PUBLIC OpenGL::OpenGL) @@ -173,28 +209,35 @@ if((OPENGL_FOUND OR OpenGLES_FOUND) AND EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/comm target_link_libraries(openxr-gfxwrapper PUBLIC EGL::EGL) endif() if(ANDROID) - target_include_directories(openxr-gfxwrapper PUBLIC ${ANDROID_NATIVE_APP_GLUE}) + target_include_directories( + openxr-gfxwrapper PUBLIC "${ANDROID_NATIVE_APP_GLUE}" + ) # Note: For some reason, just adding this to the gfxwrapper library results in failure at load time. # So, each consuming target must add $ to their sources - add_library(android_native_app_glue OBJECT "${ANDROID_NATIVE_APP_GLUE}/android_native_app_glue.c") - target_include_directories(android_native_app_glue PUBLIC ${ANDROID_NATIVE_APP_GLUE}) - target_compile_options(android_native_app_glue PRIVATE -Wno-unused-parameter) - elseif(APPLE) - set(apple_c_compile_options - -x objective-c++ + add_library( + android_native_app_glue OBJECT + "${ANDROID_NATIVE_APP_GLUE}/android_native_app_glue.c" ) - target_compile_options(openxr-gfxwrapper PRIVATE - ${apple_c_compile_options} + target_include_directories( + android_native_app_glue PUBLIC "${ANDROID_NATIVE_APP_GLUE}" ) - target_link_libraries(openxr-gfxwrapper PUBLIC - "-framework AppKit" - "-framework Foundation" - "-framework CoreGraphics" + target_compile_options( + android_native_app_glue PRIVATE -Wno-unused-parameter + ) + elseif(APPLE) + target_compile_options(openxr-gfxwrapper PRIVATE -x objective-c++) + target_link_libraries( + openxr-gfxwrapper + PUBLIC "-framework AppKit" "-framework Foundation" + "-framework CoreGraphics" ) endif() set_target_properties(openxr-gfxwrapper PROPERTIES FOLDER ${HELPER_FOLDER}) - message(STATUS "Enabling OpenGL support in hello_xr, loader_test, and conformance, if configured") + message( + STATUS + "Enabling OpenGL support in hello_xr, loader_test, and conformance, if configured" + ) endif() # Determine the presentation backend for Linux systems. @@ -216,7 +259,10 @@ elseif(APPLE) endif() elseif(ANDROID) add_definitions(-DXR_USE_PLATFORM_ANDROID) - set(OPENXR_ANDROID_VERSION_SUFFIX "" CACHE STRING "Suffix for generated Android artifacts.") + set(OPENXR_ANDROID_VERSION_SUFFIX + "" + CACHE STRING "Suffix for generated Android artifacts." + ) elseif(PRESENTATION_BACKEND MATCHES "xlib") add_definitions(-DXR_USE_PLATFORM_XLIB) elseif(PRESENTATION_BACKEND MATCHES "xcb") @@ -253,17 +299,37 @@ endif() # Find glslc shader compiler. # On Android, the NDK includes the binary, so no external dependency. if(ANDROID) - file(GLOB glslc_folders ${ANDROID_NDK}/shader-tools/*) + file( + GLOB + glslc_folders + CONFIGURE_DEPENDS + ${ANDROID_NDK}/shader-tools/* + ) find_program( GLSL_COMPILER glslc PATHS ${glslc_folders} NO_DEFAULT_PATH ) else() - file(GLOB glslc_folders $ENV{VULKAN_SDK}/*) - find_program(GLSL_COMPILER glslc PATHS ${glslc_folders}) + if($ENV{VULKAN_SDK}) + file(TO_CMAKE_PATH "$ENV{VULKAN_SDK}" VULKAN_SDK) + file( + GLOB + glslc_folders + CONFIGURE_DEPENDS + ${VULKAN_SDK}/* + ) + endif() + 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) @@ -275,30 +341,37 @@ endif() function(compile_glsl run_target_name) set(glsl_output_files "") foreach(in_file IN LISTS ARGN) - get_filename_component(glsl_stage ${in_file} NAME_WE) - set(out_file ${CMAKE_CURRENT_BINARY_DIR}/${glsl_stage}.spv) + get_filename_component(glsl_stage "${in_file}" NAME_WE) + set(out_file "${CMAKE_CURRENT_BINARY_DIR}/${glsl_stage}.spv") if(GLSL_COMPILER) # Run glslc if we can find it add_custom_command( - OUTPUT ${out_file} - COMMAND ${GLSL_COMPILER} -mfmt=c -fshader-stage=${glsl_stage} ${in_file} -o ${out_file} - DEPENDS ${in_file} + OUTPUT "${out_file}" + COMMAND + "${GLSL_COMPILER}" -mfmt=c -fshader-stage=${glsl_stage} + "${in_file}" -o "${out_file}" + DEPENDS "${in_file}" + WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}" + VERBATIM ) elseif(GLSLANG_VALIDATOR) # Run glslangValidator if we can find it add_custom_command( - OUTPUT ${out_file} - COMMAND ${GLSLANG_VALIDATOR} -V -S ${glsl_stage} ${in_file} -x -o ${out_file} - DEPENDS ${in_file} + OUTPUT "${out_file}" + COMMAND + "${GLSLANG_VALIDATOR}" -V -S ${glsl_stage} "${in_file}" -x + -o "${out_file}" + DEPENDS "${in_file}" + WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}" VERBATIM ) else() # Use the precompiled .spv files - get_filename_component(glsl_src_dir ${in_file} DIRECTORY) - set(precompiled_file ${glsl_src_dir}/${glsl_stage}.spv) - configure_file(${precompiled_file} ${out_file} COPYONLY) + get_filename_component(glsl_src_dir "${in_file}" DIRECTORY) + set(precompiled_file "${glsl_src_dir}/${glsl_stage}.spv") + configure_file("${precompiled_file}" "${out_file}" COPYONLY) endif() - list(APPEND glsl_output_files ${out_file}) + list(APPEND glsl_output_files "${out_file}") endforeach() add_custom_target(${run_target_name} ALL DEPENDS ${glsl_output_files}) set_target_properties(${run_target_name} PROPERTIES FOLDER ${HELPER_FOLDER}) @@ -307,7 +380,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() @@ -316,12 +391,9 @@ include(CheckFunctionExists) check_function_exists(secure_getenv HAVE_SECURE_GETENV) check_function_exists(__secure_getenv HAVE___SECURE_GETENV) -configure_file(${CMAKE_CURRENT_SOURCE_DIR}/common_config.h.in ${CMAKE_CURRENT_BINARY_DIR}/common_config.h) +configure_file(common_config.h.in "${CMAKE_CURRENT_BINARY_DIR}/common_config.h") add_definitions(-DOPENXR_HAVE_COMMON_CONFIG) -# Be able to find pre-generated files, if used. -include_directories(${CMAKE_CURRENT_SOURCE_DIR}/../include ${CMAKE_CURRENT_SOURCE_DIR}) - include(CheckSymbolExists) check_symbol_exists(timespec_get time.h HAVE_TIMESPEC_GET) if(HAVE_TIMESPEC_GET) @@ -331,23 +403,15 @@ endif() # Set up the OpenXR version variables, used by several targets in this project. include(${CMAKE_CURRENT_SOURCE_DIR}/version.cmake) -# Path separators ( : or ; ) are not handled well in CMake. -# This seems like a reasonable approach. -if(CMAKE_HOST_SYSTEM_NAME STREQUAL Windows) - set(CODEGEN_PYTHON_PATH - "${PROJECT_SOURCE_DIR}/specification/scripts;${PROJECT_SOURCE_DIR}/src/scripts;$ENV{PYTHONPATH}" - ) -else() - set(CODEGEN_PYTHON_PATH - "${PROJECT_SOURCE_DIR}/specification/scripts:${PROJECT_SOURCE_DIR}/src/scripts:$ENV{PYTHONPATH}" - ) -endif() - # General code generation macro used by several targets. macro(run_xr_xml_generate dependency output) - if(EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/${output}" AND NOT BUILD_FORCE_GENERATION) + if(EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/${output}" + AND NOT BUILD_FORCE_GENERATION + ) # pre-generated found - message(STATUS "Found and will use pre-generated ${output} in source tree") + message( + STATUS "Found and will use pre-generated ${output} in source tree" + ) list(APPEND GENERATED_OUTPUT "${CMAKE_CURRENT_SOURCE_DIR}/${output}") else() if(NOT PYTHON_EXECUTABLE) @@ -357,21 +421,24 @@ macro(run_xr_xml_generate dependency output) ) endif() add_custom_command( - OUTPUT ${output} + OUTPUT "${output}" COMMAND - ${CMAKE_COMMAND} -E env "PYTHONPATH=${CODEGEN_PYTHON_PATH}" - ${PYTHON_EXECUTABLE} - ${PROJECT_SOURCE_DIR}/src/scripts/src_genxr.py - -registry ${PROJECT_SOURCE_DIR}/specification/registry/xr.xml - ${output} + "${CMAKE_COMMAND}" -E env "PYTHONPATH=${CODEGEN_PYTHON_PATH}" + "${PYTHON_EXECUTABLE}" + "${PROJECT_SOURCE_DIR}/src/scripts/src_genxr.py" -registry + "${PROJECT_SOURCE_DIR}/specification/registry/xr.xml" + "${output}" WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR} - DEPENDS "${PROJECT_SOURCE_DIR}/specification/registry/xr.xml" - "${PROJECT_SOURCE_DIR}/specification/scripts/generator.py" - "${PROJECT_SOURCE_DIR}/specification/scripts/reg.py" - "${PROJECT_SOURCE_DIR}/src/scripts/${dependency}" - "${PROJECT_SOURCE_DIR}/src/scripts/src_genxr.py" - ${ARGN} - COMMENT "Generating ${output} using ${PYTHON_EXECUTABLE} on ${dependency}" + DEPENDS + "${PROJECT_SOURCE_DIR}/specification/registry/xr.xml" + "${PROJECT_SOURCE_DIR}/specification/scripts/generator.py" + "${PROJECT_SOURCE_DIR}/specification/scripts/reg.py" + "${PROJECT_SOURCE_DIR}/src/scripts/${dependency}" + "${PROJECT_SOURCE_DIR}/src/scripts/src_genxr.py" + ${ARGN} + VERBATIM + COMMENT + "Generating ${output} using ${PYTHON_EXECUTABLE} on ${dependency}" ) set_source_files_properties(${output} PROPERTIES GENERATED TRUE) list(APPEND GENERATED_OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/${output}") @@ -380,65 +447,96 @@ macro(run_xr_xml_generate dependency output) endmacro() # Layer JSON generation macro used by several targets. -macro(gen_xr_layer_json filename layername libfile version desc genbad) +macro( + gen_xr_layer_json + filename + layername + libfile + version + desc + genbad +) add_custom_command( - OUTPUT ${filename} + OUTPUT "${filename}" COMMAND - ${CMAKE_COMMAND} -E env "PYTHONPATH=${CODEGEN_PYTHON_PATH}" - ${PYTHON_EXECUTABLE} - ${PROJECT_SOURCE_DIR}/src/scripts/generate_api_layer_manifest.py - -f ${filename} - -n ${layername} - -l ${libfile} - -a ${MAJOR}.${MINOR} - -v ${version} - ${genbad} - -d ${desc} - WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR} - DEPENDS ${PROJECT_SOURCE_DIR}/src/scripts/generate_api_layer_manifest.py + "${CMAKE_COMMAND}" -E env "PYTHONPATH=${CODEGEN_PYTHON_PATH}" + "${PYTHON_EXECUTABLE}" + "${PROJECT_SOURCE_DIR}/src/scripts/generate_api_layer_manifest.py" + -f "${filename}" -n ${layername} -l ${libfile} -a ${MAJOR}.${MINOR} + -v ${version} ${genbad} -d ${desc} + WORKING_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}" + DEPENDS + "${PROJECT_SOURCE_DIR}/src/scripts/generate_api_layer_manifest.py" COMMENT "Generating API Layer JSON ${filename} using -f ${filename} -n ${layername} -l ${libfile} -a ${MAJOR}.${MINOR} -v ${version} ${genbad} -d ${desc}" + VERBATIM ) endmacro() # Custom target for generated dispatch table sources, used by several targets. -set(GENERATED_OUTPUT) -set(GENERATED_DEPENDS) +unset(GENERATED_OUTPUT) +unset(GENERATED_DEPENDS) run_xr_xml_generate(utility_source_generator.py xr_generated_dispatch_table.h) run_xr_xml_generate(utility_source_generator.py xr_generated_dispatch_table.c) set(COMMON_GENERATED_OUTPUT ${GENERATED_OUTPUT}) set(COMMON_GENERATED_DEPENDS ${GENERATED_DEPENDS}) + +if(COMMON_GENERATED_DEPENDS) + add_custom_target( + xr_common_generated_files DEPENDS ${COMMON_GENERATED_DEPENDS} + ) +else() + add_custom_target(xr_common_generated_files) +endif() + +set_target_properties( + xr_common_generated_files PROPERTIES FOLDER ${CODEGEN_FOLDER} +) + unset(GENERATED_OUTPUT) unset(GENERATED_DEPENDS) - -run_xr_xml_generate(utility_source_generator.py xr_generated_dispatch_table_core.h) -run_xr_xml_generate(utility_source_generator.py xr_generated_dispatch_table_core.c) +run_xr_xml_generate( + utility_source_generator.py xr_generated_dispatch_table_core.h +) +run_xr_xml_generate( + utility_source_generator.py xr_generated_dispatch_table_core.c +) set(LOADER_GENERATED_OUTPUT ${GENERATED_OUTPUT}) set(LOADER_GENERATED_DEPENDS ${GENERATED_DEPENDS}) + unset(GENERATED_OUTPUT) unset(GENERATED_DEPENDS) if(LOADER_GENERATED_DEPENDS) - add_custom_target(xr_global_generated_files DEPENDS ${LOADER_GENERATED_DEPENDS}) + add_custom_target( + xr_global_generated_files DEPENDS ${LOADER_GENERATED_DEPENDS} + ) else() add_custom_target(xr_global_generated_files) endif() -set_target_properties(xr_global_generated_files PROPERTIES FOLDER ${CODEGEN_FOLDER}) - -if(COMMON_GENERATED_DEPENDS) - add_custom_target(xr_common_generated_files DEPENDS ${COMMON_GENERATED_DEPENDS}) -else() - add_custom_target(xr_common_generated_files) -endif() - -set_target_properties(xr_common_generated_files PROPERTIES FOLDER ${CODEGEN_FOLDER}) +set_target_properties( + xr_global_generated_files PROPERTIES FOLDER ${CODEGEN_FOLDER} +) if(NOT MSVC) include(CheckCXXCompilerFlag) include(CheckCCompilerFlag) - foreach(FLAG -Wall -Werror=unused-parameter -Werror=unused-argument -Wpointer-arith) - string(REGEX REPLACE "[^A-Za-z0-9]" "" _flagvar "${FLAG}") + foreach( + FLAG + -Wall + -Werror=unused-parameter + -Werror=unused-argument + -Wpointer-arith + ) + string( + REGEX + REPLACE + "[^A-Za-z0-9]" + "" + _flagvar + "${FLAG}" + ) check_cxx_compiler_flag(${FLAG} SUPPORTS_${_flagvar}) if(SUPPORTS_${_flagvar}) set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${FLAG}") @@ -449,9 +547,13 @@ if(NOT MSVC) endif() endforeach() if(APPLE) - set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -Wl,-undefined,error") + set(CMAKE_SHARED_LINKER_FLAGS + "${CMAKE_SHARED_LINKER_FLAGS} -Wl,-undefined,error" + ) elseif(NOT WIN32) - set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -Wl,--no-undefined") + set(CMAKE_SHARED_LINKER_FLAGS + "${CMAKE_SHARED_LINKER_FLAGS} -Wl,--no-undefined" + ) endif() endif() diff --git a/src/cmake/FindEGL.cmake b/src/cmake/FindEGL.cmake index 16000927..c4f6c8b4 100644 --- a/src/cmake/FindEGL.cmake +++ b/src/cmake/FindEGL.cmake @@ -33,10 +33,14 @@ # Since pre-1.0.0. # SPDX-License-Identifier: BSD-3-Clause +# +# Note: This module is originally from the KDE "Extra CMake Modules" repo, +# adapted to work standalone. Original source: +# https://github.com/KDE/extra-cmake-modules/blob/3b0bf71a72789eb2b79310b4f67602115e347f56/find-modules/FindEGL.cmake #============================================================================= # Copyright 2014 Alex Merry # Copyright 2014 Martin Gräßlin -# Copyright 2019 Ryan Pavlik +# Copyright 2019, 2021 Ryan Pavlik # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions @@ -67,8 +71,12 @@ include(CMakePushCheckState) # Use pkg-config to get the directories and then use these values # in the FIND_PATH() and FIND_LIBRARY() calls -find_package(PkgConfig) -pkg_check_modules(PKG_EGL QUIET egl) +if(NOT ANDROID) + find_package(PkgConfig QUIET) + if(PKGCONFIG_FOUND) + pkg_check_modules(PKG_EGL QUIET egl) + endif() +endif() set(EGL_DEFINITIONS ${PKG_EGL_CFLAGS_OTHER}) @@ -167,5 +175,5 @@ set(EGL_VERSION_STRING ${EGL_VERSION}) include(FeatureSummary) set_package_properties(EGL PROPERTIES URL "https://www.khronos.org/egl/" - DESCRIPTION "A platform-agnostic mechanism for creating rendering surfaces for use with other graphics libraries, such as OpenGL|ES and OpenVG." + DESCRIPTION "A platform-independent mechanism for creating rendering surfaces for use with other graphics libraries, such as OpenGL|ES and OpenVG." ) diff --git a/src/cmake/FindJsonCpp.cmake b/src/cmake/FindJsonCpp.cmake index 9cd86847..e1010056 100644 --- a/src/cmake/FindJsonCpp.cmake +++ b/src/cmake/FindJsonCpp.cmake @@ -26,8 +26,8 @@ # Incorporates work from the module contributed to VRPN under the same license: # 2011 Philippe Crassous (ENSAM ParisTech / Institut Image) p.crassous _at_ free.fr # -# Copyright Philippe Crassous 2011. -# Copyright Sensics, Inc. 2016. +# Copyright 2011, Philippe Crassous +# Copyright 2016, Sensics, Inc. # # SPDX-License-Identifier: BSL-1.0 # diff --git a/src/cmake/FindOpenGLES.cmake b/src/cmake/FindOpenGLES.cmake index fc8aeb56..e024df8f 100644 --- a/src/cmake/FindOpenGLES.cmake +++ b/src/cmake/FindOpenGLES.cmake @@ -1,11 +1,11 @@ -# Copyright 2020 Collabora, Ltd. +# Copyright 2020-2021 Collabora, Ltd. # SPDX-License-Identifier: BSL-1.0 # Distributed under the Boost Software License, Version 1.0. # (See accompanying file LICENSE_1_0.txt or copy at # http://www.boost.org/LICENSE_1_0.txt) # # Original Author: -# 2020 Ryan Pavlik +# 2020-2021, Ryan Pavlik #[[.rst: FindOpenGLES @@ -53,18 +53,22 @@ set(OpenGLES_ROOT_DIR if(NOT OpenGLES_FIND_COMPONENTS) set(OpenGLES_FIND_COMPONENTS V2) endif() -find_package(PkgConfig QUIET) -if(PKG_CONFIG_FOUND) - set(_old_prefix_path "${CMAKE_PREFIX_PATH}") - # So pkg-config uses OpenGLES_ROOT_DIR too. - if(OpenGLES_ROOT_DIR) - list(APPEND CMAKE_PREFIX_PATH ${OpenGLES_ROOT_DIR}) + +if(NOT ANDROID) + find_package(PkgConfig QUIET) + if(PKG_CONFIG_FOUND) + set(_old_prefix_path "${CMAKE_PREFIX_PATH}") + # So pkg-config uses OpenGLES_ROOT_DIR too. + if(OpenGLES_ROOT_DIR) + list(APPEND CMAKE_PREFIX_PATH ${OpenGLES_ROOT_DIR}) + endif() + pkg_check_modules(PC_glesv1_cm QUIET glesv1_cm) + pkg_check_modules(PC_glesv2 QUIET glesv2) + # Restore + set(CMAKE_PREFIX_PATH "${_old_prefix_path}") endif() - pkg_check_modules(PC_glesv1_cm QUIET glesv1_cm) - pkg_check_modules(PC_glesv2 QUIET glesv2) - # Restore - set(CMAKE_PREFIX_PATH "${_old_prefix_path}") endif() + find_path( OpenGLES_V1_INCLUDE_DIR NAMES GLES/gl.h diff --git a/src/cmake/FindVulkanHeaders.cmake b/src/cmake/FindVulkanHeaders.cmake deleted file mode 100644 index 85f5bcd2..00000000 --- a/src/cmake/FindVulkanHeaders.cmake +++ /dev/null @@ -1,88 +0,0 @@ -# ~~~ -# Copyright (c) 2018-2019 Valve Corporation -# Copyright (c) 2018-2019 LunarG, 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. -# ~~~ - -#.rst: -# FindVulkanHeaders -# ----------------- -# -# Try to find Vulkan Headers and Registry. -# -# This module is intended to be used by projects that build Vulkan -# "system" components such as the loader and layers. -# Vulkan applications should instead use the FindVulkan (or similar) -# find module that locates the headers and the loader library. -# -# When using this find module to locate the headers and registry -# in a Vulkan-Headers repository, the Vulkan-Headers repository -# should be built with 'install' target and the following environment -# or CMake variable set to the location of the install directory. -# -# VULKAN_HEADERS_INSTALL_DIR -# -# IMPORTED Targets -# ^^^^^^^^^^^^^^^^ -# -# This module defines no IMPORTED targets -# -# Result Variables -# ^^^^^^^^^^^^^^^^ -# -# This module defines the following variables:: -# -# VulkanHeaders_FOUND - True if VulkanHeaders was found -# VulkanHeaders_INCLUDE_DIRS - include directories for VulkanHeaders -# -# VulkanRegistry_FOUND - True if VulkanRegistry was found -# VulkanRegistry_DIRS - directories for VulkanRegistry -# -# The module will also define two cache variables:: -# -# VulkanHeaders_INCLUDE_DIR - the VulkanHeaders include directory -# VulkanRegistry_DIR - the VulkanRegistry directory -# - -# Use HINTS instead of PATH to search these locations before -# searching system environment variables like $PATH that may -# contain SDK directories. -find_path(VulkanHeaders_INCLUDE_DIR - NAMES vulkan/vulkan.h - HINTS - ${VULKAN_HEADERS_INSTALL_DIR}/include - "$ENV{VULKAN_HEADERS_INSTALL_DIR}/include" - "$ENV{VULKAN_SDK}/include") - -if(VulkanHeaders_INCLUDE_DIR) - get_filename_component(VULKAN_REGISTRY_PATH_HINT ${VulkanHeaders_INCLUDE_DIR} DIRECTORY) - find_path(VulkanRegistry_DIR - NAMES vk.xml - HINTS "${VULKAN_REGISTRY_PATH_HINT}/share/vulkan/registry") -endif() - -set(VulkanHeaders_INCLUDE_DIRS ${VulkanHeaders_INCLUDE_DIR}) -set(VulkanRegistry_DIRS ${VulkanRegistry_DIR}) - -include(FindPackageHandleStandardArgs) -find_package_handle_standard_args(VulkanHeaders - DEFAULT_MSG - VulkanHeaders_INCLUDE_DIR) -find_package_handle_standard_args(VulkanRegistry - DEFAULT_MSG - VulkanRegistry_DIR) - -mark_as_advanced(VulkanHeaders_INCLUDE_DIR VulkanRegistry_DIR) diff --git a/src/cmake/GetGitRevisionDescription.cmake b/src/cmake/GetGitRevisionDescription.cmake new file mode 100644 index 00000000..bee0fba5 --- /dev/null +++ b/src/cmake/GetGitRevisionDescription.cmake @@ -0,0 +1,285 @@ +# - Returns a version string from Git +# +# These functions force a re-configure on each git commit so that you can +# trust the values of the variables in your build system. +# +# get_git_head_revision( [ALLOW_LOOKING_ABOVE_CMAKE_SOURCE_DIR]) +# +# Returns the refspec and sha hash of the current head revision +# +# git_describe( [ ...]) +# +# Returns the results of git describe on the source tree, and adjusting +# the output so that it tests false if an error occurs. +# +# git_describe_working_tree( [ ...]) +# +# Returns the results of git describe on the working tree (--dirty option), +# and adjusting the output so that it tests false if an error occurs. +# +# git_get_exact_tag( [ ...]) +# +# Returns the results of git describe --exact-match on the source tree, +# and adjusting the output so that it tests false if there was no exact +# matching tag. +# +# git_local_changes() +# +# Returns either "CLEAN" or "DIRTY" with respect to uncommitted changes. +# Uses the return code of "git diff-index --quiet HEAD --". +# Does not regard untracked files. +# +# Requires CMake 2.6 or newer (uses the 'function' command) +# +# Original Author: +# 2009-2020 Ryan Pavlik +# http://academic.cleardefinition.com +# +# Copyright 2009-2013, Iowa State University. +# Copyright 2013-2020, Ryan Pavlik +# Copyright 2013-2020, Contributors +# SPDX-License-Identifier: BSL-1.0 +# Distributed under the Boost Software License, Version 1.0. +# (See accompanying file LICENSE_1_0.txt or copy at +# http://www.boost.org/LICENSE_1_0.txt) + +if(__get_git_revision_description) + return() +endif() +set(__get_git_revision_description YES) + +# We must run the following at "include" time, not at function call time, +# to find the path to this module rather than the path to a calling list file +get_filename_component(_gitdescmoddir ${CMAKE_CURRENT_LIST_FILE} PATH) + +# Function _git_find_closest_git_dir finds the next closest .git directory +# that is part of any directory in the path defined by _start_dir. +# The result is returned in the parent scope variable whose name is passed +# as variable _git_dir_var. If no .git directory can be found, the +# function returns an empty string via _git_dir_var. +# +# Example: Given a path C:/bla/foo/bar and assuming C:/bla/.git exists and +# neither foo nor bar contain a file/directory .git. This will return +# C:/bla/.git +# +function(_git_find_closest_git_dir _start_dir _git_dir_var) + set(cur_dir "${_start_dir}") + set(git_dir "${_start_dir}/.git") + while(NOT EXISTS "${git_dir}") + # .git dir not found, search parent directories + set(git_previous_parent "${cur_dir}") + get_filename_component(cur_dir "${cur_dir}" DIRECTORY) + if(cur_dir STREQUAL git_previous_parent) + # We have reached the root directory, we are not in git + set(${_git_dir_var} + "" + PARENT_SCOPE) + return() + endif() + set(git_dir "${cur_dir}/.git") + endwhile() + set(${_git_dir_var} + "${git_dir}" + PARENT_SCOPE) +endfunction() + +function(get_git_head_revision _refspecvar _hashvar) + _git_find_closest_git_dir("${CMAKE_CURRENT_SOURCE_DIR}" GIT_DIR) + + if("${ARGN}" STREQUAL "ALLOW_LOOKING_ABOVE_CMAKE_SOURCE_DIR") + set(ALLOW_LOOKING_ABOVE_CMAKE_SOURCE_DIR TRUE) + else() + set(ALLOW_LOOKING_ABOVE_CMAKE_SOURCE_DIR FALSE) + endif() + if(NOT "${GIT_DIR}" STREQUAL "") + file(RELATIVE_PATH _relative_to_source_dir "${CMAKE_SOURCE_DIR}" + "${GIT_DIR}") + if("${_relative_to_source_dir}" MATCHES "[.][.]" + AND NOT ALLOW_LOOKING_ABOVE_CMAKE_SOURCE_DIR) + # We've gone above the CMake root dir. + set(GIT_DIR "") + endif() + endif() + if("${GIT_DIR}" STREQUAL "") + set(${_refspecvar} + "GITDIR-NOTFOUND" + PARENT_SCOPE) + set(${_hashvar} + "GITDIR-NOTFOUND" + PARENT_SCOPE) + return() + endif() + + # Check if the current source dir is a git submodule or a worktree. + # In both cases .git is a file instead of a directory. + # + if(NOT IS_DIRECTORY ${GIT_DIR}) + # The following git command will return a non empty string that + # points to the super project working tree if the current + # source dir is inside a git submodule. + # Otherwise the command will return an empty string. + # + execute_process( + COMMAND "${GIT_EXECUTABLE}" rev-parse + --show-superproject-working-tree + WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}" + OUTPUT_VARIABLE out + ERROR_QUIET OUTPUT_STRIP_TRAILING_WHITESPACE) + if(NOT "${out}" STREQUAL "") + # If out is empty, GIT_DIR/CMAKE_CURRENT_SOURCE_DIR is in a submodule + file(READ ${GIT_DIR} submodule) + string(REGEX REPLACE "gitdir: (.*)$" "\\1" GIT_DIR_RELATIVE + ${submodule}) + string(STRIP ${GIT_DIR_RELATIVE} GIT_DIR_RELATIVE) + get_filename_component(SUBMODULE_DIR ${GIT_DIR} PATH) + get_filename_component(GIT_DIR ${SUBMODULE_DIR}/${GIT_DIR_RELATIVE} + ABSOLUTE) + set(HEAD_SOURCE_FILE "${GIT_DIR}/HEAD") + else() + # GIT_DIR/CMAKE_CURRENT_SOURCE_DIR is in a worktree + file(READ ${GIT_DIR} worktree_ref) + # The .git directory contains a path to the worktree information directory + # inside the parent git repo of the worktree. + # + string(REGEX REPLACE "gitdir: (.*)$" "\\1" git_worktree_dir + ${worktree_ref}) + string(STRIP ${git_worktree_dir} git_worktree_dir) + _git_find_closest_git_dir("${git_worktree_dir}" GIT_DIR) + set(HEAD_SOURCE_FILE "${git_worktree_dir}/HEAD") + endif() + else() + set(HEAD_SOURCE_FILE "${GIT_DIR}/HEAD") + endif() + set(GIT_DATA "${CMAKE_CURRENT_BINARY_DIR}/CMakeFiles/git-data") + if(NOT EXISTS "${GIT_DATA}") + file(MAKE_DIRECTORY "${GIT_DATA}") + endif() + + if(NOT EXISTS "${HEAD_SOURCE_FILE}") + return() + endif() + set(HEAD_FILE "${GIT_DATA}/HEAD") + configure_file("${HEAD_SOURCE_FILE}" "${HEAD_FILE}" COPYONLY) + + configure_file("${_gitdescmoddir}/GetGitRevisionDescription.cmake.in" + "${GIT_DATA}/grabRef.cmake" @ONLY) + include("${GIT_DATA}/grabRef.cmake") + + set(${_refspecvar} + "${HEAD_REF}" + PARENT_SCOPE) + set(${_hashvar} + "${HEAD_HASH}" + PARENT_SCOPE) +endfunction() + +function(git_describe _var) + if(NOT GIT_FOUND) + find_package(Git QUIET) + endif() + get_git_head_revision(refspec hash) + if(NOT GIT_FOUND) + set(${_var} + "GIT-NOTFOUND" + PARENT_SCOPE) + return() + endif() + if(NOT hash) + set(${_var} + "HEAD-HASH-NOTFOUND" + PARENT_SCOPE) + return() + endif() + + # TODO sanitize + #if((${ARGN}" MATCHES "&&") OR + # (ARGN MATCHES "||") OR + # (ARGN MATCHES "\\;")) + # message("Please report the following error to the project!") + # message(FATAL_ERROR "Looks like someone's doing something nefarious with git_describe! Passed arguments ${ARGN}") + #endif() + + #message(STATUS "Arguments to execute_process: ${ARGN}") + + execute_process( + COMMAND "${GIT_EXECUTABLE}" describe --tags --always ${hash} ${ARGN} + WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}" + RESULT_VARIABLE res + OUTPUT_VARIABLE out + ERROR_QUIET OUTPUT_STRIP_TRAILING_WHITESPACE) + if(NOT res EQUAL 0) + set(out "${out}-${res}-NOTFOUND") + endif() + + set(${_var} + "${out}" + PARENT_SCOPE) +endfunction() + +function(git_describe_working_tree _var) + if(NOT GIT_FOUND) + find_package(Git QUIET) + endif() + if(NOT GIT_FOUND) + set(${_var} + "GIT-NOTFOUND" + PARENT_SCOPE) + return() + endif() + + execute_process( + COMMAND "${GIT_EXECUTABLE}" describe --dirty ${ARGN} + WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}" + RESULT_VARIABLE res + OUTPUT_VARIABLE out + ERROR_QUIET OUTPUT_STRIP_TRAILING_WHITESPACE) + if(NOT res EQUAL 0) + set(out "${out}-${res}-NOTFOUND") + endif() + + set(${_var} + "${out}" + PARENT_SCOPE) +endfunction() + +function(git_get_exact_tag _var) + git_describe(out --exact-match ${ARGN}) + set(${_var} + "${out}" + PARENT_SCOPE) +endfunction() + +function(git_local_changes _var) + if(NOT GIT_FOUND) + find_package(Git QUIET) + endif() + get_git_head_revision(refspec hash) + if(NOT GIT_FOUND) + set(${_var} + "GIT-NOTFOUND" + PARENT_SCOPE) + return() + endif() + if(NOT hash) + set(${_var} + "HEAD-HASH-NOTFOUND" + PARENT_SCOPE) + return() + endif() + + execute_process( + COMMAND "${GIT_EXECUTABLE}" diff-index --quiet HEAD -- + WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}" + RESULT_VARIABLE res + OUTPUT_VARIABLE out + ERROR_QUIET OUTPUT_STRIP_TRAILING_WHITESPACE) + if(res EQUAL 0) + set(${_var} + "CLEAN" + PARENT_SCOPE) + else() + set(${_var} + "DIRTY" + PARENT_SCOPE) + endif() +endfunction() diff --git a/src/cmake/GetGitRevisionDescription.cmake.in b/src/cmake/GetGitRevisionDescription.cmake.in new file mode 100644 index 00000000..462edb68 --- /dev/null +++ b/src/cmake/GetGitRevisionDescription.cmake.in @@ -0,0 +1,46 @@ +# +# Internal file for GetGitRevisionDescription.cmake +# +# Requires CMake 2.6 or newer (uses the 'function' command) +# +# Original Author: +# 2009-2023 Ryan Pavlik +# http://academic.cleardefinition.com +# Iowa State University HCI Graduate Program/VRAC +# +# Copyright 2009-2012, Iowa State University +# Copyright 2011-2023, Contributors +# Distributed under the Boost Software License, Version 1.0. +# (See accompanying file LICENSE_1_0.txt or copy at +# http://www.boost.org/LICENSE_1_0.txt) +# SPDX-License-Identifier: BSL-1.0 + +set(HEAD_HASH) + +file(READ "@HEAD_FILE@" HEAD_CONTENTS LIMIT 1024) + +string(STRIP "${HEAD_CONTENTS}" HEAD_CONTENTS) +if(HEAD_CONTENTS MATCHES "ref") + # named branch + string(REPLACE "ref: " "" HEAD_REF "${HEAD_CONTENTS}") + if(EXISTS "@GIT_DIR@/${HEAD_REF}") + configure_file("@GIT_DIR@/${HEAD_REF}" "@GIT_DATA@/head-ref" COPYONLY) + else() + if(EXISTS "@GIT_DIR@/packed-refs") + configure_file("@GIT_DIR@/packed-refs" "@GIT_DATA@/packed-refs" + COPYONLY) + file(READ "@GIT_DATA@/packed-refs" PACKED_REFS) + if(${PACKED_REFS} MATCHES "([0-9a-z]*) ${HEAD_REF}") + set(HEAD_HASH "${CMAKE_MATCH_1}") + endif() + endif() + endif() +else() + # detached HEAD + configure_file("@GIT_DIR@/HEAD" "@GIT_DATA@/head-ref" COPYONLY) +endif() + +if(NOT HEAD_HASH) + file(READ "@GIT_DATA@/head-ref" HEAD_HASH LIMIT 1024) + string(STRIP "${HEAD_HASH}" HEAD_HASH) +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/cmake/metaconfig.cmake.in b/src/cmake/metaconfig.cmake.in index 462b0cc2..0353e58a 100644 --- a/src/cmake/metaconfig.cmake.in +++ b/src/cmake/metaconfig.cmake.in @@ -1,4 +1,4 @@ -# Copyright (c) 2017 The Khronos Group Inc. +# Copyright (c) 2017-2023, The Khronos Group Inc. # # SPDX-License-Identifier: Apache-2.0 # diff --git a/src/cmake/presentation.cmake b/src/cmake/presentation.cmake index 50c41803..26363856 100644 --- a/src/cmake/presentation.cmake +++ b/src/cmake/presentation.cmake @@ -57,11 +57,14 @@ if(PRESENTATION_BACKEND MATCHES "xlib") target_compile_definitions(openxr-gfxwrapper PUBLIC OS_LINUX_XLIB) target_link_libraries(openxr-gfxwrapper PRIVATE ${X11_X11_LIB} ${X11_Xxf86vm_LIB} ${X11_Xrandr_LIB}) - if(TARGET OpenGL::OpenGL AND TARGET OpenGL::GLX) - # OpenGL::OpenGL already linked, we just need to add GLX. + # OpenGL::OpenGL already linked, we just need to add GLX. + if(TARGET OpenGL::GLX) target_link_libraries(openxr-gfxwrapper PUBLIC OpenGL::GLX) else() - target_link_libraries(openxr-gfxwrapper PUBLIC ${OPENGL_LIBRARIES} ${OPENGL_glx_LIBRARY}) + if(${OPENGL_glx_LIBRARY}) + target_link_libraries(openxr-gfxwrapper PUBLIC ${OPENGL_glx_LIBRARY}) + endif() + target_link_libraries(openxr-gfxwrapper PUBLIC ${OPENGL_LIBRARIES}) endif() endif() elseif(PRESENTATION_BACKEND MATCHES "xcb") @@ -143,10 +146,13 @@ endif() if(TARGET openxr-gfxwrapper AND NOT (PRESENTATION_BACKEND MATCHES "wayland")) - if(TARGET OpenGL::OpenGL AND TARGET OpenGL::GLX) + if(TARGET OpenGL::GLX) # OpenGL::OpenGL already linked, we just need to add GLX. target_link_libraries(openxr-gfxwrapper PUBLIC OpenGL::GLX) else() - target_link_libraries(openxr-gfxwrapper PUBLIC ${OPENGL_LIBRARIES} ${OPENGL_glx_LIBRARY}) + if(${OPENGL_glx_LIBRARY}) + target_link_libraries(openxr-gfxwrapper PUBLIC ${OPENGL_glx_LIBRARY}) + endif() + target_link_libraries(openxr-gfxwrapper PUBLIC ${OPENGL_LIBRARIES}) endif() endif() 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..e4c8fe02 100644 --- a/src/conformance/CMakeLists.txt +++ b/src/conformance/CMakeLists.txt @@ -13,9 +13,20 @@ # 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. -# -# 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) 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_cli/CMakeLists.txt b/src/conformance/conformance_cli/CMakeLists.txt index f9cfde4c..90dc566f 100644 --- a/src/conformance/conformance_cli/CMakeLists.txt +++ b/src/conformance/conformance_cli/CMakeLists.txt @@ -15,12 +15,20 @@ # limitations under the License. # -file(GLOB LOCAL_HEADERS "*.h") -file(GLOB LOCAL_SOURCE "*.cpp") +file( + GLOB + LOCAL_HEADERS + CONFIGURE_DEPENDS + "*.h" +) +file( + GLOB + LOCAL_SOURCE + CONFIGURE_DEPENDS + "*.cpp" +) -add_executable(conformance_cli - ${LOCAL_SOURCE} - ${LOCAL_HEADERS}) +add_executable(conformance_cli ${LOCAL_SOURCE} ${LOCAL_HEADERS}) source_group("Headers" FILES ${LOCAL_HEADERS}) @@ -28,42 +36,41 @@ add_dependencies(conformance_cli conformance_test) target_link_libraries(conformance_cli conformance_test) -target_include_directories(conformance_cli - PRIVATE ${CMAKE_CURRENT_LIST_DIR}/../conformance_test - PRIVATE ${CMAKE_CURRENT_LIST_DIR}/../framework - ${CMAKE_CURRENT_LIST_DIR}/../../common - PRIVATE ${PROJECT_SOURCE_DIR}/src/external +target_include_directories( + conformance_cli + PRIVATE + ../conformance_test + ../framework + ${PROJECT_SOURCE_DIR}/src/common + ${PROJECT_SOURCE_DIR}/src/external ) -target_include_directories(conformance_cli - PRIVATE - ${PROJECT_SOURCE_DIR}/src - ${PROJECT_SOURCE_DIR}/src/common - ${PROJECT_SOURCE_DIR}/include - ${PROJECT_BINARY_DIR}/include - ${PROJECT_SOURCE_DIR}/external/include +target_include_directories( + conformance_cli + PRIVATE ${PROJECT_SOURCE_DIR}/src ${PROJECT_SOURCE_DIR}/src/common + ${PROJECT_SOURCE_DIR}/external/include ) -if(Vulkan_FOUND) - target_include_directories(conformance_cli - PRIVATE ${Vulkan_INCLUDE_DIRS} - ) +if(XR_USE_GRAPHICS_API_VULKAN) + target_include_directories(conformance_cli PRIVATE ${Vulkan_INCLUDE_DIRS}) endif() # Be able to find .so when installed -set_property(TARGET conformance_cli - PROPERTY INSTALL_RPATH $ORIGIN) - -set_target_properties(conformance_cli PROPERTIES FOLDER ${CONFORMANCE_TESTS_FOLDER}) +set_property(TARGET conformance_cli PROPERTY INSTALL_RPATH $ORIGIN) -install( - TARGETS conformance_cli - RUNTIME DESTINATION conformance +set_target_properties( + conformance_cli PROPERTIES FOLDER ${CONFORMANCE_TESTS_FOLDER} ) +install(TARGETS conformance_cli RUNTIME DESTINATION conformance) + if(MSVC) - install(FILES $ DESTINATION conformance OPTIONAL) + install( + FILES $ + DESTINATION conformance + OPTIONAL + ) endif() # Install the README -install(FILES ${CMAKE_CURRENT_SOURCE_DIR}/../conformance_test/readme.md DESTINATION conformance) +install(FILES ../conformance_test/readme.md DESTINATION conformance) diff --git a/src/conformance/conformance_layer/CMakeLists.txt b/src/conformance/conformance_layer/CMakeLists.txt index bf7c9b19..0f8c4a8d 100644 --- a/src/conformance/conformance_layer/CMakeLists.txt +++ b/src/conformance/conformance_layer/CMakeLists.txt @@ -13,19 +13,36 @@ # 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. -# -# Author: -# -file(GLOB LOCAL_HEADERS "*.h") -file(GLOB LOCAL_SOURCE "*.cpp") +file( + GLOB + LOCAL_HEADERS + CONFIGURE_DEPENDS + "*.h" +) +file( + GLOB + LOCAL_SOURCE + CONFIGURE_DEPENDS + "*.cpp" +) + +run_xr_xml_generate( + conformance_layer_generator.py gen_dispatch.cpp + ${PROJECT_SOURCE_DIR}/src/scripts/template_gen_dispatch.cpp +) +run_xr_xml_generate( + conformance_layer_generator.py gen_dispatch.h + ${PROJECT_SOURCE_DIR}/src/scripts/template_gen_dispatch.h +) -run_xr_xml_generate(conformance_layer_generator.py gen_dispatch.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/../../scripts/template_gen_dispatch.cpp) -run_xr_xml_generate(conformance_layer_generator.py gen_dispatch.h - ${CMAKE_CURRENT_SOURCE_DIR}/../../scripts/template_gen_dispatch.h) +configure_file( + conformance_layer.json + ${CMAKE_CURRENT_BINARY_DIR}/XrApiLayer_runtime_conformance.json @ONLY +) -add_library(XrApiLayer_runtime_conformance MODULE +add_library( + XrApiLayer_runtime_conformance MODULE ${LOCAL_SOURCE} ${LOCAL_HEADERS} XrApiLayer_conformance_layer.def @@ -33,36 +50,37 @@ add_library(XrApiLayer_runtime_conformance MODULE ${CMAKE_CURRENT_BINARY_DIR}/gen_dispatch.cpp ${CMAKE_CURRENT_BINARY_DIR}/gen_dispatch.h ) - -configure_file(${CMAKE_CURRENT_SOURCE_DIR}/conformance_layer.json - ${CMAKE_CURRENT_BINARY_DIR}/XrApiLayer_runtime_conformance.json - @ONLY) +target_link_libraries( + XrApiLayer_runtime_conformance PRIVATE Threads::Threads OpenXR::headers +) source_group("Headers" FILES ${LOCAL_HEADERS}) -add_dependencies(XrApiLayer_runtime_conformance - generate_openxr_header - xr_common_generated_files +add_dependencies(XrApiLayer_runtime_conformance xr_common_generated_files) + +target_include_directories( + XrApiLayer_runtime_conformance + PRIVATE + ${CMAKE_CURRENT_BINARY_DIR} + ${CMAKE_CURRENT_SOURCE_DIR} + ${PROJECT_SOURCE_DIR}/src/common + ${PROJECT_SOURCE_DIR}/src + #for common_config.h: + ${PROJECT_BINARY_DIR}/src + #for xr_generated_dispatch_table_all.h: + ${CMAKE_CURRENT_BINARY_DIR}/../../api_layers ) -target_include_directories(XrApiLayer_runtime_conformance - PRIVATE ${CMAKE_CURRENT_BINARY_DIR} - PRIVATE ${CMAKE_CURRENT_SOURCE_DIR} - PRIVATE ${PROJECT_SOURCE_DIR}/src/common - -#for openxr.h: - PRIVATE ${PROJECT_BINARY_DIR}/include -#for common_config.h: - PRIVATE ${PROJECT_BINARY_DIR}/src -#for xr_generated_dispatch_table_all.h: - PRIVATE ${CMAKE_CURRENT_BINARY_DIR}/../../api_layers -) - -target_link_libraries(XrApiLayer_runtime_conformance Threads::Threads) - if(MSVC) # Right now can't build this on MinGW because of directxcolors, directxmath, etc. - target_link_libraries(XrApiLayer_runtime_conformance d3d11 d3d12 d3dcompiler dxgi) + target_link_libraries( + XrApiLayer_runtime_conformance + PRIVATE + d3d11 + d3d12 + d3dcompiler + dxgi + ) endif() # Flag generated files @@ -72,36 +90,57 @@ set_source_files_properties( # ${PROJECT_BINARY_DIR}/src/conformance/conformance_layer/gen_dispatch.h PROPERTIES GENERATED TRUE ) -if(Vulkan_FOUND) - target_include_directories(XrApiLayer_runtime_conformance - PRIVATE ${Vulkan_INCLUDE_DIRS} +if(XR_USE_GRAPHICS_API_VULKAN) + target_include_directories( + XrApiLayer_runtime_conformance PRIVATE ${Vulkan_INCLUDE_DIRS} ) endif() if(CMAKE_SYSTEM_NAME STREQUAL "Linux") target_compile_options(XrApiLayer_runtime_conformance PRIVATE -Wall) - target_link_libraries(XrApiLayer_runtime_conformance m) + target_link_libraries(XrApiLayer_runtime_conformance PRIVATE m) endif() if(ANDROID) - target_link_libraries(XrApiLayer_runtime_conformance ${ANDROID_LOG_LIBRARY}) + target_link_libraries( + XrApiLayer_runtime_conformance PRIVATE ${ANDROID_LOG_LIBRARY} + ) endif() if(APPLE) - set_target_properties(XrApiLayer_runtime_conformance PROPERTIES LINK_FLAGS "-Wl,-exported_symbols_list,${CMAKE_CURRENT_SOURCE_DIR}/XrApiLayer_conformance_layer.expsym") + set_target_properties( + XrApiLayer_runtime_conformance + PROPERTIES + LINK_FLAGS + "-Wl,-exported_symbols_list,${CMAKE_CURRENT_SOURCE_DIR}/XrApiLayer_conformance_layer.expsym" + ) elseif(NOT WIN32) - set_target_properties(XrApiLayer_runtime_conformance PROPERTIES LINK_FLAGS "-Wl,-Bsymbolic,--exclude-libs,ALL") + set_target_properties( + XrApiLayer_runtime_conformance + PROPERTIES LINK_FLAGS "-Wl,-Bsymbolic,--exclude-libs,ALL" + ) endif() if(BUILD_CONFORMANCE_CLI) # Copy conformance layer files to conformance_cli binary folder - add_custom_command(TARGET XrApiLayer_runtime_conformance POST_BUILD - COMMAND ${CMAKE_COMMAND} -E copy $ $ - COMMAND ${CMAKE_COMMAND} -E copy ${CMAKE_CURRENT_BINARY_DIR}/XrApiLayer_runtime_conformance.json $ + add_custom_command( + TARGET XrApiLayer_runtime_conformance + POST_BUILD + COMMAND + ${CMAKE_COMMAND} -E copy + $ + $ + COMMAND + ${CMAKE_COMMAND} -E copy + ${CMAKE_CURRENT_BINARY_DIR}/XrApiLayer_runtime_conformance.json + $ ) endif() -set_target_properties(XrApiLayer_runtime_conformance PROPERTIES FOLDER ${CONFORMANCE_TESTS_FOLDER}) +set_target_properties( + XrApiLayer_runtime_conformance PROPERTIES FOLDER + ${CONFORMANCE_TESTS_FOLDER} +) install( FILES ${CMAKE_CURRENT_BINARY_DIR}/XrApiLayer_runtime_conformance.json @@ -116,5 +155,9 @@ install( ) if(MSVC) - install(FILES $ DESTINATION conformance OPTIONAL) + install( + FILES $ + DESTINATION conformance + OPTIONAL + ) endif() 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..a230b48e 100644 --- a/src/conformance/conformance_test/CMakeLists.txt +++ b/src/conformance/conformance_test/CMakeLists.txt @@ -15,63 +15,99 @@ # limitations under the License. # -file(GLOB LOCAL_HEADERS "*.h") -file(GLOB LOCAL_SOURCE "*.cpp") -file(GLOB ASSETS "composition_examples/*.png" - "SourceCodePro-Regular.otf") - -# For including compiled shaders -include_directories(${CMAKE_CURRENT_BINARY_DIR}) +file( + GLOB + LOCAL_HEADERS + CONFIGURE_DEPENDS + "*.h" +) +file( + GLOB + LOCAL_SOURCE + CONFIGURE_DEPENDS + "*.cpp" +) +file( + GLOB + ASSETS + "composition_examples/*.png" + "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() -add_library(conformance_test SHARED +add_library( + conformance_test SHARED ${LOCAL_HEADERS} ${LOCAL_SOURCE} ${VULKAN_SHADERS} ${PROJECT_SOURCE_DIR}/src/common/platform_utils.hpp ) -if(ANDROID) - target_sources(conformance_test PRIVATE $) -endif() - -target_link_libraries(conformance_test PRIVATE conformance_utilities conformance_framework) -add_dependencies(conformance_test - generate_openxr_header - XrApiLayer_runtime_conformance +source_group("Header Files" FILES ${LOCAL_HEADERS}) +source_group("Source Files" FILES ${LOCAL_SOURCE}) +target_link_libraries( + conformance_test + PRIVATE conformance_utilities conformance_framework + PUBLIC OpenXR::headers ) + +add_dependencies(conformance_test XrApiLayer_runtime_conformance) openxr_add_filesystem_utils(conformance_test) -target_include_directories(conformance_test +target_include_directories( + conformance_test PRIVATE - ${CMAKE_CURRENT_LIST_DIR}/../framework - ${PROJECT_SOURCE_DIR}/src/common - # for openxr.h: - ${PROJECT_BINARY_DIR}/include - - # for common_config.h: - ${PROJECT_BINARY_DIR}/src + # For including compiled shaders + ${CMAKE_CURRENT_BINARY_DIR} + ../framework + ${PROJECT_SOURCE_DIR}/src/common + # for common_config.h: + ${PROJECT_BINARY_DIR}/src ) -target_include_directories(conformance_test - SYSTEM PRIVATE - # for helper headers - ${PROJECT_SOURCE_DIR}/external/include - - ${PROJECT_SOURCE_DIR}/src/external - # Earcut algorithm for simple polygon triangulation - ${PROJECT_SOURCE_DIR}/src/external/earcut/include +target_include_directories( + conformance_test SYSTEM + PRIVATE + # for helper headers + ${PROJECT_SOURCE_DIR}/external/include + ${PROJECT_SOURCE_DIR}/src/external + # Earcut algorithm for simple polygon triangulation + ${PROJECT_SOURCE_DIR}/src/external/earcut/include ) - -source_group("Header Files" FILES ${LOCAL_HEADERS}) -source_group("Source Files" FILES ${LOCAL_SOURCE}) - -target_link_libraries(conformance_test PRIVATE conformance_framework) - +if(ANDROID) + target_sources( + conformance_test PRIVATE $ + ) +endif() if(WIN32) target_compile_definitions(conformance_test PRIVATE _CRT_SECURE_NO_WARNINGS) if(MSVC) - target_compile_options(conformance_test PRIVATE /Zc:wchar_t /Zc:forScope /W4 /wd4996) - if(NOT CMAKE_CXX_COMPILER_ID STREQUAL "Clang") + target_compile_options( + conformance_test + PRIVATE + /Zc:wchar_t + /Zc:forScope + /W4 + /wd4996 + ) + if(NOT + CMAKE_CXX_COMPILER_ID + STREQUAL + "Clang" + ) # If actually msvc and not clang-cl target_compile_options(conformance_test PRIVATE /WX) endif() @@ -83,7 +119,9 @@ if(CMAKE_SYSTEM_NAME STREQUAL "Linux") target_link_libraries(conformance_test PRIVATE m) endif() if(CMAKE_CXX_COMPILER_ID MATCHES "Clang|GCC") - target_compile_options(conformance_test PRIVATE -Wno-missing-field-initializers) + target_compile_options( + conformance_test PRIVATE -Wno-missing-field-initializers + ) endif() if(ANDROID) target_sources( @@ -91,39 +129,42 @@ if(ANDROID) PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}/../platform_specific/android_main.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/../platform_specific/android_intent_extras.cpp" - "${CMAKE_CURRENT_SOURCE_DIR}/../../external/jnipp/jnipp.cpp" + "${PROJECT_SOURCE_DIR}/src/external/jnipp/jnipp.cpp" ) target_include_directories( - conformance_test - PRIVATE - "${CMAKE_CURRENT_SOURCE_DIR}" - "${CMAKE_CURRENT_SOURCE_DIR}/../../external") + conformance_test PRIVATE . "${PROJECT_SOURCE_DIR}/src/external" + ) target_link_libraries( - conformance_test - PRIVATE - ${ANDROID_LIBRARY} - ${ANDROID_LOG_LIBRARY} + conformance_test PRIVATE ${ANDROID_LIBRARY} ${ANDROID_LOG_LIBRARY} ) endif() - if(BUILD_CONFORMANCE_CLI) # Copy conformance_test assets files to the conformance_cli binary folder foreach(ASSET ${ASSETS}) - add_custom_command(TARGET conformance_test PRE_BUILD - COMMAND ${CMAKE_COMMAND} -E copy ${ASSET} $ + add_custom_command( + TARGET conformance_test + PRE_BUILD + COMMAND + ${CMAKE_COMMAND} -E copy ${ASSET} + $ ) - install(FILES ${ASSET} - DESTINATION conformance) + install(FILES ${ASSET} DESTINATION conformance) endforeach() # Copy conformance test binary to conformance_cli binary folder - add_custom_command(TARGET conformance_test POST_BUILD - COMMAND ${CMAKE_COMMAND} -E copy $ $ + add_custom_command( + TARGET conformance_test + POST_BUILD + COMMAND + ${CMAKE_COMMAND} -E copy $ + $ ) endif() -set_target_properties(conformance_test PROPERTIES FOLDER ${CONFORMANCE_TESTS_FOLDER}) +set_target_properties( + conformance_test PROPERTIES FOLDER ${CONFORMANCE_TESTS_FOLDER} +) install( TARGETS conformance_test @@ -132,5 +173,9 @@ install( RUNTIME DESTINATION conformance ) if(MSVC) - install(FILES $ DESTINATION conformance OPTIONAL) + install( + FILES $ + DESTINATION conformance + OPTIONAL + ) endif() diff --git a/src/conformance/conformance_test/conformance_test.cpp b/src/conformance/conformance_test/conformance_test.cpp index f04ea6f2..36781f99 100644 --- a/src/conformance/conformance_test/conformance_test.cpp +++ b/src/conformance/conformance_test/conformance_test.cpp @@ -14,7 +14,6 @@ // See the License for the specific language governing permissions and // limitations under the License. -#include "openxr/openxr_platform_defines.h" #define CATCH_CONFIG_NOSTDOUT #include "conformance_framework.h" @@ -24,12 +23,12 @@ #include "graphics_plugin.h" #include "platform_utils.hpp" // for OPENXR_API_LAYER_PATH_ENV_VAR #include "report.h" +#include "utilities/git_revision.h" #include "utilities/utils.h" -#include - #include "catch_reporter_cts.h" +#include #include #include #include @@ -37,7 +36,8 @@ #include #include -#include "xr_dependencies.h" +#include "common/xr_dependencies.h" +#include #include #include @@ -148,6 +148,35 @@ namespace } } + // Ensure this is a git checkout and that it is clean (all changes committed) + // and add a warning that will show in the output + TEST_CASE("SourceCodeRevision", "") + { + + CAPTURE(kGitRevisionSucceeded); + CAPTURE(kGitRevisionString); + CAPTURE(kGitRevisionLocalChanges); + CAPTURE(kGitRevisionExactTag); + if (kGitRevisionSucceeded) { + if (kGitRevisionLocalChanges) { + WARN( + "Uncommitted changes found in the source code working tree. " + "Recommend committing changes to a local branch. " + "Conformance packages must include diff(s) from the latest approved CTS release tag."); + } + if (!kGitRevisionExactTag) { + WARN( + "Source does not match an exact git tag: " + "Conformance packages must include a diff(s) from the latest approved CTS release tag."); + } + } + else { + WARN( + "Build system could not use git to describe the source tree with respect to a release tag. " + "Please be sure to build the conformance package in a full git clone with tags."); + } + } + // Ensure conformance is configured correctly. TEST_CASE("ValidateEnvironment") { @@ -194,12 +223,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..1884334d 100644 --- a/src/conformance/conformance_test/readme.md +++ b/src/conformance/conformance_test/readme.md @@ -24,6 +24,22 @@ does not support a command-line interface, a host application must be built for the device which the OpenXR runtime will run on. The conformance host must invoke `conformance_test`, the test suite shared library. +When you plan to submit for conformance, you must observe a few considerations +to ensure that the build system has accurate source code revision information +available to embed in the test suite and output reports. You must build from a +git repo (forked from either the internal Gitlab or public GitHub) with tags +available (a full clone, not shallow). You also must either perform a clean +build, from an empty binary tree, or at least run `cmake` immediately before +building to pick up current source tree status. If your "porting" process (as +described by the conformance process documents) involves replacing the build +system, you must populate the revision data constants in +`utilities/git_revision.cpp.in` accurately. The contents of that file affect all +"ctsxml" format outputs, as well as an automated "SourceCodeRevision" test that +warns if it cannot identify an approved release. (It only checks for the +presence of an appropriately-named tag: it does not check for a signature on the +tag, so if you have added tags to your repo it may not warn if you are not on a +release.) + Running CTS ----------- @@ -164,6 +180,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_FrameSubmission.cpp b/src/conformance/conformance_test/test_FrameSubmission.cpp index 78ca7f23..1ae93f3e 100644 --- a/src/conformance/conformance_test/test_FrameSubmission.cpp +++ b/src/conformance/conformance_test/test_FrameSubmission.cpp @@ -21,6 +21,8 @@ #include "utilities/throw_helpers.h" #include +#include +#include #include #include @@ -170,8 +172,7 @@ namespace Conformance INFO("Environment Blend Modes"); const auto supportedBlendModes = session.SupportedEnvironmentBlendModes(); - CHECK(std::find(supportedBlendModes.begin(), supportedBlendModes.end(), XR_ENVIRONMENT_BLEND_MODE_MAX_ENUM) == - supportedBlendModes.end()); + CHECK_THAT(supportedBlendModes, !Catch::Matchers::Contains(XR_ENVIRONMENT_BLEND_MODE_MAX_ENUM)); for (XrEnvironmentBlendMode blendMode : SupportedBlendModes) { CAPTURE(blendMode); 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_hand_tracking.cpp b/src/conformance/conformance_test/test_XR_EXT_hand_tracking.cpp index 7e6d09f5..282466cf 100644 --- a/src/conformance/conformance_test/test_XR_EXT_hand_tracking.cpp +++ b/src/conformance/conformance_test/test_XR_EXT_hand_tracking.cpp @@ -128,6 +128,46 @@ namespace Conformance REQUIRE(XR_SUCCESS == xrCreateHandTrackerEXT(session, &createInfo, &handTracker[i])); } + SECTION("Query Zero XrTime joint locations") + { + XrSpace localSpace = XR_NULL_HANDLE; + + XrReferenceSpaceCreateInfo localSpaceCreateInfo{XR_TYPE_REFERENCE_SPACE_CREATE_INFO}; + localSpaceCreateInfo.poseInReferenceSpace = XrPosefCPP(); + localSpaceCreateInfo.referenceSpaceType = XR_REFERENCE_SPACE_TYPE_LOCAL; + REQUIRE_RESULT(xrCreateReferenceSpace(session, &localSpaceCreateInfo, &localSpace), XR_SUCCESS); + + // Wait until the runtime is ready for us to begin a session + FrameIterator frameIterator(&session); + frameIterator.RunToSessionState(XR_SESSION_STATE_FOCUSED); + + for (auto hand : {LEFT_HAND, RIGHT_HAND}) { + std::array, HAND_COUNT> jointLocations; + for (size_t i = 0; i < jointLocations[hand].size(); ++i) { + // locationFlags should not change after invoking `xrLocateHandJointsEXT` with invalid parameters. + jointLocations[hand][i].locationFlags = 0; + } + + XrHandJointLocationsEXT locations{XR_TYPE_HAND_JOINT_LOCATIONS_EXT, nullptr}; + // isActive should not change after invoking `xrLocateHandJointsEXT` with invalid parameters. + locations.isActive = XR_FALSE; + locations.jointCount = XR_HAND_JOINT_COUNT_EXT; + locations.jointLocations = jointLocations[hand].data(); + + XrHandJointsLocateInfoEXT locateInfo{XR_TYPE_HAND_JOINTS_LOCATE_INFO_EXT, nullptr}; + locateInfo.baseSpace = localSpace; + locateInfo.time = 0; // Zero XrTimes should never be valid or return valid results. + REQUIRE(XR_ERROR_TIME_INVALID == xrLocateHandJointsEXT(handTracker[hand], &locateInfo, &locations)); + REQUIRE(locations.isActive == XR_FALSE); + + // https://registry.khronos.org/OpenXR/specs/1.0/html/xrspec.html#_locate_hand_joints + for (size_t i = 0; i < jointLocations[hand].size(); ++i) { + REQUIRE((jointLocations[hand][i].locationFlags & XR_SPACE_LOCATION_POSITION_VALID_BIT) == 0); + REQUIRE((jointLocations[hand][i].locationFlags & XR_SPACE_LOCATION_ORIENTATION_VALID_BIT) == 0); + } + } + } + SECTION("Query joint locations") { XrSpace localSpace = XR_NULL_HANDLE; 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/ctsxml.rnc b/src/conformance/ctsxml.rnc index 401abb27..5bcb827f 100644 --- a/src/conformance/ctsxml.rnc +++ b/src/conformance/ctsxml.rnc @@ -123,6 +123,7 @@ TestOptions = TestEnvironment = element ctsTestEnvironment { + RevisionInfo, InstanceProperties, TestOptions, AvailableApiLayers, @@ -130,6 +131,16 @@ TestEnvironment = element graphicsPluginDescription { text }? } +RevisionInfo = + element revisionData { + attribute revision { xsd:string }, + attribute localChanges { xsd:boolean } + } + | element revisionData { + attribute gitDescribeSucceeded { "false" }, + attribute revision { xsd:string } + } + # These are the layers and extensions enabled by the tests, as well as those specified in the options. ActiveLayersAndExtensions = element activeAPILayersAndExtensions { diff --git a/src/conformance/framework/CMakeLists.txt b/src/conformance/framework/CMakeLists.txt index c2311e77..d75b294b 100644 --- a/src/conformance/framework/CMakeLists.txt +++ b/src/conformance/framework/CMakeLists.txt @@ -2,95 +2,180 @@ # # SPDX-License-Identifier: Apache-2.0 -set(VULKAN_SHADERS ${CMAKE_CURRENT_SOURCE_DIR}/vulkan_shaders/frag.glsl - ${CMAKE_CURRENT_SOURCE_DIR}/vulkan_shaders/vert.glsl) +# 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" +) run_xr_xml_generate( - conformance_generator.py function_info.cpp - ${PROJECT_SOURCE_DIR}/src/scripts/template_function_info.cpp) + conformance_generator.py function_info.cpp + ${PROJECT_SOURCE_DIR}/src/scripts/template_function_info.cpp +) add_library( - conformance_framework STATIC - action_utils.cpp - catch_reporter_cts.cpp - composition_utils.cpp - conformance_framework.cpp - conformance_utils.cpp - environment.cpp - graphics_plugin_d3d11.cpp - graphics_plugin_d3d12.cpp - graphics_plugin_factory.cpp - graphics_plugin_opengl.cpp - graphics_plugin_opengles.cpp - graphics_plugin_vulkan.cpp - input_testinputdevice.cpp - mesh_projection_layer.cpp - platform_plugin_android.cpp - platform_plugin_posix.cpp - platform_plugin_win32.cpp - report.cpp - RGBAImage.cpp - swapchain_image_data.cpp - xml_test_environment.cpp - ${VULKAN_SHADERS} - ${CMAKE_CURRENT_BINARY_DIR}/function_info.cpp) + conformance_framework STATIC + action_utils.cpp + catch_reporter_cts.cpp + 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 + platform_plugin_posix.cpp + platform_plugin_win32.cpp + report.cpp + RGBAImage.cpp + swapchain_image_data.cpp + xml_test_environment.cpp + ${VULKAN_SHADERS} + "${CMAKE_CURRENT_BINARY_DIR}/function_info.cpp" +) target_link_libraries( - conformance_framework PUBLIC conformance_utilities OpenXR::openxr_loader - Threads::Threads Catch2) + conformance_framework + PUBLIC + conformance_utilities + OpenXR::openxr_loader + 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 - PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/.. - # Strong types for integers, etc. - ${PROJECT_SOURCE_DIR}/src/external/type-lite/include - # Backport of std::span functionality to pre-C++17 - ${PROJECT_SOURCE_DIR}/src/external/span-lite/include - # for xr_linear.h: - ${PROJECT_SOURCE_DIR}/src/common - # for openxr.h: - ${PROJECT_BINARY_DIR}/include - PRIVATE ${CMAKE_CURRENT_SOURCE_DIR} - # for compiled shaders - ${CMAKE_CURRENT_BINARY_DIR} - # for STB utilities, etc - ${PROJECT_SOURCE_DIR}/src/external) + conformance_framework + PUBLIC + .. + # Strong types for integers, etc. + ${PROJECT_SOURCE_DIR}/src/external/type-lite/include + # Backport of std::span functionality to pre-C++17 + ${PROJECT_SOURCE_DIR}/src/external/span-lite/include + # for xr_linear.h: + ${PROJECT_SOURCE_DIR}/src/common + PRIVATE + # so generated source files can find headers in here + . + # for compiled shaders + ${CMAKE_CURRENT_BINARY_DIR} + # for STB utilities, etc + ${PROJECT_SOURCE_DIR}/src/external +) + +set_target_properties( + conformance_framework PROPERTIES FOLDER ${CONFORMANCE_TESTS_FOLDER} +) compile_glsl(run_conformance_test_glsl_compiles ${VULKAN_SHADERS}) -add_dependencies(conformance_framework generate_openxr_header - run_conformance_test_glsl_compiles) +add_dependencies(conformance_framework run_conformance_test_glsl_compiles) source_group("Vulkan Shaders" FILES ${VULKAN_SHADERS}) -if(Vulkan_FOUND) - target_include_directories(conformance_framework - PUBLIC ${Vulkan_INCLUDE_DIRS}) - target_link_libraries(conformance_framework PUBLIC ${Vulkan_LIBRARY}) +if(XR_USE_GRAPHICS_API_VULKAN) + target_include_directories( + conformance_framework PUBLIC ${Vulkan_INCLUDE_DIRS} + ) + target_link_libraries(conformance_framework PUBLIC ${Vulkan_LIBRARY}) endif() if(TARGET openxr-gfxwrapper) - target_link_libraries(conformance_framework PUBLIC openxr-gfxwrapper) + target_link_libraries(conformance_framework PUBLIC openxr-gfxwrapper) endif() if(CMAKE_CXX_COMPILER_ID MATCHES "Clang|GCC") - target_compile_options(conformance_framework PUBLIC -Wno-missing-field-initializers) + target_compile_options( + conformance_framework PUBLIC -Wno-missing-field-initializers + ) endif() if(WIN32) - target_compile_definitions(conformance_framework - PUBLIC _CRT_SECURE_NO_WARNINGS) - if(MSVC) - target_compile_options(conformance_framework - PUBLIC /Zc:wchar_t /Zc:forScope /W4 /wd4996) - if(NOT CMAKE_CXX_COMPILER_ID STREQUAL "Clang") - # If actually msvc and not clang-cl - target_compile_options(conformance_framework PRIVATE /WX) - endif() + target_compile_definitions( + conformance_framework PUBLIC _CRT_SECURE_NO_WARNINGS + ) + if(MSVC) + target_compile_options( + conformance_framework + PUBLIC + /Zc:wchar_t + /Zc:forScope + /W4 + /wd4996 + ) + if(NOT + CMAKE_CXX_COMPILER_ID + STREQUAL + "Clang" + ) + # If actually msvc and not clang-cl + target_compile_options(conformance_framework PRIVATE /WX) + endif() - # Right now can't build this on MinGW because of directxcolors, directxmath, etc. - target_link_libraries(conformance_framework PUBLIC d3d11 d3d12 d3dcompiler - dxgi) - endif() + # Right now can't build this on MinGW because of directxcolors, directxmath, etc. + target_link_libraries( + conformance_framework + PUBLIC + d3d11 + d3d12 + d3dcompiler + dxgi + ) + endif() endif() 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..ab81f486 --- /dev/null +++ b/src/conformance/framework/gltf/CMakeLists.txt @@ -0,0 +1,17 @@ +# 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 .) + +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..c0da9296 --- /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 ((size_t)(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..a3e9bacd --- /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_D3D11) && !defined(MISSING_DIRECTX_COLORS) + +#include "graphics_plugin_d3d11_gltf.h" + +#include "conformance_framework.h" +#include "report.h" + +#include "pbr/D3D11/D3D11Primitive.h" +#include "pbr/D3D11/D3D11Resources.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..9a722be6 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. @@ -762,7 +744,7 @@ namespace Conformance XRC_CHECK_THROW_HRCMD(d3d12Device->CreateCommandList(0, D3D12_COMMAND_LIST_TYPE_DIRECT, commandAllocator.Get(), nullptr, __uuidof(ID3D12GraphicsCommandList), reinterpret_cast(cmdList.ReleaseAndGetAddressOf()))); - XRC_CHECK_THROW_HRCMD(commandAllocator->SetName(L"CTS ValidateSwapchainImageState cmd list")); + XRC_CHECK_THROW_HRCMD(cmdList->SetName(L"CTS ValidateSwapchainImageState cmd list")); const XrSwapchainImageD3D12KHR& image = swapchainImageVector[index]; const bool isColorFormat = GetDxgiSwapchainTestMap()[imageFormat].colorFormat; @@ -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..9217d352 --- /dev/null +++ b/src/conformance/framework/pbr/CMakeLists.txt @@ -0,0 +1,215 @@ +# 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_BINARY_DIR}" + "${PROJECT_SOURCE_DIR}/src/common" + "${PROJECT_SOURCE_DIR}/src/conformance" + # 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..9cd42892 --- /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 (after an optional parent 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.cpp b/src/conformance/framework/xml_test_environment.cpp index d7064d98..ff48f858 100644 --- a/src/conformance/framework/xml_test_environment.cpp +++ b/src/conformance/framework/xml_test_environment.cpp @@ -7,6 +7,7 @@ #include "conformance_framework.h" #include "common/hex_and_handles.h" +#include "utilities/git_revision.h" #include @@ -170,6 +171,18 @@ namespace Conformance { auto e = xml.scopedElement(CTS_XML_NS_PREFIX_QUALIFIER "ctsTestEnvironment"); + // Report the git info + if (kGitRevisionSucceeded) { + xml.scopedElement(CTS_XML_NS_PREFIX_QUALIFIER "revisionData") + .writeAttribute("revision", kGitRevisionString) + .writeAttribute("localChanges", kGitRevisionLocalChanges); + } + else { + xml.scopedElement(CTS_XML_NS_PREFIX_QUALIFIER "revisionData") + .writeAttribute("gitDescribeSucceeded", false) + .writeAttribute("revision", kGitRevisionString); + } + // Report the runtime name and info. WriteInstanceProperties(xml, globalData.GetInstanceProperties()); 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..f8b4e075 100644 --- a/src/conformance/utilities/CMakeLists.txt +++ b/src/conformance/utilities/CMakeLists.txt @@ -2,34 +2,94 @@ # # SPDX-License-Identifier: Apache-2.0 +include(GetGitRevisionDescription) + +# Pattern restricts naming to CTS tags in gitlab and github +git_describe_working_tree(CTS_GIT_REV --match "*cts-*") +git_get_exact_tag(CTS_EXACT_TAG_REV --match "*cts-*") +git_local_changes(CTS_GIT_DIRTY_STRING) + +if(CTS_GIT_REV) + set(CTS_GIT_SUCCEEDED TRUE) + if(CTS_EXACT_TAG_REV) + set(CTS_EXACT_TAG TRUE) + else() + message( + STATUS + "Not an exact release tag: conformance package must include diff(s) from the latest approved CTS release tag." + ) + set(CTS_EXACT_TAG FALSE) + endif() +else() + message( + STATUS + "Could not describe git revision relative to a CTS release tag - do not use binaries for CTS submission" + ) + # Try to get something at least. + get_git_head_revision(CTS_REFSPEC CTS_GIT_HASH) + set(CTS_GIT_REV "refspec:${CTS_REFSPEC} hash:${CTS_GIT_HASH}") + set(CTS_GIT_SUCCEEDED FALSE) +endif() +if(CTS_GIT_DIRTY_STRING STREQUAL "DIRTY") + set(CTS_GIT_DIRTY TRUE) + message(STATUS "Git revision: ${CTS_GIT_REV} - local tree has changes!") +else() + set(CTS_GIT_DIRTY FALSE) + message(STATUS "Git revision: ${CTS_GIT_REV} - local tree clean") +endif() + +configure_file( + git_revision.cpp.in "${CMAKE_CURRENT_BINARY_DIR}/git_revision.cpp" +) + add_library( - conformance_utilities STATIC - bitmask_generator.cpp - bitmask_to_string.cpp - d3d_common.cpp - event_reader.cpp - Geometry.cpp - stringification.cpp - swapchain_format_data.cpp - types_and_constants.cpp - utils.cpp) + conformance_utilities STATIC + 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 + "${CMAKE_CURRENT_BINARY_DIR}/git_revision.cpp" +) target_include_directories( - conformance_utilities - PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/.. - # Backport of std::span functionality to pre-C++17 - ${PROJECT_SOURCE_DIR}/src/external/span-lite/include - # for openxr.h: - ${PROJECT_BINARY_DIR}/include) + conformance_utilities + PUBLIC + .. + # 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 common/ + ${PROJECT_SOURCE_DIR}/src +) +target_link_libraries(conformance_utilities PUBLIC OpenXR::headers) -add_dependencies(conformance_utilities generate_openxr_header) +if(TARGET openxr-gfxwrapper) + target_link_libraries(conformance_utilities PRIVATE openxr-gfxwrapper) +endif() -if(GLSLANG_VALIDATOR AND NOT GLSLC_COMMAND) - target_compile_definitions(conformance_utilities PUBLIC USE_GLSLANGVALIDATOR) +if(GLSLANG_VALIDATOR AND NOT GLSL_COMPILER) + target_compile_definitions( + conformance_utilities PUBLIC USE_GLSLANGVALIDATOR + ) endif() -if(Vulkan_FOUND) - target_include_directories(conformance_utilities - PUBLIC ${Vulkan_INCLUDE_DIRS}) - target_link_libraries(conformance_utilities PUBLIC ${Vulkan_LIBRARY}) +if(XR_USE_GRAPHICS_API_VULKAN) + target_include_directories( + conformance_utilities PUBLIC ${Vulkan_INCLUDE_DIRS} + ) + target_link_libraries(conformance_utilities PUBLIC ${Vulkan_LIBRARY}) endif() + +set_target_properties( + conformance_utilities PROPERTIES FOLDER ${CONFORMANCE_TESTS_FOLDER} +) 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/git_revision.cpp.in b/src/conformance/utilities/git_revision.cpp.in new file mode 100644 index 00000000..6cd51fa6 --- /dev/null +++ b/src/conformance/utilities/git_revision.cpp.in @@ -0,0 +1,36 @@ +// Copyright (c) 2023, The Khronos Group Inc. +// +// SPDX-License-Identifier: Apache-2.0 + +#include "utilities/git_revision.h" + +namespace Conformance +{ + const char* kGitRevisionString = "@CTS_GIT_REV@"; + +#cmakedefine CTS_GIT_SUCCEEDED + +#ifdef CTS_GIT_SUCCEEDED + const bool kGitRevisionSucceeded = true; +#else + const bool kGitRevisionSucceeded = false; +#endif + +#cmakedefine CTS_EXACT_TAG + +#ifdef CTS_EXACT_TAG + const bool kGitRevisionExactTag = true; +#else + const bool kGitRevisionExactTag = false; +#endif + +#cmakedefine CTS_GIT_DIRTY + +#ifdef CTS_GIT_DIRTY + const bool kGitRevisionLocalChanges = true; +#else + const bool kGitRevisionLocalChanges = false; +#endif + + +} // namespace Conformance diff --git a/src/conformance/utilities/git_revision.h b/src/conformance/utilities/git_revision.h new file mode 100644 index 00000000..1d40bd11 --- /dev/null +++ b/src/conformance/utilities/git_revision.h @@ -0,0 +1,24 @@ +// Copyright (c) 2017-2023, The Khronos Group Inc. +// +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +namespace Conformance +{ + /// A string usable to describe the git revision. + /// Should be `git describe --dirty "*cts-*"` output for a proper submission. + extern const char* kGitRevisionString; + + /// Whether the build process successfully queried git for version info relative to a release tag. + /// Should be true for a proper submission. + extern const bool kGitRevisionSucceeded; + + /// Whether the revision is exactly a CTS release tag (based on the name). + extern const bool kGitRevisionExactTag; + + /// Whether the working tree had local changes ("dirty"). + /// Should be false for a proper submission + extern const bool kGitRevisionLocalChanges; + +} // 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/loader/AndroidManifest.xml b/src/loader/AndroidManifest.xml deleted file mode 100644 index 0c151c63..00000000 --- a/src/loader/AndroidManifest.xml +++ /dev/null @@ -1,24 +0,0 @@ - - - - - - - - - - - - - - - - - - - - diff --git a/src/loader/AndroidManifest.xml.in b/src/loader/AndroidManifest.xml.in index 291a1e2d..33a45153 100644 --- a/src/loader/AndroidManifest.xml.in +++ b/src/loader/AndroidManifest.xml.in @@ -7,6 +7,9 @@ SPDX-License-Identifier: Apache-2.0 OR MIT --> + + + diff --git a/src/loader/CMakeLists.txt b/src/loader/CMakeLists.txt index c6905f0b..89c9f2ca 100644 --- a/src/loader/CMakeLists.txt +++ b/src/loader/CMakeLists.txt @@ -15,20 +15,11 @@ # limitations under the License. # -if(WIN32) - # Pass version fields as preprocessor for .rc resource version on Windows. - add_definitions(-DXR_CURRENT_API_MAJOR_VERSION=${MAJOR} -DXR_CURRENT_API_MINOR_VERSION=${MINOR} -DXR_CURRENT_API_PATCH_VERSION=${PATCH}) - set(openxr_loader_RESOURCE_FILE ${CMAKE_CURRENT_SOURCE_DIR}/loader.rc) -endif() - if(NOT MSVC) set(CMAKE_C_VISIBILITY_PRESET hidden) set(CMAKE_CXX_VISIBILITY_PRESET hidden) endif() -# make cache variables for install destinations -include(GNUInstallDirs) - # List of all files externally generated outside of the loader that the loader # needs to build with. set(LOADER_EXTERNAL_GEN_FILES ${LOADER_GENERATED_OUTPUT}) @@ -39,19 +30,21 @@ run_xr_xml_generate(loader_source_generator.py xr_generated_loader.cpp) if(DYNAMIC_LOADER) add_definitions(-DXRAPI_DLL_EXPORT) set(LIBRARY_TYPE SHARED) - if(MSVC) - list(APPEND openxr_loader_RESOURCE_FILE ${CMAKE_CURRENT_SOURCE_DIR}/openxr-loader.def) - endif() -else() # build static lib +else() + # build static lib set(LIBRARY_TYPE STATIC) endif() -add_library(openxr_loader ${LIBRARY_TYPE} +add_library( + openxr_loader + ${LIBRARY_TYPE} android_utilities.cpp android_utilities.h api_layer_interface.cpp api_layer_interface.hpp loader_core.cpp + loader_init_data.cpp + loader_init_data.hpp loader_instance.cpp loader_instance.hpp loader_logger.cpp @@ -62,87 +55,104 @@ add_library(openxr_loader ${LIBRARY_TYPE} manifest_file.hpp runtime_interface.cpp runtime_interface.hpp + "${PROJECT_SOURCE_DIR}/src/common/hex_and_handles.h" + "${PROJECT_SOURCE_DIR}/src/common/object_info.cpp" + "${PROJECT_SOURCE_DIR}/src/common/object_info.h" + "${PROJECT_SOURCE_DIR}/src/common/platform_utils.hpp" ${GENERATED_OUTPUT} - ${PROJECT_SOURCE_DIR}/src/common/hex_and_handles.h - ${PROJECT_SOURCE_DIR}/src/common/object_info.cpp - ${PROJECT_SOURCE_DIR}/src/common/object_info.h - ${PROJECT_SOURCE_DIR}/src/common/platform_utils.hpp ${LOADER_EXTERNAL_GEN_FILES} - ${openxr_loader_RESOURCE_FILE} ) +set_target_properties(openxr_loader PROPERTIES FOLDER ${LOADER_FOLDER}) + +target_include_directories( + openxr_loader + # for OpenXR headers + PUBLIC $ + $ + PRIVATE + "${PROJECT_SOURCE_DIR}/src/common" + # for generated dispatch table, common_config.h + .. + "${CMAKE_CURRENT_BINARY_DIR}/.." + # for target-specific generated files + . + "${CMAKE_CURRENT_BINARY_DIR}" + INTERFACE $ +) +add_dependencies(openxr_loader xr_global_generated_files) + +target_link_libraries( + openxr_loader + PRIVATE ${CMAKE_DL_LIBS} + PUBLIC Threads::Threads OpenXR::headers +) +target_compile_definitions( + openxr_loader PRIVATE ${OPENXR_ALL_SUPPORTED_DEFINES} +) +openxr_add_filesystem_utils(openxr_loader) + +set_target_properties( + openxr_loader PROPERTIES DEBUG_POSTFIX "${OPENXR_DEBUG_POSTFIX}" +) +# TODO remove once we get rid of add_definitions() +if(Vulkan_FOUND) + target_include_directories(openxr_loader PRIVATE ${Vulkan_INCLUDE_DIR}) +endif() + +# Get jsoncpp externally or internally if(BUILD_WITH_SYSTEM_JSONCPP) target_link_libraries(openxr_loader PRIVATE JsonCpp::JsonCpp) else() - target_sources(openxr_loader + if(NOT BUILD_LOADER_WITH_EXCEPTION_HANDLING) + target_compile_definitions(openxr_loader PRIVATE JSON_USE_EXCEPTION=0) + endif() + + target_sources( + openxr_loader PRIVATE - ${PROJECT_SOURCE_DIR}/src/external/jsoncpp/src/lib_json/json_reader.cpp - ${PROJECT_SOURCE_DIR}/src/external/jsoncpp/src/lib_json/json_value.cpp - ${PROJECT_SOURCE_DIR}/src/external/jsoncpp/src/lib_json/json_writer.cpp + "${PROJECT_SOURCE_DIR}/src/external/jsoncpp/src/lib_json/json_reader.cpp" + "${PROJECT_SOURCE_DIR}/src/external/jsoncpp/src/lib_json/json_value.cpp" + "${PROJECT_SOURCE_DIR}/src/external/jsoncpp/src/lib_json/json_writer.cpp" ) - target_include_directories(openxr_loader - PRIVATE - ${PROJECT_SOURCE_DIR}/src/external/jsoncpp/include + target_include_directories( + openxr_loader + PRIVATE "${PROJECT_SOURCE_DIR}/src/external/jsoncpp/include" ) if(SUPPORTS_Werrorunusedparameter) # Don't error on this - triggered by jsoncpp target_compile_options(openxr_loader PRIVATE -Wno-unused-parameter) endif() endif() -if(BUILD_WITH_WAYLAND_HEADERS) - target_include_directories(openxr_loader PRIVATE ${WAYLAND_CLIENT_INCLUDE_DIRS}) -endif() - -set_target_properties(openxr_loader PROPERTIES FOLDER ${LOADER_FOLDER}) if(LOADER_EXTERNAL_GEN_DEPENDS) - set_source_files_properties(${LOADER_EXTERNAL_GEN_DEPENDS} PROPERTIES GENERATED TRUE) + set_source_files_properties( + ${LOADER_EXTERNAL_GEN_DEPENDS} PROPERTIES GENERATED TRUE + ) endif() -add_dependencies(openxr_loader generate_openxr_header xr_global_generated_files) -target_include_directories( - openxr_loader - # for OpenXR headers - PUBLIC $ - $ - PRIVATE ${PROJECT_SOURCE_DIR}/src/common +if(NOT BUILD_LOADER_WITH_EXCEPTION_HANDLING) + target_compile_definitions( + openxr_loader PRIVATE XRLOADER_DISABLE_EXCEPTION_HANDLING + ) - # for generated dispatch table, common_config.h - ${CMAKE_CURRENT_SOURCE_DIR}/.. - ${CMAKE_CURRENT_BINARY_DIR}/.. + # TODO: uncomment this once jnipp starts supporting -fno-exceptions + # if(ANDROID AND (CMAKE_COMPILER_IS_GNUCC OR CMAKE_C_COMPILER_ID MATCHES "Clang")) + # target_compile_options(openxr_loader + # PRIVATE + # -fno-exceptions + # ) + # endif() - # for target-specific generated files - ${CMAKE_CURRENT_SOURCE_DIR} - ${CMAKE_CURRENT_BINARY_DIR} - INTERFACE $ -) - -if(Vulkan_FOUND) - target_include_directories(openxr_loader PRIVATE ${Vulkan_INCLUDE_DIRS}) -endif() -if(NOT BUILD_LOADER_WITH_EXCEPTION_HANDLING) - target_compile_definitions(openxr_loader PRIVATE XRLOADER_DISABLE_EXCEPTION_HANDLING) endif() -target_link_libraries( - openxr_loader - PRIVATE ${CMAKE_DL_LIBS} - PUBLIC Threads::Threads -) -target_compile_definitions(openxr_loader PRIVATE ${OPENXR_ALL_SUPPORTED_DEFINES}) +# Platform details + if(ANDROID) target_link_libraries( - openxr_loader - PRIVATE - ${ANDROID_LOG_LIBRARY} - ${ANDROID_LIBRARY}) -endif() - -target_compile_definitions(openxr_loader PRIVATE API_NAME="OpenXR") -openxr_add_filesystem_utils(openxr_loader) - -set_target_properties(openxr_loader PROPERTIES DEBUG_POSTFIX "${OPENXR_DEBUG_POSTFIX}") + openxr_loader PRIVATE ${ANDROID_LOG_LIBRARY} ${ANDROID_LIBRARY} + ) -if(CMAKE_SYSTEM_NAME STREQUAL "Linux") +elseif(CMAKE_SYSTEM_NAME STREQUAL "Linux") set(FALLBACK_CONFIG_DIRS "/etc/xdg" CACHE @@ -158,105 +168,168 @@ if(CMAKE_SYSTEM_NAME STREQUAL "Linux") target_compile_definitions( openxr_loader PRIVATE - FALLBACK_CONFIG_DIRS="${FALLBACK_CONFIG_DIRS}" - FALLBACK_DATA_DIRS="${FALLBACK_DATA_DIRS}" - SYSCONFDIR="${CMAKE_INSTALL_FULL_SYSCONFDIR}" + FALLBACK_CONFIG_DIRS="${FALLBACK_CONFIG_DIRS}" + FALLBACK_DATA_DIRS="${FALLBACK_DATA_DIRS}" + SYSCONFDIR="${CMAKE_INSTALL_FULL_SYSCONFDIR}" ) if(NOT (CMAKE_INSTALL_FULL_SYSCONFDIR STREQUAL "/etc")) target_compile_definitions(openxr_loader PRIVATE EXTRASYSCONFDIR="/etc") endif() - set_target_properties(openxr_loader PROPERTIES SOVERSION "${MAJOR}" VERSION "${OPENXR_FULL_VERSION}") + set_target_properties( + openxr_loader PROPERTIES SOVERSION "${MAJOR}" VERSION + "${OPENXR_FULL_VERSION}" + ) add_custom_target( libopenxr_loader.so.${MAJOR}.${MINOR} ALL - COMMAND ${CMAKE_COMMAND} -E create_symlink libopenxr_loader.so.${OPENXR_FULL_VERSION} - libopenxr_loader.so.${MAJOR}.${MINOR} + COMMAND + "${CMAKE_COMMAND}" -E create_symlink + libopenxr_loader.so.${OPENXR_FULL_VERSION} + libopenxr_loader.so.${MAJOR}.${MINOR} ) + elseif(WIN32) + + # Pass version fields as preprocessor for .rc resource version on Windows. + target_compile_definitions( + openxr_loader + PRIVATE + XR_CURRENT_API_MAJOR_VERSION=${MAJOR} + XR_CURRENT_API_MINOR_VERSION=${MINOR} + XR_CURRENT_API_PATCH_VERSION=${PATCH} + ) + + target_sources( + openxr_loader PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}/loader.rc" + ) + if(MSVC) - # WindowsStore (UWP) apps must be compiled with dynamic CRT linkage (default) - if(NOT CMAKE_SYSTEM_NAME STREQUAL "WindowsStore") - foreach(configuration in CMAKE_C_FLAGS_DEBUG - CMAKE_C_FLAGS_RELEASE - CMAKE_C_FLAGS_RELWITHDEBINFO - CMAKE_CXX_FLAGS_DEBUG - CMAKE_CXX_FLAGS_RELEASE - CMAKE_CXX_FLAGS_RELWITHDEBINFO) - # If building DLLs, force static CRT linkage - if(DYNAMIC_LOADER) - if(${configuration} MATCHES "/MD") - string(REGEX REPLACE "/MD" "/MT" ${configuration} "${${configuration}}") - endif() - else() # Otherwise for static libs, link the CRT dynamically - if(${configuration} MATCHES "/MT") - string(REGEX REPLACE "/MT" "/MD" ${configuration} "${${configuration}}") - endif() - endif() - endforeach() + if(DYNAMIC_LOADER) + target_sources( + openxr_loader + PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}/openxr-loader.def" + ) endif() + if(DYNAMIC_LOADER AND NOT (CMAKE_SYSTEM_NAME STREQUAL "WindowsStore")) + # If building DLLs, force static CRT linkage + set_target_properties( + openxr_loader + PROPERTIES MSVC_RUNTIME_LIBRARY + "MultiThreaded$<$:Debug>" + ) + else() + # WindowsStore (UWP) apps must be compiled with dynamic CRT linkage (default) + # Otherwise for static libs, link the CRT dynamically + set_target_properties( + openxr_loader + PROPERTIES MSVC_RUNTIME_LIBRARY + "MultiThreaded$<$:Debug>DLL" + ) + endif() target_compile_options(openxr_loader PRIVATE /wd6386) endif() target_link_libraries(openxr_loader PUBLIC advapi32) +endif() - # Need to copy DLL to client directories so clients can easily load it. - if(DYNAMIC_LOADER AND (CMAKE_GENERATOR MATCHES "^Visual Studio.*")) - file(TO_NATIVE_PATH ${CMAKE_CURRENT_BINARY_DIR}/$/openxr_loader$<$:${OPENXR_DEBUG_POSTFIX}>.dll COPY_DLL_SRC_PATH) - file(TO_NATIVE_PATH ${CMAKE_CURRENT_BINARY_DIR}/$/openxr_loader$<$:${OPENXR_DEBUG_POSTFIX}>.pdb COPY_PDB_SRC_PATH) - file(TO_NATIVE_PATH ${CMAKE_CURRENT_BINARY_DIR}/../tests/hello_xr/$/ - COPY_DST_HELLO_XR_PATH - ) - file(TO_NATIVE_PATH ${CMAKE_CURRENT_BINARY_DIR}/../tests/loader_test/$/ - COPY_DST_LOADER_TEST_PATH - ) - file(TO_NATIVE_PATH ${CMAKE_CURRENT_BINARY_DIR}/../conformance/conformance_test/$/ - COPY_DST_CONFORMANCE_TEST_PATH - ) - add_custom_command( - TARGET openxr_loader POST_BUILD - COMMAND xcopy /Y /I ${COPY_DLL_SRC_PATH} ${COPY_DST_HELLO_XR_PATH} - COMMAND if exist ${COPY_PDB_SRC_PATH} xcopy /Y /I ${COPY_PDB_SRC_PATH} ${COPY_DST_HELLO_XR_PATH} - COMMAND xcopy /Y /I ${COPY_DLL_SRC_PATH} ${COPY_DST_LOADER_TEST_PATH} - COMMAND if exist ${COPY_PDB_SRC_PATH} xcopy /Y /I ${COPY_PDB_SRC_PATH} ${COPY_DST_LOADER_TEST_PATH} - COMMAND xcopy /Y /I ${COPY_DLL_SRC_PATH} ${COPY_DST_CONFORMANCE_TEST_PATH} - COMMAND if exist ${COPY_PDB_SRC_PATH} xcopy /Y /I ${COPY_PDB_SRC_PATH} ${COPY_DST_CONFORMANCE_TEST_PATH} - ) - endif() -elseif(ANDROID) - set(JNIPP_ROOT ${CMAKE_CURRENT_SOURCE_DIR}/../external/jnipp) - set(JNIWRAPPER_ROOT ${CMAKE_CURRENT_SOURCE_DIR}/../external/android-jni-wrappers) - file(GLOB ANDROID_WRAP_SOURCES ${JNIWRAPPER_ROOT}/wrap/*.cpp) - set_target_properties(openxr_loader - PROPERTIES - CXX_STANDARD 17 - CXX_STANDARD_REQUIRED TRUE) - target_sources(openxr_loader - PRIVATE - ${CMAKE_CURRENT_SOURCE_DIR}/android_utilities.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/android_utilities.h - ${ANDROID_WRAP_SOURCES} - ${JNIPP_ROOT}/jnipp.cpp +# Need to copy DLL to client directories so clients can easily load it. +if(DYNAMIC_LOADER AND (CMAKE_GENERATOR MATCHES "^Visual Studio.*")) + file( + TO_NATIVE_PATH + ${CMAKE_CURRENT_BINARY_DIR}/$/openxr_loader$<$:${OPENXR_DEBUG_POSTFIX}>.dll + COPY_DLL_SRC_PATH + ) + file( + TO_NATIVE_PATH + ${CMAKE_CURRENT_BINARY_DIR}/$/openxr_loader$<$:${OPENXR_DEBUG_POSTFIX}>.pdb + COPY_PDB_SRC_PATH + ) + file( + TO_NATIVE_PATH + ${CMAKE_CURRENT_BINARY_DIR}/../tests/hello_xr/$/ + COPY_DST_HELLO_XR_PATH + ) + file( + TO_NATIVE_PATH + ${CMAKE_CURRENT_BINARY_DIR}/../tests/loader_test/$/ + COPY_DST_LOADER_TEST_PATH + ) + file( + TO_NATIVE_PATH + ${CMAKE_CURRENT_BINARY_DIR}/../conformance/conformance_test/$/ + COPY_DST_CONFORMANCE_TEST_PATH ) - target_include_directories(openxr_loader + add_custom_command( + TARGET openxr_loader + POST_BUILD + COMMAND xcopy /Y /I ${COPY_DLL_SRC_PATH} ${COPY_DST_HELLO_XR_PATH} + COMMAND + if exist ${COPY_PDB_SRC_PATH} xcopy /Y /I ${COPY_PDB_SRC_PATH} + ${COPY_DST_HELLO_XR_PATH} + COMMAND xcopy /Y /I ${COPY_DLL_SRC_PATH} ${COPY_DST_LOADER_TEST_PATH} + COMMAND + if exist ${COPY_PDB_SRC_PATH} xcopy /Y /I ${COPY_PDB_SRC_PATH} + ${COPY_DST_LOADER_TEST_PATH} + COMMAND + xcopy /Y /I ${COPY_DLL_SRC_PATH} ${COPY_DST_CONFORMANCE_TEST_PATH} + COMMAND + if exist ${COPY_PDB_SRC_PATH} xcopy /Y /I ${COPY_PDB_SRC_PATH} + ${COPY_DST_CONFORMANCE_TEST_PATH} + ) +endif() + +# jnipp - android only +if(ANDROID) + set(JNIPP_ROOT "${PROJECT_SOURCE_DIR}/src/external/jnipp") + set(JNIWRAPPER_ROOT + "${PROJECT_SOURCE_DIR}/src/external/android-jni-wrappers" + ) + file( + GLOB + ANDROID_WRAP_SOURCES + CONFIGURE_DEPENDS + ${JNIWRAPPER_ROOT}/wrap/*.cpp + ) + set_target_properties( + openxr_loader PROPERTIES CXX_STANDARD 17 CXX_STANDARD_REQUIRED TRUE + ) + target_sources( + openxr_loader PRIVATE - ${JNIPP_ROOT} - ${JNIWRAPPER_ROOT} + ${CMAKE_CURRENT_SOURCE_DIR}/android_utilities.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/android_utilities.h + ${ANDROID_WRAP_SOURCES} + ${JNIPP_ROOT}/jnipp.cpp + ) + target_include_directories( + openxr_loader PRIVATE ${JNIPP_ROOT} ${JNIWRAPPER_ROOT} + ) + target_compile_definitions( + openxr_loader PRIVATE XR_KHR_LOADER_INIT_SUPPORT=1 ) endif() +# Make pkg-config file if(NOT MSVC) set(XR_API_VERSION "${MAJOR}.${MINOR}") set(EXTRA_LIBS ${CMAKE_THREAD_LIBS_INIT}) configure_file(openxr.pc.in openxr.pc @ONLY) - install(FILES "${CMAKE_CURRENT_BINARY_DIR}/openxr.pc" DESTINATION "${CMAKE_INSTALL_LIBDIR}/pkgconfig") + install( + FILES "${CMAKE_CURRENT_BINARY_DIR}/openxr.pc" + DESTINATION "${CMAKE_INSTALL_LIBDIR}/pkgconfig" + ) endif() # Copy loader to conformance_cli binary folder if built as dynamic if(DYNAMIC_LOADER AND BUILD_CONFORMANCE_CLI) - add_custom_command(TARGET openxr_loader POST_BUILD - COMMAND ${CMAKE_COMMAND} -E copy $ $ + add_custom_command( + TARGET openxr_loader + POST_BUILD + COMMAND + ${CMAKE_COMMAND} -E copy $ + $ ) endif() @@ -264,25 +337,37 @@ if(CMAKE_COMPILER_IS_GNUCC OR CMAKE_C_COMPILER_ID MATCHES "Clang") if(NOT MSVC) # Do not do this for clang-cl target_compile_options( - openxr_loader - PRIVATE "$<$:-fno-rtti>" - -ffunction-sections - -fdata-sections + openxr_loader PRIVATE "$<$:-fno-rtti>" + -ffunction-sections -fdata-sections ) endif() target_compile_options( - openxr_loader - PRIVATE -Wextra - -fno-strict-aliasing - -fno-builtin-memcmp + openxr_loader PRIVATE -Wextra -fno-strict-aliasing -fno-builtin-memcmp ) - # Make build depend on the version script/export map - target_sources(openxr_loader PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/openxr-loader.map) - # Add the linker flag. + + # Add the linker flag, and make build depend on the version script/export map if(APPLE) - set_target_properties(openxr_loader PROPERTIES LINK_FLAGS "-Wl,-exported_symbols_list,${CMAKE_CURRENT_SOURCE_DIR}/openxr-loader.expsym") + set_target_properties( + openxr_loader + PROPERTIES + LINK_FLAGS + "-Wl,-exported_symbols_list,\"${CMAKE_CURRENT_SOURCE_DIR}/openxr-loader.expsym\"" + ) + target_sources( + openxr_loader + PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}/openxr-loader.expsym" + ) else() - set_target_properties(openxr_loader PROPERTIES LINK_FLAGS "-Wl,--version-script=${CMAKE_CURRENT_SOURCE_DIR}/openxr-loader.map") + set_target_properties( + openxr_loader + PROPERTIES + LINK_FLAGS + "-Wl,--version-script=\"${CMAKE_CURRENT_SOURCE_DIR}/openxr-loader.map\"" + ) + target_sources( + openxr_loader + PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}/openxr-loader.map" + ) endif() # For GCC version 7.1 or greater, we need to disable the implicit fallthrough warning since # there's no consistent way to satisfy all compilers until they all accept the C++17 standard @@ -291,12 +376,6 @@ if(CMAKE_COMPILER_IS_GNUCC OR CMAKE_C_COMPILER_ID MATCHES "Clang") endif() endif() -add_library(headers INTERFACE) -target_include_directories(headers INTERFACE - $ - $ - $) - # We will never actually install here, but it's easier to set something than block all install code. if(ANDROID AND NOT INSTALL_TO_ARCHITECTURE_PREFIXES) set(CMAKE_INSTALL_BINDIR bin) @@ -304,7 +383,8 @@ if(ANDROID AND NOT INSTALL_TO_ARCHITECTURE_PREFIXES) endif() install( - TARGETS openxr_loader headers EXPORT openxr_loader_export + TARGETS openxr_loader headers + EXPORT openxr_loader_export RUNTIME DESTINATION "${CMAKE_INSTALL_BINDIR}" COMPONENT Loader LIBRARY DESTINATION "${CMAKE_INSTALL_LIBDIR}" COMPONENT Loader ARCHIVE DESTINATION "${CMAKE_INSTALL_LIBDIR}" COMPONENT Loader @@ -316,9 +396,8 @@ export( NAMESPACE OpenXR:: ) -# Create aliases so that it can be used the same whether vendored as source or found with CMake. +# Create alias so that it can be used the same whether vendored as source or found with CMake. add_library(OpenXR::openxr_loader ALIAS openxr_loader) -add_library(OpenXR::headers ALIAS headers) if(WIN32 AND NOT INSTALL_TO_ARCHITECTURE_PREFIXES) set(TARGET_DESTINATION cmake) @@ -337,8 +416,7 @@ install( include(CMakePackageConfigHelpers) set(INCLUDE_INSTALL_DIR include/) configure_package_config_file( - OpenXRConfig.cmake.in - ${CMAKE_CURRENT_BINARY_DIR}/OpenXRConfig.cmake + OpenXRConfig.cmake.in ${CMAKE_CURRENT_BINARY_DIR}/OpenXRConfig.cmake INSTALL_DESTINATION ${TARGET_DESTINATION} PATH_VARS INCLUDE_INSTALL_DIR ) @@ -354,8 +432,15 @@ install( COMPONENT CMakeConfigs ) -if(WIN32 AND NOT MINGW AND DYNAMIC_LOADER) - install(FILES $ DESTINATION ${CMAKE_INSTALL_BINDIR} OPTIONAL) +if(WIN32 + AND NOT MINGW + AND DYNAMIC_LOADER +) + install( + FILES $ + DESTINATION ${CMAKE_INSTALL_BINDIR} + OPTIONAL + ) endif() # Make the "meta" cmake config/version file to redirect to the right arch/build @@ -363,49 +448,68 @@ if(WIN32 AND INSTALL_TO_ARCHITECTURE_PREFIXES) set(TARGET_SUBDIR cmake/openxr) set(WARNING "This is a generated file - do not edit!") set(FN OpenXRConfig) - configure_file(${CMAKE_CURRENT_SOURCE_DIR}/../cmake/metaconfig.cmake.in - ${CMAKE_CURRENT_BINARY_DIR}/metaconfig.cmake - @ONLY) - install(FILES ${CMAKE_CURRENT_BINARY_DIR}/metaconfig.cmake + configure_file( + ../cmake/metaconfig.cmake.in + ${CMAKE_CURRENT_BINARY_DIR}/metaconfig.cmake @ONLY + ) + install( + FILES ${CMAKE_CURRENT_BINARY_DIR}/metaconfig.cmake DESTINATION . RENAME OpenXRConfig.cmake COMPONENT CMakeConfigs ) set(FN OpenXRConfigVersion) - configure_file(${CMAKE_CURRENT_SOURCE_DIR}/../cmake/metaconfig.cmake.in - ${CMAKE_CURRENT_BINARY_DIR}/metaconfigversion.cmake - @ONLY) - install(FILES ${CMAKE_CURRENT_BINARY_DIR}/metaconfigversion.cmake + configure_file( + ../cmake/metaconfig.cmake.in + ${CMAKE_CURRENT_BINARY_DIR}/metaconfigversion.cmake @ONLY + ) + install( + FILES ${CMAKE_CURRENT_BINARY_DIR}/metaconfigversion.cmake DESTINATION . RENAME OpenXRConfigVersion.cmake COMPONENT CMakeConfigs ) elseif(ANDROID AND INSTALL_TO_ARCHITECTURE_PREFIXES) - configure_file(${CMAKE_CURRENT_SOURCE_DIR}/abi.json - ${CMAKE_CURRENT_BINARY_DIR}/abi.json - @ONLY) - install(FILES ${CMAKE_CURRENT_BINARY_DIR}/abi.json + + # json data files for a "prefab" AAR + configure_file(abi.json "${CMAKE_CURRENT_BINARY_DIR}/abi.json" @ONLY) + install( + FILES "${CMAKE_CURRENT_BINARY_DIR}/abi.json" DESTINATION ${CMAKE_INSTALL_LIBDIR} - COMPONENT Prefab) + COMPONENT Prefab + ) - configure_file(${CMAKE_CURRENT_SOURCE_DIR}/prefab.json - ${CMAKE_CURRENT_BINARY_DIR}/prefab.json - @ONLY) - install(FILES ${CMAKE_CURRENT_BINARY_DIR}/prefab.json + configure_file(prefab.json "${CMAKE_CURRENT_BINARY_DIR}/prefab.json" @ONLY) + install( + FILES "${CMAKE_CURRENT_BINARY_DIR}/prefab.json" DESTINATION ${PREFAB_INSTALL_DIR} - COMPONENT Prefab) + COMPONENT Prefab + ) - configure_file(${CMAKE_CURRENT_SOURCE_DIR}/AndroidManifest.xml.in - ${CMAKE_CURRENT_BINARY_DIR}/AndroidManifest.xml) - install(FILES ${CMAKE_CURRENT_BINARY_DIR}/AndroidManifest.xml + configure_file( + AndroidManifest.xml.in + "${CMAKE_CURRENT_BINARY_DIR}/AndroidManifest.xml" + ) + install( + FILES "${CMAKE_CURRENT_BINARY_DIR}/AndroidManifest.xml" DESTINATION . - COMPONENT Prefab) - install(FILES module.json - DESTINATION ${PREFAB_MODULE_INSTALL_DIR} - COMPONENT Prefab) - - # This gets used directly by build-aar.sh - configure_file(${CMAKE_CURRENT_SOURCE_DIR}/openxr_loader_for_android.pom - ${CMAKE_CURRENT_BINARY_DIR}/openxr_loader_for_android-${OPENXR_FULL_VERSION}${OPENXR_ANDROID_VERSION_SUFFIX}.pom) + COMPONENT Prefab + ) + install( + FILES module.json + DESTINATION ${PREFAB_LOADER_MODULE_INSTALL_DIR} + COMPONENT Prefab + ) + + # These get used directly by build-aar.sh + configure_file( + openxr_loader_for_android.pom + "${CMAKE_CURRENT_BINARY_DIR}/openxr_loader_for_android-${OPENXR_FULL_VERSION}${OPENXR_ANDROID_VERSION_SUFFIX}.pom" + ) + + configure_file( + additional_manifest.mf.in + "${CMAKE_CURRENT_BINARY_DIR}/additional_manifest.mf" + ) endif() diff --git a/src/loader/additional_manifest.mf.in b/src/loader/additional_manifest.mf.in new file mode 100644 index 00000000..a34f865f --- /dev/null +++ b/src/loader/additional_manifest.mf.in @@ -0,0 +1,7 @@ +Name: org/khronos/openxr/openxr_loader_for_android +Specification-Title: OpenXR Loader for Android +Specification-Version: ${OPENXR_FULL_VERSION} +Specification-Vendor: The Khronos Group Inc. +Implementation-Title: OpenXR standard cross-vendor loader for Android +Implementation-Version: ${OPENXR_FULL_VERSION} +Implementation-Vendor: The Khronos Group Inc. diff --git a/src/loader/additional_manifest.mf.in.license b/src/loader/additional_manifest.mf.in.license new file mode 100644 index 00000000..4f133824 --- /dev/null +++ b/src/loader/additional_manifest.mf.in.license @@ -0,0 +1,2 @@ +Copyright (c) 2023, The Khronos Group Inc. +SPDX-License-Identifier: Apache-2.0 OR MIT diff --git a/src/loader/android_utilities.cpp b/src/loader/android_utilities.cpp index 9a3ad76c..68512d4a 100644 --- a/src/loader/android_utilities.cpp +++ b/src/loader/android_utilities.cpp @@ -245,18 +245,38 @@ static int populateFunctions(wrap::android::content::Context const &context, boo return 0; } +// The current file relies on android-jni-wrappers and jnipp, which may throw on failure. +// This is problematic when the loader is compiled with exception handling disabled - the consumers can reasonably +// expect that the compilation with -fno-exceptions will succeed, but the compiler will not accept the code that +// uses `try` & `catch` keywords. We cannot use the `exception_handling.hpp` here since we're not at an ABI boundary, +// so we define helper macros here. This is fine for now since the only occurrence of exception-handling code is in this file. +#ifdef XRLOADER_DISABLE_EXCEPTION_HANDLING + +#define ANDROID_UTILITIES_TRY +#define ANDROID_UTILITIES_CATCH_FALLBACK(...) + +#else + +#define ANDROID_UTILITIES_TRY try +#define ANDROID_UTILITIES_CATCH_FALLBACK(...) \ + catch (const std::exception &e) { \ + __VA_ARGS__ \ + } + +#endif // XRLOADER_DISABLE_EXCEPTION_HANDLING + /// Get cursor for active runtime, parameterized by whether or not we use the system broker static bool getActiveRuntimeCursor(wrap::android::content::Context const &context, jni::Array const &projection, bool systemBroker, Cursor &cursor) { auto uri = active_runtime::makeContentUri(systemBroker, XR_VERSION_MAJOR(XR_CURRENT_API_VERSION), ABI); ALOGI("getActiveRuntimeCursor: Querying URI: %s", uri.toString().c_str()); - try { - cursor = context.getContentResolver().query(uri, projection); - } catch (const std::exception &e) { + + ANDROID_UTILITIES_TRY { cursor = context.getContentResolver().query(uri, projection); } + ANDROID_UTILITIES_CATCH_FALLBACK({ ALOGW("Exception when querying %s content resolver: %s", getBrokerTypeName(systemBroker), e.what()); cursor = {}; return false; - } + }) if (cursor.isNull()) { ALOGW("Null cursor when querying %s content resolver.", getBrokerTypeName(systemBroker)); diff --git a/src/loader/api_layer_interface.cpp b/src/loader/api_layer_interface.cpp index c9e24ec4..e8f6a401 100644 --- a/src/loader/api_layer_interface.cpp +++ b/src/loader/api_layer_interface.cpp @@ -10,6 +10,7 @@ #include "api_layer_interface.hpp" #include "loader_interfaces.h" +#include "loader_init_data.hpp" #include "loader_logger.hpp" #include "loader_platform.hpp" #include "manifest_file.hpp" @@ -282,6 +283,38 @@ XrResult ApiLayerInterface::LoadApiLayers(const std::string& openxr_command, uin LoaderLogger::LogWarningMessage(openxr_command, warning_message); continue; } +#ifdef XR_KHR_LOADER_INIT_SUPPORT + if (!LoaderInitData::instance().initialized()) { + LoaderLogger::LogErrorMessage(openxr_command, "ApiLayerInterface::LoadApiLayers skipping manifest file " + + manifest_file->Filename() + + " because xrInitializeLoaderKHR was not yet called."); + + LoaderPlatformLibraryClose(layer_library); + return XR_ERROR_VALIDATION_FAILURE; + } + bool forwardedInitLoader = false; + { + // If we have xrInitializeLoaderKHR exposed as an export, forward call to it. + const auto function_name = manifest_file->GetFunctionName("xrInitializeLoaderKHR"); + auto initLoader = + reinterpret_cast(LoaderPlatformLibraryGetProcAddr(layer_library, function_name)); + if (initLoader != nullptr) { + // we found the entry point one way or another. + LoaderLogger::LogInfoMessage(openxr_command, + "ApiLayerInterface::LoadApiLayers forwarding xrInitializeLoaderKHR call to API layer " + "before calling xrNegotiateLoaderApiLayerInterface."); + XrResult res = initLoader(LoaderInitData::instance().getParam()); + if (!XR_SUCCEEDED(res)) { + LoaderLogger::LogErrorMessage( + openxr_command, "ApiLayerInterface::LoadApiLayers forwarded call to xrInitializeLoaderKHR failed."); + + LoaderPlatformLibraryClose(layer_library); + return res; + } + forwardedInitLoader = true; + } + } +#endif // Get and settle on an layer interface version (using any provided name if required). std::string function_name = manifest_file->GetFunctionName("xrNegotiateLoaderApiLayerInterface"); @@ -324,6 +357,38 @@ XrResult ApiLayerInterface::LoadApiLayers(const std::string& openxr_command, uin LoaderLogger::LogWarningMessage(openxr_command, warning_message); res = XR_ERROR_FILE_CONTENTS_INVALID; } + +#ifdef XR_KHR_LOADER_INIT_SUPPORT + if (XR_SUCCEEDED(res) && !forwardedInitLoader) { + // Forward initialize loader call, where possible and if we did not do so before. + PFN_xrVoidFunction initializeVoid = nullptr; + PFN_xrInitializeLoaderKHR initialize = nullptr; + + // Now we may try asking xrGetInstanceProcAddr on the API layer + if (XR_SUCCEEDED(api_layer_info.getInstanceProcAddr(XR_NULL_HANDLE, "xrInitializeLoaderKHR", &initializeVoid))) { + if (initializeVoid == nullptr) { + LoaderLogger::LogErrorMessage(openxr_command, + "ApiLayerInterface::LoadApiLayers got success from xrGetInstanceProcAddr " + "for xrInitializeLoaderKHR, but output a null pointer."); + res = XR_ERROR_RUNTIME_FAILURE; + } else { + initialize = reinterpret_cast(initializeVoid); + } + } + if (initialize != nullptr) { + // we found the entry point one way or another. + LoaderLogger::LogInfoMessage(openxr_command, + "ApiLayerInterface::LoadApiLayers forwarding xrInitializeLoaderKHR call to API layer " + "after calling xrNegotiateLoaderApiLayerInterface."); + res = initialize(LoaderInitData::instance().getParam()); + if (!XR_SUCCEEDED(res)) { + LoaderLogger::LogErrorMessage( + openxr_command, "ApiLayerInterface::LoadApiLayers forwarded call to xrInitializeLoaderKHR failed."); + } + } + } +#endif + if (XR_FAILED(res)) { if (!any_loaded) { last_error = res; diff --git a/src/loader/loader_core.cpp b/src/loader/loader_core.cpp index 06e68700..f173142c 100644 --- a/src/loader/loader_core.cpp +++ b/src/loader/loader_core.cpp @@ -14,6 +14,7 @@ #include "api_layer_interface.hpp" #include "exception_handling.hpp" #include "hex_and_handles.h" +#include "loader_init_data.hpp" #include "loader_instance.hpp" #include "loader_logger_recorders.hpp" #include "loader_logger.hpp" @@ -77,7 +78,7 @@ inline bool IsMissingNullTerminator(const char (&str)[max_length]) { #ifdef XR_KHR_LOADER_INIT_SUPPORT // platforms that support XR_KHR_loader_init. XRAPI_ATTR XrResult XRAPI_CALL LoaderXrInitializeLoaderKHR(const XrLoaderInitInfoBaseHeaderKHR *loaderInitInfo) XRLOADER_ABI_TRY { LoaderLogger::LogVerboseMessage("xrInitializeLoaderKHR", "Entering loader trampoline"); - return InitializeLoader(loaderInitInfo); + return InitializeLoaderInitData(loaderInitInfo); } XRLOADER_ABI_CATCH_FALLBACK #endif diff --git a/src/loader/loader_init_data.cpp b/src/loader/loader_init_data.cpp new file mode 100644 index 00000000..16b75304 --- /dev/null +++ b/src/loader/loader_init_data.cpp @@ -0,0 +1,59 @@ +// Copyright (c) 2017-2023, The Khronos Group Inc. +// Copyright (c) 2017-2019 Valve Corporation +// Copyright (c) 2017-2019 LunarG, Inc. +// +// SPDX-License-Identifier: Apache-2.0 OR MIT +// +// Initial Author: Mark Young +// + +#include "loader_init_data.hpp" + +#ifdef XR_KHR_LOADER_INIT_SUPPORT + +#ifdef XR_USE_PLATFORM_ANDROID +// Check and copy the Android-specific init data. +XrResult LoaderInitData::initialize(const XrLoaderInitInfoBaseHeaderKHR* info) { + if (info->type != XR_TYPE_LOADER_INIT_INFO_ANDROID_KHR) { + return XR_ERROR_VALIDATION_FAILURE; + } + auto cast_info = reinterpret_cast(info); + + if (cast_info->applicationVM == nullptr) { + return XR_ERROR_VALIDATION_FAILURE; + } + if (cast_info->applicationContext == nullptr) { + return XR_ERROR_VALIDATION_FAILURE; + } + + // Copy and store the JVM pointer and Android Context, ensuring the JVM is initialised. + _data = *cast_info; + _data.next = nullptr; + jni::init(static_cast(_data.applicationVM)); + const jni::Object context = jni::Object{static_cast(_data.applicationContext)}; + + // Retrieve a reference to the Android AssetManager. + const auto assetManager = context.call("getAssets()Landroid/content/res/AssetManager;"); + _android_asset_manager = AAssetManager_fromJava(jni::env(), assetManager.getHandle()); + + // Retrieve the path to the native libraries. + const auto applicationContext = context.call("getApplicationContext()Landroid/content/Context;"); + const auto applicationInfo = context.call("getApplicationInfo()Landroid/content/pm/ApplicationInfo;"); + _native_library_path = applicationInfo.get("nativeLibraryDir"); + + _initialized = true; + return XR_SUCCESS; +} +#endif // XR_USE_PLATFORM_ANDROID + +XrResult InitializeLoaderInitData(const XrLoaderInitInfoBaseHeaderKHR* loaderInitInfo) { + return LoaderInitData::instance().initialize(loaderInitInfo); +} + +#ifdef XR_USE_PLATFORM_ANDROID +std::string GetAndroidNativeLibraryDir() { return LoaderInitData::instance()._native_library_path; } + +void* Android_Get_Asset_Manager() { return LoaderInitData::instance()._android_asset_manager; } +#endif // XR_USE_PLATFORM_ANDROID + +#endif // XR_KHR_LOADER_INIT_SUPPORT diff --git a/src/loader/loader_init_data.hpp b/src/loader/loader_init_data.hpp new file mode 100644 index 00000000..7c6579cc --- /dev/null +++ b/src/loader/loader_init_data.hpp @@ -0,0 +1,92 @@ +// Copyright (c) 2017-2023, The Khronos Group Inc. +// Copyright (c) 2017-2019 Valve Corporation +// Copyright (c) 2017-2019 LunarG, Inc. +// +// SPDX-License-Identifier: Apache-2.0 OR MIT +// +// Initial Author: Mark Young +// + +#pragma once + +#include +#include + +#ifdef XR_USE_PLATFORM_ANDROID +#include +#include +#include "android_utilities.h" +#endif // XR_USE_PLATFORM_ANDROID + +#ifdef XR_KHR_LOADER_INIT_SUPPORT + +/*! + * Stores a copy of the data passed to the xrInitializeLoaderKHR function in a singleton. + */ +class LoaderInitData { + public: + /*! + * Singleton accessor. + */ + static LoaderInitData& instance() { + static LoaderInitData obj; + return obj; + } + +#ifdef XR_USE_PLATFORM_ANDROID + /*! + * Type alias for the platform-specific structure type. + */ + using StructType = XrLoaderInitInfoAndroidKHR; + /*! + * Native library path. + */ + std::string _native_library_path; + /*! + * Android asset manager. + */ + AAssetManager* _android_asset_manager; +#else +#error "Platform specific XR_KHR_loader_init structure is not defined for this platform." +#endif + + /*! + * Get our copy of the data, casted to pass to the runtime's matching method. + */ + const XrLoaderInitInfoBaseHeaderKHR* getParam() const { return reinterpret_cast(&_data); } + + /*! + * Get the data via its real structure type. + */ + const StructType& getData() const { return _data; } + + /*! + * Has this been correctly initialized? + */ + bool initialized() const noexcept { return _initialized; } + + /*! + * Initialize loader data - called by InitializeLoaderInitData() and thus ultimately by the loader's xrInitializeLoaderKHR + * implementation. Each platform that needs this extension will provide an implementation of this. + */ + XrResult initialize(const XrLoaderInitInfoBaseHeaderKHR* info); + + private: + //! Private constructor, forces use of singleton accessor. + LoaderInitData() = default; + //! Platform-specific init data + StructType _data = {}; + //! Flag for indicating whether _data is valid. + bool _initialized = false; +}; + +//! Initialize loader init data, where required. +XrResult InitializeLoaderInitData(const XrLoaderInitInfoBaseHeaderKHR* loaderInitInfo); + +#ifdef XR_USE_PLATFORM_ANDROID +XrResult GetPlatformRuntimeVirtualManifest(Json::Value& out_manifest); +std::string GetAndroidNativeLibraryDir(); +void* Android_Get_Asset_Manager(); +#endif // XR_USE_PLATFORM_ANDROID + +#endif // XR_KHR_LOADER_INIT_SUPPORT diff --git a/src/loader/manifest_file.cpp b/src/loader/manifest_file.cpp index 0683bc16..da96845e 100644 --- a/src/loader/manifest_file.cpp +++ b/src/loader/manifest_file.cpp @@ -18,6 +18,7 @@ #endif // OPENXR_HAVE_COMMON_CONFIG #include "filesystem_utils.hpp" +#include "loader_init_data.hpp" #include "loader_platform.hpp" #include "platform_utils.hpp" #include "loader_logger.hpp" @@ -666,14 +667,14 @@ XrResult RuntimeManifestFile::FindManifestFiles(std::vector #ifdef XR_USE_PLATFORM_ANDROID -#include "android_utilities.h" -#include #include // Needed for the loader init struct @@ -35,112 +34,6 @@ #include #endif // XR_USE_PLATFORM_ANDROID -#ifdef XR_KHR_LOADER_INIT_SUPPORT -namespace { -/*! - * Stores a copy of the data passed to the xrInitializeLoaderKHR function in a singleton. - */ -class LoaderInitData { - public: - /*! - * Singleton accessor. - */ - static LoaderInitData& instance() { - static LoaderInitData obj; - return obj; - } - -#ifdef XR_USE_PLATFORM_ANDROID - /*! - * Type alias for the platform-specific structure type. - */ - using StructType = XrLoaderInitInfoAndroidKHR; - /*! - * Native library path. - */ - std::string _native_library_path; - /*! - * Android asset manager. - */ - AAssetManager* _android_asset_manager; -#endif - - /*! - * Get our copy of the data, casted to pass to the runtime's matching method. - */ - const XrLoaderInitInfoBaseHeaderKHR* getParam() const { return reinterpret_cast(&_data); } - - /*! - * Get the data via its real structure type. - */ - const StructType& getData() const { return _data; } - - /*! - * Has this been correctly initialized? - */ - bool initialized() const noexcept { return _initialized; } - - /*! - * Initialize loader data - called by InitializeLoader() and thus ultimately by the loader's xrInitializeLoaderKHR - * implementation. Each platform that needs this extension will provide an implementation of this. - */ - XrResult initialize(const XrLoaderInitInfoBaseHeaderKHR* info); - - private: - //! Private constructor, forces use of singleton accessor. - LoaderInitData() = default; - //! Platform-specific init data - StructType _data = {}; - //! Flag for indicating whether _data is valid. - bool _initialized = false; -}; - -#ifdef XR_USE_PLATFORM_ANDROID -// Check and copy the Android-specific init data. -XrResult LoaderInitData::initialize(const XrLoaderInitInfoBaseHeaderKHR* info) { - if (info->type != XR_TYPE_LOADER_INIT_INFO_ANDROID_KHR) { - return XR_ERROR_VALIDATION_FAILURE; - } - auto cast_info = reinterpret_cast(info); - - if (cast_info->applicationVM == nullptr) { - return XR_ERROR_VALIDATION_FAILURE; - } - if (cast_info->applicationContext == nullptr) { - return XR_ERROR_VALIDATION_FAILURE; - } - - // Copy and store the JVM pointer and Android Context, ensuring the JVM is initialised. - _data = *cast_info; - _data.next = nullptr; - jni::init(static_cast(_data.applicationVM)); - const jni::Object context = jni::Object{static_cast(_data.applicationContext)}; - - // Retrieve a reference to the Android AssetManager. - const auto assetManager = context.call("getAssets()Landroid/content/res/AssetManager;"); - _android_asset_manager = AAssetManager_fromJava(jni::env(), assetManager.getHandle()); - - // Retrieve the path to the native libraries. - const auto applicationContext = context.call("getApplicationContext()Landroid/content/Context;"); - const auto applicationInfo = context.call("getApplicationInfo()Landroid/content/pm/ApplicationInfo;"); - _native_library_path = applicationInfo.get("nativeLibraryDir"); - - _initialized = true; - return XR_SUCCESS; -} -#endif // XR_USE_PLATFORM_ANDROID -} // namespace - -XrResult InitializeLoader(const XrLoaderInitInfoBaseHeaderKHR* loaderInitInfo) { - return LoaderInitData::instance().initialize(loaderInitInfo); -} - -std::string GetAndroidNativeLibraryDir() { return LoaderInitData::instance()._native_library_path; } - -void* Android_Get_Asset_Manager() { return LoaderInitData::instance()._android_asset_manager; } - -#endif // XR_KHR_LOADER_INIT_SUPPORT - #ifdef XR_USE_PLATFORM_ANDROID XrResult GetPlatformRuntimeVirtualManifest(Json::Value& out_manifest) { using wrap::android::content::Context; diff --git a/src/loader/runtime_interface.hpp b/src/loader/runtime_interface.hpp index 8d55ec67..8f8278f5 100644 --- a/src/loader/runtime_interface.hpp +++ b/src/loader/runtime_interface.hpp @@ -19,22 +19,10 @@ #include #include -#ifdef XR_USE_PLATFORM_ANDROID -#define XR_KHR_LOADER_INIT_SUPPORT -#endif - namespace Json { class Value; } -#ifdef XR_KHR_LOADER_INIT_SUPPORT -//! Initialize loader, where required. -XrResult InitializeLoader(const XrLoaderInitInfoBaseHeaderKHR* loaderInitInfo); -XrResult GetPlatformRuntimeVirtualManifest(Json::Value& out_manifest); -std::string GetAndroidNativeLibraryDir(); -void* Android_Get_Asset_Manager(); -#endif - class RuntimeManifestFile; struct XrGeneratedDispatchTable; diff --git a/src/scripts/api_dump_generator.py b/src/scripts/api_dump_generator.py index 5b409c33..d396560d 100755 --- a/src/scripts/api_dump_generator.py +++ b/src/scripts/api_dump_generator.py @@ -24,6 +24,8 @@ # automatic_source_generator.py class to produce the # generated source code for the API Dump layer. +import dataclasses + from automatic_source_generator import (AutomaticSourceOutputGenerator, undecorate) from generator import write @@ -604,25 +606,9 @@ def writeExpandedMember(self, base_type, is_pointer, pointer_count, member_param valid_extension_structs = member_param.valid_extension_structs member_string += prefix_string - tmp_member_param = self.MemberOrParam(type=member_param.type, - name=member_param.name, - is_const=member_param.is_const, - is_handle=member_param.is_handle, - is_bool=member_param.is_bool, - is_optional=member_param.is_optional, - no_auto_validity=member_param.no_auto_validity, - valid_extension_structs=valid_extension_structs, - is_array=member_param.is_array, - is_static_array=member_param.is_static_array, - static_array_sizes=member_param.static_array_sizes, - array_dimen=member_param.array_dimen, - array_count_var=member_param.array_count_var, - array_length_for=member_param.array_length_for, - pointer_count=pointer_count, - is_null_terminated=member_param.is_null_terminated, - pointer_count_var=member_param.pointer_count_var, - cdecl=member_param.cdecl, - values=member_param.values) + tmp_member_param = dataclasses.replace(member_param, + valid_extension_structs=valid_extension_structs, + pointer_count=pointer_count,) if member_param.is_optional and member_param.is_const and member_param.pointer_count > 0: member_string += self.writeIndent(indent) @@ -728,25 +714,15 @@ def writeExpandedArray(self, base_type, is_pointer, pointer_count, member_param, else: static_array_sizes = member_param.static_array_sizes[1:] - tmp_member_param = self.MemberOrParam(type=member_param.type, - name=member_param.name, - is_const=member_param.is_const, - is_handle=member_param.is_handle, - is_bool=member_param.is_bool, - is_optional=member_param.is_optional, - no_auto_validity=member_param.no_auto_validity, - valid_extension_structs=member_param.valid_extension_structs, - is_array=is_array, - is_static_array=is_static_array, - static_array_sizes=static_array_sizes, - array_dimen=array_dimen, - array_count_var=array_count_var, - array_length_for=array_length_for, - pointer_count=pointer_count, - is_null_terminated=member_param.is_null_terminated, - pointer_count_var=pointer_count_var, - cdecl=member_param.cdecl, - values=member_param.values) + tmp_member_param = dataclasses.replace(member_param, + is_array=is_array, + is_static_array=is_static_array, + static_array_sizes=static_array_sizes, + array_dimen=array_dimen, + array_count_var=array_count_var, + array_length_for=array_length_for, + pointer_count=pointer_count, + pointer_count_var=pointer_count_var,) member_array_string += self.writeExpandedMember(base_type, is_pointer, pointer_count, tmp_member_param, member_param_prefix, member_param_name, True, prefix_string1, prefix_string2, True, indent) diff --git a/src/scripts/automatic_source_generator.py b/src/scripts/automatic_source_generator.py index 3866fada..b470bb43 100755 --- a/src/scripts/automatic_source_generator.py +++ b/src/scripts/automatic_source_generator.py @@ -25,13 +25,15 @@ # way for the rest of the automatic source generation scripts. import re -from collections import namedtuple +from dataclasses import dataclass from inspect import currentframe, getframeinfo from typing import List, Optional, Tuple, Union +from xml.etree import ElementTree as et from generator import (GeneratorOptions, MissingRegistryError, OutputGenerator, noneStr, regSortFeatures, write) -from spec_tools.attributes import LengthEntry, parse_optional_from_param +from spec_tools.attributes import (LengthEntry, has_any_optional_in_param, + parse_optional_from_param) from spec_tools.util import getElemName @@ -41,7 +43,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:] @@ -102,7 +104,340 @@ def __init__(self, self.alignFuncParam = alignFuncParam self.genEnumBeginEndRange = genEnumBeginEndRange -# AutomaticSourceOutputGenerator - subclass of OutputGenerator. + +@dataclass +class LengthMember: + """Length Member data""" + + array_name: str + """The name of the array""" + + length_name: str + """The name of the length parameter""" + + +@dataclass +class HandleData: + """Handle data""" + + name: str + """The name of the handle""" + + parent: Optional[str] + """The name of the handle's direct parent""" + + ancestors: List[str] + """A list of all the handle's ancestors""" + + protect_value: Optional[str] + """None or a comma-delimited list indicating #define values to use around this handle""" + + protect_string: str + """Empty string or string to use after #if to protect this handle""" + + ext_name: Optional[str] + """Name of extension this handle is associated with (or None)""" + + +@dataclass +class FlagBits: + """Flag data""" + + name: str + """The name of the flag""" + + type: str + """The base type of the flag (for example uint64_t)""" + + valid_flags: str + """The list of valid flag values""" + + protect_value: Optional[str] + """None or a comma-delimited list indicating #define values to use around this flag""" + + protect_string: str + """Empty string or string to use after #if to protect this flag""" + + ext_name: Optional[str] + """Name of extension this command is associated with (or None)""" + + +@dataclass +class EnumBitValue: + """Individual Enum bit value""" + + name: str + """Name of an individual enum bit""" + + protect_value: Optional[str] + """None or a comma-delimited list indicating #define values to use around this value""" + + protect_string: str + """Empty string or string to use after #if to protect this value""" + + ext_name: Optional[str] + """Name of extension this command is associated with (or None)""" + + alias: Optional[str] + """None or the name of the type this is an alias for""" + + +@dataclass +class EnumData: + """Enum type data""" + + name: str + """The name of the enum""" + + values: List[EnumBitValue] + """List of possible EnumBitValue for this enum.""" + + protect_value: Optional[str] + """None or a comma-delimited list indicating #define values to use around this enum""" + + protect_string: str + """Empty string or string to use after #if to protect this enum""" + + ext_name: Optional[str] + """Name of extension this command is associated with (or None)""" + + +@dataclass +class MemberOrParam: + """Struct/Union member or Command parameter data""" + + type: str + """The type of this parameter""" + + is_handle: bool + """Boolean indicating if this type is a handle""" + + is_const: bool + """Boolean indicating if this has a const identifier""" + + is_bool: bool + """Boolean indicating if this is a boolean type""" + + is_optional: bool + """Boolean indicating if this is optional (pointers may be NULL, etc)""" + + is_array: bool + """Boolean indicating if this is an array""" + + array_dimen: int + """Number of dimensions for this array""" + + is_static_array: bool + """Boolean indicating if this is a statically sized array""" + + static_array_sizes: Optional[List[int]] + """List of static array sizes for each dimension""" + + array_count_var: str + """Name of array count if this is a value, and not a number""" + + array_length_for: str + """Indicates the array parameter that this is a length for""" + + pointer_count: int + """Depth of pointers for this type (* = 1, ** == 2)""" + + pointer_count_var: str + """If this is a pointer, and an array, it's the max size of that array""" + + is_null_terminated: bool + """Is this parameter null-terminated""" + + no_auto_validity: bool + """Boolean indicating if this should not have any automatic validation performed""" + + name: str + """The parameter name""" + + valid_extension_structs: Optional[str] + """Name of valid extension structs for this param (usually for 'next')""" + + cdecl: str + """The complete C-declaration for this parameter.""" + + values: Optional[str] + """None or comma-separated list of valid values""" + + +@dataclass +class StructUnionData: + """Information regarding a structure or union""" + + name: str + """Name of the structure or union""" + + ext_name: Optional[str] + """Name of extension this struct/union is associated with (or None)""" + + required_exts: List[str] + """Additional extensions required for this struct/union to be valid""" + + returned_only: bool + """Boolean indicating that this struct/union is only for returning information""" + + members: List[MemberOrParam] + """List of MemberOrParam for each member in this struct/union""" + + protect_value: Optional[str] + """None or a comma-delimited list indicating #define values to use around this structure/union""" + + protect_string: str + """Empty string or string to use after #if to protect this structure/union""" + + +@dataclass +class CommandData: + """Command data""" + + name: str + """Name of command""" + + is_create_connect: bool + """Boolean indicating this is a create/connect command""" + + is_destroy_disconnect: bool + """Boolean indicating this is a destroy/disconnect command""" + + ext_name: Optional[str] + """Name of extension this command is associated with (or None)""" + + ext_type: str + """Type of extension (instance, device)""" + + required_exts: List[str] + """Additional extensions required for this command to be valid""" + + handle_type: str + """Type of handle used as the primary type for this command""" + + handle: str + """Base handle for this command""" + + has_instance: bool + """True if an instance is used somewhere in the command""" + + return_type: Optional[et.Element] + """Type of return (or None)""" + + return_values: str + """Documented return values (core only)""" + + params: List[MemberOrParam] + """List of MemberOrParam for each parameter in this command""" + + protect_value: Optional[str] + """None or a comma-delimited list indicating #define values to use around this command""" + + protect_string: str + """Empty string or string to use after #if to protect this command""" + + begins_state: bool + """Boolean indicating this command begins some kind of state""" + + ends_state: bool + """Boolean indicating this command ends some kind of state""" + + checks_state: bool + """Boolean indicating this command checks for some kind of state""" + + cdecl: str + """The complete C-declaration for this command""" + + +@dataclass +class ExtensionData: + """Information on a given extension""" + + name: str + """Name of this extension (ex. XR_EXT_foo)""" + + vendor_tag: str + """Vendor tag associated with this extension""" + + type: str + """Type of extension (instance, device, ...)""" + + define: str + """Define containing a string version of the extension name""" + + num_commands: int + """Number of commands in the extension""" + + required_exts: List[str] + """List of required extensions for using this extension's functionality""" + + protect_value: Optional[str] + """None or a comma-delimited list indicating #define values to use around this extension""" + + protect_string: str + """Empty string or string to use after #if to protect this extension""" + + +@dataclass +class BaseTypeData: + """Base type listing (converts from a type into a name which is used as a type somewhere else).""" + + type: str + """The base type""" + + name: str + """The name of the derived type""" + + +@dataclass +class TypeData: + """Type listing""" + + name: str + """The name of the type""" + + protect_value: Optional[str] + """None or a comma-delimited list indicating #define values to use around this type""" + + protect_string: str + """Empty string or string to use after #if to protect this type""" + + alias: Optional[str] + """None or the name of the type this is an alias for""" + + +@dataclass +class StructRelationGroup: + """Structure relation group data""" + + generic_struct_name: str + """The name of the generic structure defining the common elements""" + + child_struct_names: List[str] + """Children of the base structure""" + + +@dataclass +class ApiState: + """API state listing""" + + state: str + """The name of the state""" + + type: str + """The type that is associated with the state (handle/struct/...)""" + + variable: str + """State variable""" + + begin_commands: List[CommandData] + """List of commands that begin the current state""" + + end_commands: List[CommandData] + """List of commands that end the current state""" + + check_commands: List[CommandData] + """List of commands that check the current state""" class AutomaticSourceOutputGenerator(OutputGenerator): @@ -111,216 +446,10 @@ class AutomaticSourceOutputGenerator(OutputGenerator): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) - # ** Global types for automatic source generation ** - # Length Member data - self.LengthMember = namedtuple('LengthMember', - ['array_name', # The name of the array - 'length_name']) # The name of the length parameter - # Handle data - self.HandleData = namedtuple('HandleData', - [ # The name of the handle - 'name', - # The name of the handle's direct parent - 'parent', - # A list of all the handle's ancestors - 'ancestors', - # None or a comma-delimited list indicating #define values to use around this handle - 'protect_value', - # Empty string or string to use after #if to protect this handle - 'protect_string', - # Name of extension this handle is associated with (or None) - 'ext_name']) - # Flag data - self.FlagBits = namedtuple('FlagBits', - [ # The name of the flag - 'name', - # The base type of the flag (for example uint64_t) - 'type', - # The list of valid flag values - 'valid_flags', - # None or a comma-delimited list indicating #define values to use around this flag - 'protect_value', - # Empty string or string to use after #if to protect this flag - 'protect_string', - # Name of extension this command is associated with (or None) - 'ext_name']) - - # Individual Enum bit value - self.EnumBitValue = namedtuple('EnumBitValue', - [ # Name of an individual enum bit - 'name', - # None or a comma-delimited list indicating #define values to use around this value - 'protect_value', - # Empty string or string to use after #if to protect this value - 'protect_string', - # Name of extension this command is associated with (or None) - 'ext_name', - # None or the name of the type this is an alias for - 'alias']) - # Enum type data - self.EnumData = namedtuple('EnumData', - [ # The name of the enum - 'name', - # List of possible EnumBitValue for this enum. - 'values', - # None or a comma-delimited list indicating #define values to use around this enum - 'protect_value', - # Empty string or string to use after #if to protect this enum - 'protect_string', - # Name of extension this command is associated with (or None) - 'ext_name']) - # Struct/Union member or Command parameter data - self.MemberOrParam = namedtuple('MemberOrParam', - [ # The type of this parameter - 'type', - # Boolean indicating if this type is a handle - 'is_handle', - # Boolean indicating if this has a const identifier - 'is_const', - # Boolean indicating if this is a boolean type - 'is_bool', - # Boolean indicating if this is optional (pointers may be NULL, etc) - 'is_optional', - # Boolean indicating if this is an array - 'is_array', - # Number of dimensions for this array - 'array_dimen', - # Boolean indicating if this is a statically sized array - 'is_static_array', - # List of static array sizes for each dimension - 'static_array_sizes', - # Name of array count if this is a value, and not a number - 'array_count_var', - # Indicates the array parameter that this is a length for - 'array_length_for', - # Depth of pointers for this type (* = 1, ** == 2) - 'pointer_count', - # If this is a pointer, and an array, it's the max size of that array - 'pointer_count_var', - # Is this parameter null-terminated - 'is_null_terminated', - # Boolean indicating if this should not have any automatic validation performed - 'no_auto_validity', - # The parameter name - 'name', - # Name of valid extension structs for this param (usually for 'next') - 'valid_extension_structs', - # The complete C-declaration for this parameter. - 'cdecl', - # None or comma-separated list of valid values - 'values']) - # Command data - self.CommandData = namedtuple('CommandData', - [ # Name of command - 'name', - # Boolean indicating this is a create/connect command - 'is_create_connect', - # Boolean indicating this is a destroy/disconnect command - 'is_destroy_disconnect', - # Name of extension this command is associated with (or None) - 'ext_name', - # Type of extension (instance, device) - 'ext_type', - # Additional extensions required for this command to be valid - 'required_exts', - # Type of handle used as the primary type for this command - 'handle_type', - # Base handle for this command - 'handle', - # True if an instance is used somewhere in the command - 'has_instance', - # Type of return (or None) - 'return_type', - # Documented return values (core only) - 'return_values', - # List of MemberOrParam for each parameter in this command - 'params', - # None or a comma-delimited list indicating #define values to use around this command - 'protect_value', - # Empty string or string to use after #if to protect this command - 'protect_string', - # Boolean indicating this command begins some kind of state - 'begins_state', - # Boolean indicating this command ends some kind of state - 'ends_state', - # Boolean indicating this command checks for some kind of state - 'checks_state', - # The complete C-declaration for this command - 'cdecl']) - # Information regarding a structure or union - self.StructUnionData = namedtuple('StructUnionData', - [ # Name of the structure or union - 'name', - # Name of extension this struct/union is associated with (or None) - 'ext_name', - # Additional extensions required for this struct/union to be valid - 'required_exts', - # Boolean indicating that this struct/union is only for returning information - 'returned_only', - # List of MemberOrParam for each member in this struct/union - 'members', - # None or a comma-delimited list indicating #define values to use around this structure/union - 'protect_value', - # Empty string or string to use after #if to protect this structure/union - 'protect_string']) - # Information on a given extension - self.ExtensionData = namedtuple('ExtensionData', - [ # Name of this extension (ex. XR_EXT_foo) - 'name', - # Vendor tag associated with this extension - 'vendor_tag', - # Type of extension (instance, device, ...) - 'type', - # Define containing a string version of the extension name - 'define', - # Number of commands in the extension - 'num_commands', - # List of required extensions for using this extension's functionality - 'required_exts', - # None or a comma-delimited list indicating #define values to use around this extension - 'protect_value', - # Empty string or string to use after #if to protect this extension - 'protect_string']) - # Base type listing (converts from a type into a name which is used as a type somewhere else). - self.BaseTypeData = namedtuple('BaseTypeData', - [ # The base type - 'type', - # The name of the derived type - 'name']) - # Type listing - self.TypeData = namedtuple('TypeData', - [ # The name of the type - 'name', - # None or a comma-delimited list indicating #define values to use around this type - 'protect_value', - # Empty string or string to use after #if to protect this type - 'protect_string', - # None or the name of the type this is an alias for - 'alias']) - # Structure relation group data - self.StructRelationGroup = namedtuple('StructRelationGroup', - [ # The name of the generic structure defining the common elements - 'generic_struct_name', - # Children of the base structure - 'child_struct_names']) - # API state listing - self.ApiState = namedtuple('ApiState', - [ # The name of the state - 'state', - # The type that is associated with the state (handle/struct/...) - 'type', - # State variable - 'variable', - # List of commands that begin the current state - 'begin_commands', - # List of commands that end the current state - 'end_commands', - # List of commands that check the current state - 'check_commands']) # Did we encounter an error self.encountered_error = False # List of strings containing all vendor tags - self.vendor_tags = [] + self.vendor_tags: List[str] = [] # Current vendor tag that should be used by this extension self.current_vendor_tag = '' # A define used to set the current API version in a component (in the loader, layers, etc). @@ -328,25 +457,25 @@ def __init__(self, *args, **kwargs): # A defined used to grab the current API Header's version self.header_version = '' # A list of all the API's core commands (CommandData). - self.core_commands = [] + self.core_commands: List[CommandData] = [] # A list of all the API's extension commands (CommandData). - self.ext_commands = [] + self.ext_commands: List[CommandData] = [] # A list of all extensions (ExtensionData) for this API - self.extensions = [] + self.extensions: List[ExtensionData] = [] # A list of all base data types (BaseTypeData) for this API - self.api_base_types = [] + self.api_base_types: List[BaseTypeData] = [] # A list of all handles (HandleData) for this API - self.api_handles = [] + self.api_handles: List[HandleData] = [] # A list of all structures (StructUnionData) for this API - self.api_structures = [] + self.api_structures: List[StructUnionData] = [] # A list of all unions (StructUnionData) for this API - self.api_unions = [] + self.api_unions: List[StructUnionData] = [] # A list of all enumeration (EnumData) for this API - self.api_enums = [] + self.api_enums: List[EnumData] = [] # A list of all flags (FlagBits) for this API - self.api_flags = [] + self.api_flags: List[FlagBits] = [] # A list of all bitmasks (EnumData) for this API - self.api_bitmasks = [] + self.api_bitmasks: List[EnumData] = [] # A list of all object types self.api_object_types = [] # A list of all result types @@ -572,7 +701,7 @@ def endFeature(self): if self.type == 'instance' or self.type == 'system': # Add the completed extension to the list of extensions self.extensions.append( - self.ExtensionData( + ExtensionData( name=self.currentExtension, vendor_tag=self.current_vendor_tag, type=self.type, @@ -636,7 +765,7 @@ def genGroup(self, group_info, name, alias): extension_to_check = elem.get('extname', self.currentExtension) alias = elem.get('alias') values.append( - self.EnumBitValue( + EnumBitValue( name=elem_name, protect_value=enum_protect_value, protect_string=enum_protect_string, @@ -648,7 +777,7 @@ def genGroup(self, group_info, name, alias): ' with the expected vendor tag \"%s\"' % ( name, self.currentExtension, self.current_vendor_tag)) self.api_enums.append( - self.EnumData( + EnumData( name=name, values=values, protect_value=top_protect_value, @@ -660,7 +789,7 @@ def genGroup(self, group_info, name, alias): ' end with the expected vendor tag \"%s\"' % ( name, self.currentExtension, self.current_vendor_tag)) self.api_bitmasks.append( - self.EnumData( + EnumData( name=name, values=values, protect_value=top_protect_value, @@ -684,7 +813,7 @@ def genGroup(self, group_info, name, alias): item_name, len(item_name), self.max_result_length)) alias = elem.get('alias') self.api_result_types.append( - self.TypeData( + TypeData( name=item_name, protect_value=protect_value, protect_string=protect_string, @@ -703,7 +832,7 @@ def genGroup(self, group_info, name, alias): ' not end with the expected vendor tag \"%s\"' % ( item_name, self.currentExtension, self.current_vendor_tag)) self.api_object_types.append( - self.TypeData( + TypeData( name=item_name, protect_value=protect_value, protect_string=protect_string, @@ -730,7 +859,7 @@ def genGroup(self, group_info, name, alias): if alias: self.aliases[item_name] = alias self.api_structure_types.append( - self.TypeData( + TypeData( name=item_name, protect_value=protect_value, protect_string=protect_string, @@ -799,7 +928,7 @@ def genType(self, type_info, type_name, alias): # - We need to know when it's created and destroyed, # - We need to setup unordered_maps and mutexes to track dispatch tables for each handle self.api_handles.append( - self.HandleData( + HandleData( name=type_name, parent=self.getHandleParent(type_name), ancestors=self.getHandleAncestors(type_name), @@ -813,7 +942,7 @@ def genType(self, type_info, type_name, alias): base_type = basetype_info[0] base_name = basetype_info[1] self.api_base_types.append( - self.BaseTypeData( + BaseTypeData( type=base_type, name=base_name)) elif type_category == 'define': @@ -831,7 +960,7 @@ def genType(self, type_info, type_name, alias): bitvalues = type_elem.get('bitvalues') # Record a bitmask and all it's valid flag bit values self.api_flags.append( - self.FlagBits( + FlagBits( name=mask_name, type=mask_type, valid_flags=bitvalues, @@ -927,6 +1056,16 @@ def genStructUnion(self, type_info, type_category, type_name, alias): required_exts.extend(ext_names.split(',')) returned_only = (type_info.elem.get('returnedonly') == "true") + # Check if this is a base header. + if type_name.endswith("BaseHeader{}".format(self.current_vendor_tag)): + # Check if the relation group already existed + existing_relation_group = self.getRelationGroupForBaseStruct(type_name) + if existing_relation_group is None: + # Create with an empty child list + relation_group = StructRelationGroup(generic_struct_name=type_name, + child_struct_names=[]) + self._struct_relation_groups[type_name] = relation_group + # Search through the members to determine all the array lengths arraylengths = dict() for member in members: @@ -942,10 +1081,10 @@ def genStructUnion(self, type_info, type_category, type_name, alias): static_array_sizes = [] no_auto_validity = True if member.get( 'noautovalidity') else False - is_optional = (self.paramIsOptional(member) or (member_name == 'next')) + is_optional = (member_name == 'next') or (has_any_optional_in_param(member)) is_handle = self.isHandle(member_type) - is_static_array = self.paramIsStaticArray(member) - is_array = is_static_array + static_array_dim = self.paramIsStaticArray(member) + is_array = static_array_dim > 0 array_count_var = '' # Determine if this is an array length member array_name_for_length = arraylengths.get(member_name, '') @@ -961,7 +1100,7 @@ def genStructUnion(self, type_info, type_category, type_name, alias): cdecl, member_type, member_name) # If this is a static array, grab the sizes - if is_static_array: + if static_array_dim: static_array_sizes = self.paramStaticArraySizes(member) # If the enum field is there, then this is an array with an enum @@ -977,7 +1116,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: @@ -989,26 +1128,27 @@ def genStructUnion(self, type_info, type_category, type_name, alias): # Append this member to the list of current members members_info.append( - self.MemberOrParam(type=member_type, - name=member_name, - is_const=is_const, - is_handle=is_handle, - is_bool=True if 'XrBool' in member_type else False, - is_optional=is_optional, - no_auto_validity=no_auto_validity, - valid_extension_structs=self.registry.validextensionstructs[ - type_name] if member_name == 'next' else None, - is_array=is_array, - is_static_array=is_static_array, - static_array_sizes=static_array_sizes, - array_dimen=array_dimen, - array_count_var=array_count_var, - array_length_for=array_name_for_length, - pointer_count=pointer_count, - pointer_count_var=pointer_count_var, - is_null_terminated=is_null_terminated, - cdecl=cdecl, - values=member_values)) + MemberOrParam(type=member_type, + name=member_name, + is_const=is_const, + is_handle=is_handle, + is_bool=True if 'XrBool' in member_type else False, + is_optional=is_optional, + no_auto_validity=no_auto_validity, + valid_extension_structs=self.registry.validextensionstructs[ + type_name] if member_name == 'next' else None, + is_array=is_array, + is_static_array=(static_array_dim > 0), + static_array_sizes=static_array_sizes, + array_dimen=array_dimen, + array_count_var=array_count_var, + array_length_for=array_name_for_length, + pointer_count=pointer_count, + pointer_count_var=pointer_count_var, + is_null_terminated=is_null_terminated, + cdecl=cdecl, + values=member_values)) + # If this structure/union expands a generic one, save the information and validate that # it contains at least the same elements as the generic one. if type_info.elem.get('parentstruct'): @@ -1021,8 +1161,8 @@ def genStructUnion(self, type_info, type_category, type_name, alias): else: # Create with an empty child list for now child_list = [] - relation_group = self.StructRelationGroup(generic_struct_name=generic_struct_name, - child_struct_names=child_list) + relation_group = StructRelationGroup(generic_struct_name=generic_struct_name, + child_struct_names=child_list) self._struct_relation_groups[generic_struct_name] = relation_group # Get the structure information for the group's generic structure @@ -1047,28 +1187,28 @@ 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: self.api_unions.append( - self.StructUnionData(name=type_name, - ext_name=self.currentExtension, - required_exts=required_exts, - protect_value=protect_value, - protect_string=protect_string, - returned_only=returned_only, - members=members_info)) + StructUnionData(name=type_name, + ext_name=self.currentExtension, + required_exts=required_exts, + protect_value=protect_value, + protect_string=protect_string, + returned_only=returned_only, + members=members_info)) else: self.api_structures.append( - self.StructUnionData(name=type_name, - ext_name=self.currentExtension, - required_exts=required_exts, - protect_value=protect_value, - protect_string=protect_string, - returned_only=returned_only, - members=members_info)) + StructUnionData(name=type_name, + ext_name=self.currentExtension, + required_exts=required_exts, + protect_value=protect_value, + protect_string=protect_string, + returned_only=returned_only, + members=members_info)) # Add a command to the appropriate list of commands (core or extension) # self the AutomaticSourceOutputGenerator object @@ -1077,6 +1217,8 @@ def genStructUnion(self, type_info, type_category, type_name, alias): # name the name of the command # cmd_info the XML information for this command def addCommandToDispatchList(self, ext_name, ext_type, name, cmd_info): + if self.registry is None: + raise MissingRegistryError() cmd_params = [] required_exts = [] is_core = True if self.isCoreExtensionName(ext_name) else False @@ -1138,7 +1280,7 @@ def addCommandToDispatchList(self, ext_name, ext_type, name, cmd_info): # Generate a list of commands for param in params: - is_array = self.paramIsStaticArray(param) + is_array = self.paramIsStaticArray(param) > 0 param_cdecl = self.makeCParamDecl(param, 0) array_count_var = '' pointer_count_var = '' @@ -1166,7 +1308,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 @@ -1183,16 +1325,16 @@ def addCommandToDispatchList(self, ext_name, ext_type, name, cmd_info): # Add the command parameter to the list cmd_params.append( - self.MemberOrParam( + MemberOrParam( type=param_type, name=param_name, is_const=('const' in param_cdecl.strip().lower()), is_handle=self.isHandle(param_type), is_bool=('XrBool' in paramInfo[0]), - is_optional=self.paramIsOptional(param), + is_optional=has_any_optional_in_param(param), no_auto_validity=(param.get('noautovalidity') is not None), is_array=is_array, - is_static_array=self.paramIsStaticArray(param), + is_static_array=self.paramIsStaticArray(param) > 0, static_array_sizes=self.paramStaticArraySizes( param) if self.paramIsStaticArray(param) else None, array_dimen=array_dimen, @@ -1220,44 +1362,44 @@ def addCommandToDispatchList(self, ext_name, ext_type, name, cmd_info): if handle is not None and handle_type != 'XrInstance': core_command_type = 'device' self.core_commands.append( - self.CommandData(name=name, - is_create_connect=is_create_connect, - is_destroy_disconnect=is_destroy_disconnect, - ext_name=ext_name, - ext_type=core_command_type, - required_exts=required_exts, - protect_value=protect_value, - protect_string=protect_string, - return_type=return_type, - return_values=return_values, - handle=handle, - handle_type=handle_type, - has_instance=cmd_has_instance, - params=cmd_params, - begins_state=begins_state, - ends_state=ends_state, - checks_state=checks_state, - cdecl=self.makeCDecls(cmd_info.elem)[0])) + CommandData(name=name, + is_create_connect=is_create_connect, + is_destroy_disconnect=is_destroy_disconnect, + ext_name=ext_name, + ext_type=core_command_type, + required_exts=required_exts, + protect_value=protect_value, + protect_string=protect_string, + return_type=return_type, + return_values=return_values, + handle=handle, + handle_type=handle_type, + has_instance=cmd_has_instance, + params=cmd_params, + begins_state=begins_state, + ends_state=ends_state, + checks_state=checks_state, + cdecl=self.makeCDecls(cmd_info.elem)[0])) else: self.ext_commands.append( - self.CommandData(name=name, - is_create_connect=is_create_connect, - is_destroy_disconnect=is_destroy_disconnect, - ext_name=ext_name, - ext_type=ext_type, - required_exts=required_exts, - protect_value=protect_value, - protect_string=protect_string, - return_type=return_type, - return_values=return_values, - handle=handle, - handle_type=handle_type, - has_instance=cmd_has_instance, - params=cmd_params, - begins_state=begins_state, - ends_state=ends_state, - checks_state=checks_state, - cdecl=self.makeCDecls(cmd_info.elem)[0])) + CommandData(name=name, + is_create_connect=is_create_connect, + is_destroy_disconnect=is_destroy_disconnect, + ext_name=ext_name, + ext_type=ext_type, + required_exts=required_exts, + protect_value=protect_value, + protect_string=protect_string, + return_type=return_type, + return_values=return_values, + handle=handle, + handle_type=handle_type, + has_instance=cmd_has_instance, + params=cmd_params, + begins_state=begins_state, + ends_state=ends_state, + checks_state=checks_state, + cdecl=self.makeCDecls(cmd_info.elem)[0])) def findState(self, state): for api_state in self.api_states: @@ -1291,12 +1433,12 @@ def addCommandToBeginStates(self, comma_list_states, command): begin_list.append(command) # Create and append the new state self.api_states.append( - self.ApiState(state=cur_state, - type=state_type, - variable=state_variable, - begin_commands=begin_list, - end_commands=end_list, - check_commands=check_list)) + ApiState(state=cur_state, + type=state_type, + variable=state_variable, + begin_commands=begin_list, + end_commands=end_list, + check_commands=check_list)) else: # Found, so just add a new begin command found_state.begin_commands.append(command) @@ -1327,12 +1469,12 @@ def addCommandToEndStates(self, comma_list_states, command): end_list.append(command) # Create and append the new state self.api_states.append( - self.ApiState(state=cur_state, - type=state_type, - variable=state_variable, - begin_commands=begin_list, - end_commands=end_list, - check_commands=check_list)) + ApiState(state=cur_state, + type=state_type, + variable=state_variable, + begin_commands=begin_list, + end_commands=end_list, + check_commands=check_list)) else: # Found, so just add a new end command found_state.end_commands.append(command) @@ -1363,12 +1505,12 @@ def addCommandToCheckStates(self, comma_list_states, command): check_list.append(command) # Create and append the new state self.api_states.append( - self.ApiState(state=cur_state, - type=state_type, - variable=state_variable, - begin_commands=begin_list, - end_commands=end_list, - check_commands=check_list)) + ApiState(state=cur_state, + type=state_type, + variable=state_variable, + begin_commands=begin_list, + end_commands=end_list, + check_commands=check_list)) else: # Found, so just add a new check command found_state.check_commands.append(command) @@ -1376,7 +1518,7 @@ def addCommandToCheckStates(self, comma_list_states, command): # Check if the parameter passed in is a static array # self the AutomaticSourceOutputGenerator object # param the XML information for the param - def paramIsStaticArray(self, param): + def paramIsStaticArray(self, param) -> int: is_static_array = 0 param_name = param.find('name') if param_name.tail and ('[' in param_name.tail): @@ -1534,7 +1676,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/conformance_generator.py b/src/scripts/conformance_generator.py index 279df259..8de7fe26 100644 --- a/src/scripts/conformance_generator.py +++ b/src/scripts/conformance_generator.py @@ -29,6 +29,7 @@ 'xrInitializeLoaderKHR' )) + class ConformanceGenerator(AutomaticSourceOutputGenerator): """Generate conformance source using XML element attributes from registry""" diff --git a/src/scripts/conformance_layer_generator.py b/src/scripts/conformance_layer_generator.py index 5d31c9f6..72ab685b 100644 --- a/src/scripts/conformance_layer_generator.py +++ b/src/scripts/conformance_layer_generator.py @@ -72,10 +72,10 @@ def endFile(self): skip_hooks = set(self.no_trampoline_or_terminator).union( set(MANUALLY_DEFINED_IN_LAYER)) file_data = self.template.render( - gen=self, - registry=self.registry, - sorted_cmds=sorted_cmds, - skip_hooks=skip_hooks) + gen=self, + registry=self.registry, + sorted_cmds=sorted_cmds, + skip_hooks=skip_hooks) write(file_data, file=self.outFile) # Finish processing in superclass diff --git a/src/scripts/generate_api_layer_manifest.py b/src/scripts/generate_api_layer_manifest.py index fc843f3c..570e9ffa 100755 --- a/src/scripts/generate_api_layer_manifest.py +++ b/src/scripts/generate_api_layer_manifest.py @@ -26,6 +26,8 @@ # file itself as a starting point. # json_file the JSON file we're creating # library_file the library (so/dll) file we're referencing in the JSON file + + def getRelativePath(json_file, library_file): directory_slash = '/' relative_path = '' @@ -76,6 +78,7 @@ def getRelativePath(json_file, library_file): print(relative_path) return relative_path + def main(argv): output_file = '' layer_name = '' @@ -85,7 +88,7 @@ def main(argv): description = '' generate_badjson_jsons = False - usage = '\ngenerate_api_layer_manifest.py \n' + usage = '\ngenerate_api_layer_manifest.py \n' usage += ' -f/--file \n' usage += ' -n/--name \n' usage += ' -l/--lib \n' @@ -95,7 +98,7 @@ def main(argv): usage += ' -b/--bad\n' try: - opts, _ = getopt.getopt(argv,"hbf:n:l:a:v:d:",["bad","file=","name=","lib=","api=","ver=","desc="]) + opts, _ = getopt.getopt(argv, "hbf:n:l:a:v:d:", ["bad", "file=", "name=", "lib=", "api=", "ver=", "desc="]) except getopt.GetoptError: print(usage) sys.exit(2) @@ -118,7 +121,7 @@ def main(argv): elif opt in ("-b", "--bad"): generate_badjson_jsons = True - file_text = '{\n' + file_text = '{\n' file_text += ' "file_format_version": "%s",\n' % cur_layer_json_version file_text += ' "api_layer": {\n' file_text += ' "name": "XR_APILAYER_%s",\n' % layer_name @@ -153,7 +156,7 @@ def main(argv): # Missing bad_name = '_badjson_file_ver_missing' - file_text = '{\n' + file_text = '{\n' file_text += ' "api_layer": {\n' file_text += ' "name": "XR_APILAYER_LUNARG_%s%s",\n' % (layer_name, bad_name) file_text += ' "library_path": "%s",\n' % library_location @@ -170,7 +173,7 @@ def main(argv): # Use int bad_name = '_badjson_file_ver_int' - file_text = '{\n' + file_text = '{\n' file_text += ' "file_format_version": 1,\n' file_text += ' "api_layer": {\n' file_text += ' "name": "XR_APILAYER_LUNARG_%s%s",\n' % (layer_name, bad_name) @@ -188,7 +191,7 @@ def main(argv): # Use invalid string bad_name = '_badjson_file_ver_string' - file_text = '{\n' + file_text = '{\n' file_text += ' "file_format_version": "invalid string",\n' file_text += ' "api_layer": {\n' file_text += ' "name": "XR_APILAYER_LUNARG_%s%s",\n' % (layer_name, bad_name) @@ -206,7 +209,7 @@ def main(argv): # Too low of a version bad_name = '_badjson_file_ver_all_low' - file_text = '{\n' + file_text = '{\n' file_text += ' "file_format_version": "0.0.0",\n' file_text += ' "api_layer": {\n' file_text += ' "name": "XR_APILAYER_LUNARG_%s%s",\n' % (layer_name, bad_name) @@ -224,7 +227,7 @@ def main(argv): # Too high of a major version bad_name = '_badjson_file_ver_major_high' - file_text = '{\n' + file_text = '{\n' file_text += ' "file_format_version": "15.0.0",\n' file_text += ' "api_layer": {\n' file_text += ' "name": "XR_APILAYER_LUNARG_%s%s",\n' % (layer_name, bad_name) @@ -242,7 +245,7 @@ def main(argv): # Too high of a minor version bad_name = '_badjson_file_ver_minor_high' - file_text = '{\n' + file_text = '{\n' file_text += ' "file_format_version": "1.15.0",\n' file_text += ' "api_layer": {\n' file_text += ' "name": "XR_APILAYER_LUNARG_%s%s",\n' % (layer_name, bad_name) @@ -263,7 +266,7 @@ def main(argv): # Completely Missing bad_name = '_badjson_layer_missing' - file_text = '{\n' + file_text = '{\n' file_text += ' "file_format_version": "%s",\n' % cur_layer_json_version file_text += ' "name": "XR_APILAYER_LUNARG_%s%s",\n' % (layer_name, bad_name) file_text += ' "library_path": "%s",\n' % library_location @@ -279,7 +282,7 @@ def main(argv): # Empty bad_name = '_badjson_layer_empty' - file_text = '{\n' + file_text = '{\n' file_text += ' "file_format_version": "%s",\n' % cur_layer_json_version file_text += ' "api_layer": {\n' file_text += ' },\n' @@ -300,7 +303,7 @@ def main(argv): # Use int bad_name = '_badjson_name_int' - file_text = '{\n' + file_text = '{\n' file_text += ' "file_format_version": "%s",\n' % cur_layer_json_version file_text += ' "api_layer": {\n' file_text += ' "name": 1,\n' @@ -318,7 +321,7 @@ def main(argv): # Missing bad_name = '_badjson_name_missing' - file_text = '{\n' + file_text = '{\n' file_text += ' "file_format_version": "%s",\n' % cur_layer_json_version file_text += ' "api_layer": {\n' file_text += ' "library_path": "%s",\n' % library_location @@ -338,7 +341,7 @@ def main(argv): # Missing bad_name = '_badjson_path_missing' - file_text = '{\n' + file_text = '{\n' file_text += ' "file_format_version": "%s",\n' % cur_layer_json_version file_text += ' "api_layer": {\n' file_text += ' "name": "XR_APILAYER_LUNARG_%s%s",\n' % (layer_name, bad_name) @@ -355,7 +358,7 @@ def main(argv): # Use int bad_name = '_badjson_path_int' - file_text = '{\n' + file_text = '{\n' file_text += ' "file_format_version": "%s",\n' % cur_layer_json_version file_text += ' "api_layer": {\n' file_text += ' "name": "XR_APILAYER_LUNARG_%s%s",\n' % (layer_name, bad_name) @@ -373,11 +376,11 @@ def main(argv): # Replace valid path with invalid one bad_name = '_badjson_path_no_file' - file_text = '{\n' + file_text = '{\n' file_text += ' "file_format_version": "%s",\n' % cur_layer_json_version file_text += ' "api_layer": {\n' file_text += ' "name": "XR_APILAYER_LUNARG_%s%s",\n' % (layer_name, bad_name) - file_text += ' "library_path": "%s",\n' % library_location.replace("test_layers","not_real") + file_text += ' "library_path": "%s",\n' % library_location.replace("test_layers", "not_real") file_text += ' "api_version": "%s",\n' % api_version file_text += ' "implementation_version": "%s",\n' % implementation_version file_text += ' "description": "%s"\n' % description @@ -394,7 +397,7 @@ def main(argv): # Missing bad_name = '_badjson_api_int' - file_text = '{\n' + file_text = '{\n' file_text += ' "file_format_version": "%s",\n' % cur_layer_json_version file_text += ' "api_layer": {\n' file_text += ' "name": "XR_APILAYER_LUNARG_%s%s",\n' % (layer_name, bad_name) @@ -411,7 +414,7 @@ def main(argv): # Use int bad_name = '_badjson_api_int' - file_text = '{\n' + file_text = '{\n' file_text += ' "file_format_version": "%s",\n' % cur_layer_json_version file_text += ' "api_layer": {\n' file_text += ' "name": "XR_APILAYER_LUNARG_%s%s",\n' % (layer_name, bad_name) @@ -429,7 +432,7 @@ def main(argv): # Use float bad_name = '_badjson_api_float' - file_text = '{\n' + file_text = '{\n' file_text += ' "file_format_version": "%s",\n' % cur_layer_json_version file_text += ' "api_layer": {\n' file_text += ' "name": "XR_APILAYER_LUNARG_%s%s",\n' % (layer_name, bad_name) @@ -447,7 +450,7 @@ def main(argv): # Bad string bad_name = '_badjson_api_string' - file_text = '{\n' + file_text = '{\n' file_text += ' "file_format_version": "%s",\n' % cur_layer_json_version file_text += ' "api_layer": {\n' file_text += ' "name": "XR_APILAYER_LUNARG_%s%s",\n' % (layer_name, bad_name) @@ -465,7 +468,7 @@ def main(argv): # Too high of a major API version bad_name = '_badjson_api_major_high' - file_text = '{\n' + file_text = '{\n' file_text += ' "file_format_version": "%s",\n' % cur_layer_json_version file_text += ' "api_layer": {\n' file_text += ' "name": "XR_APILAYER_LUNARG_%s%s",\n' % (layer_name, bad_name) @@ -486,7 +489,7 @@ def main(argv): # Always fail negotiate bad_name = '_badnegotiate_always' - file_text = '{\n' + file_text = '{\n' file_text += ' "file_format_version": "%s",\n' % cur_layer_json_version file_text += ' "api_layer": {\n' file_text += ' "name": "XR_APILAYER_LUNARG_%s%s",\n' % (layer_name, bad_name) @@ -508,7 +511,7 @@ def main(argv): # Pass negotiate, but return null GIPA bad_name = '_badnegotiate_invalid_gipa' - file_text = '{\n' + file_text = '{\n' file_text += ' "file_format_version": "%s",\n' % cur_layer_json_version file_text += ' "api_layer": {\n' file_text += ' "name": "XR_APILAYER_LUNARG_%s%s",\n' % (layer_name, bad_name) @@ -530,7 +533,7 @@ def main(argv): # Pass negotiate, but return invalid interface version bad_name = '_badnegotiate_invalid_interface' - file_text = '{\n' + file_text = '{\n' file_text += ' "file_format_version": "%s",\n' % cur_layer_json_version file_text += ' "api_layer": {\n' file_text += ' "name": "XR_APILAYER_LUNARG_%s%s",\n' % (layer_name, bad_name) @@ -552,7 +555,7 @@ def main(argv): # Pass negotiate, but return invalid api version bad_name = '_badnegotiate_invalid_api' - file_text = '{\n' + file_text = '{\n' file_text += ' "file_format_version": "%s",\n' % cur_layer_json_version file_text += ' "api_layer": {\n' file_text += ' "name": "XR_APILAYER_LUNARG_%s%s",\n' % (layer_name, bad_name) @@ -578,7 +581,7 @@ def main(argv): # Provide a good relative path layer_suffix_name = '_good_relative_path' - file_text = '{\n' + file_text = '{\n' file_text += ' "file_format_version": "%s",\n' % cur_layer_json_version file_text += ' "api_layer": {\n' file_text += ' "name": "XR_APILAYER_LUNARG_%s%s",\n' % (layer_name, layer_suffix_name) @@ -596,11 +599,11 @@ def main(argv): # Provide a bad relative path layer_suffix_name = '_badjson_relative_path' - file_text = '{\n' + file_text = '{\n' file_text += ' "file_format_version": "%s",\n' % cur_layer_json_version file_text += ' "api_layer": {\n' file_text += ' "name": "XR_APILAYER_LUNARG_%s%s",\n' % (layer_name, layer_suffix_name) - file_text += ' "library_path": "%s",\n' % relative_lib.replace("test_layers","not_real") + file_text += ' "library_path": "%s",\n' % relative_lib.replace("test_layers", "not_real") file_text += ' "api_version": "%s",\n' % api_version file_text += ' "implementation_version": "%s",\n' % implementation_version file_text += ' "description": "%s"\n' % description @@ -612,5 +615,6 @@ def main(argv): f.write(file_text) f.close() + if __name__ == "__main__": main(sys.argv[1:]) diff --git a/src/scripts/generate_runtime_manifest.py b/src/scripts/generate_runtime_manifest.py index 8de78dac..985108fb 100755 --- a/src/scripts/generate_runtime_manifest.py +++ b/src/scripts/generate_runtime_manifest.py @@ -21,18 +21,19 @@ cur_runtime_json_version = '1.0.0' + def main(argv): output_file = '' library_location = '' generate_badjson_jsons = False - usage = '\ngenerate_runtime_manifest.py \n' + usage = '\ngenerate_runtime_manifest.py \n' usage += ' -f/--file \n' usage += ' -l/--lib \n' usage += ' -b/--bad\n' try: - opts, args = getopt.getopt(argv,"hbf:l:",["bad","file=","lib="]) + opts, args = getopt.getopt(argv, "hbf:l:", ["bad", "file=", "lib="]) except getopt.GetoptError: print(usage) sys.exit(2) @@ -47,7 +48,7 @@ def main(argv): elif opt in ("-b", "--bad"): generate_badjson_jsons = True - file_text = '{\n' + file_text = '{\n' file_text += ' "file_format_version": "%s",\n' % cur_runtime_json_version file_text += ' "runtime": {\n' file_text += ' "library_path": "%s",\n' % library_location @@ -66,7 +67,7 @@ def main(argv): # Missing bad_name = '_badjson_file_ver_missing.json' - file_text = '{\n' + file_text = '{\n' file_text += ' "runtime": {\n' file_text += ' "library_path": "%s",\n' % library_location file_text += ' }\n' @@ -78,7 +79,7 @@ def main(argv): # Use int bad_name = '_badjson_file_ver_int.json' - file_text = '{\n' + file_text = '{\n' file_text += ' "file_format_version": 1,\n' file_text += ' "runtime": {\n' file_text += ' "library_path": "%s",\n' % library_location @@ -91,7 +92,7 @@ def main(argv): # Use invalid string bad_name = '_badjson_file_ver_string.json' - file_text = '{\n' + file_text = '{\n' file_text += ' "file_format_version": "invalid string",\n' file_text += ' "runtime": {\n' file_text += ' "library_path": "%s",\n' % library_location @@ -104,7 +105,7 @@ def main(argv): # Too low of a version bad_name = '_badjson_file_ver_all_low.json' - file_text = '{\n' + file_text = '{\n' file_text += ' "file_format_version": "0.0.0",\n' file_text += ' "runtime": {\n' file_text += ' "library_path": "%s",\n' % library_location @@ -117,7 +118,7 @@ def main(argv): # Too high of a major version bad_name = '_badjson_file_ver_major_high.json' - file_text = '{\n' + file_text = '{\n' file_text += ' "file_format_version": "15.0.0",\n' file_text += ' "runtime": {\n' file_text += ' "library_path": "%s",\n' % library_location @@ -130,7 +131,7 @@ def main(argv): # Too high of a minor version bad_name = '_badjson_file_ver_minor_high.json' - file_text = '{\n' + file_text = '{\n' file_text += ' "file_format_version": "1.15.0",\n' file_text += ' "runtime": {\n' file_text += ' "library_path": "%s",\n' % library_location @@ -146,7 +147,7 @@ def main(argv): # Completely Missing bad_name = '_badjson_runtime_missing.json' - file_text = '{\n' + file_text = '{\n' file_text += ' "file_format_version": "%s",\n' % cur_runtime_json_version file_text += ' "library_path": "%s",\n' % library_location file_text += '}\n' @@ -157,7 +158,7 @@ def main(argv): # Empty bad_name = '_badjson_runtime_empty.json' - file_text = '{\n' + file_text = '{\n' file_text += ' "file_format_version": "%s",\n' % cur_runtime_json_version file_text += ' "runtime": {\n' file_text += ' },\n' @@ -173,7 +174,7 @@ def main(argv): # Missing bad_name = '_badjson_path_missing.json' - file_text = '{\n' + file_text = '{\n' file_text += ' "file_format_version": "%s",\n' % cur_runtime_json_version file_text += ' "runtime": {\n' file_text += ' }\n' @@ -185,7 +186,7 @@ def main(argv): # Use int bad_name = '_badjson_path_int.json' - file_text = '{\n' + file_text = '{\n' file_text += ' "file_format_version": "%s",\n' % cur_runtime_json_version file_text += ' "runtime": {\n' file_text += ' "library_path": 1,\n' @@ -198,10 +199,10 @@ def main(argv): # Replace valid path with invalid one bad_name = '_badjson_path_no_file.json' - file_text = '{\n' + file_text = '{\n' file_text += ' "file_format_version": "%s",\n' % cur_runtime_json_version file_text += ' "runtime": {\n' - file_text += ' "library_path": "%s",\n' % library_location.replace("test_runtimes","not_real") + file_text += ' "library_path": "%s",\n' % library_location.replace("test_runtimes", "not_real") file_text += ' }\n' file_text += '}\n' bad_file = output_file.replace(".json", bad_name) @@ -214,7 +215,7 @@ def main(argv): # Always fail negotiate bad_name = '_badnegotiate_always.json' - file_text = '{\n' + file_text = '{\n' file_text += ' "file_format_version": "%s",\n' % cur_runtime_json_version file_text += ' "runtime": {\n' file_text += ' "library_path": "%s",\n' % library_location @@ -231,7 +232,7 @@ def main(argv): # Pass negotiate, but return null GIPA bad_name = '_badnegotiate_invalid_gipa.json' - file_text = '{\n' + file_text = '{\n' file_text += ' "file_format_version": "%s",\n' % cur_runtime_json_version file_text += ' "runtime": {\n' file_text += ' "library_path": "%s",\n' % library_location @@ -248,7 +249,7 @@ def main(argv): # Pass negotiate, but return invalid interface version bad_name = '_badnegotiate_invalid_interface.json' - file_text = '{\n' + file_text = '{\n' file_text += ' "file_format_version": "%s",\n' % cur_runtime_json_version file_text += ' "runtime": {\n' file_text += ' "library_path": "%s",\n' % library_location @@ -265,7 +266,7 @@ def main(argv): # Pass negotiate, but return invalid api version bad_name = '_badnegotiate_invalid_api.json' - file_text = '{\n' + file_text = '{\n' file_text += ' "file_format_version": "%s",\n' % cur_runtime_json_version file_text += ' "runtime": {\n' file_text += ' "library_path": "%s",\n' % library_location @@ -280,5 +281,6 @@ def main(argv): f.write(file_text) f.close() + if __name__ == "__main__": main(sys.argv[1:]) diff --git a/src/scripts/loader_source_generator.py b/src/scripts/loader_source_generator.py index 188865c0..83f26e73 100755 --- a/src/scripts/loader_source_generator.py +++ b/src/scripts/loader_source_generator.py @@ -16,6 +16,8 @@ # automatic_source_generator.py class to produce the # generated source code for the loader. +import dataclasses + from automatic_source_generator import (AutomaticSourceOutputGenerator, undecorate) from generator import write @@ -235,32 +237,21 @@ 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 - (pointer_count == 0)) + assert ((not cur_cmd.is_destroy_disconnect) or + (pointer_count == 0)) else: tramp_variable_defines += self.printCodeGenErrorMessage( 'Command %s does not have an OpenXR Object handle as the first parameter.' % cur_cmd.name) tramp_param_replace.append( - self.MemberOrParam(type=param.type, + dataclasses.replace(param, name=cmd_tramp_param_name, is_const=is_const, is_handle=cmd_tramp_is_handle, - is_bool=param.is_bool, - is_optional=param.is_optional, - no_auto_validity=param.no_auto_validity, - is_array=param.is_array, - is_static_array=param.is_static_array, static_array_sizes=static_array_sizes, array_dimen=array_dimen, - array_count_var=param.array_count_var, - array_length_for=param.array_length_for, pointer_count=pointer_count, - pointer_count_var=param.pointer_count_var, - is_null_terminated=param.is_null_terminated, - valid_extension_structs=None, - cdecl=param.cdecl, - values=param.values)) + valid_extension_structs=None)) count = count + 1 if cur_cmd.protect_value: diff --git a/src/scripts/src_genxr.py b/src/scripts/src_genxr.py index 922e9d90..b90248f9 100755 --- a/src/scripts/src_genxr.py +++ b/src/scripts/src_genxr.py @@ -93,8 +93,8 @@ def makeGenOpts(args): allFeatures = allExtensions = r'.*' # Turn lists of names/patterns into matching regular expressions - emitExtensionsPat = makeREstring(emitExtensions, allExtensions) - featuresPat = makeREstring(features, allFeatures) + emitExtensionsPat = makeREstring(emitExtensions, allExtensions) + featuresPat = makeREstring(features, allFeatures) # REUSE-IgnoreStart # Copyright text prefixing all headers (list of strings). @@ -134,64 +134,64 @@ def makeGenOpts(args): genOpts['function_info.cpp'] = [ ConformanceGenerator, AutomaticSourceGeneratorOptions( - conventions = conventions, - filename = 'function_info.cpp', - directory = directory, - apiname = 'openxr', - profile = None, - versions = featuresPat, - emitversions = featuresPat, - defaultExtensions = 'openxr', - addExtensions = None, - removeExtensions = None, - emitExtensions = emitExtensionsPat, - prefixText = prefixStrings + xrPrefixStrings, - protectFeature = False, - protectProto = '#ifndef', - protectProtoStr = 'XR_NO_PROTOTYPES', - apicall = 'XRAPI_ATTR ', - apientry = 'XRAPI_CALL ', - apientryp = 'XRAPI_PTR *', - alignFuncParam = 48) - ] + conventions=conventions, + filename='function_info.cpp', + directory=directory, + apiname='openxr', + profile=None, + versions=featuresPat, + emitversions=featuresPat, + defaultExtensions='openxr', + addExtensions=None, + removeExtensions=None, + emitExtensions=emitExtensionsPat, + prefixText=prefixStrings + xrPrefixStrings, + protectFeature=False, + protectProto='#ifndef', + protectProtoStr='XR_NO_PROTOTYPES', + apicall='XRAPI_ATTR ', + apientry='XRAPI_CALL ', + apientryp='XRAPI_PTR *', + alignFuncParam=48) + ] genOpts['gen_dispatch.cpp'] = [ ConformanceLayerGenerator, AutomaticSourceGeneratorOptions( - conventions = conventions, - filename = 'gen_dispatch.cpp', - directory = directory, - apiname = 'openxr', - profile = None, - versions = featuresPat, - emitversions = featuresPat, - defaultExtensions = 'openxr', - addExtensions = None, - removeExtensions = None, - emitExtensions = emitExtensionsPat, - apicall = 'XRAPI_ATTR ', - apientry = 'XRAPI_CALL ', - apientryp = 'XRAPI_PTR *',) - ] + conventions=conventions, + filename='gen_dispatch.cpp', + directory=directory, + apiname='openxr', + profile=None, + versions=featuresPat, + emitversions=featuresPat, + defaultExtensions='openxr', + addExtensions=None, + removeExtensions=None, + emitExtensions=emitExtensionsPat, + apicall='XRAPI_ATTR ', + apientry='XRAPI_CALL ', + apientryp='XRAPI_PTR *',) + ] genOpts['gen_dispatch.h'] = [ ConformanceLayerGenerator, AutomaticSourceGeneratorOptions( - conventions = conventions, - filename = 'gen_dispatch.h', - directory = directory, - apiname = 'openxr', - profile = None, - versions = featuresPat, - emitversions = featuresPat, - defaultExtensions = 'openxr', - addExtensions = None, - removeExtensions = None, - emitExtensions = emitExtensionsPat, - apicall = 'XRAPI_ATTR ', - apientry = 'XRAPI_CALL ', - apientryp = 'XRAPI_PTR *',) - ] + conventions=conventions, + filename='gen_dispatch.h', + directory=directory, + apiname='openxr', + profile=None, + versions=featuresPat, + emitversions=featuresPat, + defaultExtensions='openxr', + addExtensions=None, + removeExtensions=None, + emitExtensions=emitExtensionsPat, + apicall='XRAPI_ATTR ', + apientry='XRAPI_CALL ', + apientryp='XRAPI_PTR *',) + ] DISPATCH_TABLE_FILES = [ 'xr_generated_dispatch_table.h', @@ -202,146 +202,147 @@ def makeGenOpts(args): for filename in DISPATCH_TABLE_FILES: genOpts[filename] = [ - UtilitySourceOutputGenerator, - AutomaticSourceGeneratorOptions( - conventions = conventions, - filename = filename, - directory = directory, - apiname = 'openxr', - profile = None, - versions = featuresPat, - emitversions = featuresPat, - defaultExtensions = 'openxr', - addExtensions = None, - removeExtensions = None, - emitExtensions = emitExtensionsPat) - ] + UtilitySourceOutputGenerator, + AutomaticSourceGeneratorOptions( + conventions=conventions, + filename=filename, + directory=directory, + apiname='openxr', + profile=None, + versions=featuresPat, + emitversions=featuresPat, + defaultExtensions='openxr', + addExtensions=None, + removeExtensions=None, + emitExtensions=emitExtensionsPat) + ] genOpts['xr_generated_loader.hpp'] = [ - LoaderSourceOutputGenerator, - AutomaticSourceGeneratorOptions( - conventions = conventions, - filename = 'xr_generated_loader.hpp', - directory = directory, - apiname = 'openxr', - profile = None, - versions = featuresPat, - emitversions = featuresPat, - defaultExtensions = 'openxr', - addExtensions = None, - removeExtensions = None, - emitExtensions = emitExtensionsPat, - prefixText = prefixStrings + xrPrefixStrings, - protectFeature = False, - protectProto = '#ifndef', - protectProtoStr = 'XR_NO_PROTOTYPES', - apicall = 'XRAPI_ATTR ', - apientry = 'XRAPI_CALL ', - apientryp = 'XRAPI_PTR *', - alignFuncParam = 48) - ] + LoaderSourceOutputGenerator, + AutomaticSourceGeneratorOptions( + conventions=conventions, + filename='xr_generated_loader.hpp', + directory=directory, + apiname='openxr', + profile=None, + versions=featuresPat, + emitversions=featuresPat, + defaultExtensions='openxr', + addExtensions=None, + removeExtensions=None, + emitExtensions=emitExtensionsPat, + prefixText=prefixStrings + xrPrefixStrings, + protectFeature=False, + protectProto='#ifndef', + protectProtoStr='XR_NO_PROTOTYPES', + apicall='XRAPI_ATTR ', + apientry='XRAPI_CALL ', + apientryp='XRAPI_PTR *', + alignFuncParam=48) + ] genOpts['xr_generated_loader.cpp'] = [ - LoaderSourceOutputGenerator, - AutomaticSourceGeneratorOptions( - conventions = conventions, - filename = 'xr_generated_loader.cpp', - directory = directory, - apiname = 'openxr', - profile = None, - versions = featuresPat, - emitversions = featuresPat, - defaultExtensions = 'openxr', - addExtensions = None, - removeExtensions = None, - emitExtensions = emitExtensionsPat, - prefixText = prefixStrings + xrPrefixStrings, - protectFeature = False, - protectProto = '#ifndef', - protectProtoStr = 'XR_NO_PROTOTYPES', - apicall = 'XRAPI_ATTR ', - apientry = 'XRAPI_CALL ', - apientryp = 'XRAPI_PTR *', - alignFuncParam = 48) - ] + LoaderSourceOutputGenerator, + AutomaticSourceGeneratorOptions( + conventions=conventions, + filename='xr_generated_loader.cpp', + directory=directory, + apiname='openxr', + profile=None, + versions=featuresPat, + emitversions=featuresPat, + defaultExtensions='openxr', + addExtensions=None, + removeExtensions=None, + emitExtensions=emitExtensionsPat, + prefixText=prefixStrings + xrPrefixStrings, + protectFeature=False, + protectProto='#ifndef', + protectProtoStr='XR_NO_PROTOTYPES', + apicall='XRAPI_ATTR ', + apientry='XRAPI_CALL ', + apientryp='XRAPI_PTR *', + alignFuncParam=48) + ] # Source files generated for the api_dump layer genOpts['xr_generated_api_dump.cpp'] = [ - ApiDumpOutputGenerator, - AutomaticSourceGeneratorOptions( - conventions = conventions, - filename = 'xr_generated_api_dump.cpp', - directory = directory, - apiname = 'openxr', - profile = None, - versions = featuresPat, - emitversions = featuresPat, - defaultExtensions = 'openxr', - addExtensions = None, - removeExtensions = None, - emitExtensions = emitExtensionsPat, - apicall = 'XRAPI_ATTR ', - apientry = 'XRAPI_CALL ', - apientryp = 'XRAPI_PTR *') - ] + ApiDumpOutputGenerator, + AutomaticSourceGeneratorOptions( + conventions=conventions, + filename='xr_generated_api_dump.cpp', + directory=directory, + apiname='openxr', + profile=None, + versions=featuresPat, + emitversions=featuresPat, + defaultExtensions='openxr', + addExtensions=None, + removeExtensions=None, + emitExtensions=emitExtensionsPat, + apicall='XRAPI_ATTR ', + apientry='XRAPI_CALL ', + apientryp='XRAPI_PTR *') + ] genOpts['xr_generated_api_dump.hpp'] = [ - ApiDumpOutputGenerator, - AutomaticSourceGeneratorOptions( - conventions = conventions, - filename = 'xr_generated_api_dump.hpp', - directory = directory, - apiname = 'openxr', - profile = None, - versions = featuresPat, - emitversions = featuresPat, - defaultExtensions = 'openxr', - addExtensions = None, - removeExtensions = None, - emitExtensions = emitExtensionsPat, - apicall = 'XRAPI_ATTR ', - apientry = 'XRAPI_CALL ', - apientryp = 'XRAPI_PTR *') - ] + ApiDumpOutputGenerator, + AutomaticSourceGeneratorOptions( + conventions=conventions, + filename='xr_generated_api_dump.hpp', + directory=directory, + apiname='openxr', + profile=None, + versions=featuresPat, + emitversions=featuresPat, + defaultExtensions='openxr', + addExtensions=None, + removeExtensions=None, + emitExtensions=emitExtensionsPat, + apicall='XRAPI_ATTR ', + apientry='XRAPI_CALL ', + apientryp='XRAPI_PTR *') + ] # Source files generated for the core validation layer genOpts['xr_generated_core_validation.hpp'] = [ - ValidationSourceOutputGenerator, - AutomaticSourceGeneratorOptions( - conventions = conventions, - filename = 'xr_generated_core_validation.hpp', - directory = directory, - apiname = 'openxr', - profile = None, - versions = featuresPat, - emitversions = featuresPat, - defaultExtensions = 'openxr', - addExtensions = None, - removeExtensions = None, - emitExtensions = emitExtensionsPat, - apicall = 'XRAPI_ATTR ', - apientry = 'XRAPI_CALL ', - apientryp = 'XRAPI_PTR *') - ] + ValidationSourceOutputGenerator, + AutomaticSourceGeneratorOptions( + conventions=conventions, + filename='xr_generated_core_validation.hpp', + directory=directory, + apiname='openxr', + profile=None, + versions=featuresPat, + emitversions=featuresPat, + defaultExtensions='openxr', + addExtensions=None, + removeExtensions=None, + emitExtensions=emitExtensionsPat, + apicall='XRAPI_ATTR ', + apientry='XRAPI_CALL ', + apientryp='XRAPI_PTR *') + ] genOpts['xr_generated_core_validation.cpp'] = [ - ValidationSourceOutputGenerator, - AutomaticSourceGeneratorOptions( - conventions = conventions, - filename = 'xr_generated_core_validation.cpp', - directory = directory, - apiname = 'openxr', - profile = None, - versions = featuresPat, - emitversions = featuresPat, - defaultExtensions = 'openxr', - addExtensions = None, - removeExtensions = None, - emitExtensions = emitExtensionsPat, - apicall = 'XRAPI_ATTR ', - apientry = 'XRAPI_CALL ', - apientryp = 'XRAPI_PTR *') - ] + ValidationSourceOutputGenerator, + AutomaticSourceGeneratorOptions( + conventions=conventions, + filename='xr_generated_core_validation.cpp', + directory=directory, + apiname='openxr', + profile=None, + versions=featuresPat, + emitversions=featuresPat, + defaultExtensions='openxr', + addExtensions=None, + removeExtensions=None, + emitExtensions=emitExtensionsPat, + apicall='XRAPI_ATTR ', + apientry='XRAPI_CALL ', + apientryp='XRAPI_PTR *') + ] + def genTarget(args): """Create an API generator and corresponding generator options based on @@ -383,6 +384,7 @@ def genTarget(args): args.target, file=sys.stderr) sys.exit(1) + # -feature name # -extension name # For both, "name" may be a single name, or a space-separated list @@ -453,7 +455,7 @@ def genTarget(args): if args.time: # Log diagnostics and warnings - setLogFile(setDiag = True, setWarn = True, filename = '-') + setLogFile(setDiag=True, setWarn=True, filename='-') # Create the API generator & generator options (gen, options) = genTarget(args) diff --git a/src/scripts/template_gen_dispatch.cpp b/src/scripts/template_gen_dispatch.cpp index e7065971..91a8049c 100644 --- a/src/scripts/template_gen_dispatch.cpp +++ b/src/scripts/template_gen_dispatch.cpp @@ -192,6 +192,11 @@ XRAPI_ATTR XrResult XRAPI_CALL ConformanceLayer_xrGetInstanceProcAddr( const char* name, PFN_xrVoidFunction* function) try { + if (instance == XR_NULL_HANDLE) { + *function = nullptr; + return XR_ERROR_FUNCTION_UNSUPPORTED; + } + HandleState* const handleState = GetHandleState({ HandleToInt(instance), XR_OBJECT_TYPE_INSTANCE }); *function = ConformanceLayer_InnerGetInstanceProcAddr(name, handleState); 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..8f683241 100644 --- a/src/scripts/validation_layer_generator.py +++ b/src/scripts/validation_layer_generator.py @@ -24,7 +24,7 @@ import re from automatic_source_generator import (AutomaticSourceOutputGenerator, - undecorate) + undecorate, MemberOrParam) from generator import write # The following commands have a manually defined component to them. @@ -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 @@ -1384,7 +1387,7 @@ def writeValidateInlineHandleValidation(self, cmd_name, vuid_name, member_param, inline_validate_handle += '}\n' return inline_validate_handle - def outputParamMemberContents(self, is_command, struct_command_name, param_member, param_member_prefix, instance_info_variable, + def outputParamMemberContents(self, is_command, struct_command_name, param_member: MemberOrParam, param_member_prefix, instance_info_variable, command_name_variable, is_first_param, primary_handle, primary_handle_desc_name, primary_handle_tuple, wrote_handle_proto, indent): param_member_contents = '' @@ -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) diff --git a/src/version.cmake b/src/version.cmake index 5e64c70d..3a6e0398 100644 --- a/src/version.cmake +++ b/src/version.cmake @@ -21,28 +21,76 @@ set(PATCH "0") set(OPENXR_SDK_HOTFIX_VERSION) if(EXISTS "${PROJECT_SOURCE_DIR}/specification/registry/xr.xml") - file(STRINGS ${PROJECT_SOURCE_DIR}/specification/registry/xr.xml lines REGEX "#define XR_CURRENT_API_VERSION") + file( + STRINGS ${PROJECT_SOURCE_DIR}/specification/registry/xr.xml lines + REGEX "#define XR_CURRENT_API_VERSION" + ) else() - file(STRINGS ${PROJECT_SOURCE_DIR}/include/openxr/openxr.h lines REGEX "#define XR_CURRENT_API_VERSION") + file( + STRINGS ${PROJECT_SOURCE_DIR}/include/openxr/openxr.h lines + REGEX "#define XR_CURRENT_API_VERSION" + ) endif() list(LENGTH lines len) if(${len} EQUAL 1) - list(GET lines 0 cur_line) + list( + GET + lines + 0 + cur_line + ) # Grab just the stuff in the parentheses of XR_MAKE_VERSION( ), # by replacing the whole line with the stuff in the parentheses - string(REGEX REPLACE "^.+\\(([^\)]+)\\).+$" "\\1" VERSION_WITH_WHITESPACE ${cur_line}) + string( + REGEX + REPLACE + "^.+\\(([^\)]+)\\).+$" + "\\1" + VERSION_WITH_WHITESPACE + ${cur_line} + ) # Remove whitespace - string(REPLACE " " "" VERSION_NO_WHITESPACE ${VERSION_WITH_WHITESPACE}) + string( + REPLACE + " " + "" + VERSION_NO_WHITESPACE + ${VERSION_WITH_WHITESPACE} + ) # Grab components - string(REGEX REPLACE "^([0-9]+)\\,[0-9]+\\,[0-9]+" "\\1" MAJOR "${VERSION_NO_WHITESPACE}") - string(REGEX REPLACE "^[0-9]+\\,([0-9]+)\\,[0-9]+" "\\1" MINOR "${VERSION_NO_WHITESPACE}") - string(REGEX REPLACE "^[0-9]+\\,[0-9]+\\,([0-9]+)" "\\1" PATCH "${VERSION_NO_WHITESPACE}") + string( + REGEX + REPLACE + "^([0-9]+)\\,[0-9]+\\,[0-9]+" + "\\1" + MAJOR + "${VERSION_NO_WHITESPACE}" + ) + string( + REGEX + REPLACE + "^[0-9]+\\,([0-9]+)\\,[0-9]+" + "\\1" + MINOR + "${VERSION_NO_WHITESPACE}" + ) + string( + REGEX + REPLACE + "^[0-9]+\\,[0-9]+\\,([0-9]+)" + "\\1" + PATCH + "${VERSION_NO_WHITESPACE}" + ) else() - message(FATAL_ERROR "Unable to fetch major/minor/patch version from registry or header") + message( + FATAL_ERROR + "Unable to fetch major/minor/patch version from registry or header" + ) endif() # Check for an SDK hotfix version indicator file.