diff --git a/.github/workflows/build-vs-package.yml b/.github/workflows/build-vs-package.yml index 5c935a1f05..2590e52744 100644 --- a/.github/workflows/build-vs-package.yml +++ b/.github/workflows/build-vs-package.yml @@ -1,18 +1,6 @@ name: Build Visual Studio Package on: - pull_request: - paths: - - '.github/workflows/build-vs-package.yml' - - 'build/Stride.VisualStudio.sln' - - 'sources/tools/Stride.VisualStudio.*/**' - - 'sources/targets/**' - - '!**/.all-contributorsrc' - - '!**/.editorconfig' - - '!**/.gitignore' - - '!**/*.md' - - '!crowdin.yml' - types: [opened, synchronize, reopened, ready_for_review] workflow_dispatch: inputs: build-type: diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 4da35cb0c7..f6de2d5bda 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -94,14 +94,16 @@ jobs: graphics-api: ${{ matrix.graphics-api }} ### Tests ### - Windows-Tests: + Windows-Tests-Simple: needs: Windows-Runtime - strategy: - matrix: - build-type: [Release] - test-category: [Simple, Game] - uses: ./.github/workflows/test-windows.yml + uses: ./.github/workflows/test-windows-simple.yml secrets: inherit with: - build-type: ${{ matrix.build-type }} - test-category: ${{ matrix.test-category }} + build-type: Debug + + Windows-Tests-Game: + needs: Windows-Runtime + uses: ./.github/workflows/test-windows-game.yml + secrets: inherit + with: + build-type: Debug diff --git a/.github/workflows/test-gpu-warp.yml b/.github/workflows/test-gpu-warp.yml deleted file mode 100644 index d0dc7dcd54..0000000000 --- a/.github/workflows/test-gpu-warp.yml +++ /dev/null @@ -1,71 +0,0 @@ -name: Test GPU (WARP) - -on: - push: - workflow_dispatch: - -env: - STRIDE_GRAPHICS_SOFTWARE_RENDERING: "1" - -jobs: - GPU-WARP-Test: - name: GPU Tests (D3D11 WARP) - runs-on: windows-2025-vs2026 - steps: - - uses: actions/checkout@v4 - with: - lfs: true - - - uses: actions/setup-dotnet@v4 - with: - dotnet-version: '10.0.x' - - - name: Build Graphics Tests - run: | - dotnet build sources\engine\Stride.Graphics.Tests\Stride.Graphics.Tests.Windows.csproj ` - -p:StrideNativeBuildMode=Clang ` - -m:1 -nr:false ` - -v:m -p:WarningLevel=0 ` - -p:Configuration=Debug ` - -p:StridePlatforms=Windows ` - -p:StrideGraphicsApis=Direct3D11 - dotnet build sources\engine\Stride.Graphics.Tests.10_0\Stride.Graphics.Tests.10_0.Windows.csproj ` - -p:StrideNativeBuildMode=Clang ` - -m:1 -nr:false ` - -v:m -p:WarningLevel=0 ` - -p:Configuration=Debug ` - -p:StridePlatforms=Windows ` - -p:StrideGraphicsApis=Direct3D11 - - - name: Run Graphics Tests (WARP) - run: | - dotnet test sources\engine\Stride.Graphics.Tests\Stride.Graphics.Tests.Windows.csproj ` - --no-build ` - -p:Configuration=Debug ` - --logger "trx;LogFileName=test-results.trx" ` - -- xunit.parallelizeTestCollections=false - - - name: Run Graphics Tests 10_0 (WARP) - if: always() - run: | - dotnet test sources\engine\Stride.Graphics.Tests.10_0\Stride.Graphics.Tests.10_0.Windows.csproj ` - --no-build ` - -p:Configuration=Debug ` - --logger "trx;LogFileName=test-results-10_0.trx" ` - -- xunit.parallelizeTestCollections=false - - - name: Upload test results - if: always() - uses: actions/upload-artifact@v4 - with: - name: test-results-warp - path: '**/*.trx' - retention-days: 7 - - - name: Upload rendered images - if: always() - uses: actions/upload-artifact@v4 - with: - name: rendered-images-warp - path: tests/local/**/WARP/** - retention-days: 7 diff --git a/.github/workflows/test-windows-game.yml b/.github/workflows/test-windows-game.yml new file mode 100644 index 0000000000..c69be821eb --- /dev/null +++ b/.github/workflows/test-windows-game.yml @@ -0,0 +1,175 @@ +name: Test Windows (Game) + +on: + workflow_dispatch: + inputs: + build-type: + description: Build + default: Debug + type: choice + options: + - Debug + - Release + graphics-api: + description: Graphics API (blank = all) + default: '' + type: choice + options: + - '' + - Direct3D11 + - Direct3D12 + - Vulkan + workflow_call: + inputs: + build-type: + default: Debug + type: string + +concurrency: + group: test-windows-game-${{ github.event.pull_request.number || github.ref }}-${{ github.event.inputs.build-type || inputs.build-type || 'Debug' }} + cancel-in-progress: true + +jobs: + # + # Game tests - API-independent projects (run once) + # + Game-Common: + name: Game Common (${{ github.event.inputs.build-type || inputs.build-type || 'Debug' }}) + runs-on: windows-2025-vs2026 + env: + STRIDE_GRAPHICS_SOFTWARE_RENDERING: "1" + STRIDE_TESTS_RENDERDOC: "error" + STRIDE_MAX_PARALLELISM: "8" + steps: + - uses: actions/checkout@v4 + with: + lfs: true + - uses: actions/setup-dotnet@v4 + with: + dotnet-version: '10.0.x' + - name: Disable WER dialogs + run: reg add "HKLM\SOFTWARE\Microsoft\Windows\Windows Error Reporting" /v DontShowUI /t REG_DWORD /d 1 /f + - name: Build + run: | + dotnet build build\Stride.Tests.Game.slnf ` + -p:StrideNativeBuildMode=Clang ` + -m:1 -nr:false ` + -v:m -p:WarningLevel=0 ` + -p:Configuration=${{ github.event.inputs.build-type || inputs.build-type || 'Debug' }} ` + -p:StridePlatforms=Windows ` + -p:StrideGraphicsApis=Direct3D11 + - name: Test + run: | + dotnet test build\Stride.Tests.Game.slnf ` + --no-build ` + -p:Configuration=${{ github.event.inputs.build-type || inputs.build-type || 'Debug' }} + - name: Upload test artifacts + if: always() + uses: actions/upload-artifact@v4 + with: + name: test-artifacts-game-common + path: tests/local/ + if-no-files-found: ignore + + # + # Game tests - Graphics API-dependent projects (run per API) + # + Game-GraphicsApi: + name: Game ${{ matrix.graphics-api }} (${{ github.event.inputs.build-type || inputs.build-type || 'Debug' }}) + runs-on: windows-2025-vs2026 + strategy: + fail-fast: false + matrix: + graphics-api: ${{ github.event.inputs.graphics-api != '' && fromJson(format('["{0}"]', github.event.inputs.graphics-api)) || fromJson('["Direct3D11","Direct3D12","Vulkan"]') }} + env: + STRIDE_GRAPHICS_SOFTWARE_RENDERING: "1" + STRIDE_TESTS_RENDERDOC: "error" + STRIDE_MAX_PARALLELISM: "8" + # Crash dump collection (covers 3 crash types): + STRIDE_TESTS_CRASH_DUMPS: "1" # Type 1: SEHException logging + minidump via FirstChanceException + STRIDE_TESTS_CRASH_DUMP_DIR: "${{ github.workspace }}\\crash-dumps" # Shared dump directory for all crash types + DOTNET_DbgEnableMiniDump: "1" # Type 2: .NET unhandled exceptions + DOTNET_DbgMiniDumpType: "1" # MiniDumpNormal + DOTNET_DbgMiniDumpName: "${{ github.workspace }}\\crash-dumps\\dotnet_%p.dmp" # Type 2: dump path + steps: + - uses: actions/checkout@v4 + with: + lfs: true + - uses: actions/setup-dotnet@v4 + with: + dotnet-version: '10.0.x' + - name: Configure crash dumps + shell: pwsh + run: | + # WER DontShowUI: suppress dialogs without disabling dump generation + reg add "HKLM\SOFTWARE\Microsoft\Windows\Windows Error Reporting" /v DontShowUI /t REG_DWORD /d 1 /f + # WER LocalDumps: capture native crashes (type 3) + $dumpDir = "${{ github.workspace }}\crash-dumps" + New-Item -Path $dumpDir -ItemType Directory -Force | Out-Null + reg add "HKLM\SOFTWARE\Microsoft\Windows\Windows Error Reporting\LocalDumps" /v DumpFolder /t REG_EXPAND_SZ /d $dumpDir /f + reg add "HKLM\SOFTWARE\Microsoft\Windows\Windows Error Reporting\LocalDumps" /v DumpType /t REG_DWORD /d 1 /f + reg add "HKLM\SOFTWARE\Microsoft\Windows\Windows Error Reporting\LocalDumps" /v DumpCount /t REG_DWORD /d 10 /f + - name: Install Vulkan SDK + if: matrix.graphics-api == 'Vulkan' + uses: jakoch/install-vulkan-sdk-action@v1 + with: + vulkan_version: 1.4.304.1 + install_runtime: true + cache: true + stripdown: true + - name: Register SwiftShader ICD + if: matrix.graphics-api == 'Vulkan' + shell: pwsh + run: | + # Restore with StrideGraphicsApi=Vulkan so the conditional SwiftShader PackageReference is evaluated + # (Stride's custom multi-build targets don't run during restore) + dotnet restore build\Stride.Tests.Game.GPU.slnf -p:StrideGraphicsApis=Vulkan -p:StrideGraphicsApi=Vulkan + # Find SwiftShader DLL in NuGet cache and register an ICD before build + # (CompilerApp needs SwiftShader at build time for Skybox asset compilation) + $dll = Get-ChildItem -Recurse -Path "$env:USERPROFILE\.nuget\packages\stride.dependencies.swiftshader" -Filter vk_swiftshader.dll -ErrorAction SilentlyContinue | Select-Object -First 1 + if ($dll) { + $icdPath = Join-Path $dll.DirectoryName "vk_swiftshader_icd.json" + $dllPath = $dll.FullName -replace '\\', '\\\\' + Set-Content -Path $icdPath -Value "{`"file_format_version`":`"1.0.0`",`"ICD`":{`"library_path`":`"$dllPath`",`"api_version`":`"1.0.5`"}}" + New-Item -Path "HKLM:\SOFTWARE\Khronos\Vulkan\Drivers" -Force | Out-Null + New-ItemProperty -Path "HKLM:\SOFTWARE\Khronos\Vulkan\Drivers" -Name $icdPath -Value 0 -PropertyType DWord -Force | Out-Null + Write-Host "Registered SwiftShader ICD: $icdPath" + } else { + Write-Warning "SwiftShader DLL not found in NuGet cache" + } + - name: Build + run: | + dotnet build build\Stride.Tests.Game.GPU.slnf ` + -p:StrideNativeBuildMode=Clang ` + -m:1 -nr:false ` + -v:m -p:WarningLevel=0 ` + -p:Configuration=${{ github.event.inputs.build-type || inputs.build-type || 'Debug' }} ` + -p:StridePlatforms=Windows ` + -p:StrideGraphicsApis=${{ matrix.graphics-api }} + - name: Test + run: | + dotnet test build\Stride.Tests.Game.GPU.slnf ` + --no-build ` + -p:Configuration=${{ github.event.inputs.build-type || inputs.build-type || 'Debug' }} ` + -p:StrideGraphicsApis=${{ matrix.graphics-api }} ` + -- RunConfiguration.MaxCpuCount=1 + - name: Collect symbols for crash analysis + if: always() + shell: pwsh + run: | + $dumpDir = "${{ github.workspace }}\crash-dumps" + if (Get-ChildItem $dumpDir -Filter *.dmp -ErrorAction SilentlyContinue) { + $symDir = Join-Path $dumpDir "symbols" + New-Item -Path $symDir -ItemType Directory -Force | Out-Null + Get-ChildItem bin -Recurse -Include *.pdb,*.dll,*.exe | Copy-Item -Destination $symDir + Write-Host "Collected $(( Get-ChildItem $symDir ).Count) symbol files for crash analysis" + } + - name: Upload test artifacts + if: always() + uses: actions/upload-artifact@v4 + with: + name: test-artifacts-game-${{ matrix.graphics-api }} + path: | + tests/local/ + crash-dumps/ + if-no-files-found: ignore diff --git a/.github/workflows/test-windows-simple.yml b/.github/workflows/test-windows-simple.yml new file mode 100644 index 0000000000..a5bdff16fa --- /dev/null +++ b/.github/workflows/test-windows-simple.yml @@ -0,0 +1,47 @@ +name: Test Windows (Simple) + +on: + workflow_dispatch: + inputs: + build-type: + description: Build + default: Debug + type: choice + options: + - Debug + - Release + workflow_call: + inputs: + build-type: + default: Debug + type: string + +concurrency: + group: test-windows-simple-${{ github.event.pull_request.number || github.ref }}-${{ github.event.inputs.build-type || inputs.build-type || 'Debug' }} + cancel-in-progress: true + +jobs: + Windows-Tests: + name: Test (${{ github.event.inputs.build-type || inputs.build-type || 'Debug' }}, Simple) + runs-on: windows-2025-vs2026 + steps: + - uses: actions/checkout@v4 + with: + lfs: true + - uses: actions/setup-dotnet@v4 + with: + dotnet-version: '10.0.x' + - name: Build + run: | + dotnet build build\Stride.Tests.Simple.slnf ` + -p:StrideNativeBuildMode=Clang ` + -m:1 -nr:false ` + -v:m -p:WarningLevel=0 ` + -p:Configuration=${{ github.event.inputs.build-type || inputs.build-type || 'Debug' }} ` + -p:StridePlatforms=Windows ` + -p:StrideGraphicsApis=Direct3D11 + - name: Test + run: | + dotnet test build\Stride.Tests.Simple.slnf ` + --no-build ` + -p:Configuration=${{ github.event.inputs.build-type || inputs.build-type || 'Debug' }} diff --git a/.github/workflows/test-windows.yml b/.github/workflows/test-windows.yml deleted file mode 100644 index 0d32e1fdef..0000000000 --- a/.github/workflows/test-windows.yml +++ /dev/null @@ -1,78 +0,0 @@ -name: Test Windows - -on: - pull_request: - paths: - - '.github/workflows/test-windows.yml' - - 'build/Stride.Tests.*.slnf' - - 'deps/**' - - 'sources/**' - - '!**/.all-contributorsrc' - - '!**/.editorconfig' - - '!**/.gitignore' - - '!**/*.md' - - '!crowdin.yml' - types: [opened, synchronize, reopened, ready_for_review] - workflow_dispatch: - inputs: - build-type: - description: Build - default: Release - type: choice - options: - - Debug - - Release - test-category: - description: Category of tests - default: Simple - type: choice - options: - - Simple - - Game - - VSPackage - workflow_call: - inputs: - build-type: - default: Release - type: string - test-category: - default: Simple - type: string - -concurrency: - group: test-windows-${{ github.event.pull_request.number || github.ref }}-${{ github.event.inputs.build-type || inputs.build-type || 'Debug' }}-${{ github.event.inputs.test-category || inputs.test-category || 'Simple' }} - cancel-in-progress: true - -jobs: - # - # Test Stride on Windows - # - Windows-Tests: - if: ${{ github.event_name == 'workflow_dispatch' || github.event_name == 'workflow_call' || github.event.pull_request.draft == false }} - name: Test (${{ github.event.inputs.build-type || inputs.build-type || 'Debug' }}, ${{ github.event.inputs.test-category || inputs.test-category || 'Simple' }}) - runs-on: windows-2025-vs2026 - steps: - - uses: actions/checkout@v4 - with: - lfs: true - - uses: actions/setup-dotnet@v4 - with: - dotnet-version: '10.0.x' - - name: Build - run: | - dotnet build build\Stride.Tests.${{ github.event.inputs.test-category || inputs.test-category || 'Simple' }}.slnf ` - -p:StrideNativeBuildMode=Clang ` - -m:1 -nr:false ` - -v:m -p:WarningLevel=0 ` - -p:Configuration=${{ github.event.inputs.build-type || inputs.build-type || 'Debug' }} ` - -p:StridePlatforms=Windows ` - -p:StrideGraphicsApis=Direct3D11 - env: - STRIDE_GRAPHICS_SOFTWARE_RENDERING: ${{ (github.event.inputs.test-category || inputs.test-category || 'Simple') == 'Game' && '1' || '0' }} - - name: Test - run: | - dotnet test build\Stride.Tests.${{ github.event.inputs.test-category || inputs.test-category || 'Simple' }}.slnf ` - --no-build ` - -p:Configuration=${{ github.event.inputs.build-type || inputs.build-type || 'Debug' }} - env: - STRIDE_GRAPHICS_SOFTWARE_RENDERING: ${{ (github.event.inputs.test-category || inputs.test-category || 'Simple') == 'Game' && '1' || '0' }} diff --git a/README.md b/README.md index c7e6af41a8..ab81b20f07 100644 --- a/README.md +++ b/README.md @@ -58,7 +58,7 @@ Our [Roadmap](https://doc.stride3d.net/latest/en/contributors/roadmap.html) comm 1. **Clone the repository** using a Git UI client or from the command line: ```bash - git lfs clone https://github.com/stride3d/stride.git + git clone https://github.com/stride3d/stride.git ``` 2. **Open the solution:** - Open `\build\Stride.sln` with Visual Studio 2026. diff --git a/build/GameTests-GPU.runsettings b/build/GameTests-GPU.runsettings new file mode 100644 index 0000000000..87e6a19f41 --- /dev/null +++ b/build/GameTests-GPU.runsettings @@ -0,0 +1,14 @@ + + + + + + 1 + + + diff --git a/build/Stride.Tests.Game.GPU.slnf b/build/Stride.Tests.Game.GPU.slnf new file mode 100644 index 0000000000..1feb3234c8 --- /dev/null +++ b/build/Stride.Tests.Game.GPU.slnf @@ -0,0 +1,13 @@ +{ + "solution": { + "path": "Stride.sln", + "projects": [ + "..\\sources\\engine\\Stride.Graphics.Tests\\Stride.Graphics.Tests.Windows.csproj", + "..\\sources\\engine\\Stride.Graphics.Tests.10_0\\Stride.Graphics.Tests.10_0.Windows.csproj", + "..\\sources\\engine\\Stride.Graphics.Tests.11_0\\Stride.Graphics.Tests.11_0.Windows.csproj", + "..\\sources\\engine\\Stride.Engine.Tests\\Stride.Engine.Tests.Windows.csproj", + "..\\sources\\engine\\Stride.Physics.Tests\\Stride.Physics.Tests.Windows.csproj", + "..\\sources\\engine\\Stride.UI.Tests\\Stride.UI.Tests.Windows.csproj" + ] + } +} diff --git a/build/Stride.Tests.Game.slnf b/build/Stride.Tests.Game.slnf index c19298114f..a18b762e61 100644 --- a/build/Stride.Tests.Game.slnf +++ b/build/Stride.Tests.Game.slnf @@ -5,15 +5,9 @@ "..\\sources\\engine\\Stride.Assets.Tests\\Stride.Assets.Tests.csproj", "..\\sources\\engine\\Stride.Audio.Tests\\Stride.Audio.Tests.Windows.csproj", "..\\sources\\engine\\Stride.Engine.NoAssets.Tests\\Stride.Engine.NoAssets.Tests.Windows.csproj", - "..\\sources\\engine\\Stride.Engine.Tests\\Stride.Engine.Tests.Windows.csproj", - "..\\sources\\engine\\Stride.Graphics.Tests.10_0\\Stride.Graphics.Tests.10_0.Windows.csproj", - "..\\sources\\engine\\Stride.Graphics.Tests.11_0\\Stride.Graphics.Tests.11_0.Windows.csproj", - "..\\sources\\engine\\Stride.Graphics.Tests\\Stride.Graphics.Tests.Windows.csproj", "..\\sources\\engine\\Stride.Input.Tests\\Stride.Input.Tests.Windows.csproj", "..\\sources\\engine\\Stride.Navigation.Tests\\Stride.Navigation.Tests.Windows.csproj", - "..\\sources\\engine\\Stride.Particles.Tests\\Stride.Particles.Tests.Windows.csproj", - "..\\sources\\engine\\Stride.Physics.Tests\\Stride.Physics.Tests.Windows.csproj", - "..\\sources\\engine\\Stride.UI.Tests\\Stride.UI.Tests.Windows.csproj" + "..\\sources\\engine\\Stride.Particles.Tests\\Stride.Particles.Tests.Windows.csproj" ] } -} \ No newline at end of file +} diff --git a/deps/LLVM/README.md b/deps/LLVM/README.md new file mode 100644 index 0000000000..059deec77e --- /dev/null +++ b/deps/LLVM/README.md @@ -0,0 +1,37 @@ +# LLVM Toolchain + +Pre-built LLVM binaries used for native C/C++ compilation in Stride. + +## Version + +**LLVM 22.1.2** (March 2026) + +Source: https://github.com/llvm/llvm-project/releases/tag/llvmorg-22.1.2 +Package: `clang+llvm-22.1.2-x86_64-pc-windows-msvc.tar.xz` + +## Binaries + +| File | Purpose | +|------|---------| +| `clang.exe` | C/C++ compiler | +| `clang-cl.exe` | MSVC-compatible C/C++ compiler driver | +| `lld.exe` | Generic linker | +| `lld-link.exe` | Windows MSVC linker (lld with COFF flavor) | +| `ld.lld.exe` | Unix/ELF linker (lld with ELF flavor) | +| `ld64.lld.exe` | macOS linker (lld with Mach-O flavor, replaces old darwin_ld) | +| `lipo.exe` | Universal binary tool (llvm-lipo, replaces old Apple lipo) | +| `llvm-ar.exe` | Archive/static library tool | + +## Usage + +- **Windows native build**: `clang.exe` + `lld-link.exe` (via `-fuse-ld=lld`) +- **Linux cross-compilation**: `clang.exe` + `ld.lld.exe` +- **macOS cross-compilation**: `clang.exe` + `ld64.lld.exe` +- **iOS cross-compilation**: `clang.exe` + `llvm-ar.exe` + `lipo.exe` + +## Upgrading + +1. Download the latest `clang+llvm-*-x86_64-pc-windows-msvc.tar.xz` from https://github.com/llvm/llvm-project/releases +2. Extract and copy `clang.exe`, `clang-cl.exe`, `lld.exe`, `lld-link.exe`, `ld.lld.exe`, `ld64.lld.exe`, `llvm-ar.exe` to this directory +3. Copy `llvm-lipo.exe` as `lipo.exe` +4. Update this README with the new version diff --git a/deps/LLVM/clang-cl.exe b/deps/LLVM/clang-cl.exe index 362c8f9ea2..9ebb85a423 100644 --- a/deps/LLVM/clang-cl.exe +++ b/deps/LLVM/clang-cl.exe @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:43dd4210d948e24510ad5c431858cc083393ec0c9a4547aaa167439d6629c4c7 -size 61692416 +oid sha256:ac210a076eadbb97f111189a8418665209c3640949505e482062f7bd2f30fbcf +size 108426240 diff --git a/deps/LLVM/clang.exe b/deps/LLVM/clang.exe index 362c8f9ea2..9ebb85a423 100644 --- a/deps/LLVM/clang.exe +++ b/deps/LLVM/clang.exe @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:43dd4210d948e24510ad5c431858cc083393ec0c9a4547aaa167439d6629c4c7 -size 61692416 +oid sha256:ac210a076eadbb97f111189a8418665209c3640949505e482062f7bd2f30fbcf +size 108426240 diff --git a/deps/LLVM/cyggcc_s-seh-1.dll b/deps/LLVM/cyggcc_s-seh-1.dll deleted file mode 100644 index 6ef5586bc6..0000000000 --- a/deps/LLVM/cyggcc_s-seh-1.dll +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:1924840ef4773d63a5a973c45679875072fa5710f81cb88e07338d868869f75c -size 71187 diff --git a/deps/LLVM/cygiconv-2.dll b/deps/LLVM/cygiconv-2.dll deleted file mode 100644 index 8f0fec667b..0000000000 --- a/deps/LLVM/cygiconv-2.dll +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:151e00218fc1666cf0cda74028e4d17d5d9205ccd1458808d4ef7e3f504ed5a0 -size 1033235 diff --git a/deps/LLVM/cygintl-8.dll b/deps/LLVM/cygintl-8.dll deleted file mode 100644 index 790efa871b..0000000000 --- a/deps/LLVM/cygintl-8.dll +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:aa8c8cf490eb497c5b60543a9b5d1ee296f9edd23cec4d3769e527e9d6675b65 -size 40979 diff --git a/deps/LLVM/cygstdc++-6.dll b/deps/LLVM/cygstdc++-6.dll deleted file mode 100644 index 2b8f477e91..0000000000 --- a/deps/LLVM/cygstdc++-6.dll +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:cd58c192fba87bd9df3ffc6e41d8b17f0796f39692bc3bcc0cf9d6201ea97c0e -size 1369107 diff --git a/deps/LLVM/cyguuid-1.dll b/deps/LLVM/cyguuid-1.dll deleted file mode 100644 index 2ebbf24661..0000000000 --- a/deps/LLVM/cyguuid-1.dll +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:5d0ec46bf67a1d42bbb59a8230b43ff403a16b9f6d720108f3f1280f1e03b84d -size 15379 diff --git a/deps/LLVM/cygwin1.dll b/deps/LLVM/cygwin1.dll deleted file mode 100644 index eb1714a2c8..0000000000 --- a/deps/LLVM/cygwin1.dll +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:329b496892113f682bc45c7830c9c365b51f0bfd6524cd2b2880d6718c0e13fe -size 3539372 diff --git a/deps/LLVM/darwin_ld.exe b/deps/LLVM/darwin_ld.exe deleted file mode 100644 index 002216d35a..0000000000 --- a/deps/LLVM/darwin_ld.exe +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:1e02c7a7e07052413edd0759fc7f4ab41a612ade74e14616de0896e17aae2212 -size 2818235 diff --git a/deps/LLVM/ld.lld.exe b/deps/LLVM/ld.lld.exe new file mode 100644 index 0000000000..0970d89cd8 --- /dev/null +++ b/deps/LLVM/ld.lld.exe @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:24b8e2d4efc663728abafa6d4cfb453aef34e7e303d8d6a3c0825ccd2e781c4c +size 72396288 diff --git a/deps/LLVM/ld64.lld.exe b/deps/LLVM/ld64.lld.exe new file mode 100644 index 0000000000..0970d89cd8 --- /dev/null +++ b/deps/LLVM/ld64.lld.exe @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:24b8e2d4efc663728abafa6d4cfb453aef34e7e303d8d6a3c0825ccd2e781c4c +size 72396288 diff --git a/deps/LLVM/lipo.exe b/deps/LLVM/lipo.exe index ba21bd053f..1900862363 100644 --- a/deps/LLVM/lipo.exe +++ b/deps/LLVM/lipo.exe @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:3ba15dd8223c2b35834af10c15c36d6a436cc2543deff917240ab62ebc7fffa8 -size 152909 +oid sha256:19dd89f7071e02c92ef310817bb204894a4cf72fe7a8dd7745000ebe24e9813d +size 18330112 diff --git a/deps/LLVM/lld-link.exe b/deps/LLVM/lld-link.exe new file mode 100644 index 0000000000..0970d89cd8 --- /dev/null +++ b/deps/LLVM/lld-link.exe @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:24b8e2d4efc663728abafa6d4cfb453aef34e7e303d8d6a3c0825ccd2e781c4c +size 72396288 diff --git a/deps/LLVM/lld.exe b/deps/LLVM/lld.exe index ec41345003..0970d89cd8 100644 --- a/deps/LLVM/lld.exe +++ b/deps/LLVM/lld.exe @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:62b8ec1e555575bfa3df4560391e80eac6b2fb1a5111de6228f11de42729ad59 -size 42303488 +oid sha256:24b8e2d4efc663728abafa6d4cfb453aef34e7e303d8d6a3c0825ccd2e781c4c +size 72396288 diff --git a/deps/LLVM/llvm-ar.exe b/deps/LLVM/llvm-ar.exe index 486453428d..5ac279730f 100644 --- a/deps/LLVM/llvm-ar.exe +++ b/deps/LLVM/llvm-ar.exe @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:6fc4040efe914cf38937dc16d086a4b4abfa41a909f8c8f4fdf42e4dd7d29314 -size 14857728 +oid sha256:6b0e78a7019401d9e0de603f52d1cced70ae4e47b486ca7f0aa9ae564836fba5 +size 18496000 diff --git a/sources/Directory.Packages.props b/sources/Directory.Packages.props index 162d31fe50..9b79d71218 100644 --- a/sources/Directory.Packages.props +++ b/sources/Directory.Packages.props @@ -90,6 +90,7 @@ + diff --git a/sources/core/Stride.Core/Threading/Dispatcher.cs b/sources/core/Stride.Core/Threading/Dispatcher.cs index 69e1c712d6..2a3f94ff8f 100644 --- a/sources/core/Stride.Core/Threading/Dispatcher.cs +++ b/sources/core/Stride.Core/Threading/Dispatcher.cs @@ -19,7 +19,10 @@ public static class Dispatcher #if STRIDE_PLATFORM_IOS || STRIDE_PLATFORM_ANDROID public static readonly int MaxDegreeOfParallelism = 1; #else - public static int MaxDegreeOfParallelism { get; set; } = Environment.ProcessorCount; + public static int MaxDegreeOfParallelism { get; set; } = + int.TryParse(Environment.GetEnvironmentVariable("STRIDE_MAX_PARALLELISM"), out var envValue) && envValue > 0 + ? envValue + : Environment.ProcessorCount; #endif private static readonly ProfilingKey DispatcherSortKey = new("Dispatcher.Sort"); diff --git a/sources/editor/Stride.Assets.Presentation/SceneEditor/PickingSceneRenderer.cs b/sources/editor/Stride.Assets.Presentation/SceneEditor/PickingSceneRenderer.cs index b9e9c49b3e..1cafdd0180 100644 --- a/sources/editor/Stride.Assets.Presentation/SceneEditor/PickingSceneRenderer.cs +++ b/sources/editor/Stride.Assets.Presentation/SceneEditor/PickingSceneRenderer.cs @@ -101,8 +101,8 @@ protected override void DrawCore(RenderContext context, RenderDrawContext drawCo drawContext.CommandList.Clear(pickingRenderTarget, Color.Transparent); drawContext.CommandList.Clear(pickingDepthStencil, DepthStencilClearOptions.DepthBuffer); - drawContext.CommandList.ResourceBarrierTransition(pickingRenderTarget, GraphicsResourceState.RenderTarget); - drawContext.CommandList.ResourceBarrierTransition(pickingDepthStencil, GraphicsResourceState.DepthWrite); + drawContext.CommandList.ResourceBarrierTransition(pickingRenderTarget, BarrierLayout.RenderTarget); + drawContext.CommandList.ResourceBarrierTransition(pickingDepthStencil, BarrierLayout.DepthStencilWrite); drawContext.CommandList.SetRenderTargetAndViewport(pickingDepthStencil, pickingRenderTarget); drawContext.CommandList.SetScissorRectangle(new Rectangle(x, y, 1, 1)); diff --git a/sources/engine/Stride.Engine.Tests/AnimatedModelTests.cs b/sources/engine/Stride.Engine.Tests/AnimatedModelTests.cs index 63f34ad2cc..0a0f05ec3b 100644 --- a/sources/engine/Stride.Engine.Tests/AnimatedModelTests.cs +++ b/sources/engine/Stride.Engine.Tests/AnimatedModelTests.cs @@ -64,7 +64,7 @@ protected override async Task LoadContent() CameraComponent = camera.Camera; Script.Add(camera); - camera.Position = new Vector3(6.0f, 2.5f, 1.5f); + camera.Position = new Vector3(6.0f, 2.5f, 1.6f); camera.SetTarget(knight, true); } diff --git a/sources/engine/Stride.Engine.Tests/Properties/launchSettings.json b/sources/engine/Stride.Engine.Tests/Properties/launchSettings.json index 719e25cc64..26edd5bd18 100644 --- a/sources/engine/Stride.Engine.Tests/Properties/launchSettings.json +++ b/sources/engine/Stride.Engine.Tests/Properties/launchSettings.json @@ -1,13 +1,15 @@ { "profiles": { - "WARP": { + "Software": { "commandName": "Project", - "environmentVariables": { - "STRIDE_GRAPHICS_SOFTWARE_RENDERING": "1" - } + "nativeDebugging": true }, "GPU": { - "commandName": "Project" + "commandName": "Project", + "environmentVariables": { + "STRIDE_TESTS_GPU": "1" + }, + "nativeDebugging": true } } } diff --git a/sources/engine/Stride.Engine.Tests/SpriteRenderer3DTests.cs b/sources/engine/Stride.Engine.Tests/SpriteRenderer3DTests.cs index ff9bc2ce72..c93e2973e5 100644 --- a/sources/engine/Stride.Engine.Tests/SpriteRenderer3DTests.cs +++ b/sources/engine/Stride.Engine.Tests/SpriteRenderer3DTests.cs @@ -143,7 +143,7 @@ private void UpdateSprites(float time) ratio.Transform.Scale = new Vector3(time % 5f) / 5; } - [Fact] + [SkippableFact] public void SpriteRender3DRun() { RunGameTest(new SpriteRenderer3DTests()); diff --git a/sources/engine/Stride.Engine.Tests/TesselationTest.cs b/sources/engine/Stride.Engine.Tests/TesselationTest.cs index 3d87d98824..849d7c8e3d 100644 --- a/sources/engine/Stride.Engine.Tests/TesselationTest.cs +++ b/sources/engine/Stride.Engine.Tests/TesselationTest.cs @@ -1,6 +1,7 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. +using System; using System.Collections.Generic; using System.Threading.Tasks; @@ -110,12 +111,20 @@ protected override void RegisterTests() { base.RegisterTests(); + // D3D12 WARP has tessellation rendering bugs on subsequent material/model changes, + // but the first frame renders correctly. Limit to first frame only for D3D12 WARP. + bool limitedFrames = GraphicsDevice.Platform == GraphicsPlatform.Direct3D12 + && Environment.GetEnvironmentVariable("STRIDE_GRAPHICS_SOFTWARE_RENDERING") == "1"; + FrameGameSystem.Draw(() => ChangeMaterial(1)).TakeScreenshot(); - FrameGameSystem.Draw(() => ChangeMaterial(1)).TakeScreenshot(); - FrameGameSystem.Draw(() => ChangeMaterial(1)).Draw(() => ChangeModel(1)).TakeScreenshot(); - FrameGameSystem.Draw(() => ChangeMaterial(1)).Draw(() => ChangeModel(-1)).TakeScreenshot(); - FrameGameSystem.Draw(() => ChangeMaterial(1)).TakeScreenshot(); - FrameGameSystem.Draw(() => ChangeMaterial(1)).TakeScreenshot(); + if (!limitedFrames) + { + FrameGameSystem.Draw(() => ChangeMaterial(1)).TakeScreenshot(); + FrameGameSystem.Draw(() => ChangeMaterial(1)).Draw(() => ChangeModel(1)).TakeScreenshot(); + FrameGameSystem.Draw(() => ChangeMaterial(1)).Draw(() => ChangeModel(-1)).TakeScreenshot(); + FrameGameSystem.Draw(() => ChangeMaterial(1)).TakeScreenshot(); + FrameGameSystem.Draw(() => ChangeMaterial(1)).TakeScreenshot(); + } } protected override void Draw(GameTime gameTime) @@ -204,6 +213,7 @@ public void RunTestGame() SkipTestForGraphicPlatform(GraphicsPlatform.OpenGL); SkipTestForGraphicPlatform(GraphicsPlatform.OpenGLES); SkipTestForGraphicPlatform(GraphicsPlatform.Vulkan); + // Note: D3D12 WARP tessellation is limited to first frame only (see RegisterTests) RunGameTest(new TesselationTest()); } diff --git a/sources/engine/Stride.Engine.Tests/TestCameraProcessor.cs b/sources/engine/Stride.Engine.Tests/TestCameraProcessor.cs index e2624924bb..58dd0eb211 100644 --- a/sources/engine/Stride.Engine.Tests/TestCameraProcessor.cs +++ b/sources/engine/Stride.Engine.Tests/TestCameraProcessor.cs @@ -19,10 +19,11 @@ namespace Stride.Engine.Tests /// /// Tests for . /// - public class TestCameraProcessor + public class TestCameraProcessor : IDisposable { private CustomEntityManager entityManager; private GraphicsCompositor graphicsCompositor; + private GraphicsDevice graphicsDevice; private RenderContext context; public TestCameraProcessor() @@ -34,7 +35,7 @@ public TestCameraProcessor() // Create graphics compositor graphicsCompositor = new GraphicsCompositor(); - var graphicsDevice = GraphicsDevice.New(DeviceCreationFlags.Debug); + graphicsDevice = GraphicsDevice.New(DeviceCreationFlags.Debug); services.AddService(new GraphicsDeviceServiceLocal(graphicsDevice)); services.AddService(new EffectSystem(services)); services.AddService(new GraphicsContext(graphicsDevice)); @@ -42,6 +43,11 @@ public TestCameraProcessor() context.PushTagAndRestore(GraphicsCompositor.Current, graphicsCompositor); } + public void Dispose() + { + graphicsDevice?.Dispose(); + } + private CameraComponent AddCamera(bool enabled, SceneCameraSlotId slot) { var camera = new CameraComponent { Enabled = enabled, Slot = slot }; diff --git a/sources/engine/Stride.Engine/Rendering/Compositing/ForwardRenderer.cs b/sources/engine/Stride.Engine/Rendering/Compositing/ForwardRenderer.cs index f69c442f5d..f5b915add5 100644 --- a/sources/engine/Stride.Engine/Rendering/Compositing/ForwardRenderer.cs +++ b/sources/engine/Stride.Engine/Rendering/Compositing/ForwardRenderer.cs @@ -858,7 +858,7 @@ private void PrepareRenderTargets(RenderDrawContext drawContext, Texture outputR currentRenderTargets[index] = PushScopedResource(drawContext.GraphicsContext.Allocator.GetTemporaryTexture2D(textureDescription)); } - drawContext.CommandList.ResourceBarrierTransition(currentRenderTargets[index], GraphicsResourceState.RenderTarget); + drawContext.CommandList.ResourceBarrierTransition(currentRenderTargets[index], BarrierLayout.RenderTarget); } // Prepare depth buffer @@ -872,7 +872,7 @@ private void PrepareRenderTargets(RenderDrawContext drawContext, Texture outputR var textureDescription = TextureDescription.New2D(description.Width, description.Height, 1, description.Format, TextureFlags.DepthStencil | TextureFlags.ShaderResource, 1, GraphicsResourceUsage.Default, actualMultisampleCount); currentDepthStencil = PushScopedResource(drawContext.GraphicsContext.Allocator.GetTemporaryTexture2D(textureDescription)); } - drawContext.CommandList.ResourceBarrierTransition(currentDepthStencil, GraphicsResourceState.DepthWrite); + drawContext.CommandList.ResourceBarrierTransition(currentDepthStencil, BarrierLayout.DepthStencilWrite); } /// diff --git a/sources/engine/Stride.Games/GameBase.cs b/sources/engine/Stride.Games/GameBase.cs index 525f50ced1..6be8b805e9 100644 --- a/sources/engine/Stride.Games/GameBase.cs +++ b/sources/engine/Stride.Games/GameBase.cs @@ -781,9 +781,7 @@ protected virtual bool BeginDraw() // Perform begin of frame presenter operations if (GraphicsDevice.Presenter != null) { - GraphicsContext.CommandList.ResourceBarrierTransition(GraphicsDevice.Presenter.DepthStencilBuffer, GraphicsResourceState.DepthWrite); - GraphicsContext.CommandList.ResourceBarrierTransition(GraphicsDevice.Presenter.BackBuffer, GraphicsResourceState.RenderTarget); - + // Note: RT/DS transitions are handled by SetRenderTargetsImpl when targets are bound. GraphicsDevice.Presenter.BeginDraw(GraphicsContext.CommandList); } @@ -822,7 +820,7 @@ protected virtual void EndDraw(bool present) // Perform end of frame presenter operations GraphicsDevice.Presenter.EndDraw(GraphicsContext.CommandList, present); - GraphicsContext.CommandList.ResourceBarrierTransition(GraphicsDevice.Presenter.BackBuffer, GraphicsResourceState.Present); + GraphicsContext.CommandList.ResourceBarrierTransition(GraphicsDevice.Presenter.BackBuffer, BarrierLayout.Present); } GraphicsContext.ResourceGroupAllocator.Flush(); diff --git a/sources/engine/Stride.Graphics.Regression/AllowGpuValidationErrorAttribute.cs b/sources/engine/Stride.Graphics.Regression/AllowGpuValidationErrorAttribute.cs new file mode 100644 index 0000000000..6bc9eae313 --- /dev/null +++ b/sources/engine/Stride.Graphics.Regression/AllowGpuValidationErrorAttribute.cs @@ -0,0 +1,29 @@ +// Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) +// Distributed under the MIT license. See the LICENSE.md file in the project root for more information. + +using System; + +namespace Stride.Graphics.Regression; + +/// +/// Marks a test method or class as allowing a specific GPU validation error that is a known false positive. +/// The error is suppressed during assertion if its text contains the specified substring. +/// +[AttributeUsage(AttributeTargets.Method | AttributeTargets.Class, AllowMultiple = true, Inherited = true)] +public class AllowGpuValidationErrorAttribute(GraphicsPlatform platform, string messageSubstring) : Attribute +{ + /// + /// The graphics platform this error is allowed on. + /// + public GraphicsPlatform Platform { get; } = platform; + + /// + /// A substring that must appear in the validation error message for it to be suppressed. + /// + public string MessageSubstring { get; } = messageSubstring; + + /// + /// An optional explanation of why this error is allowed. + /// + public string Reason { get; set; } +} diff --git a/sources/engine/Stride.Graphics.Regression/GameTestBase.cs b/sources/engine/Stride.Graphics.Regression/GameTestBase.cs index e62042e9ba..27c57e1934 100644 --- a/sources/engine/Stride.Graphics.Regression/GameTestBase.cs +++ b/sources/engine/Stride.Graphics.Regression/GameTestBase.cs @@ -6,6 +6,8 @@ using System.Diagnostics.CodeAnalysis; using System.IO; using System.Linq; +using System.Reflection; +using System.Runtime.CompilerServices; using System.Runtime.ExceptionServices; using System.Text.RegularExpressions; using System.Threading.Tasks; @@ -104,21 +106,18 @@ public abstract class GameTestBase : Game #if STRIDE_PLATFORM_DESKTOP /// - /// Forces render doc capture even if test didn't fail. This can be used only when is set. + /// Controls RenderDoc capture behavior for tests. + /// Set via the STRIDE_TESTS_RENDERDOC environment variable: + /// + /// error — capture frames only for failing tests (discard on success) + /// always — capture frames for all tests + /// /// - public static bool ForceCaptureRenderDocOnSuccess = false; + private static readonly string RenderDocMode = + Environment.GetEnvironmentVariable("STRIDE_TESTS_RENDERDOC")?.ToLowerInvariant(); - /// - /// Gets or sets a value indicating whether RenderDoc should capture a frame when an error occurs - /// or a test fails. - /// - /// Enabling this feature may cause an Out-of-Memory exception on 32-bit processes. - public static bool CaptureRenderDocOnError = - #if STRIDE_TESTS_CAPTURE_RENDERDOC_ON_ERROR - true; - #else - string.Equals(Environment.GetEnvironmentVariable("STRIDE_TESTS_CAPTURE_RENDERDOC_ON_ERROR"), "true", StringComparison.OrdinalIgnoreCase); - #endif + private static bool CaptureRenderDocOnError => RenderDocMode is "error" or "always"; + private static bool ForceCaptureRenderDocOnSuccess => RenderDocMode is "always"; private RenderDocManager renderDocManager; #endif @@ -443,6 +442,27 @@ private void DiscardFrameCapture() renderDocManager?.DiscardFrameCapture(GraphicsDevice, IntPtr.Zero); } + /// + /// Ends or discards the RenderDoc capture based on test results. + /// Must be called while the graphics device is still alive. + /// + internal void EndOrDiscardRenderDocCapture() + { + if (renderDocManager is null || !CaptureRenderDocOnError) + return; + + if (comparisonFailedMessages.Count == 0 && + comparisonMissingMessages.Count == 0 && + !ForceCaptureRenderDocOnSuccess) + { + DiscardFrameCapture(); + } + else + { + EndFrameCapture(); + } + } + /// protected override void Update(GameTime gameTime) { @@ -587,40 +607,67 @@ protected virtual void RegisterTests() /// Executes a game test using the specified instance. /// /// The game test instance to run. Must not be . - protected static void RunGameTest(GameTestBase game) + protected static void RunGameTest(GameTestBase game, [CallerMemberName] string callerName = null) { game.EnableSimulatedInputSource(); game.ScreenShotAutomationEnabled = !ForceInteractiveMode; - ExceptionDispatchInfo exceptionOrFailedAssert = null; + // Collect allowed GPU validation errors from [AllowGpuValidationError] attributes on the calling test method and class + var allowedErrors = new List(); + if (callerName != null) + { + var callerType = new System.Diagnostics.StackTrace().GetFrames() + .Select(f => f.GetMethod()) + .FirstOrDefault(m => m?.Name == callerName)?.DeclaringType; + if (callerType != null) + { + // Class-level attributes + allowedErrors.AddRange(callerType.GetCustomAttributes(true)); + // Method-level attributes + var method = callerType.GetMethod(callerName); + if (method != null) + allowedErrors.AddRange(method.GetCustomAttributes(true)); + } + } + + // Track GPU validation errors and warnings during the test + var gpuValidationErrors = new List(); + var gpuValidationWarnings = new List(); + void OnGlobalMessage(ILogMessage msg) + { + if (msg.Module != nameof(GraphicsDevice)) + return; + if (msg.Type == LogMessageType.Error) + gpuValidationErrors.Add(msg.Text); + else if (msg.Type == LogMessageType.Warning && !IsKnownHarmlessWarning(msg.Text)) + gpuValidationWarnings.Add(msg.Text); + } + GlobalLogger.GlobalMessageLogged += OnGlobalMessage; try { GameTester.RunGameTest(game); } - catch (Exception ex) + finally { - // This catches both errors in the test execution and assertion failures - exceptionOrFailedAssert = ExceptionDispatchInfo.Capture(ex); + GlobalLogger.GlobalMessageLogged -= OnGlobalMessage; } -#if STRIDE_PLATFORM_DESKTOP - if (CaptureRenderDocOnError) - { - // If no comparison errors, and no test errors, discard the capture - if (game.comparisonFailedMessages.Count == 0 && - game.comparisonMissingMessages.Count == 0 && - exceptionOrFailedAssert is null && - !ForceCaptureRenderDocOnSuccess) - { - game.DiscardFrameCapture(); - } - else game.EndFrameCapture(); - } -#endif - // If there was an exception, rethrow it now - exceptionOrFailedAssert?.Throw(); + // Filter out allowed errors for the current platform + var platform = GraphicsDevice.Platform; + gpuValidationErrors.RemoveAll(error => + allowedErrors.Any(a => a.Platform == platform && error.Contains(a.MessageSubstring))); + + // Assert no GPU validation errors + if (gpuValidationErrors.Count > 0) + Assert.Fail($"GPU validation reported {gpuValidationErrors.Count} error(s):" + Environment.NewLine + + string.Join(Environment.NewLine, gpuValidationErrors)); + + // Assert no GPU validation warnings + if (gpuValidationWarnings.Count > 0) + Assert.Fail($"GPU validation reported {gpuValidationWarnings.Count} warning(s):" + Environment.NewLine + + string.Join(Environment.NewLine, gpuValidationWarnings)); // If there were comparison failures, assert them now if (game.ScreenShotAutomationEnabled) @@ -646,6 +693,15 @@ void AssertMissingComparisonImages() } } + /// + /// Filters out known harmless D3D11 debug layer warnings that cannot be avoided + /// without significant refactoring. + /// + private static bool IsKnownHarmlessWarning(string text) + { + return text.Contains("Live "); // D3D11/D3D12: ReportLiveObjects at shutdown + } + /// /// Searches for the root folder of the Stride solution by traversing upward from the test's binary directory. /// @@ -753,9 +809,19 @@ private string GetPlatformSpecificDirectory() { if (Platform.Type == PlatformType.Windows) { - var deviceName = Environment.GetEnvironmentVariable("STRIDE_GRAPHICS_SOFTWARE_RENDERING") == "1" - ? "WARP" - : GraphicsDevice.Adapter.Description.Split('\0')[0].TrimEnd(' '); + string deviceName; + if (Environment.GetEnvironmentVariable("STRIDE_GRAPHICS_SOFTWARE_RENDERING") == "1") + { + deviceName = GraphicsDevice.Platform switch + { + GraphicsPlatform.Vulkan => "SwiftShader", + _ => "WARP" + }; + } + else + { + deviceName = GraphicsDevice.Adapter.Description.Split('\0')[0].TrimEnd(' '); + } return $"Windows.{GraphicsDevice.Platform}\\{deviceName}"; } else @@ -859,6 +925,11 @@ public static void SkipTestForGraphicPlatform(GraphicsPlatform platform) Skip.If(GraphicsDevice.Platform == platform, $"This test is not valid for the '{platform}' graphic platform. It has been skipped"); } + public static void SkipTestForGraphicPlatform(GraphicsPlatform platform, string reason) + { + Skip.If(GraphicsDevice.Platform == platform, $"{platform}: {reason}"); + } + /// /// Skips the test on any other graphics platform than the provided one. /// diff --git a/sources/engine/Stride.Graphics.Regression/GameTester.cs b/sources/engine/Stride.Graphics.Regression/GameTester.cs index 9c4da601e8..eb45fc0237 100644 --- a/sources/engine/Stride.Graphics.Regression/GameTester.cs +++ b/sources/engine/Stride.Graphics.Regression/GameTester.cs @@ -1,21 +1,21 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. -#if STRIDE_PLATFORM_ANDROID || STRIDE_PLATFORM_IOS using System; +using System.Diagnostics; +using Stride.Core.Diagnostics; +using Stride.Engine; + +#if STRIDE_PLATFORM_ANDROID || STRIDE_PLATFORM_IOS using System.Runtime.ExceptionServices; using System.Threading.Tasks; +using Stride.Games; +#endif #if STRIDE_PLATFORM_IOS using UIKit; #endif -using Stride.Games; -#endif - -using Stride.Core.Diagnostics; -using Stride.Engine; - namespace Stride.Graphics.Regression { /// @@ -54,9 +54,22 @@ public static void RunGameTest(Game game) using (game) { - game.Run(); + try + { + game.Run(); + } + finally + { + // End/Discard RenderDoc capture while the device is still alive (before Dispose/Destroy) + if (game is GameTestBase testGame) + testGame.EndOrDiscardRenderDocCapture(); + } } + // Log process memory for diagnostics (helps detect resource leaks across tests) + using var process = Process.GetCurrentProcess(); + Logger.Info($"Process memory: working set={process.WorkingSet64 / 1024 / 1024}MB, private={process.PrivateMemorySize64 / 1024 / 1024}MB, GC={GC.GetTotalMemory(false) / 1024 / 1024}MB"); + #elif STRIDE_PLATFORM_UWP throw new NotImplementedException(); diff --git a/sources/engine/Stride.Graphics.Regression/Module.cs b/sources/engine/Stride.Graphics.Regression/Module.cs new file mode 100644 index 0000000000..59882f18f8 --- /dev/null +++ b/sources/engine/Stride.Graphics.Regression/Module.cs @@ -0,0 +1,113 @@ +// Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) +// Distributed under the MIT license. See the LICENSE.md file in the project root for more information. + +using System; +using System.Diagnostics; +using System.IO; +using System.Runtime.CompilerServices; +using System.Runtime.ExceptionServices; +using System.Runtime.InteropServices; + +namespace Stride.Graphics.Regression; + +internal static class Module +{ + [DllImport("kernel32.dll")] + private static extern uint SetErrorMode(uint uMode); + + [DllImport("dbghelp.dll", SetLastError = true)] + private static extern bool MiniDumpWriteDump(IntPtr hProcess, uint processId, IntPtr hFile, + uint dumpType, IntPtr exceptionParam, IntPtr userStreamParam, IntPtr callbackParam); + + private static string crashDumpDir; + + [ModuleInitializer] + internal static void Initialize() + { + if (OperatingSystem.IsWindows()) + { + // Prevent critical-error and open-file-error dialogs that would hang CI. + // NOTE: We intentionally do NOT set SEM_NOGPFAULTERRORBOX so that WER LocalDumps + // can generate crash dumps for pure native crashes (type 3). + SetErrorMode(0x0001 /* SEM_FAILCRITICALERRORS */ | 0x8000 /* SEM_NOOPENFILEERRORBOX */); + } + + // GPU drivers (including software renderers like WARP and SwiftShader) can crash with + // native access violations when used incorrectly (e.g., releasing resources still in use). + // These crashes are hard to diagnose: .NET 8+ can't catch them via try/catch, and WER + // doesn't trigger because .NET handles the exception dispatch internally. + // When STRIDE_TESTS_CRASH_DUMPS is set, we register a FirstChanceException handler to log + // the stack trace and write a minidump before the process terminates. This complements + // DOTNET_DbgEnableMiniDump (for .NET unhandled exceptions) and WER LocalDumps (for pure + // native crashes). + var crashDiag = Environment.GetEnvironmentVariable("STRIDE_TESTS_CRASH_DUMPS"); + if (crashDiag == "1") + { + // Use STRIDE_TESTS_CRASH_DUMP_DIR if set (e.g., from CI), otherwise fall back to working dir + crashDumpDir = Environment.GetEnvironmentVariable("STRIDE_TESTS_CRASH_DUMP_DIR") + ?? Path.Combine(Environment.CurrentDirectory, "crash-dumps"); + Directory.CreateDirectory(crashDumpDir); + + AppDomain.CurrentDomain.FirstChanceException += OnFirstChanceException; + } + + // Default to software rendering unless STRIDE_TESTS_GPU=1 is set. + // This ensures Test Explorer and dotnet test match the gold images out of the box. + if (Environment.GetEnvironmentVariable("STRIDE_TESTS_GPU") != "1") + { + Environment.SetEnvironmentVariable("STRIDE_GRAPHICS_SOFTWARE_RENDERING", "1"); + + if (string.IsNullOrEmpty(Environment.GetEnvironmentVariable("STRIDE_MAX_PARALLELISM"))) + Environment.SetEnvironmentVariable("STRIDE_MAX_PARALLELISM", "8"); + + // Auto-configure SwiftShader ICD path for Vulkan software rendering + if (string.IsNullOrEmpty(Environment.GetEnvironmentVariable("VK_DRIVER_FILES"))) + { + var icdPath = Path.Combine(AppContext.BaseDirectory, "vk_swiftshader_icd.json"); + if (File.Exists(icdPath)) + Environment.SetEnvironmentVariable("VK_DRIVER_FILES", icdPath); + } + } + } + + /// + /// Logs SEHException details on first-chance and writes a minidump. These are native access + /// violations that .NET 8+ cannot catch via try/catch and will terminate the process. + /// + private static void OnFirstChanceException(object sender, FirstChanceExceptionEventArgs e) + { + if (e.Exception is SEHException seh) + { + Console.Error.WriteLine($"[CrashDiag] FirstChanceException: SEHException HResult=0x{seh.HResult:X8}"); + Console.Error.WriteLine($"[CrashDiag] Exception stack: {seh.StackTrace}"); + foreach (var line in Environment.StackTrace.Split('\n')) + Console.Error.WriteLine($"[CrashDiag] {line.TrimEnd()}"); + + WriteMiniDump("firstchance_seh"); + } + } + + private static void WriteMiniDump(string tag) + { + if (!OperatingSystem.IsWindows()) + return; + + try + { + var path = Path.Combine(crashDumpDir, $"{tag}_{Environment.ProcessId}_{DateTime.UtcNow:yyyyMMdd_HHmmss}.dmp"); + using var fs = File.Create(path); + var process = Process.GetCurrentProcess(); + const uint MiniDumpWithThreadInfo = 0x00001000; + const uint MiniDumpWithFullMemoryInfo = 0x00000800; + bool ok = MiniDumpWriteDump(process.Handle, (uint)process.Id, fs.SafeFileHandle.DangerousGetHandle(), + MiniDumpWithThreadInfo | MiniDumpWithFullMemoryInfo, IntPtr.Zero, IntPtr.Zero, IntPtr.Zero); + Console.Error.WriteLine(ok + ? $"[CrashDiag] Dump written: {path}" + : $"[CrashDiag] Dump failed: error {Marshal.GetLastWin32Error()}"); + } + catch (Exception ex) + { + Console.Error.WriteLine($"[CrashDiag] Dump exception: {ex.Message}"); + } + } +} diff --git a/sources/engine/Stride.Graphics.Regression/Stride.Graphics.Regression.csproj b/sources/engine/Stride.Graphics.Regression/Stride.Graphics.Regression.csproj index 939a1fb942..5d37af7a65 100644 --- a/sources/engine/Stride.Graphics.Regression/Stride.Graphics.Regression.csproj +++ b/sources/engine/Stride.Graphics.Regression/Stride.Graphics.Regression.csproj @@ -30,6 +30,7 @@ runtime; build; native; contentfiles; analyzers; buildtransitive + diff --git a/sources/engine/Stride.Graphics.Tests.10_0/LightingTests.cs b/sources/engine/Stride.Graphics.Tests.10_0/LightingTests.cs index 43c5de4d4f..23f3bef791 100644 --- a/sources/engine/Stride.Graphics.Tests.10_0/LightingTests.cs +++ b/sources/engine/Stride.Graphics.Tests.10_0/LightingTests.cs @@ -14,7 +14,16 @@ namespace Stride.Graphics.Tests /// /// Test lighting and shadows. /// - /// Note: shadows stabilization is still not working, new golds will be needed when fixed. + // Vulkan validation layer false positive: the shadow map atlas is transitioned from DepthStencilWrite + // to ShaderReadOnly in a command buffer that is submitted via a separate vkQueueSubmit call (with + // timeline semaphore ordering) before the worker command buffers that sample the shadow map. + // The validation layer doesn't track image layout transitions across timeline-semaphore-ordered + // submissions, so it reports the image as still being in DepthStencilAttachmentOptimal. + // Verified correct with STRIDE_MAX_PARALLELISM=1 (single command buffer, 0 failures in 10 runs). + // Related (but not identical): https://github.com/KhronosGroup/Vulkan-ValidationLayers/issues/10185 + [AllowGpuValidationError(GraphicsPlatform.Vulkan, + "to be in layout VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL--instead, current layout is VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL", + Reason = "Validation layer doesn't track image layouts across timeline-semaphore-ordered command buffer submissions (https://github.com/KhronosGroup/Vulkan-ValidationLayers/issues/10185)")] public class LightingTests : GameTestBase { public Action SetupLighting { get; set; } diff --git a/sources/engine/Stride.Graphics.Tests.10_0/NextGen/NextGenTest1.cs b/sources/engine/Stride.Graphics.Tests.10_0/NextGen/NextGenTest1.cs index a06b8fef7b..9b1bd047d8 100644 --- a/sources/engine/Stride.Graphics.Tests.10_0/NextGen/NextGenTest1.cs +++ b/sources/engine/Stride.Graphics.Tests.10_0/NextGen/NextGenTest1.cs @@ -185,7 +185,7 @@ static void Main(string[] args) /// /// Run the test /// - [Fact] + [SkippableFact(Skip = "Crashes on D3D12 and Vulkan - needs investigation")] public void RunNextGenTest() { RunGameTest(new NextGenTest1()); diff --git a/sources/engine/Stride.Graphics.Tests.10_0/Properties/launchSettings.json b/sources/engine/Stride.Graphics.Tests.10_0/Properties/launchSettings.json index 719e25cc64..26edd5bd18 100644 --- a/sources/engine/Stride.Graphics.Tests.10_0/Properties/launchSettings.json +++ b/sources/engine/Stride.Graphics.Tests.10_0/Properties/launchSettings.json @@ -1,13 +1,15 @@ { "profiles": { - "WARP": { + "Software": { "commandName": "Project", - "environmentVariables": { - "STRIDE_GRAPHICS_SOFTWARE_RENDERING": "1" - } + "nativeDebugging": true }, "GPU": { - "commandName": "Project" + "commandName": "Project", + "environmentVariables": { + "STRIDE_TESTS_GPU": "1" + }, + "nativeDebugging": true } } } diff --git a/sources/engine/Stride.Graphics.Tests.11_0/Properties/launchSettings.json b/sources/engine/Stride.Graphics.Tests.11_0/Properties/launchSettings.json index 719e25cc64..26edd5bd18 100644 --- a/sources/engine/Stride.Graphics.Tests.11_0/Properties/launchSettings.json +++ b/sources/engine/Stride.Graphics.Tests.11_0/Properties/launchSettings.json @@ -1,13 +1,15 @@ { "profiles": { - "WARP": { + "Software": { "commandName": "Project", - "environmentVariables": { - "STRIDE_GRAPHICS_SOFTWARE_RENDERING": "1" - } + "nativeDebugging": true }, "GPU": { - "commandName": "Project" + "commandName": "Project", + "environmentVariables": { + "STRIDE_TESTS_GPU": "1" + }, + "nativeDebugging": true } } } diff --git a/sources/engine/Stride.Graphics.Tests/FixedAspectRatioTests.cs b/sources/engine/Stride.Graphics.Tests/FixedAspectRatioTests.cs index 870957d38a..eac9ed1dac 100644 --- a/sources/engine/Stride.Graphics.Tests/FixedAspectRatioTests.cs +++ b/sources/engine/Stride.Graphics.Tests/FixedAspectRatioTests.cs @@ -68,6 +68,7 @@ protected override async Task LoadContent() [Fact] public void TestFixedRatio() { + // Note: SwiftShader produces a 1-pixel black line at the viewport boundary; this is a known SwiftShader rasterization difference RunGameTest(new FixedAspectRatioTests()); } } diff --git a/sources/engine/Stride.Graphics.Tests/Properties/launchSettings.json b/sources/engine/Stride.Graphics.Tests/Properties/launchSettings.json index 719e25cc64..26edd5bd18 100644 --- a/sources/engine/Stride.Graphics.Tests/Properties/launchSettings.json +++ b/sources/engine/Stride.Graphics.Tests/Properties/launchSettings.json @@ -1,13 +1,15 @@ { "profiles": { - "WARP": { + "Software": { "commandName": "Project", - "environmentVariables": { - "STRIDE_GRAPHICS_SOFTWARE_RENDERING": "1" - } + "nativeDebugging": true }, "GPU": { - "commandName": "Project" + "commandName": "Project", + "environmentVariables": { + "STRIDE_TESTS_GPU": "1" + }, + "nativeDebugging": true } } } diff --git a/sources/engine/Stride.Graphics.Tests/TestCustomEffect.cs b/sources/engine/Stride.Graphics.Tests/TestCustomEffect.cs index 3c0558366f..e849d25ad3 100644 --- a/sources/engine/Stride.Graphics.Tests/TestCustomEffect.cs +++ b/sources/engine/Stride.Graphics.Tests/TestCustomEffect.cs @@ -63,7 +63,7 @@ private void DrawCustomEffect() /// /// Run the test /// - [Fact] + [SkippableFact] public void RunCustomEffect() { RunGameTest(new TestCustomEffect()); diff --git a/sources/engine/Stride.Graphics.Tests/TestFixedSizeUI.cs b/sources/engine/Stride.Graphics.Tests/TestFixedSizeUI.cs index 2be447243b..3696d3a3b6 100644 --- a/sources/engine/Stride.Graphics.Tests/TestFixedSizeUI.cs +++ b/sources/engine/Stride.Graphics.Tests/TestFixedSizeUI.cs @@ -104,10 +104,6 @@ protected override async Task LoadContent() Window.AllowUserResizing = true; - await base.LoadContent(); - - Window.AllowUserResizing = true; - arial16 = Content.Load("DynamicFonts/Arial16"); // Instantiate a scene with a single entity and model component diff --git a/sources/engine/Stride.Graphics.Tests/TestGeometricPrimitives.cs b/sources/engine/Stride.Graphics.Tests/TestGeometricPrimitives.cs index 24e6b605d4..525be232df 100644 --- a/sources/engine/Stride.Graphics.Tests/TestGeometricPrimitives.cs +++ b/sources/engine/Stride.Graphics.Tests/TestGeometricPrimitives.cs @@ -159,7 +159,7 @@ private void DrawPrimitives() /// /// Run the test /// - [Fact] + [SkippableFact] public void RunGeometricPrimitives() { RunGameTest(new TestGeometricPrimitives()); diff --git a/sources/engine/Stride.Graphics.Tests/TestRenderToTexture.cs b/sources/engine/Stride.Graphics.Tests/TestRenderToTexture.cs index bd84969058..893c2154b1 100644 --- a/sources/engine/Stride.Graphics.Tests/TestRenderToTexture.cs +++ b/sources/engine/Stride.Graphics.Tests/TestRenderToTexture.cs @@ -97,7 +97,7 @@ private void RenderToTexture() GraphicsContext.CommandList.SetRenderTargetAndViewport(GraphicsDevice.Presenter.DepthStencilBuffer, GraphicsDevice.Presenter.BackBuffer); GraphicsContext.CommandList.SetViewport(new Viewport(width / 2, 0, width / 2, height / 2)); - GraphicsContext.CommandList.ResourceBarrierTransition(offlineTarget0, GraphicsResourceState.PixelShaderResource); + GraphicsContext.CommandList.ResourceBarrierTransition(offlineTarget0, BarrierLayout.ShaderResource); GraphicsContext.DrawTexture(offlineTarget0); // 2 intermediate RTs @@ -107,12 +107,12 @@ private void RenderToTexture() GraphicsContext.CommandList.Clear(depthBuffer, DepthStencilClearOptions.DepthBuffer); GraphicsContext.CommandList.SetRenderTargetAndViewport(depthBuffer, offlineTarget2); - GraphicsContext.CommandList.ResourceBarrierTransition(offlineTarget1, GraphicsResourceState.PixelShaderResource); + GraphicsContext.CommandList.ResourceBarrierTransition(offlineTarget1, BarrierLayout.ShaderResource); GraphicsContext.DrawTexture(offlineTarget1); GraphicsContext.CommandList.SetRenderTargetAndViewport(GraphicsDevice.Presenter.DepthStencilBuffer, GraphicsDevice.Presenter.BackBuffer); GraphicsContext.CommandList.SetViewport(new Viewport(0, height / 2, width / 2, height / 2)); - GraphicsContext.CommandList.ResourceBarrierTransition(offlineTarget2, GraphicsResourceState.PixelShaderResource); + GraphicsContext.CommandList.ResourceBarrierTransition(offlineTarget2, BarrierLayout.ShaderResource); GraphicsContext.DrawTexture(offlineTarget2); // draw quad on screen @@ -130,7 +130,7 @@ private void DrawGeometry() /// /// Run the test /// - [Fact] + [SkippableFact] public void RunRenderToTexture() { RunGameTest(new TestRenderToTexture()); diff --git a/sources/engine/Stride.Graphics.Tests/TestTexture.cs b/sources/engine/Stride.Graphics.Tests/TestTexture.cs index 992caebaf2..a99c0de1d5 100644 --- a/sources/engine/Stride.Graphics.Tests/TestTexture.cs +++ b/sources/engine/Stride.Graphics.Tests/TestTexture.cs @@ -492,7 +492,7 @@ public void TestLoadDraw(ImageFileType sourceFormat) GraphicsProfile.Level_9_1); } - [Theory] + [SkippableTheory] [InlineData(GraphicsProfile.Level_9_1, GraphicsResourceUsage.Staging)] [InlineData(GraphicsProfile.Level_9_1, GraphicsResourceUsage.Default)] [InlineData(GraphicsProfile.Level_10_0, GraphicsResourceUsage.Staging)] @@ -527,7 +527,7 @@ public void TestGetData(GraphicsProfile profile, GraphicsResourceUsage usage) profile); } - [Theory] + [SkippableTheory] [InlineData(GraphicsProfile.Level_9_1, GraphicsResourceUsage.Staging)] [InlineData(GraphicsProfile.Level_10_0, GraphicsResourceUsage.Staging)] [InlineData(GraphicsProfile.Level_9_1, GraphicsResourceUsage.Default)] diff --git a/sources/engine/Stride.Graphics/BarrierAccess.cs b/sources/engine/Stride.Graphics/BarrierAccess.cs new file mode 100644 index 0000000000..518ca9e97c --- /dev/null +++ b/sources/engine/Stride.Graphics/BarrierAccess.cs @@ -0,0 +1,89 @@ +// Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) +// Distributed under the MIT license. See the LICENSE.md file in the project root for more information. + +using System; + +namespace Stride.Graphics; + +/// +/// Defines flags for the type of access a GPU operation performs on a graphics resource. +/// This is a cross-platform abstraction over D3D12 Enhanced Barrier access flags and Vulkan access flags. +/// +[Flags] +public enum BarrierAccess +{ + /// + /// No access. + /// + None = 0, + + /// + /// The resource is read as a vertex buffer. + /// + VertexBuffer = 1 << 0, + + /// + /// The resource is read as an index buffer. + /// + IndexBuffer = 1 << 1, + + /// + /// The resource is read as a constant buffer. + /// + ConstantBuffer = 1 << 2, + + /// + /// The resource is read by a shader (SRV). + /// + ShaderRead = 1 << 3, + + /// + /// The resource is written by a shader (UAV). + /// + ShaderWrite = 1 << 4, + + /// + /// The resource is written as a render target. + /// + RenderTarget = 1 << 5, + + /// + /// The resource is read as a depth-stencil buffer. + /// + DepthStencilRead = 1 << 6, + + /// + /// The resource is written as a depth-stencil buffer. + /// + DepthStencilWrite = 1 << 7, + + /// + /// The resource is read as a copy source. + /// + CopyRead = 1 << 8, + + /// + /// The resource is written as a copy destination. + /// + CopyWrite = 1 << 9, + + /// + /// The resource is read as an indirect argument buffer. + /// + IndirectArgument = 1 << 10, + + /// + /// The resource is read as a resolve source. + /// + ResolveRead = 1 << 11, + + /// + /// The resource is written as a resolve destination. + /// + ResolveWrite = 1 << 12, + + /// + /// The resource is used for presentation. + /// + Present = 1 << 13, +} diff --git a/sources/engine/Stride.Graphics/BarrierLayout.cs b/sources/engine/Stride.Graphics/BarrierLayout.cs new file mode 100644 index 0000000000..d9351252e6 --- /dev/null +++ b/sources/engine/Stride.Graphics/BarrierLayout.cs @@ -0,0 +1,71 @@ +// Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) +// Distributed under the MIT license. See the LICENSE.md file in the project root for more information. + +namespace Stride.Graphics; + +/// +/// Defines the memory layout of a graphics resource for barrier transitions. +/// This is a cross-platform abstraction over D3D12 Enhanced Barrier layouts and Vulkan image layouts. +/// +public enum BarrierLayout +{ + /// + /// The resource layout is undefined. Contents may be discarded. + /// + Undefined, + + /// + /// General-purpose layout compatible with all operations. + /// + Common, + + /// + /// The resource is used as a render target. + /// + RenderTarget, + + /// + /// The resource is used as a writable depth-stencil buffer. + /// + DepthStencilWrite, + + /// + /// The resource is used as a read-only depth-stencil buffer. + /// + DepthStencilRead, + + /// + /// The resource is used as a shader resource (SRV) for reading. + /// + ShaderResource, + + /// + /// The resource is used for unordered access (UAV) for reading and writing. + /// + UnorderedAccess, + + /// + /// The resource is used as the source of a copy operation. + /// + CopySource, + + /// + /// The resource is used as the destination of a copy operation. + /// + CopyDest, + + /// + /// The resource is used for presentation to the screen. + /// + Present, + + /// + /// The resource is used as the source of a multisample resolve operation. + /// + ResolveSource, + + /// + /// The resource is used as the destination of a multisample resolve operation. + /// + ResolveDest, +} diff --git a/sources/engine/Stride.Graphics/BarrierSync.cs b/sources/engine/Stride.Graphics/BarrierSync.cs new file mode 100644 index 0000000000..2d70cf591b --- /dev/null +++ b/sources/engine/Stride.Graphics/BarrierSync.cs @@ -0,0 +1,69 @@ +// Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) +// Distributed under the MIT license. See the LICENSE.md file in the project root for more information. + +using System; + +namespace Stride.Graphics; + +/// +/// Defines flags for GPU pipeline synchronization points used in barrier transitions. +/// This is a cross-platform abstraction over D3D12 Enhanced Barrier sync flags and Vulkan pipeline stage flags. +/// +[Flags] +public enum BarrierSync +{ + /// + /// No synchronization. + /// + None = 0, + + /// + /// Synchronize with all GPU work. + /// + All = 1 << 0, + + /// + /// Synchronize with draw operations. + /// + Draw = 1 << 1, + + /// + /// Synchronize with vertex input assembly. + /// + VertexInput = 1 << 2, + + /// + /// Synchronize with pixel shader execution. + /// + PixelShader = 1 << 3, + + /// + /// Synchronize with non-pixel shader execution (vertex, geometry, hull, domain). + /// + NonPixelShader = 1 << 4, + + /// + /// Synchronize with compute shader execution. + /// + ComputeShader = 1 << 5, + + /// + /// Synchronize with depth-stencil operations. + /// + DepthStencil = 1 << 6, + + /// + /// Synchronize with render target operations. + /// + RenderTarget = 1 << 7, + + /// + /// Synchronize with copy operations. + /// + Copy = 1 << 8, + + /// + /// Synchronize with multisample resolve operations. + /// + Resolve = 1 << 9, +} diff --git a/sources/engine/Stride.Graphics/Direct3D11/CommandList.Direct3D11.cs b/sources/engine/Stride.Graphics/Direct3D11/CommandList.Direct3D11.cs index d26d6f2fce..a7ae8a290e 100644 --- a/sources/engine/Stride.Graphics/Direct3D11/CommandList.Direct3D11.cs +++ b/sources/engine/Stride.Graphics/Direct3D11/CommandList.Direct3D11.cs @@ -373,7 +373,7 @@ internal void SetSamplerState(ShaderStage stage, int slot, SamplerState samplerS { samplerStates[slotIndex] = samplerState; - var nativeSampler = samplerState is not null ? samplerState.NativeSamplerState : default; + var nativeSampler = (samplerState ?? GraphicsDevice.SamplerStates.LinearClamp).NativeSamplerState; switch (stage) { @@ -653,6 +653,11 @@ public void SetIndexBuffer(Buffer buffer, int offset, bool is32bits) /// /// The Direct3D 11 implementation does not have synchronization barriers for Graphics Resource transitions. /// + public void ResourceBarrierTransition(GraphicsResource resource, BarrierLayout newLayout) + { + // Nothing to do + } + public void ResourceBarrierTransition(GraphicsResource resource, GraphicsResourceState newState) { // Nothing to do diff --git a/sources/engine/Stride.Graphics/Direct3D11/GraphicsDevice.Direct3D11.cs b/sources/engine/Stride.Graphics/Direct3D11/GraphicsDevice.Direct3D11.cs index a16a718adc..7cd2e7619d 100644 --- a/sources/engine/Stride.Graphics/Direct3D11/GraphicsDevice.Direct3D11.cs +++ b/sources/engine/Stride.Graphics/Direct3D11/GraphicsDevice.Direct3D11.cs @@ -13,6 +13,7 @@ using Silk.NET.Direct3D11; using Stride.Core; +using Stride.Core.Diagnostics; using Stride.Core.UnsafeExtensions; using static Stride.Graphics.ComPtrHelpers; @@ -22,6 +23,8 @@ namespace Stride.Graphics { public unsafe partial class GraphicsDevice { + private static readonly Logger Log = GlobalLogger.GetLogger(nameof(GraphicsDevice)); + internal readonly int ConstantBufferDataPlacementAlignment = 16; private const GraphicsPlatform GraphicPlatform = GraphicsPlatform.Direct3D11; @@ -341,6 +344,28 @@ private unsafe partial void InitializePlatformDevice(GraphicsProfile[] graphicsP infoQueue.SetBreakOnSeverity(MessageSeverity.Corruption, true); infoQueue.SetBreakOnSeverity(MessageSeverity.Error, true); infoQueue.SetBreakOnSeverity(MessageSeverity.Warning, false); + + // Suppress known harmless warnings: + // - RT/SRV binding hazards: texture still bound as SRV while set as RT (or vice versa). + // D3D11 auto-unbinds the conflicting slot — on D3D12/Vulkan this is handled + // explicitly by SubresourceLayoutTracker which issues the proper barriers. + // - SetPrivateData changing params: debug name size mismatch when reusing resources. + var deniedMessages = stackalloc MessageID[] + { + MessageID.DeviceOmsetrendertargetsHazard, + MessageID.DevicePssetshaderresourcesHazard, + MessageID.SetprivatedataChangingparams, + }; + + Silk.NET.Direct3D11.InfoQueueFilter filter = new() + { + DenyList = new Silk.NET.Direct3D11.InfoQueueFilterDesc + { + NumIDs = 3, + PIDList = deniedMessages, + } + }; + infoQueue.AddStorageFilterEntries(ref filter); } } @@ -443,14 +468,24 @@ static bool IsRenderDocLoaded() /// The message received from the InfoQueue. private void OnDeviceInfoQueueMessage(ref readonly Message message) { - var eventHandler = DeviceInfoQueueMessage; - if (eventHandler is null) - return; - var descriptionSpan = new ReadOnlySpan(message.PDescription, (int) message.DescriptionByteLength); var description = descriptionSpan.GetString(); - eventHandler(in message, description); + Debug.WriteLine($"D3D11: {message.Severity} {description}"); + + // Log warnings and errors to Stride logger + switch (message.Severity) + { + case MessageSeverity.Corruption: + case MessageSeverity.Error: + Log.Error($"[D3D11] {message.Severity}: {description}"); + break; + case MessageSeverity.Warning: + Log.Warning($"[D3D11] {description}"); + break; + } + + DeviceInfoQueueMessage?.Invoke(in message, description); } /// @@ -465,14 +500,6 @@ internal void ProcessInfoQueueMessages() if (numMessages == 0) return; - // If no event handler is registered, just clear the messages - var eventHandler = DeviceInfoQueueMessage; - if (eventHandler is null) - { - nativeInfoQueue->ClearStoredMessages(); - return; - } - for (var i = 0ul; i < numMessages; i++) { ProcessMessage(i); diff --git a/sources/engine/Stride.Graphics/Direct3D12/BarrierMapping.Direct3D12.cs b/sources/engine/Stride.Graphics/Direct3D12/BarrierMapping.Direct3D12.cs new file mode 100644 index 0000000000..01305c9279 --- /dev/null +++ b/sources/engine/Stride.Graphics/Direct3D12/BarrierMapping.Direct3D12.cs @@ -0,0 +1,109 @@ +// Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) +// Distributed under the MIT license. See the LICENSE.md file in the project root for more information. + +#if STRIDE_GRAPHICS_API_DIRECT3D12 + +using Silk.NET.Direct3D12; + +namespace Stride.Graphics; + +/// +/// Provides mapping between cross-platform barrier enums and D3D12-specific types. +/// +internal static class BarrierMapping +{ + /// + /// Converts a to a legacy D3D12 . + /// + internal static ResourceStates ToResourceStates(BarrierLayout layout) => layout switch + { + BarrierLayout.Undefined => ResourceStates.Common, + BarrierLayout.Common => ResourceStates.Common, + BarrierLayout.RenderTarget => ResourceStates.RenderTarget, + BarrierLayout.DepthStencilWrite => ResourceStates.DepthWrite, + BarrierLayout.DepthStencilRead => ResourceStates.DepthRead, + BarrierLayout.ShaderResource => ResourceStates.PixelShaderResource | ResourceStates.NonPixelShaderResource, + BarrierLayout.UnorderedAccess => ResourceStates.UnorderedAccess, + BarrierLayout.CopySource => ResourceStates.CopySource, + BarrierLayout.CopyDest => ResourceStates.CopyDest, + BarrierLayout.Present => ResourceStates.Common, // Present == Common in D3D12 + BarrierLayout.ResolveSource => ResourceStates.ResolveSource, + BarrierLayout.ResolveDest => ResourceStates.ResolveDest, + _ => ResourceStates.Common, + }; + + /// + /// Converts a legacy D3D12 to a . + /// + internal static BarrierLayout ToBarrierLayout(ResourceStates state) => state switch + { + ResourceStates.RenderTarget => BarrierLayout.RenderTarget, + ResourceStates.DepthWrite => BarrierLayout.DepthStencilWrite, + ResourceStates.DepthRead => BarrierLayout.DepthStencilRead, + ResourceStates.UnorderedAccess => BarrierLayout.UnorderedAccess, + ResourceStates.CopySource => BarrierLayout.CopySource, + ResourceStates.CopyDest => BarrierLayout.CopyDest, + ResourceStates.ResolveSource => BarrierLayout.ResolveSource, + ResourceStates.ResolveDest => BarrierLayout.ResolveDest, + ResourceStates.PixelShaderResource | ResourceStates.NonPixelShaderResource => BarrierLayout.ShaderResource, + ResourceStates.PixelShaderResource => BarrierLayout.ShaderResource, + ResourceStates.NonPixelShaderResource => BarrierLayout.ShaderResource, + _ => BarrierLayout.Common, + }; + + /// + /// Converts a to a D3D12 Enhanced . + /// + internal static Silk.NET.Direct3D12.BarrierLayout ToEnhancedLayout(BarrierLayout layout) => layout switch + { + BarrierLayout.Undefined => Silk.NET.Direct3D12.BarrierLayout.BarrierLayoutUndefined, + BarrierLayout.Common => Silk.NET.Direct3D12.BarrierLayout.BarrierLayoutCommon, + BarrierLayout.RenderTarget => Silk.NET.Direct3D12.BarrierLayout.BarrierLayoutRenderTarget, + BarrierLayout.DepthStencilWrite => Silk.NET.Direct3D12.BarrierLayout.BarrierLayoutDepthStencilWrite, + BarrierLayout.DepthStencilRead => Silk.NET.Direct3D12.BarrierLayout.BarrierLayoutDepthStencilRead, + BarrierLayout.ShaderResource => Silk.NET.Direct3D12.BarrierLayout.BarrierLayoutShaderResource, + BarrierLayout.UnorderedAccess => Silk.NET.Direct3D12.BarrierLayout.BarrierLayoutUnorderedAccess, + BarrierLayout.CopySource => Silk.NET.Direct3D12.BarrierLayout.BarrierLayoutCopySource, + BarrierLayout.CopyDest => Silk.NET.Direct3D12.BarrierLayout.BarrierLayoutCopyDest, + BarrierLayout.Present => Silk.NET.Direct3D12.BarrierLayout.BarrierLayoutPresent, + BarrierLayout.ResolveSource => Silk.NET.Direct3D12.BarrierLayout.BarrierLayoutResolveSource, + BarrierLayout.ResolveDest => Silk.NET.Direct3D12.BarrierLayout.BarrierLayoutResolveDest, + _ => Silk.NET.Direct3D12.BarrierLayout.BarrierLayoutCommon, + }; + + /// + /// Derives the D3D12 Enhanced Barriers access flags from a . + /// + internal static D3D12BarrierAccess ToEnhancedAccess(BarrierLayout layout) => layout switch + { + BarrierLayout.RenderTarget => D3D12BarrierAccess.RenderTarget, + BarrierLayout.DepthStencilWrite => D3D12BarrierAccess.DepthStencilWrite, + BarrierLayout.DepthStencilRead => D3D12BarrierAccess.DepthStencilRead, + BarrierLayout.ShaderResource => D3D12BarrierAccess.ShaderResource, + BarrierLayout.UnorderedAccess => D3D12BarrierAccess.UnorderedAccess, + BarrierLayout.CopySource => D3D12BarrierAccess.CopySource, + BarrierLayout.CopyDest => D3D12BarrierAccess.CopyDest, + BarrierLayout.ResolveSource => D3D12BarrierAccess.ResolveSource, + BarrierLayout.ResolveDest => D3D12BarrierAccess.ResolveDest, + _ => D3D12BarrierAccess.Common, + }; + + /// + /// Derives the D3D12 Enhanced Barriers sync flags from a . + /// + internal static D3D12BarrierSync ToEnhancedSync(BarrierLayout layout) => layout switch + { + BarrierLayout.RenderTarget => D3D12BarrierSync.RenderTarget, + BarrierLayout.DepthStencilWrite => D3D12BarrierSync.DepthStencil, + BarrierLayout.DepthStencilRead => D3D12BarrierSync.DepthStencil, + BarrierLayout.ShaderResource => D3D12BarrierSync.AllShading, + BarrierLayout.UnorderedAccess => D3D12BarrierSync.AllShading, + BarrierLayout.CopySource => D3D12BarrierSync.Copy, + BarrierLayout.CopyDest => D3D12BarrierSync.Copy, + BarrierLayout.ResolveSource => D3D12BarrierSync.Resolve, + BarrierLayout.ResolveDest => D3D12BarrierSync.Resolve, + _ => D3D12BarrierSync.All, + }; +} + +#endif diff --git a/sources/engine/Stride.Graphics/Direct3D12/Buffer.Direct3D12.cs b/sources/engine/Stride.Graphics/Direct3D12/Buffer.Direct3D12.cs index 558eb2e18d..28586875ef 100644 --- a/sources/engine/Stride.Graphics/Direct3D12/Buffer.Direct3D12.cs +++ b/sources/engine/Stride.Graphics/Direct3D12/Buffer.Direct3D12.cs @@ -283,6 +283,8 @@ public void Recreate(IntPtr dataPointer) } } + LayoutTracker.Initialize(BarrierMapping.ToBarrierLayout(NativeResourceState), 1); + NativeShaderResourceView = GetShaderResourceView(ViewFormat); NativeUnorderedAccessView = GetUnorderedAccessView(ViewFormat); } diff --git a/sources/engine/Stride.Graphics/Direct3D12/CommandList.Direct3D12.cs b/sources/engine/Stride.Graphics/Direct3D12/CommandList.Direct3D12.cs index 4e598addcf..7947888d7c 100644 --- a/sources/engine/Stride.Graphics/Direct3D12/CommandList.Direct3D12.cs +++ b/sources/engine/Stride.Graphics/Direct3D12/CommandList.Direct3D12.cs @@ -6,6 +6,7 @@ using System; using System.Collections.Generic; using System.Diagnostics; +using System.Runtime.CompilerServices; using Silk.NET.Core.Native; using Silk.NET.DXGI; @@ -35,8 +36,9 @@ public unsafe partial class CommandList private int samplerHeapOffset = GraphicsDevice.SamplerHeapSize; private PipelineState boundPipelineState; + private DescriptorSet[] boundDescriptorSets; private readonly ID3D12DescriptorHeap*[] descriptorHeaps = new ID3D12DescriptorHeap*[2]; - private readonly List resourceBarriers = new(16); + private readonly List pendingBarriers = new(16); // Mappings from CPU-side Descriptor Handles to GPU-side Descriptor Handles private readonly Dictionary srvMapping = []; @@ -71,9 +73,31 @@ private CommandList(GraphicsDevice device) : base(device) /// protected internal override void OnDestroyed(bool immediately = false) { - // Recycle heaps - ResetSrvHeap(createNewHeap: false); - ResetSamplerHeap(createNewHeap: false); + // Release current active heaps + if (srvHeap.Heap.IsNotNull()) + { + srvHeap.Heap.Release(); + srvHeap.Heap = default; + } + if (samplerHeap.Heap.IsNotNull()) + { + samplerHeap.Heap.Release(); + samplerHeap.Heap = default; + } + + // Release any heaps accumulated in the current command list + if (currentCommandList.SrvHeaps is not null) + { + foreach (var heap in currentCommandList.SrvHeaps) + heap.Release(); + currentCommandList.SrvHeaps.Clear(); + } + if (currentCommandList.SamplerHeaps is not null) + { + foreach (var heap in currentCommandList.SamplerHeaps) + heap.Release(); + currentCommandList.SamplerHeaps.Clear(); + } // Available right now (NextFenceValue - 1) // TODO: Note that it won't be available right away because CommandAllocators is currently not using a PriorityQueue but a simple Queue @@ -182,7 +206,12 @@ public partial CompiledCommandList Close() HResult result = currentCommandList.NativeCommandList.Close(); if (result.IsFailure) + { + if (IsDebugMode) + GraphicsDevice.FlushDebugMessages(); + result.Throw(); + } // Staging resources not updated anymore foreach (var stagingResource in currentCommandList.StagingResources) @@ -246,6 +275,18 @@ private void ResetTargetsImpl() { } /// The Render Targets to bind. private partial void SetRenderTargetsImpl(Texture depthStencilBuffer, ReadOnlySpan renderTargetViews) { + // Transition render targets and depth-stencil to the correct state + for (int i = 0; i < renderTargetViews.Length; ++i) + { + var rt = renderTargetViews[i]; + ResourceBarrierTransition(rt, BarrierLayout.RenderTarget, GetTextureSubresource(rt)); + } + + if (depthStencilBuffer is not null) + ResourceBarrierTransition(depthStencilBuffer, BarrierLayout.DepthStencilWrite, GetTextureSubresource(depthStencilBuffer)); + + FlushResourceBarriers(); + int renderTargetCount = renderTargetViews.Length; var renderTargetHandles = stackalloc CpuDescriptorHandle[renderTargetCount]; @@ -359,10 +400,62 @@ private unsafe partial void SetScissorRectanglesImpl(ReadOnlySpan sci /// private void PrepareDraw() { + TransitionDescriptorResources(); FlushResourceBarriers(); SetViewportImpl(); } + /// + /// Transitions all resources bound in descriptor sets to the correct state for shader access. + /// SRV resources are transitioned to PixelShaderResource | NonPixelShaderResource, + /// UAV resources are transitioned to UnorderedAccess. + /// + private void TransitionDescriptorResources() + { + if (boundDescriptorSets is null) + return; + + for (int i = 0; i < boundDescriptorSets.Length; i++) + { + var tracking = boundDescriptorSets[i].Tracking; + if (tracking is null) + continue; + + var resources = tracking.Resources; + var isUAV = tracking.IsUAV; + + for (int j = 0; j < resources.Length; j++) + { + var resource = resources[j]; + if (resource is null) + continue; + + // Skip resources on upload/readback heaps (GenericRead/CopyDest) — they can't be transitioned + if (resource.NativeResourceState == ResourceStates.GenericRead || + resource.NativeResourceState == ResourceStates.CopyDest) + continue; + + if (isUAV[j]) + ResourceBarrierTransition(resource, BarrierLayout.UnorderedAccess); + else + ResourceBarrierTransition(resource, BarrierLayout.ShaderResource); + } + } + } + + /// + /// Computes the D3D12 subresource index for a texture or texture view. + /// Returns for whole-resource (non-view) textures. + /// + private static uint GetTextureSubresource(Texture texture) + { + if (texture.ParentTexture is null) + return uint.MaxValue; // Whole resource + + var parent = texture.ParentTexture; + return (uint)(texture.MipLevel + texture.ArraySlice * parent.MipLevelCount); + } + /// /// Sets the reference value for Depth-Stencil tests. /// @@ -492,37 +585,54 @@ public void SetIndexBuffer(Buffer buffer, int offset, bool is32Bits) // TODO: Us /// /// The Graphics Resource to transition to a different state. /// The new state of . - public void ResourceBarrierTransition(GraphicsResource resource, GraphicsResourceState newState) + /// + /// Transitions a resource to a new layout using the cross-platform barrier abstraction. + /// + public void ResourceBarrierTransition(GraphicsResource resource, BarrierLayout newLayout, uint subresource = uint.MaxValue) { Debug.Assert(resource is not null, "Resource must not be null."); - // Find parent resource - if (resource.ParentResource is not null) - resource = resource.ParentResource; + // Texture views share the native resource of their parent; barrier the parent instead + if (resource is Texture { ParentTexture: not null } textureView) + resource = textureView.ParentTexture; - var targetState = (ResourceStates) newState; - - if (resource.IsTransitionNeeded(targetState)) + if (resource.LayoutTracker.NeedsTransition(subresource, newLayout)) { - var transitionBarrier = new ResourceBarrier + // When per-subresource tracking is active and a whole-resource transition is requested, + // only emit barriers for subresources that actually differ + if (subresource == uint.MaxValue && resource.LayoutTracker.HasPerSubresourceTracking) { - Type = ResourceBarrierType.Transition, - Flags = ResourceBarrierFlags.None, - - Transition = new ResourceTransitionBarrier + var layouts = resource.LayoutTracker.PerSubresourceLayouts; + for (int i = 0; i < layouts.Length; i++) { - PResource = resource.NativeResource, - Subresource = uint.MaxValue, - StateBefore = resource.NativeResourceState, - StateAfter = targetState + if (layouts[i] != newLayout) + { + pendingBarriers.Add(new ResourceBarrierDescription(resource, layouts[i], newLayout) + { + Subresource = (uint)i + }); + } } - }; + } + else + { + pendingBarriers.Add(new ResourceBarrierDescription(resource, resource.LayoutTracker.Get(subresource), newLayout) + { + Subresource = subresource + }); + } - resourceBarriers.Add(transitionBarrier); - resource.NativeResourceState = targetState; + resource.LayoutTracker.Set(subresource, newLayout); + resource.NativeResourceState = BarrierMapping.ToResourceStates(newLayout); } } + [Obsolete("Use BarrierLayout overload instead.")] + public void ResourceBarrierTransition(GraphicsResource resource, GraphicsResourceState newState) + { + ResourceBarrierTransition(resource, BarrierMapping.ToBarrierLayout((ResourceStates)newState)); + } + /// /// Flushes all pending Graphics Resource barriers. /// @@ -532,34 +642,161 @@ public void ResourceBarrierTransition(GraphicsResource resource, GraphicsResourc /// private unsafe void FlushResourceBarriers() { - int count = resourceBarriers.Count; + int count = pendingBarriers.Count; if (count == 0) return; + // Coalesce duplicate barriers for the same resource+subresource. + // Sort by (Resource, Subresource), then merge consecutive entries: + // keep the first LayoutBefore and the last LayoutAfter, drop no-ops. + if (count > 1) + { + pendingBarriers.Sort(static (a, b) => + { + int cmp = RuntimeHelpers.GetHashCode(a.Resource).CompareTo(RuntimeHelpers.GetHashCode(b.Resource)); + return cmp != 0 ? cmp : a.Subresource.CompareTo(b.Subresource); + }); + + int write = 0; + for (int read = 1; read < count; read++) + { + if (pendingBarriers[write].Resource == pendingBarriers[read].Resource && + pendingBarriers[write].Subresource == pendingBarriers[read].Subresource) + { + // Merge: keep LayoutBefore from [write], take LayoutAfter from [read] + var merged = pendingBarriers[write]; + merged.LayoutAfter = pendingBarriers[read].LayoutAfter; + pendingBarriers[write] = merged; + } + else + { + // Keep previous entry only if it's not a no-op (A→B→A) + if (pendingBarriers[write].LayoutBefore != pendingBarriers[write].LayoutAfter) + write++; + pendingBarriers[write] = pendingBarriers[read]; + } + } + + // Final entry: keep if not a no-op + count = pendingBarriers[write].LayoutBefore != pendingBarriers[write].LayoutAfter ? write + 1 : write; + if (count < pendingBarriers.Count) + pendingBarriers.RemoveRange(count, pendingBarriers.Count - count); + } + + count = pendingBarriers.Count; + if (count == 0) + return; + + if (GraphicsDevice.SupportsEnhancedBarriers) + FlushResourceBarriersEnhanced(count); + else + FlushResourceBarriersLegacy(count); + } + + private unsafe void FlushResourceBarriersLegacy(int count) + { scoped Span barriers = stackalloc ResourceBarrier[count]; - resourceBarriers.CopyTo(barriers); - resourceBarriers.Clear(); + for (int i = 0; i < count; i++) + { + var desc = pendingBarriers[i]; + + barriers[i] = new ResourceBarrier + { + Type = ResourceBarrierType.Transition, + Flags = ResourceBarrierFlags.None, + Transition = new ResourceTransitionBarrier + { + PResource = desc.Resource.NativeResource, + Subresource = desc.Subresource, + StateBefore = BarrierMapping.ToResourceStates(desc.LayoutBefore), + StateAfter = BarrierMapping.ToResourceStates(desc.LayoutAfter), + } + }; + } + + pendingBarriers.Clear(); - currentCommandList.NativeCommandList.ResourceBarrier(NumBarriers: (uint) count, barriers); + currentCommandList.NativeCommandList.ResourceBarrier(NumBarriers: (uint)count, barriers); } - /// - /// Transitions the specified Graphics Resource to a new state and returns an object that - /// can restore the resource to its previous state. - /// - /// The Graphics Resource to transition. Cannot be . - /// The state to which the resource will be transitioned. - /// - /// A object that can be used to restore - /// the resource to its original state. - /// - private ResourceBarrierTransitionRestore ResourceBarrierTransitionAndRestore(GraphicsResource resource, GraphicsResourceState newState) + private unsafe void FlushResourceBarriersEnhanced(int count) { - var currentState = resource.NativeResourceState; - ResourceBarrierTransition(resource, newState); + // Separate texture and buffer barriers + scoped Span textureBarriers = stackalloc D3D12TextureBarrier[count]; + scoped Span bufferBarriers = stackalloc D3D12BufferBarrier[count]; + int textureCount = 0; + int bufferCount = 0; - return new ResourceBarrierTransitionRestore(this, resource, (GraphicsResourceState) currentState); + for (int i = 0; i < count; i++) + { + var desc = pendingBarriers[i]; + + if (desc.Resource is Texture) + { + textureBarriers[textureCount++] = new D3D12TextureBarrier + { + SyncBefore = BarrierMapping.ToEnhancedSync(desc.LayoutBefore), + SyncAfter = BarrierMapping.ToEnhancedSync(desc.LayoutAfter), + AccessBefore = BarrierMapping.ToEnhancedAccess(desc.LayoutBefore), + AccessAfter = BarrierMapping.ToEnhancedAccess(desc.LayoutAfter), + LayoutBefore = BarrierMapping.ToEnhancedLayout(desc.LayoutBefore), + LayoutAfter = BarrierMapping.ToEnhancedLayout(desc.LayoutAfter), + PResource = desc.Resource.NativeResource, + Subresources = D3D12SubresourceRange.All, + Flags = 0, + }; + } + else + { + bufferBarriers[bufferCount++] = new D3D12BufferBarrier + { + SyncBefore = BarrierMapping.ToEnhancedSync(desc.LayoutBefore), + SyncAfter = BarrierMapping.ToEnhancedSync(desc.LayoutAfter), + AccessBefore = BarrierMapping.ToEnhancedAccess(desc.LayoutBefore), + AccessAfter = BarrierMapping.ToEnhancedAccess(desc.LayoutAfter), + PResource = desc.Resource.NativeResource, + Offset = 0, + Size = ulong.MaxValue, // Entire buffer + }; + } + } + + pendingBarriers.Clear(); + + // Submit barrier groups — both fixed blocks must be active during the Barrier call + fixed (D3D12TextureBarrier* pTextureBarriers = textureBarriers) + fixed (D3D12BufferBarrier* pBufferBarriers = bufferBarriers) + { + var groups = stackalloc D3D12BarrierGroup[2]; + int groupCount = 0; + + if (textureCount > 0) + { + groups[groupCount++] = new D3D12BarrierGroup + { + Type = D3D12BarrierType.Texture, + NumBarriers = (uint)textureCount, + PBarriers = pTextureBarriers, + }; + } + + if (bufferCount > 0) + { + groups[groupCount++] = new D3D12BarrierGroup + { + Type = D3D12BarrierType.Buffer, + NumBarriers = (uint)bufferCount, + PBarriers = pBufferBarriers, + }; + } + + if (groupCount > 0) + { + var commandList7 = (ID3D12GraphicsCommandList7*)currentCommandList.NativeCommandList.Handle; + commandList7->Barrier((uint)groupCount, (BarrierGroup*)groups); + } + } } /// @@ -575,6 +812,8 @@ private ResourceBarrierTransitionRestore ResourceBarrierTransitionAndRestore(Gra /// public void SetDescriptorSets(int index, DescriptorSet[] descriptorSets) { + boundDescriptorSets = descriptorSets; + RestartWithNewHeap: var descriptorTableIndex = 0; @@ -933,8 +1172,22 @@ public void WriteTimestamp(QueryPool queryPool, int index) /// public void BeginProfile(Color4 profileColor, string name) { - if (IsDebugMode) + if (!IsDebugMode) + return; + + if (WinPixNative.IsAvailable) + { WinPixNative.PIXBeginEventOnCommandList(currentCommandList.NativeCommandList, profileColor, name); + } + else + { + // Fallback: use D3D12 built-in event markers (visible in RenderDoc, NSight, etc.) + var maxBytes = System.Text.Encoding.ASCII.GetMaxByteCount(name.Length) + 1; + var nameBytes = stackalloc byte[maxBytes]; + int written = System.Text.Encoding.ASCII.GetBytes(name, new Span(nameBytes, maxBytes)); + nameBytes[written] = 0; + currentCommandList.NativeCommandList.BeginEvent(Metadata: 0, nameBytes, (uint)(written + 1)); + } } /// @@ -943,8 +1196,13 @@ public void BeginProfile(Color4 profileColor, string name) /// public void EndProfile() { - if (IsDebugMode) + if (!IsDebugMode) + return; + + if (WinPixNative.IsAvailable) WinPixNative.PIXEndEventOnCommandList(currentCommandList.NativeCommandList); + else + currentCommandList.NativeCommandList.EndEvent(); } // TODO: Unused, remove? @@ -967,7 +1225,7 @@ public void Clear(Texture depthStencilBuffer, DepthStencilClearOptions options, { ArgumentNullException.ThrowIfNull(depthStencilBuffer); - using var _ = ResourceBarrierTransitionAndRestore(depthStencilBuffer, GraphicsResourceState.DepthWrite); + ResourceBarrierTransition(depthStencilBuffer, BarrierLayout.DepthStencilWrite); FlushResourceBarriers(); // Check that the Depth-Stencil Buffer has a Stencil if Clear Stencil is requested @@ -994,7 +1252,7 @@ public void Clear(Texture renderTarget, Color4 color) { ArgumentNullException.ThrowIfNull(renderTarget); - using var _ = ResourceBarrierTransitionAndRestore(renderTarget, GraphicsResourceState.RenderTarget); + ResourceBarrierTransition(renderTarget, BarrierLayout.RenderTarget); FlushResourceBarriers(); scoped ref SilkBox2I nullRect = ref NullRef(); @@ -1018,6 +1276,9 @@ public void ClearReadWrite(Buffer buffer, Vector4 value) if (buffer.NativeUnorderedAccessView.Ptr == 0) throw new ArgumentException("Expecting a Buffer supporting UAV", nameof(buffer)); + ResourceBarrierTransition(buffer, BarrierLayout.UnorderedAccess); + FlushResourceBarriers(); + var cpuHandle = buffer.NativeUnorderedAccessView; var gpuHandle = GetGpuDescriptorHandle(cpuHandle); @@ -1042,6 +1303,9 @@ public void ClearReadWrite(Buffer buffer, Int4 value) if (buffer.NativeUnorderedAccessView.Ptr == 0) throw new ArgumentException("Expecting a Buffer supporting UAV", nameof(buffer)); + ResourceBarrierTransition(buffer, BarrierLayout.UnorderedAccess); + FlushResourceBarriers(); + var cpuHandle = buffer.NativeUnorderedAccessView; var gpuHandle = GetGpuDescriptorHandle(cpuHandle); @@ -1066,6 +1330,9 @@ public void ClearReadWrite(Buffer buffer, UInt4 value) if (buffer.NativeUnorderedAccessView.Ptr == 0) throw new ArgumentException("Expecting a Buffer supporting UAV", nameof(buffer)); + ResourceBarrierTransition(buffer, BarrierLayout.UnorderedAccess); + FlushResourceBarriers(); + var cpuHandle = buffer.NativeUnorderedAccessView; var gpuHandle = GetGpuDescriptorHandle(cpuHandle); @@ -1090,6 +1357,9 @@ public void ClearReadWrite(Texture texture, Vector4 value) if (texture.NativeUnorderedAccessView.Ptr == 0) throw new ArgumentException("Expecting texture supporting UAV", nameof(texture)); + ResourceBarrierTransition(texture, BarrierLayout.UnorderedAccess); + FlushResourceBarriers(); + var cpuHandle = texture.NativeUnorderedAccessView; var gpuHandle = GetGpuDescriptorHandle(cpuHandle); @@ -1114,6 +1384,9 @@ public void ClearReadWrite(Texture texture, Int4 value) if (texture.NativeUnorderedAccessView.Ptr == 0) throw new ArgumentException("Expecting texture supporting UAV", nameof(texture)); + ResourceBarrierTransition(texture, BarrierLayout.UnorderedAccess); + FlushResourceBarriers(); + var cpuHandle = texture.NativeUnorderedAccessView; var gpuHandle = GetGpuDescriptorHandle(cpuHandle); @@ -1138,6 +1411,9 @@ public void ClearReadWrite(Texture texture, UInt4 value) if (texture.NativeUnorderedAccessView.Ptr == 0) throw new ArgumentException("Expecting texture supporting UAV", nameof(texture)); + ResourceBarrierTransition(texture, BarrierLayout.UnorderedAccess); + FlushResourceBarriers(); + var cpuHandle = texture.NativeUnorderedAccessView; var gpuHandle = GetGpuDescriptorHandle(cpuHandle); @@ -1256,6 +1532,13 @@ void CopyBetweenTextures(Texture sourceTexture, Texture destinationTexture) // void CopyStagingTextureToStagingTexture(Texture sourceTexture, Texture destinationTexture) { + // Wait for any pending GPU copies (e.g. InitializeStagingData) before reading + if (sourceTexture.CopyFenceValue.HasValue) + { + GraphicsDevice.CopyFence.WaitForFenceCPUInternal(sourceTexture.CopyFenceValue.Value); + sourceTexture.CopyFenceValue = null; + } + var size = destinationTexture.ComputeBufferTotalSize(); scoped ref var fullRange = ref NullRef(); @@ -1284,8 +1567,8 @@ void CopyStagingTextureToStagingTexture(Texture sourceTexture, Texture destinati // void CopyTextureToStagingTexture(Texture sourceTexture, Texture sourceParent, Texture destinationTexture) { - using var _1 = ResourceBarrierTransitionAndRestore(sourceTexture, GraphicsResourceState.CopySource); - using var _2 = ResourceBarrierTransitionAndRestore(destinationTexture, GraphicsResourceState.CopyDestination); + ResourceBarrierTransition(sourceTexture, BarrierLayout.CopySource); + ResourceBarrierTransition(destinationTexture, BarrierLayout.CopyDest); FlushResourceBarriers(); int copyOffset = 0; @@ -1330,8 +1613,8 @@ void CopyTextureToStagingTexture(Texture sourceTexture, Texture sourceParent, Te // void CopyTextureToTexture(Texture sourceTexture, Texture destinationTexture) { - using var _1 = ResourceBarrierTransitionAndRestore(sourceTexture, GraphicsResourceState.CopySource); - using var _2 = ResourceBarrierTransitionAndRestore(destinationTexture, GraphicsResourceState.CopyDestination); + ResourceBarrierTransition(sourceTexture, BarrierLayout.CopySource); + ResourceBarrierTransition(destinationTexture, BarrierLayout.CopyDest); FlushResourceBarriers(); currentCommandList.NativeCommandList.CopyResource(destinationTexture.NativeResource, sourceTexture.NativeResource); @@ -1342,8 +1625,8 @@ void CopyTextureToTexture(Texture sourceTexture, Texture destinationTexture) // void CopyBetweenBuffers(Buffer sourceBuffer, Buffer destinationBuffer) { - using var _1 = ResourceBarrierTransitionAndRestore(sourceBuffer, GraphicsResourceState.CopySource); - using var _2 = ResourceBarrierTransitionAndRestore(destinationBuffer, GraphicsResourceState.CopyDestination); + ResourceBarrierTransition(sourceBuffer, BarrierLayout.CopySource); + ResourceBarrierTransition(destinationBuffer, BarrierLayout.CopyDest); FlushResourceBarriers(); currentCommandList.NativeCommandList.CopyResource(destinationBuffer.NativeResource, sourceBuffer.NativeResource); @@ -1423,6 +1706,10 @@ public void CopyMultisample(Texture sourceMultiSampledTexture, int sourceSubReso if (!sourceMultiSampledTexture.IsMultiSampled) throw new ArgumentException("Source Texture is not a MSAA Texture", nameof(sourceMultiSampledTexture)); + ResourceBarrierTransition(sourceMultiSampledTexture, BarrierLayout.ResolveSource); + ResourceBarrierTransition(destinationTexture, BarrierLayout.ResolveDest); + FlushResourceBarriers(); + currentCommandList.NativeCommandList.ResolveSubresource(sourceMultiSampledTexture.NativeResource, (uint) sourceSubResourceIndex, destinationTexture.NativeResource, (uint) destinationSubResourceIndex, (Format)(format == PixelFormat.None ? destinationTexture.Format : format)); @@ -1509,8 +1796,8 @@ void CopyBetweenTextures(Texture sourceTexture, Texture destinationTexture) throw new NotImplementedException("Copy region of staging resources is not supported yet"); // TODO: Implement copy region for staging resources } - using var _1 = ResourceBarrierTransitionAndRestore(source, GraphicsResourceState.CopySource); - using var _2 = ResourceBarrierTransitionAndRestore(destination, GraphicsResourceState.CopyDestination); + ResourceBarrierTransition(source, BarrierLayout.CopySource); + ResourceBarrierTransition(destination, BarrierLayout.CopyDest); FlushResourceBarriers(); if (sourceTexture.Usage == GraphicsResourceUsage.Staging) @@ -1582,8 +1869,8 @@ void CopyBetweenTextures(Texture sourceTexture, Texture destinationTexture) // void CopyBetweenBuffers(Buffer sourceBuffer, Buffer destinationBuffer) { - using var _1 = ResourceBarrierTransitionAndRestore(source, GraphicsResourceState.CopySource); - using var _2 = ResourceBarrierTransitionAndRestore(destination, GraphicsResourceState.CopyDestination); + ResourceBarrierTransition(source, BarrierLayout.CopySource); + ResourceBarrierTransition(destination, BarrierLayout.CopyDest); FlushResourceBarriers(); currentCommandList.NativeCommandList.CopyBufferRegion(destinationBuffer.NativeResource, (ulong)dstX, @@ -1617,6 +1904,10 @@ public void CopyCount(Buffer sourceBuffer, Buffer destinationBuffer, int destina ArgumentNullException.ThrowIfNull(sourceBuffer); ArgumentNullException.ThrowIfNull(destinationBuffer); + ResourceBarrierTransition(sourceBuffer, BarrierLayout.CopySource); + ResourceBarrierTransition(destinationBuffer, BarrierLayout.CopyDest); + FlushResourceBarriers(); + currentCommandList.NativeCommandList.CopyBufferRegion(destinationBuffer.NativeResource, (ulong) destinationOffsetInBytes, sourceBuffer.NativeResource, SrcOffset: 0, NumBytes: sizeof(uint)); } @@ -1818,8 +2109,7 @@ void UpdateTexture(Texture texture) if (result.IsFailure) result.Throw(); - lock (GraphicsDevice.TemporaryResources) - GraphicsDevice.TemporaryResources.Enqueue((GraphicsDevice.FrameFence.NextFenceValue, nativeUploadTexture)); + GraphicsDevice.FrameTemporaryResources.Enqueue(GraphicsDevice.FrameFence.NextFenceValue, nativeUploadTexture); scoped ref var fullBox = ref NullRef(); @@ -1829,7 +2119,7 @@ void UpdateTexture(Texture texture) result.Throw(); // Trigger copy - using var _ = ResourceBarrierTransitionAndRestore(resource, GraphicsResourceState.CopyDestination); + ResourceBarrierTransition(resource, BarrierLayout.CopyDest); FlushResourceBarriers(); var destRegion = new TextureCopyLocation @@ -1858,7 +2148,7 @@ void UpdateBuffer() MemoryUtilities.CopyWithAlignmentFallback((void*) uploadMemory, (void*) sourceData.DataPointer, (uint) uploadSize); - using var _ = ResourceBarrierTransitionAndRestore(resource, GraphicsResourceState.CopyDestination); + ResourceBarrierTransition(resource, BarrierLayout.CopyDest); FlushResourceBarriers(); currentCommandList.NativeCommandList.CopyBufferRegion(pDstBuffer: resource.NativeResource, DstOffset: (ulong)region.Left, @@ -2123,31 +2413,6 @@ public DescriptorHeapWrapper(ComPtr heap) #endregion - #region ResourceBarrierTransitionRestore structure - - /// - /// Provides a mechanism to temporarily transition a Graphics Resource to a new state - /// and automatically restore its previous state when disposed. - /// - /// - /// This structure is typically used in conjunction with a statement - /// to guarantee state restoration even if an exception occurs. - /// If not used with an statement, the caller is responsible for calling - /// to restore the resource state. - /// - /// The Command List used to record the resource state transition operations. - /// The Graphics Resource to transition between states. - /// The original state to which the resource will be restored when this instance is disposed. - private readonly struct ResourceBarrierTransitionRestore(CommandList commandList, GraphicsResource Resource, GraphicsResourceState OldState) : IDisposable - { - /// - public readonly void Dispose() - { - commandList.ResourceBarrierTransition(Resource, OldState); - } - } - - #endregion } } diff --git a/sources/engine/Stride.Graphics/Direct3D12/DeferredReleaseQueue.Direct3D12.cs b/sources/engine/Stride.Graphics/Direct3D12/DeferredReleaseQueue.Direct3D12.cs new file mode 100644 index 0000000000..9cb813fece --- /dev/null +++ b/sources/engine/Stride.Graphics/Direct3D12/DeferredReleaseQueue.Direct3D12.cs @@ -0,0 +1,75 @@ +// Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) +// Distributed under the MIT license. See the LICENSE.md file in the project root for more information. + +#if STRIDE_GRAPHICS_API_DIRECT3D12 + +using System.Collections.Generic; + +using Silk.NET.Core.Native; +using Silk.NET.Direct3D12; + +namespace Stride.Graphics; + +/// +/// A thread-safe queue of COM resources awaiting deferred release once a GPU fence has been reached. +/// +internal struct DeferredReleaseQueue +{ + private readonly Queue<(ulong FenceValue, object Resource)> queue; + + public DeferredReleaseQueue() + { + queue = new(); + } + + /// + /// Enqueues a resource for deferred release after the specified fence value is reached. + /// + public void Enqueue(ulong fenceValue, object resource) + { + lock (queue) + queue.Enqueue((fenceValue, resource)); + } + + /// + /// Releases all resources whose associated fence value has been reached. + /// + public void ReleaseCompleted(GraphicsDevice.FenceHelper fence) + { + lock (queue) + { + while (queue.Count > 0 && fence.IsFenceCompleteInternal(queue.Peek().FenceValue)) + { + ReleaseResource(queue.Dequeue().Resource); + } + } + } + + /// + /// Releases all remaining resources regardless of fence state. Used during device shutdown. + /// + public void ReleaseAll() + { + lock (queue) + { + while (queue.Count > 0) + { + ReleaseResource(queue.Dequeue().Resource); + } + } + } + + private static void ReleaseResource(object resource) + { + if (resource is ComPtr comPtr) + { + comPtr.Release(); + } + else if (resource is GraphicsResourceLink resourceLink) + { + resourceLink.ReferenceCount--; + } + } +} + +#endif diff --git a/sources/engine/Stride.Graphics/Direct3D12/DescriptorSet.Direct3D12.cs b/sources/engine/Stride.Graphics/Direct3D12/DescriptorSet.Direct3D12.cs index a17bbb68b2..a2b5ff58f8 100644 --- a/sources/engine/Stride.Graphics/Direct3D12/DescriptorSet.Direct3D12.cs +++ b/sources/engine/Stride.Graphics/Direct3D12/DescriptorSet.Direct3D12.cs @@ -24,6 +24,11 @@ public readonly unsafe partial struct DescriptorSet /// internal readonly DescriptorSetLayout? Description; + /// + /// Tracks which GraphicsResource is bound at each SRV/UAV/CBV slot for automatic barrier transitions. + /// + internal readonly ResourceTracking? Tracking; + /// /// Gets a value indicating if the Descriptor Set is valid. /// @@ -70,6 +75,7 @@ private DescriptorSet(GraphicsDevice graphicsDevice, DescriptorPool pool, Descri // TODO: different mechanism? nativeDevice = null; Description = null; + Tracking = null; SrvStart = default; SamplerStart = default; return; @@ -79,6 +85,7 @@ private DescriptorSet(GraphicsDevice graphicsDevice, DescriptorPool pool, Descri nativeDevice = graphicsDevice.NativeDevice; Description = layout; + Tracking = layout.SrvCount > 0 ? new ResourceTracking(layout.SrvCount, graphicsDevice.SrvHandleIncrementSize) : null; // Store starting CpuDescriptorHandle for SRVs, UAVs, etc. var startHandle = layout.SrvCount > 0 @@ -98,6 +105,18 @@ private DescriptorSet(GraphicsDevice graphicsDevice, DescriptorPool pool, Descri // TODO: D3D12: Thread safety? pool.SrvOffset += layout.SrvCount; pool.SamplerOffset += layout.SamplerCount; + + // Initialize all sampler slots with LinearClamp as a default fallback. + // Unlike D3D11, D3D12 has no default sampler — uninitialized sampler descriptors cause undefined behavior. + if (layout.SamplerCount > 0) + { + var defaultSampler = graphicsDevice.SamplerStates.LinearClamp.NativeSampler; + for (int i = 0; i < layout.SamplerCount; i++) + { + var dest = new CpuDescriptorHandle(SamplerStart.Ptr + (nuint)(i * graphicsDevice.SamplerHandleIncrementSize)); + nativeDevice.CopyDescriptorsSimple(1, dest, defaultSampler, DescriptorHeapType.Sampler); + } + } } @@ -128,10 +147,13 @@ public void SetShaderResourceView(int slot, GraphicsResource shaderResourceView) if (shaderResourceView.NativeShaderResourceView.Ptr == 0) return; - var destDescriptorRangeStart = new CpuDescriptorHandle(SrvStart.Ptr + (nuint) BindingOffsets[slot]); + var bindingOffset = BindingOffsets[slot]; + var destDescriptorRangeStart = new CpuDescriptorHandle(SrvStart.Ptr + (nuint) bindingOffset); nativeDevice.CopyDescriptorsSimple(NumDescriptors: 1, destDescriptorRangeStart, shaderResourceView.NativeShaderResourceView, DescriptorHeapType.CbvSrvUav); + + Tracking?.Set(bindingOffset, shaderResourceView, isUav: false); } /// @@ -172,9 +194,12 @@ public void SetConstantBuffer(int slot, Buffer buffer, int offset, int size) SizeInBytes = (uint) ((size + D3D12.ConstantBufferDataPlacementAlignment) / D3D12.ConstantBufferDataPlacementAlignment * D3D12.ConstantBufferDataPlacementAlignment) }; - var destDescriptorHandle = new CpuDescriptorHandle(SrvStart.Ptr + (nuint) BindingOffsets[slot]); + var bindingOffset = BindingOffsets[slot]; + var destDescriptorHandle = new CpuDescriptorHandle(SrvStart.Ptr + (nuint) bindingOffset); nativeDevice.CreateConstantBufferView(in cbufferViewDesc, destDescriptorHandle); + + // Constant buffers are typically on upload heaps (GenericRead) and don't need transitions } /// @@ -190,10 +215,37 @@ public void SetUnorderedAccessView(int slot, GraphicsResource unorderedAccessVie if (unorderedAccessView.NativeUnorderedAccessView.Ptr == 0) throw new ArgumentException($"Resource '{unorderedAccessView}' has missing Unordered Access View."); - var destDescriptorRangeStart = new CpuDescriptorHandle(SrvStart.Ptr + (nuint) BindingOffsets[slot]); + var bindingOffset = BindingOffsets[slot]; + var destDescriptorRangeStart = new CpuDescriptorHandle(SrvStart.Ptr + (nuint) bindingOffset); nativeDevice.CopyDescriptorsSimple(NumDescriptors: 1, destDescriptorRangeStart, unorderedAccessView.NativeUnorderedAccessView, DescriptorHeapType.CbvSrvUav); + + Tracking?.Set(bindingOffset, unorderedAccessView, isUav: true); + } + } + + /// + /// Tracks which resources are bound in a descriptor set's SRV/UAV slots for automatic barrier insertion. + /// + internal sealed class ResourceTracking + { + internal readonly GraphicsResource[] Resources; + internal readonly bool[] IsUAV; + private readonly int handleIncrementSize; + + internal ResourceTracking(int srvCount, int handleIncrementSize) + { + Resources = new GraphicsResource[srvCount]; + IsUAV = new bool[srvCount]; + this.handleIncrementSize = handleIncrementSize; + } + + internal void Set(int bindingOffset, GraphicsResource resource, bool isUav) + { + int index = bindingOffset / handleIncrementSize; + Resources[index] = resource; + IsUAV[index] = isUav; } } } diff --git a/sources/engine/Stride.Graphics/Direct3D12/EnhancedBarriers.Direct3D12.cs b/sources/engine/Stride.Graphics/Direct3D12/EnhancedBarriers.Direct3D12.cs new file mode 100644 index 0000000000..fd18b7be49 --- /dev/null +++ b/sources/engine/Stride.Graphics/Direct3D12/EnhancedBarriers.Direct3D12.cs @@ -0,0 +1,145 @@ +// Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) +// Distributed under the MIT license. See the LICENSE.md file in the project root for more information. + +// Manual definitions for D3D12 Enhanced Barriers structs not yet in Silk.NET 2.22.0. +// These match the native D3D12 ABI and can be removed when Silk.NET is updated. + +#if STRIDE_GRAPHICS_API_DIRECT3D12 + +using System; +using System.Runtime.InteropServices; + +using Silk.NET.Direct3D12; + +using NativeBarrierLayout = Silk.NET.Direct3D12.BarrierLayout; + +namespace Stride.Graphics; + +/// +/// D3D12 Enhanced Barriers synchronization flags. +/// +[Flags] +internal enum D3D12BarrierSync : uint +{ + None = 0, + All = 0x1, + Draw = 0x2, + InputAssembler = 0x4, + VertexShading = 0x8, + PixelShading = 0x10, + DepthStencil = 0x20, + RenderTarget = 0x40, + ComputeShading = 0x80, + Raytracing = 0x100, + Copy = 0x200, + Resolve = 0x400, + ExecuteIndirect = 0x800, + AllShading = 0x1000, + NonPixelShading = 0x2000, + BuildRaytracingAccelerationStructure = 0x8000, + CopyRaytracingAccelerationStructure = 0x10000, +} + +/// +/// D3D12 Enhanced Barriers access flags. +/// +[Flags] +internal enum D3D12BarrierAccess : uint +{ + Common = 0, + VertexBuffer = 0x1, + ConstantBuffer = 0x2, + IndexBuffer = 0x4, + RenderTarget = 0x8, + UnorderedAccess = 0x10, + DepthStencilWrite = 0x20, + DepthStencilRead = 0x40, + ShaderResource = 0x80, + StreamOutput = 0x100, + IndirectArgument = 0x200, + CopyDest = 0x400, + CopySource = 0x800, + ResolveDest = 0x1000, + ResolveSource = 0x2000, + NoAccess = 0x80000000, +} + +/// +/// D3D12 Enhanced Barriers barrier type. +/// +internal enum D3D12BarrierType : uint +{ + Global = 0, + Texture = 1, + Buffer = 2, +} + +/// +/// D3D12 texture barrier for Enhanced Barriers. +/// +[StructLayout(LayoutKind.Sequential)] +internal unsafe struct D3D12TextureBarrier +{ + public D3D12BarrierSync SyncBefore; + public D3D12BarrierSync SyncAfter; + public D3D12BarrierAccess AccessBefore; + public D3D12BarrierAccess AccessAfter; + public NativeBarrierLayout LayoutBefore; + public NativeBarrierLayout LayoutAfter; + public ID3D12Resource* PResource; + public D3D12SubresourceRange Subresources; + public uint Flags; // D3D12_TEXTURE_BARRIER_FLAGS +} + +/// +/// D3D12 buffer barrier for Enhanced Barriers. +/// +[StructLayout(LayoutKind.Sequential)] +internal unsafe struct D3D12BufferBarrier +{ + public D3D12BarrierSync SyncBefore; + public D3D12BarrierSync SyncAfter; + public D3D12BarrierAccess AccessBefore; + public D3D12BarrierAccess AccessAfter; + public ID3D12Resource* PResource; + public ulong Offset; + public ulong Size; +} + +/// +/// D3D12 subresource range for Enhanced Barriers. +/// +[StructLayout(LayoutKind.Sequential)] +internal struct D3D12SubresourceRange +{ + public uint IndexOrFirstMipLevel; + public uint NumMipLevels; + public uint FirstArraySlice; + public uint NumArraySlices; + public uint FirstPlane; + public uint NumPlanes; + + public static readonly D3D12SubresourceRange All = new() + { + IndexOrFirstMipLevel = 0xFFFFFFFF, + NumMipLevels = 0, + FirstArraySlice = 0, + NumArraySlices = 0, + FirstPlane = 0, + NumPlanes = 0, + }; +} + +/// +/// D3D12 barrier group for Enhanced Barriers — matches the native D3D12_BARRIER_GROUP layout. +/// +[StructLayout(LayoutKind.Sequential)] +internal unsafe struct D3D12BarrierGroup +{ + public D3D12BarrierType Type; + public uint NumBarriers; + // Union: pointer to TextureBarrier[], BufferBarrier[], or GlobalBarrier[] + public void* PBarriers; +} + +#endif diff --git a/sources/engine/Stride.Graphics/Direct3D12/GraphicsDevice.Direct3D12.Allocators.cs b/sources/engine/Stride.Graphics/Direct3D12/GraphicsDevice.Direct3D12.Allocators.cs index 49314e31fc..2d4334c40f 100644 --- a/sources/engine/Stride.Graphics/Direct3D12/GraphicsDevice.Direct3D12.Allocators.cs +++ b/sources/engine/Stride.Graphics/Direct3D12/GraphicsDevice.Direct3D12.Allocators.cs @@ -4,6 +4,7 @@ #if STRIDE_GRAPHICS_API_DIRECT3D12 using System; +using System.Collections.Generic; using System.Diagnostics; using Silk.NET.Core.Native; using Silk.NET.Direct3D12; @@ -29,6 +30,7 @@ internal class DescriptorAllocator(GraphicsDevice device, DescriptorHeapType des // Cached size of a descriptor handle for the given heap type private readonly int descriptorSize = (int) device.NativeDevice.GetDescriptorHandleIncrementSize(descriptorHeapType); + private readonly List> allocatedHeaps = []; private ComPtr currentHeap; private CpuDescriptorHandle currentHandle; private int remainingHandles; @@ -37,7 +39,13 @@ internal class DescriptorAllocator(GraphicsDevice device, DescriptorHeapType des /// public void Dispose() { - SafeRelease(ref currentHeap); + foreach (var heap in allocatedHeaps) + { + var h = heap; + SafeRelease(ref h); + } + allocatedHeaps.Clear(); + currentHeap = default; } @@ -89,7 +97,8 @@ void CreateNewHeap() if (result.IsFailure) result.Throw(); - currentHeap = descriptorHeap; // TODO: What do we do with the previous heap? Should we release it? + allocatedHeaps.Add(descriptorHeap); + currentHeap = descriptorHeap; remainingHandles = DescriptorPerHeap; currentHandle = descriptorHeap.GetCPUDescriptorHandleForHeapStart(); diff --git a/sources/engine/Stride.Graphics/Direct3D12/GraphicsDevice.Direct3D12.cs b/sources/engine/Stride.Graphics/Direct3D12/GraphicsDevice.Direct3D12.cs index acc97d6f06..1b01d1536c 100644 --- a/sources/engine/Stride.Graphics/Direct3D12/GraphicsDevice.Direct3D12.cs +++ b/sources/engine/Stride.Graphics/Direct3D12/GraphicsDevice.Direct3D12.cs @@ -6,11 +6,13 @@ using System; using System.Collections.Generic; using System.Diagnostics; +using System.Runtime.InteropServices; using Silk.NET.Core.Native; using Silk.NET.DXGI; using Silk.NET.Direct3D12; +using Stride.Core.Diagnostics; using Stride.Core.Threading; using static System.Runtime.CompilerServices.Unsafe; @@ -20,6 +22,8 @@ namespace Stride.Graphics { public unsafe partial class GraphicsDevice { + private static readonly Logger Log = GlobalLogger.GetLogger(nameof(GraphicsDevice)); + internal readonly int ConstantBufferDataPlacementAlignment = D3D12.ConstantBufferDataPlacementAlignment; private const GraphicsPlatform GraphicPlatform = GraphicsPlatform.Direct3D12; @@ -28,6 +32,8 @@ public unsafe partial class GraphicsDevice private static object debugLayerLock = new(); private static bool debugLayerLoaded = false; + private ID3D12InfoQueue* nativeInfoQueue; + /// /// Concurrent pool for lists of Graphics Resources that are used for staging operations. /// @@ -40,6 +46,11 @@ public unsafe partial class GraphicsDevice private bool simulateReset = false; private string rendererName; + /// + /// Whether D3D12 Enhanced Barriers are supported by the device. + /// + internal bool SupportsEnhancedBarriers; + private ID3D12Device* nativeDevice; private ID3D12CommandQueue* nativeCommandQueue; @@ -188,9 +199,14 @@ public unsafe partial class GraphicsDevice internal FenceHelper CopyFence; /// - /// Temporary or destroyed Graphics Resources that are kept around until the GPU doesn't need them anymore. + /// Resources awaiting deferred release once the frame fence has been reached. /// - internal Queue<(ulong FenceValue, object Resource)> TemporaryResources = new(); + internal DeferredReleaseQueue FrameTemporaryResources = new(); + + /// + /// Resources awaiting deferred release once the copy fence has been reached. + /// + internal DeferredReleaseQueue CopyTemporaryResources = new(); /// /// Gets the tick frquency of timestamp queries, in hertz. @@ -212,7 +228,7 @@ public GraphicsDeviceStatus GraphicsDeviceStatus var result = (DxgiConstants.DeviceRemoveReason) nativeDevice->GetDeviceRemovedReason(); - return result switch + var status = result switch { DxgiConstants.DeviceRemoveReason.DeviceRemoved => GraphicsDeviceStatus.Removed, DxgiConstants.DeviceRemoveReason.DeviceReset => GraphicsDeviceStatus.Reset, @@ -223,6 +239,15 @@ public GraphicsDeviceStatus GraphicsDeviceStatus < 0 => GraphicsDeviceStatus.Reset, _ => GraphicsDeviceStatus.Normal }; + + if (status != GraphicsDeviceStatus.Normal && IsDebugMode) + { + Log.Error($"[D3D12] Device removed! Reason: {result} (status: {status})"); + FlushDebugMessages(); + LogDredData(); + } + + return status; } } @@ -293,13 +318,17 @@ public void ExecuteCommandLists(int count, CompiledCommandList[] commandLists) RecycleCommandListResources(commandList, commandListFenceValue + 1); } - CommandListFence.Wait(NativeCommandQueue, commandListFenceValue); + if (commandListFenceValue > 0) + CommandListFence.Wait(NativeCommandQueue, commandListFenceValue); // Submit and signal the fence nativeCommandQueue->ExecuteCommandLists((uint) count, commandListToExecute); CommandListFence.Signal(NativeCommandQueue, commandListFenceValue + 1); + if (IsDebugMode) + FlushDebugMessages(); + ReleaseTemporaryResources(); } @@ -384,6 +413,10 @@ private unsafe partial void InitializePlatformDevice(GraphicsProfile[] graphicsP RequestedProfile = graphicsProfile; CurrentFeatureLevel = featureLevel; + + // Check Enhanced Barriers support (D3D12_FEATURE_D3D12_OPTIONS12 = 41) + CheckEnhancedBarriersSupport(); + break; } @@ -457,7 +490,8 @@ private unsafe partial void InitializePlatformDevice(GraphicsProfile[] graphicsP //infoQueue.SetBreakOnSeverity(Silk.NET.Direct3D12.MessageSeverity.Error, true); //infoQueue.SetBreakOnSeverity(Silk.NET.Direct3D12.MessageSeverity.Warning, true); - infoQueue.Release(); + // Keep reference to drain messages to log + nativeInfoQueue = infoQueue; } debugDevice.Release(); } @@ -510,16 +544,58 @@ void EnableDebugLayer() { debugInterface.EnableDebugLayer(); - // TODO: Probably should be added as extra debug flags (much slower) - result = debugInterface.QueryInterface(out var debug1); - if (result.IsSuccess && debug1.IsNotNull()) + // GPU-based validation instruments every shader — extremely slow on software renderers. + // Only enable on real hardware. + bool isSoftwareRenderer = Environment.GetEnvironmentVariable("STRIDE_GRAPHICS_SOFTWARE_RENDERING") == "1"; + if (!isSoftwareRenderer) { - debug1.SetEnableGPUBasedValidation(true); - debug1.SetEnableSynchronizedCommandQueueValidation(true); - debug1.Release(); + result = debugInterface.QueryInterface(out var debug1); + if (result.IsSuccess && debug1.IsNotNull()) + { + debug1.SetEnableGPUBasedValidation(true); + debug1.SetEnableSynchronizedCommandQueueValidation(true); + debug1.Release(); + } } debugInterface.Release(); } + + // Enable DRED (Device Removed Extended Data) for GPU crash diagnostics + result = d3d12.GetDebugInterface(out ComPtr dredSettings); + if (result.IsSuccess && dredSettings.IsNotNull()) + { + dredSettings.SetAutoBreadcrumbsEnablement(DredEnablement.ForcedOn); + dredSettings.SetPageFaultEnablement(DredEnablement.ForcedOn); + dredSettings.Release(); + } + } + + // + // Checks if the device supports D3D12 Enhanced Barriers. + // + void CheckEnhancedBarriersSupport() + { + // D3D12_FEATURE_D3D12_OPTIONS12 = 41 + // The struct has EnhancedBarriersSupported as a BOOL at a known offset. + // Since Silk.NET may not have this struct, we use raw CheckFeatureSupport. + const int D3D12_FEATURE_D3D12_OPTIONS12 = 41; + + // D3D12_FEATURE_DATA_D3D12_OPTIONS12 is a large struct; EnhancedBarriersSupported + // is at byte offset 8 (after MSAAAlignedCountSupported and RelaxedFormatCasting BoolS). + // We allocate enough space and read the BOOL at offset 8. + Span options12 = stackalloc byte[64]; // oversized to be safe + options12.Clear(); + + fixed (byte* pOptions = options12) + { + HResult hr = nativeDevice->CheckFeatureSupport((Silk.NET.Direct3D12.Feature) D3D12_FEATURE_D3D12_OPTIONS12, + pOptions, (uint) options12.Length); + if (hr.IsSuccess) + { + // EnhancedBarriersSupported is a BOOL (4 bytes) at offset 8 + SupportsEnhancedBarriers = *(int*)(pOptions + 8) != 0; + } + } } } @@ -557,9 +633,7 @@ internal IntPtr AllocateUploadBuffer(int size, out ComPtr resour { nativeUploadBuffer->Unmap(Subresource: 0, pWrittenRange: null); - lock (TemporaryResources) - TemporaryResources.Enqueue((FrameFence.NextFenceValue, ToComPtr(nativeUploadBuffer))); - // TODO: Keep a separate temporary resource list for COM pointers to avoid boxing + FrameTemporaryResources.Enqueue(FrameFence.NextFenceValue, ToComPtr(nativeUploadBuffer)); } // Allocate new buffer @@ -646,23 +720,8 @@ internal ulong ExecuteAndWaitCopyQueueGPU() /// internal void ReleaseTemporaryResources() { - lock (TemporaryResources) - { - // Release previous frame resources - while (TemporaryResources.Count > 0 && FrameFence.IsFenceCompleteInternal(TemporaryResources.Peek().FenceValue)) - { - var temporaryResource = TemporaryResources.Dequeue().Resource; - - if (temporaryResource is ComPtr resource) - { - resource.Release(); - } - else if (temporaryResource is GraphicsResourceLink referenceLink) - { - referenceLink.ReferenceCount--; - } - } - } + FrameTemporaryResources.ReleaseCompleted(FrameFence); + CopyTemporaryResources.ReleaseCompleted(CopyFence); } /// @@ -674,6 +733,12 @@ private partial void AdjustDefaultPipelineStateDescription(ref PipelineStateDesc /// /// Releases the platform-specific Graphics Device and all its associated resources. /// + partial void WaitForGPUIdle() + { + FrameFence.Signal(NativeCommandQueue, FrameFence.NextFenceValue); + FrameFence.WaitForFenceCPUInternal(FrameFence.NextFenceValue); + } + protected partial void DestroyPlatformDevice() { ReleaseDevice(); @@ -684,10 +749,6 @@ protected partial void DestroyPlatformDevice() /// private void ReleaseDevice() { - // Wait for completion of everything queued - FrameFence.Signal(NativeCommandQueue, FrameFence.NextFenceValue); - FrameFence.WaitForFenceCPUInternal(FrameFence.NextFenceValue); - // Release command queue SafeRelease(ref nativeCommandQueue); @@ -697,8 +758,9 @@ private void ReleaseDevice() SafeRelease(ref nativeUploadBuffer); - // Release temporary resources - ReleaseTemporaryResources(); + // Release all deferred resources (GPU is fully flushed at this point) + FrameTemporaryResources.ReleaseAll(); + CopyTemporaryResources.ReleaseAll(); FrameFence.Dispose(); CommandListFence.Dispose(); @@ -717,6 +779,8 @@ private void ReleaseDevice() if (IsDebugMode) { + FlushDebugMessages(); + HResult result = nativeDevice->QueryInterface(out ComPtr debugDevice); if (result.IsSuccess && debugDevice.IsNotNull()) @@ -724,6 +788,8 @@ private void ReleaseDevice() debugDevice.ReportLiveDeviceObjects(RldoFlags.Detail); debugDevice.Release(); } + + SafeRelease(ref nativeInfoQueue); } SafeRelease(ref nativeDevice); @@ -741,6 +807,95 @@ internal void OnDestroyed(bool immediately = false) { } + /// + /// Drains all pending D3D12 debug messages from the InfoQueue and logs them. + /// + internal void FlushDebugMessages() + { + if (nativeInfoQueue is null) + return; + + var numMessages = nativeInfoQueue->GetNumStoredMessages(); + for (ulong i = 0; i < numMessages; i++) + { + nuint messageLength = 0; + nativeInfoQueue->GetMessageA(i, null, ref messageLength); + + if (messageLength == 0) + continue; + + var messageBytes = new byte[(int) messageLength]; + fixed (byte* pMessage = messageBytes) + { + var message = (Silk.NET.Direct3D12.Message*) pMessage; + nativeInfoQueue->GetMessageA(i, message, ref messageLength); + + var description = Marshal.PtrToStringAnsi((nint) message->PDescription) ?? "(no description)"; + + Debug.WriteLine($"D3D12: {message->Severity} {description}"); + + switch (message->Severity) + { + case MessageSeverity.Corruption: + case MessageSeverity.Error: + Log.Error($"[D3D12] {message->Severity}: {description}"); + break; + case MessageSeverity.Warning: + Log.Warning($"[D3D12] {description}"); + break; + } + } + } + + nativeInfoQueue->ClearStoredMessages(); + } + + /// + /// Logs DRED (Device Removed Extended Data) information after a device removal event. + /// + internal void LogDredData() + { + if (nativeDevice is null) + return; + + HResult result = nativeDevice->QueryInterface(out ComPtr dred); + if (result.IsFailure || dred.IsNull()) + return; + + DredAutoBreadcrumbsOutput breadcrumbsOutput = default; + result = dred.GetAutoBreadcrumbsOutput(ref breadcrumbsOutput); + if (result.IsSuccess && breadcrumbsOutput.PHeadAutoBreadcrumbNode is not null) + { + Log.Error("[DRED] Auto-breadcrumbs:"); + var node = breadcrumbsOutput.PHeadAutoBreadcrumbNode; + while (node is not null) + { + var cmdListName = Marshal.PtrToStringUni((nint) node->PCommandListDebugNameW) ?? "(unnamed)"; + var cmdQueueName = Marshal.PtrToStringUni((nint) node->PCommandQueueDebugNameW) ?? "(unnamed)"; + Log.Error($"[DRED] CmdList={cmdListName}, CmdQueue={cmdQueueName}, BreadcrumbCount={node->BreadcrumbCount}, LastCompleted={*node->PLastBreadcrumbValue}"); + + // Log the breadcrumb operations + for (uint i = 0; i < node->BreadcrumbCount && i < 64; i++) + { + var op = node->PCommandHistory[i]; + var marker = i < *node->PLastBreadcrumbValue ? "DONE" : (i == *node->PLastBreadcrumbValue ? ">>LAST>>" : "pending"); + Log.Error($"[DRED] [{marker}] {i}: {op}"); + } + + node = node->PNext; + } + } + + DredPageFaultOutput pageFaultOutput = default; + result = dred.GetPageFaultAllocationOutput(ref pageFaultOutput); + if (result.IsSuccess) + { + Log.Error($"[DRED] Page fault at VA: 0x{pageFaultOutput.PageFaultVA:X16}"); + } + + dred.Release(); + } + /// /// Executes a Compiled Command List. @@ -756,7 +911,8 @@ internal ulong ExecuteCommandListInternal(CompiledCommandList commandList) { var commandListFenceValue = CommandListFence.NextFenceValue++; - CommandListFence.Wait(NativeCommandQueue, commandListFenceValue); + if (commandListFenceValue > 0) + CommandListFence.Wait(NativeCommandQueue, commandListFenceValue); // Submit and signal fence var nativeCommandList = commandList.NativeCommandList.AsComPtr(); @@ -840,16 +996,14 @@ internal partial void TagResourceAsNotAlive(GraphicsResourceLink resourceLink) { // Increase the reference count until GPU is done with the resource resourceLink.ReferenceCount++; - lock (TemporaryResources) - TemporaryResources.Enqueue((FrameFence.NextFenceValue, resourceLink)); + FrameTemporaryResources.Enqueue(FrameFence.NextFenceValue, resourceLink); } if (resourceLink.Resource is Buffer { Usage: GraphicsResourceUsage.Dynamic }) { // Increase the reference count until GPU is done with the resource resourceLink.ReferenceCount++; - lock (TemporaryResources) - TemporaryResources.Enqueue((FrameFence.NextFenceValue, resourceLink)); + FrameTemporaryResources.Enqueue(FrameFence.NextFenceValue, resourceLink); } } } diff --git a/sources/engine/Stride.Graphics/Direct3D12/GraphicsResource.Direct3D12.cs b/sources/engine/Stride.Graphics/Direct3D12/GraphicsResource.Direct3D12.cs index 71e3949de7..527ff0ab6d 100644 --- a/sources/engine/Stride.Graphics/Direct3D12/GraphicsResource.Direct3D12.cs +++ b/sources/engine/Stride.Graphics/Direct3D12/GraphicsResource.Direct3D12.cs @@ -9,11 +9,6 @@ namespace Stride.Graphics { public abstract partial class GraphicsResource { - /// - /// A reference to the parent that owns this resource, if any. - /// - internal GraphicsResource ParentResource; - /// /// Fence value used with during resource initialization. Need to be waited on for CPU access. /// @@ -75,7 +70,6 @@ internal override void SwapInternal(GraphicsResourceBase other) base.SwapInternal(other); - (ParentResource, otherResource.ParentResource) = (otherResource.ParentResource, ParentResource); (CommandListFenceValue, otherResource.CommandListFenceValue) = (otherResource.CommandListFenceValue, CommandListFenceValue); (UpdatingCommandList, otherResource.UpdatingCommandList) = (otherResource.UpdatingCommandList, UpdatingCommandList); (NativeShaderResourceView, otherResource.NativeShaderResourceView) = (otherResource.NativeShaderResourceView, NativeShaderResourceView); diff --git a/sources/engine/Stride.Graphics/Direct3D12/GraphicsResourceBase.Direct3D12.cs b/sources/engine/Stride.Graphics/Direct3D12/GraphicsResourceBase.Direct3D12.cs index daf39754d2..9519ae9e9a 100644 --- a/sources/engine/Stride.Graphics/Direct3D12/GraphicsResourceBase.Direct3D12.cs +++ b/sources/engine/Stride.Graphics/Direct3D12/GraphicsResourceBase.Direct3D12.cs @@ -176,8 +176,7 @@ protected internal virtual partial void OnDestroyed(bool immediately = false) else { // Schedule the resource for destruction (as soon as we are done with it) - lock (GraphicsDevice.TemporaryResources) - GraphicsDevice.TemporaryResources.Enqueue((GraphicsDevice.FrameFence.NextFenceValue, NativeResource)); + GraphicsDevice.FrameTemporaryResources.Enqueue(GraphicsDevice.FrameFence.NextFenceValue, NativeResource); } } diff --git a/sources/engine/Stride.Graphics/Direct3D12/Texture.Direct3D12.cs b/sources/engine/Stride.Graphics/Direct3D12/Texture.Direct3D12.cs index fc742bd8a8..8ab5dfe836 100644 --- a/sources/engine/Stride.Graphics/Direct3D12/Texture.Direct3D12.cs +++ b/sources/engine/Stride.Graphics/Direct3D12/Texture.Direct3D12.cs @@ -200,14 +200,13 @@ private partial void InitializeFromImpl(DataBox[] dataBoxes) { if (Usage == GraphicsResourceUsage.Staging) { - // Per our own definition of staging resource (read-back only) + InitializeStagingTexture(); + + // D3D12 staging resources use READBACK heaps which can't receive data from the CPU directly. + // Upload data via an intermediate default texture, then copy to staging. if (dataBoxes?.Length > 0) - throw new NotSupportedException("D3D12: Staging Textures can't be created with initial data."); + InitializeStagingData(dataBoxes); - // If it is a staging Texture, initialize it and finish - // (staging resources do not need to have views, they are only intermediate buffers to copy - // to a final destination resource) - InitializeStagingTexture(); return; } @@ -229,6 +228,7 @@ private partial void InitializeFromImpl(DataBox[] dataBoxes) void InitializeStagingTexture() { NativeResourceState = ResourceStates.CopyDest; + LayoutTracker.Initialize(BarrierLayout.CopyDest, ArraySize * MipLevelCount); NativeTextureDescription = GetTextureDescription(Dimension); int totalSize = ComputeBufferTotalSize(); @@ -244,6 +244,112 @@ void InitializeStagingTexture() SetNativeDeviceChild(stagingTextureResource.AsDeviceChild()); } + // + // Copies initial data into a staging texture by creating a temporary default texture, + // uploading data to it, then copying from the texture to the staging buffer. + // This is needed because D3D12 READBACK heaps can't receive data from the CPU directly. + // + void InitializeStagingData(DataBox[] initialData) + { + var subresourceCount = initialData.Length; + var nativeDescription = GetTextureDescription(Dimension); + + // Create a temporary default texture to hold the data + var heap = new HeapProperties { Type = HeapType.Default }; + HResult result = NativeDevice.CreateCommittedResource(in heap, HeapFlags.None, in nativeDescription, ResourceStates.CopyDest, + pOptimizedClearValue: null, out ComPtr tempTexture); + if (result.IsFailure) + result.Throw(); + + var commandList = GraphicsDevice.NativeCopyCommandList; + lock (GraphicsDevice.NativeCopyCommandListLock) + { + scoped ref var nullPipelineState = ref NullRef(); + result = commandList.Reset(GraphicsDevice.NativeCopyCommandAllocator, pInitialState: ref nullPipelineState); + if (result.IsFailure) + result.Throw(); + + // Upload data to the temporary texture (same logic as InitializeTexture) + scoped Span placedSubresources = stackalloc PlacedSubresourceFootprint[subresourceCount]; + scoped Span rowCounts = stackalloc uint[subresourceCount]; + scoped Span rowSizeInBytes = stackalloc ulong[subresourceCount]; + ulong textureCopySize = 0; + + NativeDevice.GetCopyableFootprints(in nativeDescription, FirstSubresource: 0, (uint)subresourceCount, BaseOffset: 0, + ref placedSubresources.GetReference(), ref rowCounts.GetReference(), ref rowSizeInBytes.GetReference(), ref textureCopySize); + + nint uploadMemory = GraphicsDevice.AllocateUploadBuffer((int)textureCopySize, + out ComPtr uploadResource, out int uploadOffset, D3D12.TextureDataPlacementAlignment); + + for (int i = 0; i < subresourceCount; ++i) + { + scoped ref readonly var databox = ref initialData[i]; + scoped ref var placedSubresource = ref placedSubresources[i]; + + var rowCount = rowCounts[i]; + var sliceCount = placedSubresource.Footprint.Depth; + var rowSize = (int)rowSizeInBytes[i]; + var destRowPitch = placedSubresource.Footprint.RowPitch; + + for (int zSlice = 0; zSlice < sliceCount; zSlice++) + { + var uploadCurrent = uploadMemory + (int)placedSubresource.Offset + zSlice * destRowPitch * rowCount; + var dataCurrent = databox.DataPointer + zSlice * databox.SlicePitch; + + for (int row = 0; row < rowCount; ++row) + { + MemoryUtilities.CopyWithAlignmentFallback((void*)uploadCurrent, (void*)dataCurrent, (uint)rowSize); + uploadCurrent += destRowPitch; + dataCurrent += databox.RowPitch; + } + } + + placedSubresource.Offset += (ulong)uploadOffset; + + // Copy upload buffer -> temp texture + var dest = new TextureCopyLocation { Type = TextureCopyType.SubresourceIndex, PResource = tempTexture, SubresourceIndex = (uint)i }; + var src = new TextureCopyLocation { Type = TextureCopyType.PlacedFootprint, PResource = uploadResource, PlacedFootprint = placedSubresource }; + commandList.CopyTextureRegion(in dest, DstX: 0, DstY: 0, DstZ: 0, in src, pSrcBox: in NullRef()); + } + + // Transition temp texture to CopySrc + var barrier = new ResourceBarrier { Type = ResourceBarrierType.Transition }; + barrier.Transition.PResource = tempTexture; + barrier.Transition.Subresource = 0xFFFFFFFF; + barrier.Transition.StateBefore = ResourceStates.CopyDest; + barrier.Transition.StateAfter = ResourceStates.CopySource; + commandList.ResourceBarrier(1, in barrier); + + // Copy temp texture -> staging buffer (with proper footprint alignment) + for (int i = 0; i < subresourceCount; ++i) + { + // Re-query footprints for the staging buffer destination + scoped Span stagingFootprints = stackalloc PlacedSubresourceFootprint[1]; + scoped Span stagingRowCounts = stackalloc uint[1]; + scoped Span stagingRowSizes = stackalloc ulong[1]; + ulong stagingSize = 0; + + NativeDevice.GetCopyableFootprints(in nativeDescription, (uint)i, 1, (ulong)ComputeBufferOffset(i, 0), + ref stagingFootprints.GetReference(), ref stagingRowCounts.GetReference(), ref stagingRowSizes.GetReference(), ref stagingSize); + + var stagingDest = new TextureCopyLocation { Type = TextureCopyType.PlacedFootprint, PResource = NativeResource, PlacedFootprint = stagingFootprints[0] }; + var texSrc = new TextureCopyLocation { Type = TextureCopyType.SubresourceIndex, PResource = tempTexture, SubresourceIndex = (uint)i }; + commandList.CopyTextureRegion(in stagingDest, DstX: 0, DstY: 0, DstZ: 0, in texSrc, pSrcBox: in NullRef()); + } + + result = commandList.Close(); + if (result.IsFailure) + result.Throw(); + + CopyFenceValue = GraphicsDevice.ExecuteAndWaitCopyQueueGPU(); + } + + // Defer temp texture release until the copy queue finishes executing. + // The copy commands reference this resource, so we can't release it immediately. + GraphicsDevice.CopyTemporaryResources.Enqueue(CopyFenceValue.Value, tempTexture); + tempTexture.Handle = null; + } + // // Initializes the Texture as a regular texture resource and copies the initial data // to that new texture. @@ -257,20 +363,30 @@ void InitializeTexture(DataBox[] initialData) var nativeDescription = NativeTextureDescription = GetTextureDescription(Dimension); + // Initialize resource state based on texture usage. + if (Usage == GraphicsResourceUsage.Staging) + NativeResourceState = ResourceStates.CopyDest; + else if (ViewFlags.HasFlag(TextureFlags.DepthStencil)) + NativeResourceState = ResourceStates.DepthWrite; + else if (ViewFlags.HasFlag(TextureFlags.RenderTarget)) + NativeResourceState = ResourceStates.RenderTarget; + else if (ViewFlags.HasFlag(TextureFlags.ShaderResource)) + NativeResourceState = ResourceStates.PixelShaderResource | ResourceStates.NonPixelShaderResource; + else + NativeResourceState = ResourceStates.Common; + var desiredResourceState = NativeResourceState; - var currentResourceState = desiredResourceState; bool hasInitData = initialData?.Length > 0; - // If the resource must be initialized with data, it is initially in the state - // CopyDest so we can copy from an upload buffer - if (hasInitData) - currentResourceState = ResourceStates.CopyDest; + // Always create in the desired state. For textures with init data that aren't + // already in CopyDest, we'll transition explicitly within the command list. + var initialResourceState = desiredResourceState; // TODO: D3D12: Move that to a global allocator in bigger committed resources var heap = new HeapProperties { Type = HeapType.Default }; - HResult result = NativeDevice.CreateCommittedResource(in heap, HeapFlags.None, in nativeDescription, currentResourceState, + HResult result = NativeDevice.CreateCommittedResource(in heap, HeapFlags.None, in nativeDescription, initialResourceState, in clearValueRef, out ComPtr textureResource); if (result.IsFailure) result.Throw(); @@ -278,7 +394,7 @@ void InitializeTexture(DataBox[] initialData) SetNativeDeviceChild(textureResource.AsDeviceChild()); GraphicsDevice.RegisterTextureMemoryUsage(SizeInBytes); - if (hasInitData || currentResourceState != desiredResourceState) + if (hasInitData) { var commandList = GraphicsDevice.NativeCopyCommandList; lock (GraphicsDevice.NativeCopyCommandListLock) @@ -289,71 +405,78 @@ void InitializeTexture(DataBox[] initialData) if (result.IsFailure) result.Throw(); - if (hasInitData) + const uint D3D12_RESOURCE_BARRIER_ALL_SUBRESOURCES = 0xFFFFFFFF; + var resourceBarrier = new ResourceBarrier { Type = ResourceBarrierType.Transition }; + resourceBarrier.Transition.PResource = NativeResource; + resourceBarrier.Transition.Subresource = D3D12_RESOURCE_BARRIER_ALL_SUBRESOURCES; + + // Transition to CopyDest for the upload + if (initialResourceState != ResourceStates.CopyDest) { - var subresourceCount = initialData.Length; - scoped Span placedSubresources = stackalloc PlacedSubresourceFootprint[subresourceCount]; - scoped Span rowCounts = stackalloc uint[subresourceCount]; - scoped Span rowSizeInBytes = stackalloc ulong[subresourceCount]; - - ulong textureCopySize = 0; - - NativeDevice.GetCopyableFootprints(in nativeDescription, FirstSubresource: 0, (uint) subresourceCount, BaseOffset: 0, - ref placedSubresources.GetReference(), - ref rowCounts.GetReference(), - ref rowSizeInBytes.GetReference(), - ref textureCopySize); - - nint uploadMemory = GraphicsDevice.AllocateUploadBuffer((int) textureCopySize, - out ComPtr uploadResource, - out int uploadOffset, - D3D12.TextureDataPlacementAlignment); - for (int i = 0; i < subresourceCount; ++i) - { - scoped ref readonly var databox = ref initialData[i]; - scoped ref var placedSubresource = ref placedSubresources[i]; + resourceBarrier.Transition.StateBefore = initialResourceState; + resourceBarrier.Transition.StateAfter = ResourceStates.CopyDest; + commandList.ResourceBarrier(1, in resourceBarrier); + } - var dataPointer = databox.DataPointer; + var subresourceCount = initialData.Length; + scoped Span placedSubresources = stackalloc PlacedSubresourceFootprint[subresourceCount]; + scoped Span rowCounts = stackalloc uint[subresourceCount]; + scoped Span rowSizeInBytes = stackalloc ulong[subresourceCount]; - var rowCount = rowCounts[i]; - var sliceCount = placedSubresource.Footprint.Depth; - var rowSize = (int) rowSizeInBytes[i]; - var destRowPitch = placedSubresource.Footprint.RowPitch; + ulong textureCopySize = 0; - // Copy the init data to the upload buffer - for (int zSlice = 0; zSlice < sliceCount; zSlice++) - { - var uploadMemoryCurrent = uploadMemory + (int) placedSubresource.Offset + zSlice * destRowPitch * rowCount; - var dataPointerCurrent = dataPointer + zSlice * databox.SlicePitch; - - for (int row = 0; row < rowCount; ++row) - { - MemoryUtilities.CopyWithAlignmentFallback((void*) uploadMemoryCurrent, (void*) dataPointerCurrent, (uint) rowSize); - uploadMemoryCurrent += destRowPitch; - dataPointerCurrent += databox.RowPitch; - } - } + NativeDevice.GetCopyableFootprints(in nativeDescription, FirstSubresource: 0, (uint) subresourceCount, BaseOffset: 0, + ref placedSubresources.GetReference(), + ref rowCounts.GetReference(), + ref rowSizeInBytes.GetReference(), + ref textureCopySize); + + nint uploadMemory = GraphicsDevice.AllocateUploadBuffer((int) textureCopySize, + out ComPtr uploadResource, + out int uploadOffset, + D3D12.TextureDataPlacementAlignment); + for (int i = 0; i < subresourceCount; ++i) + { + scoped ref readonly var databox = ref initialData[i]; + scoped ref var placedSubresource = ref placedSubresources[i]; + + var dataPointer = databox.DataPointer; - // Adjust upload offset (circular dependency between GetCopyableFootprints and AllocateUploadBuffer) - placedSubresource.Offset += (ulong) uploadOffset; + var rowCount = rowCounts[i]; + var sliceCount = placedSubresource.Footprint.Depth; + var rowSize = (int) rowSizeInBytes[i]; + var destRowPitch = placedSubresource.Footprint.RowPitch; - var dest = new TextureCopyLocation { Type = TextureCopyType.SubresourceIndex, PResource = NativeResource, SubresourceIndex = (uint) i }; - var src = new TextureCopyLocation { Type = TextureCopyType.PlacedFootprint, PResource = uploadResource, PlacedFootprint = placedSubresource }; + // Copy the init data to the upload buffer + for (int zSlice = 0; zSlice < sliceCount; zSlice++) + { + var uploadMemoryCurrent = uploadMemory + (int) placedSubresource.Offset + zSlice * destRowPitch * rowCount; + var dataPointerCurrent = dataPointer + zSlice * databox.SlicePitch; - commandList.CopyTextureRegion(in dest, DstX: 0, DstY: 0, DstZ: 0, in src, pSrcBox: in NullRef()); + for (int row = 0; row < rowCount; ++row) + { + MemoryUtilities.CopyWithAlignmentFallback((void*) uploadMemoryCurrent, (void*) dataPointerCurrent, (uint) rowSize); + uploadMemoryCurrent += destRowPitch; + dataPointerCurrent += databox.RowPitch; + } } - } - const uint D3D12_RESOURCE_BARRIER_ALL_SUBRESOURCES = 0xFFFFFFFF; + // Adjust upload offset (circular dependency between GetCopyableFootprints and AllocateUploadBuffer) + placedSubresource.Offset += (ulong) uploadOffset; - // Once initialized, transition the Texture (and its subresources) to its final state - var resourceBarrier = new ResourceBarrier { Type = ResourceBarrierType.Transition }; - resourceBarrier.Transition.PResource = NativeResource; - resourceBarrier.Transition.Subresource = D3D12_RESOURCE_BARRIER_ALL_SUBRESOURCES; - resourceBarrier.Transition.StateBefore = currentResourceState; - resourceBarrier.Transition.StateAfter = desiredResourceState; + var dest = new TextureCopyLocation { Type = TextureCopyType.SubresourceIndex, PResource = NativeResource, SubresourceIndex = (uint) i }; + var src = new TextureCopyLocation { Type = TextureCopyType.PlacedFootprint, PResource = uploadResource, PlacedFootprint = placedSubresource }; - commandList.ResourceBarrier(1, in resourceBarrier); + commandList.CopyTextureRegion(in dest, DstX: 0, DstY: 0, DstZ: 0, in src, pSrcBox: in NullRef()); + } + + // Transition back to the desired state + if (initialResourceState != ResourceStates.CopyDest) + { + resourceBarrier.Transition.StateBefore = ResourceStates.CopyDest; + resourceBarrier.Transition.StateAfter = desiredResourceState; + commandList.ResourceBarrier(1, in resourceBarrier); + } result = commandList.Close(); @@ -368,6 +491,7 @@ ref rowSizeInBytes.GetReference(), } NativeResourceState = desiredResourceState; + LayoutTracker.Initialize(BarrierMapping.ToBarrierLayout(desiredResourceState), ArraySize * MipLevelCount); } // diff --git a/sources/engine/Stride.Graphics/Direct3D12/WinPixNative.cs b/sources/engine/Stride.Graphics/Direct3D12/WinPixNative.cs index 833e1de460..df6b7b3de1 100644 --- a/sources/engine/Stride.Graphics/Direct3D12/WinPixNative.cs +++ b/sources/engine/Stride.Graphics/Direct3D12/WinPixNative.cs @@ -29,9 +29,22 @@ public static class WinPixNative private const string RuntimeDllName = "WinPixEventRuntime.dll"; private const string CapturerDllName = "WinPixGpuCapturer.dll"; + /// + /// Whether the PIX runtime DLL was successfully loaded. + /// + internal static bool IsAvailable { get; private set; } + internal static void PreLoad() { - NativeLibraryHelper.PreloadLibrary("WinPixEventRuntime", typeof(WinPixNative)); + try + { + NativeLibraryHelper.PreloadLibrary("WinPixEventRuntime", typeof(WinPixNative)); + IsAvailable = GetModuleHandle(RuntimeDllName) != IntPtr.Zero; + } + catch + { + IsAvailable = false; + } } static WinPixNative() diff --git a/sources/engine/Stride.Graphics/GraphicsDevice.cs b/sources/engine/Stride.Graphics/GraphicsDevice.cs index d119dd31a7..d06d3188f0 100644 --- a/sources/engine/Stride.Graphics/GraphicsDevice.cs +++ b/sources/engine/Stride.Graphics/GraphicsDevice.cs @@ -205,6 +205,9 @@ public void Recreate(GraphicsAdapter adapter, GraphicsProfile[] graphicsProfiles /// protected override void Destroy() { + // Wait for all GPU work to complete before releasing resources + WaitForGPUIdle(); + SamplerStates.Dispose(); SamplerStates = null; @@ -254,6 +257,11 @@ void DisposeSharedData() } } + /// + /// Waits for all submitted GPU work to complete. Called before releasing resources during teardown. + /// + partial void WaitForGPUIdle(); + /// /// Releases the platform-specific Graphics Device and all its associated resources. /// diff --git a/sources/engine/Stride.Graphics/GraphicsDeviceExtensions.cs b/sources/engine/Stride.Graphics/GraphicsDeviceExtensions.cs index d47d13cbdc..2fa7b618c6 100644 --- a/sources/engine/Stride.Graphics/GraphicsDeviceExtensions.cs +++ b/sources/engine/Stride.Graphics/GraphicsDeviceExtensions.cs @@ -151,4 +151,25 @@ static unsafe Texture CreateWhiteTexture(GraphicsDevice device) return texture; } } + + /// + /// Gets a shared 1x1 depth Texture for use as a placeholder when no shadow map is available. + /// This avoids Vulkan validation errors when a depth-comparison sampler references a color texture. + /// + /// The Graphics Device for which to retrieve the shared depth Texture. + /// A with a 1x1 depth format. + public static Texture GetSharedDepthTexture(this GraphicsDevice device) + { + return device.GetOrCreateSharedData("DepthTexture", static device => + { + var texture = device.IsDebugMode + ? new Texture(device, "DepthTexture") + : new Texture(device); + + var description = TextureDescription.New2D(1, 1, PixelFormat.D32_Float, TextureFlags.DepthStencil | TextureFlags.ShaderResource); + texture.InitializeFrom(description); + + return texture; + }); + } } diff --git a/sources/engine/Stride.Graphics/GraphicsMarshal.cs b/sources/engine/Stride.Graphics/GraphicsMarshal.cs index 2ff35c5faa..092f59cfdb 100644 --- a/sources/engine/Stride.Graphics/GraphicsMarshal.cs +++ b/sources/engine/Stride.Graphics/GraphicsMarshal.cs @@ -1,8 +1,9 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. -#if STRIDE_GRAPHICS_API_DIRECT3D11 || STRIDE_GRAPHICS_API_DIRECT3D12 +#if STRIDE_GRAPHICS_API_DIRECT3D11 || STRIDE_GRAPHICS_API_DIRECT3D12 || STRIDE_GRAPHICS_API_VULKAN +#if STRIDE_GRAPHICS_API_DIRECT3D11 || STRIDE_GRAPHICS_API_DIRECT3D12 using Silk.NET.Core.Native; using Silk.NET.DXGI; #if STRIDE_GRAPHICS_API_DIRECT3D11 @@ -10,6 +11,7 @@ #elif STRIDE_GRAPHICS_API_DIRECT3D12 using Silk.NET.Direct3D12; #endif +#endif namespace Stride.Graphics; @@ -19,6 +21,7 @@ namespace Stride.Graphics; /// public static unsafe class GraphicsMarshal { +#if STRIDE_GRAPHICS_API_DIRECT3D11 || STRIDE_GRAPHICS_API_DIRECT3D12 /// /// Gets the underlying DXGI swap-chain. /// @@ -31,6 +34,7 @@ public static unsafe class GraphicsMarshal => presenter is SwapChainGraphicsPresenter swapChainPresenter ? swapChainPresenter.NativeSwapChain : null; +#endif #if STRIDE_GRAPHICS_API_DIRECT3D11 /// @@ -176,6 +180,22 @@ public static Texture CreateTextureFromNative(GraphicsDevice device, ID3D12Resou return texture; } #endif + +#if STRIDE_GRAPHICS_API_VULKAN + /// + /// Gets the native Vulkan instance. + /// + /// The Stride Graphics Device. + /// The native . + public static Vortice.Vulkan.VkInstance GetNativeInstance(GraphicsDevice device) => device.NativeInstance; + + /// + /// Gets the native Vulkan device. + /// + /// The Stride Graphics Device. + /// The native . + public static Vortice.Vulkan.VkDevice GetNativeDevice(GraphicsDevice device) => device.NativeDevice; +#endif } #endif diff --git a/sources/engine/Stride.Graphics/GraphicsResource.cs b/sources/engine/Stride.Graphics/GraphicsResource.cs index 5f0c91f984..1c6ab86879 100644 --- a/sources/engine/Stride.Graphics/GraphicsResource.cs +++ b/sources/engine/Stride.Graphics/GraphicsResource.cs @@ -10,6 +10,11 @@ namespace Stride.Graphics /// public abstract partial class GraphicsResource : GraphicsResourceBase { + /// + /// Tracks the layout of this resource's subresources for cross-platform barrier transitions. + /// + internal SubresourceLayoutTracker LayoutTracker; + /// /// Initializes a new instance of the class. /// diff --git a/sources/engine/Stride.Graphics/GraphicsResourceState.cs b/sources/engine/Stride.Graphics/GraphicsResourceState.cs index c215a8d928..0f172d69d4 100644 --- a/sources/engine/Stride.Graphics/GraphicsResourceState.cs +++ b/sources/engine/Stride.Graphics/GraphicsResourceState.cs @@ -8,6 +8,11 @@ namespace Stride.Graphics; /// /// Defines constants that specify the state of a Graphics Resource regarding how the resource is being used. /// +/// +/// This enum is being replaced by for cross-platform barrier transitions. +/// New code should use instead. +/// +[Obsolete("Use BarrierLayout instead for cross-platform barrier transitions.")] [Flags] public enum GraphicsResourceState { diff --git a/sources/engine/Stride.Graphics/Null/CommandList.Null.cs b/sources/engine/Stride.Graphics/Null/CommandList.Null.cs index d023da94db..cf88735a86 100644 --- a/sources/engine/Stride.Graphics/Null/CommandList.Null.cs +++ b/sources/engine/Stride.Graphics/Null/CommandList.Null.cs @@ -150,6 +150,11 @@ public void SetIndexBuffer(Buffer buffer, int offset, bool is32bits) NullHelper.ToImplement(); } + public void ResourceBarrierTransition(GraphicsResource resource, BarrierLayout newLayout) + { + NullHelper.ToImplement(); + } + public void ResourceBarrierTransition(GraphicsResource resource, GraphicsResourceState newState) { NullHelper.ToImplement(); diff --git a/sources/engine/Stride.Graphics/OpenGL/CommandList.OpenGL.cs b/sources/engine/Stride.Graphics/OpenGL/CommandList.OpenGL.cs index 23ced5e5b5..7a22bf6e9d 100644 --- a/sources/engine/Stride.Graphics/OpenGL/CommandList.OpenGL.cs +++ b/sources/engine/Stride.Graphics/OpenGL/CommandList.OpenGL.cs @@ -1401,6 +1401,11 @@ public void SetIndexBuffer(Buffer buffer, int offset, bool is32bits) } } + public void ResourceBarrierTransition(GraphicsResource resource, BarrierLayout newLayout) + { + // Nothing to do + } + public void ResourceBarrierTransition(GraphicsResource resource, GraphicsResourceState newState) { // Nothing to do diff --git a/sources/engine/Stride.Graphics/OpenGL/GraphicsDevice.OpenGL.cs b/sources/engine/Stride.Graphics/OpenGL/GraphicsDevice.OpenGL.cs index 9a3c30bb03..0b32164363 100644 --- a/sources/engine/Stride.Graphics/OpenGL/GraphicsDevice.OpenGL.cs +++ b/sources/engine/Stride.Graphics/OpenGL/GraphicsDevice.OpenGL.cs @@ -32,7 +32,7 @@ public partial class GraphicsDevice { internal readonly int ConstantBufferDataPlacementAlignment = 16; - private static readonly Logger Log = GlobalLogger.GetLogger("GraphicsDevice"); + private static readonly Logger Log = GlobalLogger.GetLogger(nameof(GraphicsDevice)); internal int FrameCounter; diff --git a/sources/engine/Stride.Graphics/ResourceBarrierDescription.cs b/sources/engine/Stride.Graphics/ResourceBarrierDescription.cs new file mode 100644 index 0000000000..d51da76850 --- /dev/null +++ b/sources/engine/Stride.Graphics/ResourceBarrierDescription.cs @@ -0,0 +1,73 @@ +// Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) +// Distributed under the MIT license. See the LICENSE.md file in the project root for more information. + +namespace Stride.Graphics; + +/// +/// Describes a resource barrier transition for GPU synchronization. +/// This is a cross-platform abstraction over D3D12 Enhanced Barriers and Vulkan pipeline barriers. +/// +public struct ResourceBarrierDescription +{ + /// + /// The resource to transition. + /// + public GraphicsResource Resource; + + /// + /// GPU pipeline stages that must complete before the barrier takes effect. + /// + public BarrierSync SyncBefore; + + /// + /// GPU pipeline stages that are blocked until the barrier completes. + /// + public BarrierSync SyncAfter; + + /// + /// The type of access performed before the barrier. + /// + public BarrierAccess AccessBefore; + + /// + /// The type of access performed after the barrier. + /// + public BarrierAccess AccessAfter; + + /// + /// The resource layout before the barrier. + /// + public BarrierLayout LayoutBefore; + + /// + /// The resource layout after the barrier. + /// + public BarrierLayout LayoutAfter; + + /// + /// The subresource index, or for all subresources. + /// + public uint Subresource; + + /// + /// Creates a barrier for all subresources of a resource. + /// + public ResourceBarrierDescription( + GraphicsResource resource, + BarrierLayout layoutBefore, + BarrierLayout layoutAfter, + BarrierAccess accessBefore = BarrierAccess.None, + BarrierAccess accessAfter = BarrierAccess.None, + BarrierSync syncBefore = BarrierSync.All, + BarrierSync syncAfter = BarrierSync.All) + { + Resource = resource; + LayoutBefore = layoutBefore; + LayoutAfter = layoutAfter; + AccessBefore = accessBefore; + AccessAfter = accessAfter; + SyncBefore = syncBefore; + SyncAfter = syncAfter; + Subresource = uint.MaxValue; + } +} diff --git a/sources/engine/Stride.Graphics/ResourceBinder.cs b/sources/engine/Stride.Graphics/ResourceBinder.cs index b295aee1d5..41899aae22 100644 --- a/sources/engine/Stride.Graphics/ResourceBinder.cs +++ b/sources/engine/Stride.Graphics/ResourceBinder.cs @@ -121,7 +121,8 @@ public readonly void BindResources(CommandList commandList, DescriptorSet[] desc break; case EffectParameterClass.Sampler: - commandList.SetSamplerState(bindingOperation.Stage, bindingOperation.SlotStart, bindingOperation.ImmutableSampler ?? (SamplerState) value.Value); + commandList.SetSamplerState(bindingOperation.Stage, bindingOperation.SlotStart, + bindingOperation.ImmutableSampler ?? (SamplerState) value.Value ?? commandList.GraphicsDevice.SamplerStates.LinearClamp); break; case EffectParameterClass.ShaderResourceView: diff --git a/sources/engine/Stride.Graphics/SubresourceLayoutTracker.cs b/sources/engine/Stride.Graphics/SubresourceLayoutTracker.cs new file mode 100644 index 0000000000..f6ae536e07 --- /dev/null +++ b/sources/engine/Stride.Graphics/SubresourceLayoutTracker.cs @@ -0,0 +1,97 @@ +// Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) +// Distributed under the MIT license. See the LICENSE.md file in the project root for more information. + +using System; + +namespace Stride.Graphics; + +/// +/// Tracks the of a resource's subresources for barrier transitions. +/// Uses a single layout for the common case (whole-resource transitions) and lazily allocates +/// a per-subresource array when individual subresources are transitioned independently. +/// +internal struct SubresourceLayoutTracker +{ + private BarrierLayout singleLayout; + private BarrierLayout[]? perSubresource; + private int subresourceCount; + + /// + /// Initializes the tracker with the given initial layout and subresource count. + /// + public void Initialize(BarrierLayout initial, int subresourceCount) + { + singleLayout = initial; + this.subresourceCount = subresourceCount; + perSubresource = null; + } + + /// + /// Gets the tracked layout for a subresource. + /// Use for the whole-resource layout. + /// + public readonly BarrierLayout Get(uint subresource) + { + if (perSubresource == null || subresource >= (uint)perSubresource.Length) + return singleLayout; + return perSubresource[subresource]; + } + + /// + /// Sets the tracked layout for a subresource. + /// Use for a whole-resource transition. + /// + public void Set(uint subresource, BarrierLayout layout) + { + if (subresource == uint.MaxValue || subresourceCount <= 1) + { + // Whole resource transition + singleLayout = layout; + if (perSubresource != null) + Array.Fill(perSubresource, layout); + } + else + { + // Per-subresource — lazy allocate + if (perSubresource == null) + { + perSubresource = new BarrierLayout[subresourceCount]; + Array.Fill(perSubresource, singleLayout); + } + perSubresource[subresource] = layout; + } + } + + /// + /// Determines if a transition is needed for the given subresource to reach the target layout. + /// + public readonly bool NeedsTransition(uint subresource, BarrierLayout target) + { + if (subresource != uint.MaxValue || perSubresource == null) + return Get(subresource) != target; + + // Whole-resource check with per-subresource tracking: + // any subresource that differs means a transition is needed + for (int i = 0; i < perSubresource.Length; i++) + { + if (perSubresource[i] != target) + return true; + } + return false; + } + + /// + /// Whether per-subresource tracking is active (some subresources may have different layouts). + /// + internal readonly bool HasPerSubresourceTracking => perSubresource != null; + + /// + /// The number of subresources tracked. + /// + internal readonly int SubresourceCount => subresourceCount; + + /// + /// Gets the per-subresource array. Only valid when is true. + /// + internal readonly ReadOnlySpan PerSubresourceLayouts => perSubresource; +} diff --git a/sources/engine/Stride.Graphics/Texture.cs b/sources/engine/Stride.Graphics/Texture.cs index 86a3467959..44b35fedc9 100644 --- a/sources/engine/Stride.Graphics/Texture.cs +++ b/sources/engine/Stride.Graphics/Texture.cs @@ -1812,6 +1812,9 @@ private static unsafe DataBox GetDataBox(PixelFormat format, int width, i /// The other Texture. internal void Swap([NotNull] Texture other) { + if (ParentTexture is not null || other.ParentTexture is not null) + throw new NotSupportedException("Cannot swap texture views; only root textures can be swapped."); + (textureDescription, other.textureDescription) = (other.textureDescription, textureDescription); (textureViewDescription, other.textureViewDescription) = (other.textureViewDescription, textureViewDescription); (mipmapDescriptions, other.mipmapDescriptions) = (other.mipmapDescriptions, mipmapDescriptions); diff --git a/sources/engine/Stride.Graphics/Vulkan/BarrierMapping.Vulkan.cs b/sources/engine/Stride.Graphics/Vulkan/BarrierMapping.Vulkan.cs new file mode 100644 index 0000000000..ba982ff5bb --- /dev/null +++ b/sources/engine/Stride.Graphics/Vulkan/BarrierMapping.Vulkan.cs @@ -0,0 +1,89 @@ +// Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) +// Distributed under the MIT license. See the LICENSE.md file in the project root for more information. + +#if STRIDE_GRAPHICS_API_VULKAN + +using Vortice.Vulkan; + +namespace Stride.Graphics; + +/// +/// Provides mapping between cross-platform barrier enums and Vulkan-specific types. +/// +internal static class BarrierMapping +{ + /// + /// Converts a to a . + /// + internal static VkImageLayout ToVkImageLayout(BarrierLayout layout) => layout switch + { + BarrierLayout.Undefined => VkImageLayout.Undefined, + BarrierLayout.Common => VkImageLayout.General, + BarrierLayout.RenderTarget => VkImageLayout.ColorAttachmentOptimal, + BarrierLayout.DepthStencilWrite => VkImageLayout.DepthStencilAttachmentOptimal, + BarrierLayout.DepthStencilRead => VkImageLayout.DepthStencilReadOnlyOptimal, + BarrierLayout.ShaderResource => VkImageLayout.ShaderReadOnlyOptimal, + BarrierLayout.UnorderedAccess => VkImageLayout.General, + BarrierLayout.CopySource => VkImageLayout.TransferSrcOptimal, + BarrierLayout.CopyDest => VkImageLayout.TransferDstOptimal, + BarrierLayout.Present => VkImageLayout.PresentSrcKHR, + BarrierLayout.ResolveSource => VkImageLayout.TransferSrcOptimal, + BarrierLayout.ResolveDest => VkImageLayout.TransferDstOptimal, + _ => VkImageLayout.General, + }; + + /// + /// Converts a to a . + /// + internal static BarrierLayout ToBarrierLayout(VkImageLayout layout) => layout switch + { + VkImageLayout.Undefined => BarrierLayout.Undefined, + VkImageLayout.General => BarrierLayout.Common, + VkImageLayout.ColorAttachmentOptimal => BarrierLayout.RenderTarget, + VkImageLayout.DepthStencilAttachmentOptimal => BarrierLayout.DepthStencilWrite, + VkImageLayout.DepthStencilReadOnlyOptimal => BarrierLayout.DepthStencilRead, + VkImageLayout.ShaderReadOnlyOptimal => BarrierLayout.ShaderResource, + VkImageLayout.TransferSrcOptimal => BarrierLayout.CopySource, + VkImageLayout.TransferDstOptimal => BarrierLayout.CopyDest, + VkImageLayout.PresentSrcKHR => BarrierLayout.Present, + _ => BarrierLayout.Common, + }; + + /// + /// Derives the Vulkan access flags from a . + /// + internal static VkAccessFlags ToVkAccessFlags(BarrierLayout layout) => layout switch + { + BarrierLayout.RenderTarget => VkAccessFlags.ColorAttachmentWrite, + BarrierLayout.DepthStencilWrite => VkAccessFlags.DepthStencilAttachmentWrite, + BarrierLayout.DepthStencilRead => VkAccessFlags.DepthStencilAttachmentRead, + BarrierLayout.ShaderResource => VkAccessFlags.ShaderRead, + BarrierLayout.UnorderedAccess => VkAccessFlags.ShaderRead | VkAccessFlags.ShaderWrite, + BarrierLayout.CopySource => VkAccessFlags.TransferRead, + BarrierLayout.CopyDest => VkAccessFlags.TransferWrite, + BarrierLayout.Present => VkAccessFlags.MemoryRead, + BarrierLayout.ResolveSource => VkAccessFlags.TransferRead, + BarrierLayout.ResolveDest => VkAccessFlags.TransferWrite, + _ => VkAccessFlags.None, + }; + + /// + /// Derives the Vulkan pipeline stage flags from a . + /// + internal static VkPipelineStageFlags ToVkPipelineStageFlags(BarrierLayout layout) => layout switch + { + BarrierLayout.RenderTarget => VkPipelineStageFlags.ColorAttachmentOutput, + BarrierLayout.DepthStencilWrite => VkPipelineStageFlags.ColorAttachmentOutput | VkPipelineStageFlags.EarlyFragmentTests | VkPipelineStageFlags.LateFragmentTests, + BarrierLayout.DepthStencilRead => VkPipelineStageFlags.EarlyFragmentTests | VkPipelineStageFlags.LateFragmentTests, + BarrierLayout.ShaderResource => VkPipelineStageFlags.FragmentShader | VkPipelineStageFlags.ComputeShader, + BarrierLayout.UnorderedAccess => VkPipelineStageFlags.ComputeShader, + BarrierLayout.CopySource => VkPipelineStageFlags.Transfer, + BarrierLayout.CopyDest => VkPipelineStageFlags.Transfer, + BarrierLayout.Present => VkPipelineStageFlags.BottomOfPipe, + BarrierLayout.ResolveSource => VkPipelineStageFlags.Transfer, + BarrierLayout.ResolveDest => VkPipelineStageFlags.Transfer, + _ => VkPipelineStageFlags.TopOfPipe, + }; +} + +#endif diff --git a/sources/engine/Stride.Graphics/Vulkan/Buffer.Vulkan.cs b/sources/engine/Stride.Graphics/Vulkan/Buffer.Vulkan.cs index 0e43a74d2b..26a1b74d6a 100644 --- a/sources/engine/Stride.Graphics/Vulkan/Buffer.Vulkan.cs +++ b/sources/engine/Stride.Graphics/Vulkan/Buffer.Vulkan.cs @@ -215,7 +215,8 @@ public unsafe void Recreate(IntPtr dataPointer) // Barrier var bufferMemoryBarrier = new VkBufferMemoryBarrier(NativeBuffer, VkAccessFlags.TransferWrite, NativeAccessMask); - GraphicsDevice.NativeDeviceApi.vkCmdPipelineBarrier(commandBuffer, VkPipelineStageFlags.Transfer, VkPipelineStageFlags.AllCommands, VkDependencyFlags.None, memoryBarrierCount: 0, memoryBarriers: null, bufferMemoryBarrierCount: 1, &bufferMemoryBarrier, imageMemoryBarrierCount: 0, imageMemoryBarriers: null); + var dstStages = CommandList.FixStagesForAccess(NativePipelineStageMask, NativeAccessMask); + GraphicsDevice.NativeDeviceApi.vkCmdPipelineBarrier(commandBuffer, VkPipelineStageFlags.Transfer, dstStages, VkDependencyFlags.None, memoryBarrierCount: 0, memoryBarriers: null, bufferMemoryBarrierCount: 1, &bufferMemoryBarrier, imageMemoryBarrierCount: 0, imageMemoryBarriers: null); // Close and submit GraphicsDevice.CheckResult(GraphicsDevice.NativeDeviceApi.vkEndCommandBuffer(commandBuffer)); diff --git a/sources/engine/Stride.Graphics/Vulkan/CommandList.Vulkan.cs b/sources/engine/Stride.Graphics/Vulkan/CommandList.Vulkan.cs index a30f53bf40..e4a5072a45 100644 --- a/sources/engine/Stride.Graphics/Vulkan/CommandList.Vulkan.cs +++ b/sources/engine/Stride.Graphics/Vulkan/CommandList.Vulkan.cs @@ -14,6 +14,13 @@ namespace Stride.Graphics { public partial class CommandList { + /// + /// Unique ID for this command list, incremented on each Reset. + /// Used to detect when a resource is first used on a different command list, + /// requiring barriers to be re-issued even if the layout already matches. + /// + internal int CommandListId; + internal CommandBufferPool CommandBufferPool; private VkRenderPass activeRenderPass; @@ -65,6 +72,9 @@ public unsafe partial void Reset() if (currentCommandList.Builder != null) return; + // New command list ID so resources know they need to re-issue barriers + CommandListId = System.Threading.Interlocked.Increment(ref GraphicsDevice.NextCommandListId); + CleanupRenderPass(); boundDescriptorSets.Clear(); @@ -268,6 +278,9 @@ private unsafe partial void SetScissorRectanglesImpl(ReadOnlySpan sci /// Cannot GraphicsDevice.Draw*() without an effect being previously applied with Effect.Apply() method private unsafe void PrepareDraw() { + // Transition resources to correct layouts before starting the render pass + TransitionBoundResources(); + // Lazily set the render pass and frame buffer EnsureRenderPass(); BindPipeline(); @@ -276,6 +289,69 @@ private unsafe void PrepareDraw() GraphicsDevice.NativeDeviceApi.vkCmdSetStencilReference(currentCommandList.NativeCommandBuffer, VkStencilFaceFlags.FrontAndBack, activeStencilReference ?? 0); } + /// + /// Automatically transitions bound textures to the layouts expected by the pipeline: + /// render targets to ColorAttachmentOptimal, depth to DepthStencilAttachmentOptimal, + /// sampled images to ShaderReadOnlyOptimal, storage images to General. + /// Must be called BEFORE EnsureRenderPass since barriers cannot be issued inside a render pass. + /// + private void TransitionBoundResources() + { + if (activePipeline == null) + return; + + // Transition render target attachments + for (int i = 0; i < RenderTargetCount; i++) + { + var rt = renderTargets[i]; + if (rt != null) + { + var parent = rt.ParentTexture ?? rt; + if (parent.NativeLayout != VkImageLayout.ColorAttachmentOptimal) + ResourceBarrierTransition(rt, BarrierLayout.RenderTarget); + } + } + + if (depthStencilBuffer != null) + { + var parent = depthStencilBuffer.ParentTexture ?? depthStencilBuffer; + if (parent.NativeLayout != VkImageLayout.DepthStencilAttachmentOptimal) + ResourceBarrierTransition(depthStencilBuffer, BarrierLayout.DepthStencilWrite); + } + + // Transition sampled/storage textures bound in descriptors + var bindingCount = activePipeline.DescriptorBindingMapping.Count; + for (int index = 0; index < bindingCount; index++) + { + var mapping = activePipeline.DescriptorBindingMapping[index]; + if (mapping.DescriptorType != VkDescriptorType.SampledImage && + mapping.DescriptorType != VkDescriptorType.StorageImage) + continue; + + var sourceSet = boundDescriptorSets[mapping.SourceSet]; + var heapObject = sourceSet.HeapObjects[sourceSet.DescriptorStartOffset + mapping.SourceBinding]; + if (heapObject.Value is Texture texture) + { + var parent = texture.ParentTexture ?? texture; + + // Don't transition swapchain images that are queued for presentation + if (parent.NativeLayout == VkImageLayout.PresentSrcKHR) + continue; + + var expectedLayout = mapping.DescriptorType == VkDescriptorType.SampledImage + ? VkImageLayout.ShaderReadOnlyOptimal + : VkImageLayout.General; + + // Always call ResourceBarrierTransition — even if the layout matches, the barrier + // must be re-issued when the resource was last transitioned by a different command list. + var layout = mapping.DescriptorType == VkDescriptorType.SampledImage + ? BarrierLayout.ShaderResource + : BarrierLayout.UnorderedAccess; + ResourceBarrierTransition(parent, layout); + } + } + } + private unsafe void BindDescriptorSets() { // Keep track of descriptor pool usage @@ -456,61 +532,39 @@ public void SetIndexBuffer(Buffer buffer, int offset, bool is32bits) GraphicsDevice.NativeDeviceApi.vkCmdBindIndexBuffer(currentCommandList.NativeCommandBuffer, buffer.NativeBuffer, (ulong) offset, is32bits ? VkIndexType.Uint32 : VkIndexType.Uint16); } - public unsafe void ResourceBarrierTransition(GraphicsResource resource, GraphicsResourceState newState) + /// + /// Transitions a resource to a new layout using the cross-platform barrier abstraction. + /// + public unsafe void ResourceBarrierTransition(GraphicsResource resource, BarrierLayout newLayout) { if (resource is Texture texture) { if (texture.ParentTexture != null) texture = texture.ParentTexture; - // TODO VULKAN: Check for change - var oldLayout = texture.NativeLayout; var oldAccessMask = texture.NativeAccessMask; - - var sourceStages = resource.NativePipelineStageMask; - - switch (newState) - { - case GraphicsResourceState.RenderTarget: - texture.NativeLayout = VkImageLayout.ColorAttachmentOptimal; - texture.NativeAccessMask = VkAccessFlags.ColorAttachmentWrite; - texture.NativePipelineStageMask = VkPipelineStageFlags.ColorAttachmentOutput; - break; - case GraphicsResourceState.Present: - texture.NativeLayout = VkImageLayout.PresentSrcKHR; - texture.NativeAccessMask = VkAccessFlags.MemoryRead; - texture.NativePipelineStageMask = VkPipelineStageFlags.BottomOfPipe; - break; - case GraphicsResourceState.DepthWrite: - texture.NativeLayout = VkImageLayout.DepthStencilAttachmentOptimal; - texture.NativeAccessMask = VkAccessFlags.DepthStencilAttachmentWrite; - texture.NativePipelineStageMask = VkPipelineStageFlags.ColorAttachmentOutput | VkPipelineStageFlags.EarlyFragmentTests | VkPipelineStageFlags.LateFragmentTests; - break; - case GraphicsResourceState.PixelShaderResource: - texture.NativeLayout = VkImageLayout.ShaderReadOnlyOptimal; - texture.NativeAccessMask = VkAccessFlags.ShaderRead; - texture.NativePipelineStageMask = VkPipelineStageFlags.FragmentShader | VkPipelineStageFlags.ComputeShader; - break; - case GraphicsResourceState.GenericRead: - texture.NativeLayout = VkImageLayout.General; - texture.NativeAccessMask = VkAccessFlags.ShaderRead | VkAccessFlags.TransferRead | VkAccessFlags.IndirectCommandRead | VkAccessFlags.ColorAttachmentRead | VkAccessFlags.DepthStencilAttachmentRead | VkAccessFlags.InputAttachmentRead | VkAccessFlags.VertexAttributeRead | VkAccessFlags.IndexRead | VkAccessFlags.UniformRead; - texture.NativePipelineStageMask = VkPipelineStageFlags.AllCommands; - break; - default: - texture.NativeLayout = VkImageLayout.General; - texture.NativeAccessMask = (VkAccessFlags)0x1FFFF; // TODO VULKAN: Don't hard-code this - texture.NativePipelineStageMask = VkPipelineStageFlags.AllCommands; - break; - } - - if (oldLayout == texture.NativeLayout && oldAccessMask == texture.NativeAccessMask) + var sourceStages = texture.NativePipelineStageMask; + + // Update native state from BarrierLayout via mapping + texture.NativeLayout = BarrierMapping.ToVkImageLayout(newLayout); + texture.NativeAccessMask = BarrierMapping.ToVkAccessFlags(newLayout); + texture.NativePipelineStageMask = BarrierMapping.ToVkPipelineStageFlags(newLayout); + texture.LayoutTracker.Set(uint.MaxValue, newLayout); + + // Skip if the layout already matches AND this command list was the one that set it. + // If a different command list set the layout, we must re-issue the barrier so that + // this command buffer has the transition recorded (required by Vulkan validation). + if (oldLayout == texture.NativeLayout && oldAccessMask == texture.NativeAccessMask + && texture.LastBarrierCommandListId == CommandListId) return; + texture.LastBarrierCommandListId = CommandListId; + if (oldLayout == VkImageLayout.Undefined || oldLayout == VkImageLayout.PresentSrcKHR) sourceStages = VkPipelineStageFlags.TopOfPipe; - // End render pass, so barrier effects all commands in the buffer + // End render pass, so barrier affects all commands in the buffer CleanupRenderPass(); var memoryBarrier = new VkImageMemoryBarrier(texture.NativeImage, new VkImageSubresourceRange(texture.NativeImageAspect, 0, uint.MaxValue, 0, uint.MaxValue), oldAccessMask, texture.NativeAccessMask, oldLayout, texture.NativeLayout); @@ -522,6 +576,38 @@ public unsafe void ResourceBarrierTransition(GraphicsResource resource, Graphics } } + [Obsolete("Use BarrierLayout overload instead.")] + public void ResourceBarrierTransition(GraphicsResource resource, GraphicsResourceState newState) + { + var layout = (int)newState switch + { + 0x04 => BarrierLayout.RenderTarget, + 0x08 => BarrierLayout.UnorderedAccess, + 0x10 => BarrierLayout.DepthStencilWrite, + 0x20 => BarrierLayout.DepthStencilRead, + 0x80 => BarrierLayout.ShaderResource, + 0xC0 => BarrierLayout.ShaderResource, + 0x400 => BarrierLayout.CopyDest, + 0x800 => BarrierLayout.CopySource, + 0x1000 => BarrierLayout.ResolveDest, + 0x2000 => BarrierLayout.ResolveSource, + _ => BarrierLayout.Common, + }; + ResourceBarrierTransition(resource, layout); + } + + /// + /// Ensures the pipeline stage mask is compatible with the access flags in a buffer barrier. + /// HOST_READ/HOST_WRITE access requires VK_PIPELINE_STAGE_HOST_BIT, which is not included + /// in VK_PIPELINE_STAGE_ALL_COMMANDS_BIT. + /// + internal static VkPipelineStageFlags FixStagesForAccess(VkPipelineStageFlags stages, VkAccessFlags access) + { + if ((access & (VkAccessFlags.HostRead | VkAccessFlags.HostWrite)) != 0) + stages |= VkPipelineStageFlags.Host; + return stages; + } + #if !STRIDE_GRAPHICS_NO_DESCRIPTOR_COPIES private readonly FastList boundDescriptorSets = new FastList(); #else @@ -548,6 +634,7 @@ public void SetDescriptorSets(int index, DescriptorSet[] descriptorSets) public void Dispatch(int threadCountX, int threadCountY, int threadCountZ) { CleanupRenderPass(); + TransitionBoundResources(); BindDescriptorSets(); GraphicsDevice.NativeDeviceApi.vkCmdDispatch(currentCommandList.NativeCommandBuffer, (uint)threadCountX, (uint)threadCountY, (uint)threadCountZ); } @@ -560,6 +647,7 @@ public void Dispatch(int threadCountX, int threadCountY, int threadCountZ) public void Dispatch(Buffer indirectBuffer, int offsetInBytes) { CleanupRenderPass(); + TransitionBoundResources(); BindDescriptorSets(); GraphicsDevice.NativeDeviceApi.vkCmdDispatchIndirect(currentCommandList.NativeCommandBuffer, indirectBuffer.NativeBuffer, (ulong)offsetInBytes); } @@ -834,33 +922,52 @@ public void ClearReadWrite(Buffer buffer, UInt4 value) /// The value. /// texture /// Expecting buffer supporting UAV;texture - public void ClearReadWrite(Texture texture, Vector4 value) + public unsafe void ClearReadWrite(Texture texture, Vector4 value) { - throw new NotImplementedException(); + var clearValue = new VkClearColorValue(value.X, value.Y, value.Z, value.W); + ClearReadWriteImpl(texture, clearValue); } - /// - /// Clears a read-write Texture. This texture must have been created with read-write/unordered access. - /// - /// The texture. - /// The value. - /// texture - /// Expecting buffer supporting UAV;texture - public void ClearReadWrite(Texture texture, Int4 value) + public unsafe void ClearReadWrite(Texture texture, Int4 value) { - throw new NotImplementedException(); + // D3D11 ClearUnorderedAccessViewUint writes raw integer values. + // Vulkan vkCmdClearColorImage expects float values for UNORM/SNORM formats, not raw integers. + // Convert to float to match D3D11 behavior. + if (texture.ViewFormat.IsUNorm) + { + float scale = 1.0f / texture.ViewFormat.UNormMaxValue; + ClearReadWrite(texture, new Vector4(value.X * scale, value.Y * scale, value.Z * scale, value.W * scale)); + return; + } + var clearValue = new VkClearColorValue(*(uint*)&value.X, *(uint*)&value.Y, *(uint*)&value.Z, *(uint*)&value.W); + ClearReadWriteImpl(texture, clearValue); } - /// - /// Clears a read-write Texture. This texture must have been created with read-write/unordered access. - /// - /// The texture. - /// The value. - /// texture - /// Expecting buffer supporting UAV;texture - public void ClearReadWrite(Texture texture, UInt4 value) + public unsafe void ClearReadWrite(Texture texture, UInt4 value) { - throw new NotImplementedException(); + if (texture.ViewFormat.IsUNorm) + { + float scale = 1.0f / texture.ViewFormat.UNormMaxValue; + ClearReadWrite(texture, new Vector4(value.X * scale, value.Y * scale, value.Z * scale, value.W * scale)); + return; + } + var clearValue = new VkClearColorValue(value.X, value.Y, value.Z, value.W); + ClearReadWriteImpl(texture, clearValue); + } + + private unsafe void ClearReadWriteImpl(Texture texture, VkClearColorValue clearValue) + { + ArgumentNullException.ThrowIfNull(texture); + CleanupRenderPass(); + + var clearRange = texture.NativeResourceRange; + var memoryBarrier = new VkImageMemoryBarrier(texture.NativeImage, clearRange, texture.NativeAccessMask, VkAccessFlags.TransferWrite, texture.NativeLayout, VkImageLayout.General); + GraphicsDevice.NativeDeviceApi.vkCmdPipelineBarrier(currentCommandList.NativeCommandBuffer, texture.NativePipelineStageMask, VkPipelineStageFlags.Transfer, VkDependencyFlags.None, 0, null, 0, null, 1, &memoryBarrier); + + GraphicsDevice.NativeDeviceApi.vkCmdClearColorImage(currentCommandList.NativeCommandBuffer, texture.NativeImage, VkImageLayout.General, &clearValue, 1, &clearRange); + + memoryBarrier = new VkImageMemoryBarrier(texture.NativeImage, clearRange, VkAccessFlags.TransferWrite, texture.NativeAccessMask, VkImageLayout.General, texture.NativeLayout); + GraphicsDevice.NativeDeviceApi.vkCmdPipelineBarrier(currentCommandList.NativeCommandBuffer, VkPipelineStageFlags.Transfer, texture.NativePipelineStageMask, VkDependencyFlags.None, 0, null, 0, null, 1, &memoryBarrier); } public unsafe void Copy(GraphicsResource source, GraphicsResource destination) @@ -906,7 +1013,8 @@ public unsafe void Copy(GraphicsResource source, GraphicsResource destination) imageBarriers[imageBarrierCount++] = new VkImageMemoryBarrier(destinationParent.NativeImage, new VkImageSubresourceRange(destinationParent.NativeImageAspect, baseMipLevel: 0, levelCount: uint.MaxValue, baseArrayLayer: 0, layerCount: uint.MaxValue), destinationTexture.NativeAccessMask, VkAccessFlags.TransferWrite, destinationTexture.NativeLayout, VkImageLayout.TransferDstOptimal); } - GraphicsDevice.NativeDeviceApi.vkCmdPipelineBarrier(currentCommandList.NativeCommandBuffer, sourceTexture.NativePipelineStageMask | destinationParent.NativePipelineStageMask, VkPipelineStageFlags.Transfer, VkDependencyFlags.None, memoryBarrierCount: 0, memoryBarriers: null, bufferBarrierCount, bufferBarriers, imageBarrierCount, imageBarriers); + var copySrcStages = FixStagesForAccess(sourceTexture.NativePipelineStageMask | destinationParent.NativePipelineStageMask, sourceTexture.NativeAccessMask | destinationTexture.NativeAccessMask); + GraphicsDevice.NativeDeviceApi.vkCmdPipelineBarrier(currentCommandList.NativeCommandBuffer, copySrcStages, VkPipelineStageFlags.Transfer, VkDependencyFlags.None, memoryBarrierCount: 0, memoryBarriers: null, bufferBarrierCount, bufferBarriers, imageBarrierCount, imageBarriers); // TODO: compute all regions at once in a single call for (var subresource = 0; subresource < sourceTexture.MipLevelCount * sourceTexture.ArraySize; ++subresource) @@ -1017,26 +1125,29 @@ public unsafe void Copy(GraphicsResource source, GraphicsResource destination) imageBarrierCount++; } - GraphicsDevice.NativeDeviceApi.vkCmdPipelineBarrier(currentCommandList.NativeCommandBuffer, VkPipelineStageFlags.Transfer, sourceTexture.NativePipelineStageMask | destinationParent.NativePipelineStageMask, VkDependencyFlags.None, memoryBarrierCount: 0, memoryBarriers: null, bufferBarrierCount, bufferBarriers, imageBarrierCount, imageBarriers); + var dstStages = FixStagesForAccess(sourceTexture.NativePipelineStageMask | destinationParent.NativePipelineStageMask, sourceParent.NativeAccessMask | destinationParent.NativeAccessMask); + GraphicsDevice.NativeDeviceApi.vkCmdPipelineBarrier(currentCommandList.NativeCommandBuffer, VkPipelineStageFlags.Transfer, dstStages, VkDependencyFlags.None, memoryBarrierCount: 0, memoryBarriers: null, bufferBarrierCount, bufferBarriers, imageBarrierCount, imageBarriers); } else if (source is Buffer sourceBuffer && destination is Buffer destinationBuffer) { var bufferBarriers = stackalloc VkBufferMemoryBarrier[2]; bufferBarriers[0] = new VkBufferMemoryBarrier(sourceBuffer.NativeBuffer, sourceBuffer.NativeAccessMask, VkAccessFlags.TransferRead); bufferBarriers[1] = new VkBufferMemoryBarrier(destinationBuffer.NativeBuffer, destinationBuffer.NativeAccessMask, VkAccessFlags.TransferWrite); - GraphicsDevice.NativeDeviceApi.vkCmdPipelineBarrier(currentCommandList.NativeCommandBuffer, sourceBuffer.NativePipelineStageMask, VkPipelineStageFlags.Transfer, VkDependencyFlags.None, memoryBarrierCount: 0, memoryBarriers: null, bufferMemoryBarrierCount: 2, bufferBarriers, imageMemoryBarrierCount: 0, imageMemoryBarriers: null); + var srcStages = FixStagesForAccess(sourceBuffer.NativePipelineStageMask | destinationBuffer.NativePipelineStageMask, sourceBuffer.NativeAccessMask | destinationBuffer.NativeAccessMask); + GraphicsDevice.NativeDeviceApi.vkCmdPipelineBarrier(currentCommandList.NativeCommandBuffer, srcStages, VkPipelineStageFlags.Transfer, VkDependencyFlags.None, memoryBarrierCount: 0, memoryBarriers: null, bufferMemoryBarrierCount: 2, bufferBarriers, imageMemoryBarrierCount: 0, imageMemoryBarriers: null); var copy = new VkBufferCopy { srcOffset = 0, dstOffset = 0, - size = (uint) sourceBuffer.SizeInBytes + size = (uint)sourceBuffer.SizeInBytes }; GraphicsDevice.NativeDeviceApi.vkCmdCopyBuffer(currentCommandList.NativeCommandBuffer, sourceBuffer.NativeBuffer, destinationBuffer.NativeBuffer, regionCount: 1, ©); bufferBarriers[0] = new VkBufferMemoryBarrier(sourceBuffer.NativeBuffer, VkAccessFlags.TransferRead, sourceBuffer.NativeAccessMask); bufferBarriers[1] = new VkBufferMemoryBarrier(destinationBuffer.NativeBuffer, VkAccessFlags.TransferWrite, destinationBuffer.NativeAccessMask); - GraphicsDevice.NativeDeviceApi.vkCmdPipelineBarrier(currentCommandList.NativeCommandBuffer, VkPipelineStageFlags.Transfer, sourceBuffer.NativePipelineStageMask, VkDependencyFlags.None, memoryBarrierCount: 0, memoryBarriers: null, bufferMemoryBarrierCount: 2, bufferBarriers, imageMemoryBarrierCount: 0, imageMemoryBarriers: null); + var bufDstStages = FixStagesForAccess(sourceBuffer.NativePipelineStageMask | destinationBuffer.NativePipelineStageMask, sourceBuffer.NativeAccessMask | destinationBuffer.NativeAccessMask); + GraphicsDevice.NativeDeviceApi.vkCmdPipelineBarrier(currentCommandList.NativeCommandBuffer, VkPipelineStageFlags.Transfer, bufDstStages, VkDependencyFlags.None, memoryBarrierCount: 0, memoryBarriers: null, bufferMemoryBarrierCount: 2, bufferBarriers, imageMemoryBarrierCount: 0, imageMemoryBarriers: null); } else { @@ -1089,7 +1200,8 @@ public unsafe void CopyRegion(GraphicsResource source, int sourceSubresource, Re imageBarriers[imageBarrierCount++] = new VkImageMemoryBarrier(destinationParent.NativeImage, new VkImageSubresourceRange(destinationParent.NativeImageAspect, baseMipLevel: 0, levelCount: uint.MaxValue, baseArrayLayer: 0, layerCount: uint.MaxValue), destinationParent.NativeAccessMask, VkAccessFlags.TransferWrite, destinationParent.NativeLayout, VkImageLayout.TransferDstOptimal); } - GraphicsDevice.NativeDeviceApi.vkCmdPipelineBarrier(currentCommandList.NativeCommandBuffer, sourceTexture.NativePipelineStageMask | destinationParent.NativePipelineStageMask, VkPipelineStageFlags.Transfer, VkDependencyFlags.None, memoryBarrierCount: 0, memoryBarriers: null, bufferBarrierCount, bufferBarriers, imageBarrierCount, imageBarriers); + var copySrcStages3 = FixStagesForAccess(sourceTexture.NativePipelineStageMask | destinationParent.NativePipelineStageMask, sourceParent.NativeAccessMask | destinationParent.NativeAccessMask); + GraphicsDevice.NativeDeviceApi.vkCmdPipelineBarrier(currentCommandList.NativeCommandBuffer, copySrcStages3, VkPipelineStageFlags.Transfer, VkDependencyFlags.None, memoryBarrierCount: 0, memoryBarriers: null, bufferBarrierCount, bufferBarriers, imageBarrierCount, imageBarriers); // Copy if (destinationTexture.Usage == GraphicsResourceUsage.Staging) @@ -1195,7 +1307,8 @@ public unsafe void CopyRegion(GraphicsResource source, int sourceSubresource, Re imageBarrierCount++; } - GraphicsDevice.NativeDeviceApi.vkCmdPipelineBarrier(currentCommandList.NativeCommandBuffer, VkPipelineStageFlags.Transfer, sourceTexture.NativePipelineStageMask | destinationParent.NativePipelineStageMask, VkDependencyFlags.None, memoryBarrierCount: 0, memoryBarriers: null, bufferBarrierCount, bufferBarriers, imageBarrierCount, imageBarriers); + var dstStages2 = FixStagesForAccess(sourceTexture.NativePipelineStageMask | destinationParent.NativePipelineStageMask, sourceParent.NativeAccessMask | destinationParent.NativeAccessMask); + GraphicsDevice.NativeDeviceApi.vkCmdPipelineBarrier(currentCommandList.NativeCommandBuffer, VkPipelineStageFlags.Transfer, dstStages2, VkDependencyFlags.None, memoryBarrierCount: 0, memoryBarriers: null, bufferBarrierCount, bufferBarriers, imageBarrierCount, imageBarriers); } else { @@ -1339,7 +1452,7 @@ internal unsafe partial void UpdateSubResource(GraphicsResource resource, int su { var mipSlice = subResourceIndex % texture.MipLevelCount; var arraySlice = subResourceIndex / texture.MipLevelCount; - var subresourceRange = new VkImageSubresourceRange(VkImageAspectFlags.Color, (uint) mipSlice, levelCount: 1, (uint) arraySlice, 1); + var subresourceRange = new VkImageSubresourceRange(texture.NativeImageAspect, (uint) mipSlice, levelCount: 1, (uint) arraySlice, 1); var memoryBarrier = new VkImageMemoryBarrier(texture.NativeImage, subresourceRange, texture.NativeAccessMask, VkAccessFlags.TransferWrite, texture.NativeLayout, VkImageLayout.TransferDstOptimal); GraphicsDevice.NativeDeviceApi.vkCmdPipelineBarrier(currentCommandList.NativeCommandBuffer, texture.NativePipelineStageMask | VkPipelineStageFlags.Host, VkPipelineStageFlags.Transfer, VkDependencyFlags.None, memoryBarrierCount: 0, memoryBarriers: null, bufferMemoryBarrierCount: 1, &uploadBufferMemoryBarrier, imageMemoryBarrierCount: 1, &memoryBarrier); @@ -1349,7 +1462,7 @@ internal unsafe partial void UpdateSubResource(GraphicsResource resource, int su var bufferCopy = new VkBufferImageCopy { bufferOffset = (ulong) (uploadOffset + alignment), - imageSubresource = new VkImageSubresourceLayers { aspectMask = VkImageAspectFlags.Color, baseArrayLayer = (uint) arraySlice, layerCount = 1, mipLevel = (uint) mipSlice }, + imageSubresource = new VkImageSubresourceLayers { aspectMask = texture.NativeImageAspect, baseArrayLayer = (uint) arraySlice, layerCount = 1, mipLevel = (uint) mipSlice }, bufferRowLength = (uint) (databox.RowPitch * texture.Format.BlockWidth / texture.Format.BlockSize), bufferImageHeight = (uint) (databox.SlicePitch * texture.Format.BlockHeight / databox.RowPitch), imageOffset = new VkOffset3D(region.Left, region.Top, region.Front), @@ -1380,7 +1493,8 @@ internal unsafe partial void UpdateSubResource(GraphicsResource resource, int su GraphicsDevice.NativeDeviceApi.vkCmdCopyBuffer(currentCommandList.NativeCommandBuffer, uploadResource, buffer.NativeBuffer, regionCount: 1, &bufferCopy); var memoryBarrier = new VkBufferMemoryBarrier(buffer.NativeBuffer, VkAccessFlags.TransferWrite, buffer.NativeAccessMask, bufferCopy.dstOffset, bufferCopy.size); - GraphicsDevice.NativeDeviceApi.vkCmdPipelineBarrier(currentCommandList.NativeCommandBuffer, VkPipelineStageFlags.Transfer, buffer.NativePipelineStageMask, VkDependencyFlags.None, memoryBarrierCount: 0, memoryBarriers: null, bufferMemoryBarrierCount: 1, &memoryBarrier, imageMemoryBarrierCount: 0, imageMemoryBarriers: null); + var uploadDstStages = FixStagesForAccess(buffer.NativePipelineStageMask, buffer.NativeAccessMask); + GraphicsDevice.NativeDeviceApi.vkCmdPipelineBarrier(currentCommandList.NativeCommandBuffer, VkPipelineStageFlags.Transfer, uploadDstStages, VkDependencyFlags.None, memoryBarrierCount: 0, memoryBarriers: null, bufferMemoryBarrierCount: 1, &memoryBarrier, imageMemoryBarrierCount: 0, imageMemoryBarriers: null); } else { diff --git a/sources/engine/Stride.Graphics/Vulkan/GraphicsAdapterFactory.Vulkan.cs b/sources/engine/Stride.Graphics/Vulkan/GraphicsAdapterFactory.Vulkan.cs index 83c0132433..4e55096921 100644 --- a/sources/engine/Stride.Graphics/Vulkan/GraphicsAdapterFactory.Vulkan.cs +++ b/sources/engine/Stride.Graphics/Vulkan/GraphicsAdapterFactory.Vulkan.cs @@ -96,9 +96,9 @@ internal class GraphicsAdapterFactoryInstance : IDisposable internal VkInstance NativeInstance; internal VkInstanceApi NativeInstanceApi; internal bool HasXlibSurfaceSupport; + internal bool HasSurfaceSupport; - // We use GraphicsDevice (similar to OpenGL) - private static readonly Logger Log = GlobalLogger.GetLogger("GraphicsDevice"); + private static readonly Logger Log = GlobalLogger.GetLogger(nameof(GraphicsDevice)); public unsafe GraphicsAdapterFactoryInstance(bool enableValidation) { @@ -106,7 +106,7 @@ public unsafe GraphicsAdapterFactoryInstance(bool enableValidation) var applicationInfo = new VkApplicationInfo { pEngineName = pEngineName, - apiVersion = new VkVersion(1, 4, 304) + apiVersion = new VkVersion(1, 3, 0) }; Span validationLayerNames = stackalloc VkUtf8String[] @@ -151,12 +151,16 @@ public unsafe GraphicsAdapterFactoryInstance(bool enableValidation) }; var supportedExtensions = new Span(supportedExtensionNames, 6); var availableExtensionNames = GetAvailableExtensionNames(supportedExtensions); - ValidateSurfaceExtensionNamesAvailability(availableExtensionNames); - var desiredExtensionNames = new HashSet + // Surface extensions are optional at instance creation (not available with headless ICDs like SwiftShader). + // They are validated later when a swapchain is actually created. + var desiredExtensionNames = new HashSet(); + HasSurfaceSupport = availableExtensionNames.Contains(VK_KHR_SURFACE_EXTENSION_NAME); + if (HasSurfaceSupport) { - VK_KHR_SURFACE_EXTENSION_NAME, - GetPlatformRelatedSurfaceExtensionName(availableExtensionNames) - }; + ValidateSurfaceExtensionNamesAvailability(availableExtensionNames); + desiredExtensionNames.Add(VK_KHR_SURFACE_EXTENSION_NAME); + desiredExtensionNames.Add(GetPlatformRelatedSurfaceExtensionName(availableExtensionNames)); + } HasXlibSurfaceSupport = desiredExtensionNames.Contains(VK_KHR_XLIB_SURFACE_EXTENSION_NAME); @@ -183,8 +187,10 @@ public unsafe GraphicsAdapterFactoryInstance(bool enableValidation) NativeInstanceApi = GetApi(NativeInstance); - // Check if validation layer was available (otherwise detected count is 0) - if (enableValidation) + // Create debug messenger only if the extension was actually enabled and the function is available. + // The Vulkan loader may advertise VK_EXT_debug_utils but fail to provide the function + // if no validation layers are installed. + if (enableDebugReport && NativeInstanceApi.vkCreateDebugUtilsMessengerEXT_ptr != default) { var createInfo = new VkDebugUtilsMessengerCreateInfoEXT { @@ -276,22 +282,14 @@ private unsafe static uint DebugReport(VkDebugUtilsMessageSeverityFlagsEXT sever var message = new VkUtf8String(pCallbackData->pMessage).ToString(); Debug.WriteLine($"Vulkan: {severity} {message}"); - // Redirect to log + // Redirect warnings and errors to log if (severity == VkDebugUtilsMessageSeverityFlagsEXT.Error) { - Log.Error(message); + Log.Error($"[Vulkan] {message}"); } else if (severity == VkDebugUtilsMessageSeverityFlagsEXT.Warning) { - Log.Warning(message); - } - else if (severity == VkDebugUtilsMessageSeverityFlagsEXT.Info) - { - Log.Info(message); - } - else if (severity == VkDebugUtilsMessageSeverityFlagsEXT.Verbose) - { - Log.Verbose(message); + Log.Warning($"[Vulkan] {message}"); } return VK_FALSE; diff --git a/sources/engine/Stride.Graphics/Vulkan/GraphicsDevice.Vulkan.cs b/sources/engine/Stride.Graphics/Vulkan/GraphicsDevice.Vulkan.cs index 793a26b1f7..7e998f9cea 100644 --- a/sources/engine/Stride.Graphics/Vulkan/GraphicsDevice.Vulkan.cs +++ b/sources/engine/Stride.Graphics/Vulkan/GraphicsDevice.Vulkan.cs @@ -17,6 +17,11 @@ namespace Stride.Graphics { public partial class GraphicsDevice { + /// + /// Counter for generating unique command list IDs. Incremented atomically on each CommandList.Reset. + /// + internal int NextCommandListId; + internal int ConstantBufferDataPlacementAlignment; internal readonly ConcurrentPool> DescriptorPoolLists = new ConcurrentPool>(() => new List()); @@ -398,10 +403,11 @@ void SetMaxDescriptorTypeCount(VkDescriptorType type, uint limit) var availableExtensionProperties = GetAvailableExtensionProperties(supportedExtensionProperties); ValidateExtensionPropertiesAvailability(availableExtensionProperties); - var desiredExtensionProperties = new HashSet - { - VK_KHR_SWAPCHAIN_EXTENSION_NAME - }; + var desiredExtensionProperties = new HashSet(); + + // Swapchain extension is only needed for presentation (not for headless/asset compilation) + if (availableExtensionProperties.Contains(VK_KHR_SWAPCHAIN_EXTENSION_NAME)) + desiredExtensionProperties.Add(VK_KHR_SWAPCHAIN_EXTENSION_NAME); if (availableExtensionProperties.Contains(VK_EXT_DEBUG_MARKER_EXTENSION_NAME) && IsDebugMode) { @@ -409,8 +415,23 @@ void SetMaxDescriptorTypeCount(VkDescriptorType type, uint limit) IsProfilingSupported = true; } - var timelineSemaphoreFeatures = new VkPhysicalDeviceTimelineSemaphoreFeatures(); - timelineSemaphoreFeatures.sType = VkStructureType.PhysicalDeviceTimelineSemaphoreFeatures; + // Timeline semaphores (core in Vulkan 1.2+, extension in 1.1) + // Check if the feature is supported before requesting it + var timelineSemaphoreFeatures = new VkPhysicalDeviceTimelineSemaphoreFeatures + { + sType = VkStructureType.PhysicalDeviceTimelineSemaphoreFeatures, + }; + var physicalDeviceFeatures2 = new VkPhysicalDeviceFeatures2 + { + sType = VkStructureType.PhysicalDeviceFeatures2, + pNext = &timelineSemaphoreFeatures, + }; + NativeInstanceApi.vkGetPhysicalDeviceFeatures2(NativePhysicalDevice, &physicalDeviceFeatures2); + + if (!timelineSemaphoreFeatures.timelineSemaphore) + throw new InvalidOperationException("Vulkan: Timeline semaphores are not supported by this device, but are required by Stride."); + + // Re-set to request the feature timelineSemaphoreFeatures.timelineSemaphore = VkBool32.True; using VkStringArray ppEnabledExtensionNames = new(desiredExtensionProperties); @@ -596,6 +617,11 @@ private partial void AdjustDefaultPipelineStateDescription(ref PipelineStateDesc /// /// Releases the platform-specific Graphics Device and all its associated resources. /// + partial void WaitForGPUIdle() + { + CheckResult(NativeDeviceApi.vkDeviceWaitIdle(nativeDevice)); + } + protected partial void DestroyPlatformDevice() { ReleaseDevice(); @@ -611,9 +637,6 @@ private unsafe void ReleaseDevice() EmptyTexture.Dispose(); EmptyTexture = null; - // Wait for all queues to be idle - CheckResult(NativeDeviceApi.vkDeviceWaitIdle(nativeDevice)); - // Mark upload buffer for destruction if (nativeUploadBuffer != VkBuffer.Null) { @@ -1069,6 +1092,26 @@ public static unsafe implicit operator NativeResource(VkQueryPool handle) return new NativeResource(VkDebugReportObjectTypeEXT.QueryPool, *(ulong*)&handle); } + public static unsafe implicit operator NativeResource(VkPipeline handle) + { + return new NativeResource(VkDebugReportObjectTypeEXT.Pipeline, *(ulong*)&handle); + } + + public static unsafe implicit operator NativeResource(VkPipelineLayout handle) + { + return new NativeResource(VkDebugReportObjectTypeEXT.PipelineLayout, *(ulong*)&handle); + } + + public static unsafe implicit operator NativeResource(VkRenderPass handle) + { + return new NativeResource(VkDebugReportObjectTypeEXT.RenderPass, *(ulong*)&handle); + } + + public static unsafe implicit operator NativeResource(VkDescriptorSetLayout handle) + { + return new NativeResource(VkDebugReportObjectTypeEXT.DescriptorSetLayout, *(ulong*)&handle); + } + public unsafe void Destroy(GraphicsDevice device) { var handleCopy = handle; @@ -1105,6 +1148,18 @@ public unsafe void Destroy(GraphicsDevice device) case VkDebugReportObjectTypeEXT.QueryPool: device.NativeDeviceApi.vkDestroyQueryPool(device.NativeDevice, *(VkQueryPool*)&handleCopy, null); break; + case VkDebugReportObjectTypeEXT.Pipeline: + device.NativeDeviceApi.vkDestroyPipeline(device.NativeDevice, *(VkPipeline*)&handleCopy, null); + break; + case VkDebugReportObjectTypeEXT.PipelineLayout: + device.NativeDeviceApi.vkDestroyPipelineLayout(device.NativeDevice, *(VkPipelineLayout*)&handleCopy, null); + break; + case VkDebugReportObjectTypeEXT.RenderPass: + device.NativeDeviceApi.vkDestroyRenderPass(device.NativeDevice, *(VkRenderPass*)&handleCopy, null); + break; + case VkDebugReportObjectTypeEXT.DescriptorSetLayout: + device.NativeDeviceApi.vkDestroyDescriptorSetLayout(device.NativeDevice, *(VkDescriptorSetLayout*)&handleCopy, null); + break; } } } diff --git a/sources/engine/Stride.Graphics/Vulkan/GraphicsResource.Vulkan.cs b/sources/engine/Stride.Graphics/Vulkan/GraphicsResource.Vulkan.cs index c48ac5b00b..485fede7b0 100644 --- a/sources/engine/Stride.Graphics/Vulkan/GraphicsResource.Vulkan.cs +++ b/sources/engine/Stride.Graphics/Vulkan/GraphicsResource.Vulkan.cs @@ -24,6 +24,13 @@ public abstract partial class GraphicsResource /// internal CommandList UpdatingCommandList; + /// + /// ID of the command list that last recorded a barrier transition for this resource. + /// Used to detect when a resource is first used on a different command list, in which + /// case the barrier must be re-issued even if the layout already matches. + /// + internal int LastBarrierCommandListId; + internal VkDeviceMemory NativeMemory; internal VkPipelineStageFlags NativePipelineStageMask; diff --git a/sources/engine/Stride.Graphics/Vulkan/PipelineState.Vulkan.cs b/sources/engine/Stride.Graphics/Vulkan/PipelineState.Vulkan.cs index dba294121a..e5aa929d40 100644 --- a/sources/engine/Stride.Graphics/Vulkan/PipelineState.Vulkan.cs +++ b/sources/engine/Stride.Graphics/Vulkan/PipelineState.Vulkan.cs @@ -333,11 +333,15 @@ protected internal override unsafe void OnDestroyed(bool immediately = false) { if (NativePipeline != VkPipeline.Null) { - GraphicsDevice.NativeDeviceApi.vkDestroyRenderPass(GraphicsDevice.NativeDevice, NativeRenderPass, allocator: null); - GraphicsDevice.NativeDeviceApi.vkDestroyPipeline(GraphicsDevice.NativeDevice, NativePipeline, allocator: null); - GraphicsDevice.NativeDeviceApi.vkDestroyPipelineLayout(GraphicsDevice.NativeDevice, NativeLayout, allocator: null); - - GraphicsDevice.NativeDeviceApi.vkDestroyDescriptorSetLayout(GraphicsDevice.NativeDevice, NativeDescriptorSetLayout, allocator: null); + GraphicsDevice.Collect(NativeRenderPass); + GraphicsDevice.Collect(NativePipeline); + GraphicsDevice.Collect(NativeLayout); + GraphicsDevice.Collect(NativeDescriptorSetLayout); + + NativeRenderPass = VkRenderPass.Null; + NativePipeline = VkPipeline.Null; + NativeLayout = VkPipelineLayout.Null; + NativeDescriptorSetLayout = VkDescriptorSetLayout.Null; } base.OnDestroyed(immediately); diff --git a/sources/engine/Stride.Graphics/Vulkan/SwapChainGraphicsPresenter.Vulkan.cs b/sources/engine/Stride.Graphics/Vulkan/SwapChainGraphicsPresenter.Vulkan.cs index 57a4121e57..12374b80a0 100644 --- a/sources/engine/Stride.Graphics/Vulkan/SwapChainGraphicsPresenter.Vulkan.cs +++ b/sources/engine/Stride.Graphics/Vulkan/SwapChainGraphicsPresenter.Vulkan.cs @@ -406,15 +406,27 @@ private unsafe void CreateSwapChain(int width, int height, PixelFormat desiredFo .Select((properties, index) => index).First(); // Surface format - var backBufferFormat = VulkanConvertExtensions.ConvertPixelFormat(Description.BackBufferFormat); - GraphicsDevice.NativeInstanceApi.vkGetPhysicalDeviceSurfaceFormatsKHR(GraphicsDevice.NativePhysicalDevice, surface, out uint surfaceFormatCount); Span surfaceFormats = stackalloc VkSurfaceFormatKHR[(int)surfaceFormatCount]; GraphicsDevice.NativeInstanceApi.vkGetPhysicalDeviceSurfaceFormatsKHR(GraphicsDevice.NativePhysicalDevice, surface, surfaceFormats); + var backBufferFormat = VulkanConvertExtensions.ConvertPixelFormat(Description.BackBufferFormat); if ((surfaceFormats.Length != 1 || surfaceFormats[0].format != VkFormat.Undefined) && !surfaceFormats.ToArray().Any(x => x.format == backBufferFormat)) { - backBufferFormat = surfaceFormats[0].format; + // Requested format not available as a surface format (e.g. R8G8B8A8 requested but only B8G8R8A8 available). + // Try the RGB<->BGR swapped equivalent before falling back to the first available format. + var swappedFormat = backBufferFormat switch + { + VkFormat.R8G8B8A8Unorm => VkFormat.B8G8R8A8Unorm, + VkFormat.B8G8R8A8Unorm => VkFormat.R8G8B8A8Unorm, + VkFormat.R8G8B8A8Srgb => VkFormat.B8G8R8A8Srgb, + VkFormat.B8G8R8A8Srgb => VkFormat.R8G8B8A8Srgb, + _ => backBufferFormat, + }; + backBufferFormat = surfaceFormats.ToArray().Any(x => x.format == swappedFormat) + ? swappedFormat + : surfaceFormats[0].format; + Description.BackBufferFormat = VulkanConvertExtensions.ConvertPixelFormat(backBufferFormat); } // Create swapchain @@ -497,6 +509,11 @@ private unsafe void CreateSurface() { throw new ArgumentException("DeviceWindowHandle cannot be null"); } + + // Validate surface extension support (not available with headless ICDs like SwiftShader) + if (!GraphicsAdapterFactory.GetInstance(GraphicsDevice.IsDebugMode).HasSurfaceSupport) + throw new InvalidOperationException("Cannot create a swapchain: Vulkan surface extensions are not available. This may happen when using a headless ICD (e.g. SwiftShader)."); + // Create surface #if STRIDE_UI_SDL if (Description.DeviceWindowHandle.Context == Games.AppContextType.DesktopSDL) diff --git a/sources/engine/Stride.Graphics/Vulkan/Texture.Vulkan.cs b/sources/engine/Stride.Graphics/Vulkan/Texture.Vulkan.cs index fdc835757e..9bc6eb32b9 100644 --- a/sources/engine/Stride.Graphics/Vulkan/Texture.Vulkan.cs +++ b/sources/engine/Stride.Graphics/Vulkan/Texture.Vulkan.cs @@ -23,7 +23,17 @@ public partial class Texture internal VkImageSubresourceRange NativeResourceRange; private bool isNotOwningResources; - internal bool IsInitialized; + internal bool IsInitialized + { + get => ParentTexture?.IsInitialized ?? field; + set + { + if (ParentTexture != null) + ParentTexture.IsInitialized = value; + else + field = value; + } + } internal VkFormat NativeFormat; internal bool HasStencil; @@ -92,7 +102,7 @@ private partial void InitializeFromImpl(DataBox[] dataBoxes = null) NativeFormat = VulkanConvertExtensions.ConvertPixelFormat(ViewFormat); HasStencil = IsStencilFormat(ViewFormat); - NativeImageAspect = IsDepthStencil ? VkImageAspectFlags.Depth : VkImageAspectFlags.Color; + NativeImageAspect = IsDepthStencil || IsDepthFormat(ViewFormat) ? VkImageAspectFlags.Depth : VkImageAspectFlags.Color; if (HasStencil) NativeImageAspect |= VkImageAspectFlags.Stencil; @@ -148,6 +158,8 @@ private partial void InitializeFromImpl(DataBox[] dataBoxes = null) IsShaderResource ? VkImageLayout.ShaderReadOnlyOptimal : VkImageLayout.General; + LayoutTracker.Initialize(BarrierMapping.ToBarrierLayout(NativeLayout), ArraySize * MipLevelCount); + if (NativeLayout == VkImageLayout.TransferDstOptimal) NativeAccessMask = VkAccessFlags.TransferRead; @@ -350,7 +362,7 @@ private unsafe void InitializeData(DataBox[] dataBoxes) var copy = new VkBufferImageCopy { bufferOffset = (ulong) uploadOffset, - imageSubresource = new VkImageSubresourceLayers(VkImageAspectFlags.Color, (uint) mipSlice, (uint) arraySlice, layerCount: 1), + imageSubresource = new VkImageSubresourceLayers(NativeImageAspect, (uint) mipSlice, (uint) arraySlice, layerCount: 1), bufferRowLength = (uint) (dataBoxes[i].RowPitch * Format.BlockWidth / Format.BlockSize), bufferImageHeight = (uint) (dataBoxes[i].SlicePitch * Format.BlockHeight / dataBoxes[i].RowPitch), imageOffset = new VkOffset3D(0, 0, 0), @@ -368,7 +380,8 @@ private unsafe void InitializeData(DataBox[] dataBoxes) if (Usage == GraphicsResourceUsage.Staging) { bufferBarriers[0] = new VkBufferMemoryBarrier(NativeBuffer, VkAccessFlags.TransferWrite, NativeAccessMask); - GraphicsDevice.NativeDeviceApi.vkCmdPipelineBarrier(commandBuffer, VkPipelineStageFlags.Transfer, VkPipelineStageFlags.AllCommands, VkDependencyFlags.None, memoryBarrierCount: 0, memoryBarriers: null, bufferMemoryBarrierCount: 1, bufferBarriers, imageMemoryBarrierCount: 0, imageMemoryBarriers: null); + var stagingDstStages = CommandList.FixStagesForAccess(NativePipelineStageMask, NativeAccessMask); + GraphicsDevice.NativeDeviceApi.vkCmdPipelineBarrier(commandBuffer, VkPipelineStageFlags.Transfer, stagingDstStages, VkDependencyFlags.None, memoryBarrierCount: 0, memoryBarriers: null, bufferMemoryBarrierCount: 1, bufferBarriers, imageMemoryBarrierCount: 0, imageMemoryBarriers: null); } IsInitialized = true; @@ -381,7 +394,8 @@ private unsafe void InitializeData(DataBox[] dataBoxes) new VkImageSubresourceRange(NativeImageAspect, baseMipLevel: 0, levelCount: uint.MaxValue, baseArrayLayer: 0, layerCount: uint.MaxValue), dataBoxes == null || dataBoxes.Length == 0 ? VkAccessFlags.None : VkAccessFlags.TransferWrite, NativeAccessMask, dataBoxes == null || dataBoxes.Length == 0 ? VkImageLayout.Undefined : VkImageLayout.TransferDstOptimal, NativeLayout); - GraphicsDevice.NativeDeviceApi.vkCmdPipelineBarrier(commandBuffer, VkPipelineStageFlags.Transfer, VkPipelineStageFlags.AllCommands, VkDependencyFlags.None, memoryBarrierCount: 0, memoryBarriers: null, bufferMemoryBarrierCount: 0, bufferMemoryBarriers: null, imageMemoryBarrierCount: 1, &imageMemoryBarrier); + var texDstStages = CommandList.FixStagesForAccess(NativePipelineStageMask, NativeAccessMask); + GraphicsDevice.NativeDeviceApi.vkCmdPipelineBarrier(commandBuffer, VkPipelineStageFlags.Transfer, texDstStages, VkDependencyFlags.None, memoryBarrierCount: 0, memoryBarriers: null, bufferMemoryBarrierCount: 0, bufferMemoryBarriers: null, imageMemoryBarrierCount: 1, &imageMemoryBarrier); } // Close and submit @@ -486,17 +500,18 @@ private unsafe VkImageView GetImageView(ViewType viewType, int arrayOrDepthSlice format = NativeFormat, //VulkanConvertExtensions.ConvertPixelFormat(ViewFormat), image = NativeImage, components = VkComponentMapping.Identity, - subresourceRange = new VkImageSubresourceRange(IsDepthStencil ? VkImageAspectFlags.Depth : VkImageAspectFlags.Color, (uint) mipIndex, (uint) mipCount, (uint) arrayOrDepthSlice, (uint) layerCount) // TODO VULKAN: Select between depth and stencil? + // For shader resource views, use only the depth aspect (not stencil) when sampling depth-stencil textures + subresourceRange = new VkImageSubresourceRange(HasStencil ? VkImageAspectFlags.Depth : NativeImageAspect, (uint) mipIndex, (uint) mipCount, (uint) arrayOrDepthSlice, (uint) layerCount) }; if (IsMultiSampled) throw new NotImplementedException(); - if (this.ArraySize > 1) - { - if (IsMultiSampled && Dimension != TextureDimension.Texture2D) - throw new NotSupportedException("Multisample is only supported for 2D Textures"); + if (IsMultiSampled && Dimension != TextureDimension.Texture2D) + throw new NotSupportedException("Multisample is only supported for 2D Textures"); + if (layerCount > 1) + { if (Dimension == TextureDimension.Texture3D) throw new NotSupportedException("Texture Array is not supported for Texture3D"); @@ -509,26 +524,21 @@ private unsafe VkImageView GetImageView(ViewType viewType, int arrayOrDepthSlice createInfo.viewType = VkImageViewType.Image2DArray; break; case TextureDimension.TextureCube: - if (ArraySize % 6 != 0) throw new NotSupportedException("Texture cubes require an ArraySize which is a multiple of 6"); + if (layerCount % 6 != 0) throw new NotSupportedException("Texture cube views require a layerCount which is a multiple of 6"); - createInfo.viewType = ArraySize > 6 ? VkImageViewType.ImageCubeArray : VkImageViewType.ImageCube; + createInfo.viewType = layerCount > 6 ? VkImageViewType.ImageCubeArray : VkImageViewType.ImageCube; break; } } else { - if (IsMultiSampled && Dimension != TextureDimension.Texture2D) - throw new NotSupportedException("Multisample is only supported for 2D RenderTarget Textures"); - - if (Dimension == TextureDimension.TextureCube) - throw new NotSupportedException("TextureCube dimension is expecting an arraysize > 1"); - switch (Dimension) { case TextureDimension.Texture1D: createInfo.viewType = VkImageViewType.Image1D; break; case TextureDimension.Texture2D: + case TextureDimension.TextureCube: createInfo.viewType = VkImageViewType.Image2D; break; case TextureDimension.Texture3D: @@ -553,32 +563,15 @@ private unsafe VkImageView GetColorAttachmentView(ViewType viewType, int arrayOr var createInfo = new VkImageViewCreateInfo { sType = VkStructureType.ImageViewCreateInfo, - viewType = VkImageViewType.Image2D, - format = NativeFormat, // VulkanConvertExtensions.ConvertPixelFormat(ViewFormat), + viewType = GetViewType(Dimension), + format = NativeFormat, image = NativeImage, components = VkComponentMapping.Identity, subresourceRange = new VkImageSubresourceRange(VkImageAspectFlags.Color, (uint) mipIndex, (uint) mipCount, (uint) arrayOrDepthSlice, 1) }; - if (IsMultiSampled) - throw new NotImplementedException(); - - if (this.ArraySize > 1) - { - if (IsMultiSampled && Dimension != TextureDimension.Texture2D) - throw new NotSupportedException("Multisample is only supported for 2D Textures"); - - if (Dimension == TextureDimension.Texture3D) - throw new NotSupportedException("Texture Array is not supported for Texture3D"); - } - else - { - if (IsMultiSampled && Dimension != TextureDimension.Texture2D) - throw new NotSupportedException("Multisample is only supported for 2D RenderTarget Textures"); - - if (Dimension == TextureDimension.TextureCube) - throw new NotSupportedException("TextureCube dimension is expecting an arraysize > 1"); - } + if (IsMultiSampled && Dimension != TextureDimension.Texture2D) + throw new NotSupportedException("Multisample is only supported for 2D RenderTarget Textures"); GraphicsDevice.CheckResult(GraphicsDevice.NativeDeviceApi.vkCreateImageView(GraphicsDevice.NativeDevice, &createInfo, null, out var imageView)); return imageView; @@ -593,12 +586,11 @@ private unsafe VkImageView GetDepthStencilView() //if (ComputeShaderResourceFormatFromDepthFormat(ViewFormat) == PixelFormat.None) // throw new NotSupportedException("Depth stencil format [{0}] not supported".ToFormat(ViewFormat)); - // Create a Depth stencil view on this texture2D var createInfo = new VkImageViewCreateInfo { sType = VkStructureType.ImageViewCreateInfo, - viewType = VkImageViewType.Image2D, - format = NativeFormat, //VulkanConvertExtensions.ConvertPixelFormat(ViewFormat), + viewType = GetViewType(Dimension), + format = NativeFormat, image = NativeImage, components = VkComponentMapping.Identity, subresourceRange = new VkImageSubresourceRange(NativeImageAspect, baseMipLevel: 0, levelCount: 1, baseArrayLayer: 0, layerCount: 1) @@ -619,6 +611,13 @@ private unsafe VkImageView GetDepthStencilView() return imageView; } + private static VkImageViewType GetViewType(TextureDimension dimension) => dimension switch + { + TextureDimension.Texture1D => VkImageViewType.Image1D, + TextureDimension.Texture3D => VkImageViewType.Image3D, + _ => VkImageViewType.Image2D, + }; + /// /// Indicates if the Texture is flipped vertically, i.e. if the rows are ordered bottom-to-top instead of top-to-bottom. /// @@ -710,6 +709,14 @@ internal static VkFormat GetFallbackDepthStencilFormat(GraphicsDevice device, Vk return format; } + internal static bool IsDepthFormat(PixelFormat format) + { + return format is PixelFormat.D16_UNorm + or PixelFormat.D32_Float + or PixelFormat.D24_UNorm_S8_UInt + or PixelFormat.D32_Float_S8X24_UInt; + } + internal static bool IsStencilFormat(PixelFormat format) { switch (format) diff --git a/sources/engine/Stride.Graphics/Vulkan/VulkanConvertExtensions.cs b/sources/engine/Stride.Graphics/Vulkan/VulkanConvertExtensions.cs index 180bcc2668..b612f1ecf6 100644 --- a/sources/engine/Stride.Graphics/Vulkan/VulkanConvertExtensions.cs +++ b/sources/engine/Stride.Graphics/Vulkan/VulkanConvertExtensions.cs @@ -216,6 +216,50 @@ public static VkBlendFactor ConvertBlend(Blend blend) } } + public static PixelFormat ConvertPixelFormat(VkFormat inputFormat) => inputFormat switch + { + VkFormat.R8Unorm => PixelFormat.R8_UNorm, + VkFormat.R8Snorm => PixelFormat.R8_SNorm, + VkFormat.R8Uint => PixelFormat.R8_UInt, + VkFormat.R8Sint => PixelFormat.R8_SInt, + VkFormat.R8G8B8A8Unorm => PixelFormat.R8G8B8A8_UNorm, + VkFormat.R8G8B8A8Uint => PixelFormat.R8G8B8A8_UInt, + VkFormat.R8G8B8A8Sint => PixelFormat.R8G8B8A8_SInt, + VkFormat.B8G8R8A8Unorm => PixelFormat.B8G8R8A8_UNorm, + VkFormat.R8G8B8A8Srgb => PixelFormat.R8G8B8A8_UNorm_SRgb, + VkFormat.B8G8R8A8Srgb => PixelFormat.B8G8R8A8_UNorm_SRgb, + VkFormat.A2R10G10B10UintPack32 => PixelFormat.R10G10B10A2_UInt, + VkFormat.A2R10G10B10UnormPack32 => PixelFormat.R10G10B10A2_UNorm, + VkFormat.R16Sfloat => PixelFormat.R16_Float, + VkFormat.R16Unorm => PixelFormat.R16_UNorm, + VkFormat.R16Uint => PixelFormat.R16_UInt, + VkFormat.R16Sint => PixelFormat.R16_SInt, + VkFormat.R16G16Sfloat => PixelFormat.R16G16_Float, + VkFormat.R16G16Snorm => PixelFormat.R16G16_SNorm, + VkFormat.R16G16Unorm => PixelFormat.R16G16_UNorm, + VkFormat.R16G16B16A16Sfloat => PixelFormat.R16G16B16A16_Float, + VkFormat.R16G16B16A16Unorm => PixelFormat.R16G16B16A16_UNorm, + VkFormat.R16G16B16A16Snorm => PixelFormat.R16G16B16A16_SNorm, + VkFormat.R16G16B16A16Uint => PixelFormat.R16G16B16A16_UInt, + VkFormat.R16G16B16A16Sint => PixelFormat.R16G16B16A16_SInt, + VkFormat.R32Uint => PixelFormat.R32_UInt, + VkFormat.R32Sfloat => PixelFormat.R32_Float, + VkFormat.R32G32Sfloat => PixelFormat.R32G32_Float, + VkFormat.R32G32Uint => PixelFormat.R32G32_UInt, + VkFormat.R32G32Sint => PixelFormat.R32G32_SInt, + VkFormat.R32G32B32Sfloat => PixelFormat.R32G32B32_Float, + VkFormat.R32G32B32Sint => PixelFormat.R32G32B32_SInt, + VkFormat.R32G32B32Uint => PixelFormat.R32G32B32_UInt, + VkFormat.R32G32B32A32Sfloat => PixelFormat.R32G32B32A32_Float, + VkFormat.R32G32B32A32Sint => PixelFormat.R32G32B32A32_SInt, + VkFormat.R32G32B32A32Uint => PixelFormat.R32G32B32A32_UInt, + VkFormat.D16Unorm => PixelFormat.D16_UNorm, + VkFormat.D24UnormS8Uint => PixelFormat.D24_UNorm_S8_UInt, + VkFormat.D32Sfloat => PixelFormat.D32_Float, + VkFormat.D32SfloatS8Uint => PixelFormat.D32_Float_S8X24_UInt, + _ => throw new NotSupportedException($"VkFormat '{inputFormat}' has no equivalent PixelFormat"), + }; + public static VkFormat ConvertPixelFormat(PixelFormat inputFormat) { ConvertPixelFormat(inputFormat, out var format, out _, out _); diff --git a/sources/engine/Stride.Particles.Tests/Properties/launchSettings.json b/sources/engine/Stride.Particles.Tests/Properties/launchSettings.json index 719e25cc64..26edd5bd18 100644 --- a/sources/engine/Stride.Particles.Tests/Properties/launchSettings.json +++ b/sources/engine/Stride.Particles.Tests/Properties/launchSettings.json @@ -1,13 +1,15 @@ { "profiles": { - "WARP": { + "Software": { "commandName": "Project", - "environmentVariables": { - "STRIDE_GRAPHICS_SOFTWARE_RENDERING": "1" - } + "nativeDebugging": true }, "GPU": { - "commandName": "Project" + "commandName": "Project", + "environmentVariables": { + "STRIDE_TESTS_GPU": "1" + }, + "nativeDebugging": true } } } diff --git a/sources/engine/Stride.Rendering/Rendering/Images/ColorTransforms/ColorTransformGroup.cs b/sources/engine/Stride.Rendering/Rendering/Images/ColorTransforms/ColorTransformGroup.cs index 77a93a12ce..5b310821c6 100644 --- a/sources/engine/Stride.Rendering/Rendering/Images/ColorTransforms/ColorTransformGroup.cs +++ b/sources/engine/Stride.Rendering/Rendering/Images/ColorTransforms/ColorTransformGroup.cs @@ -130,7 +130,7 @@ protected override void DrawCore(RenderDrawContext context) for (int i = 0; i < transformContext.Inputs.Count; i++) { transformGroupEffect.SetInput(i, transformContext.Inputs[i]); - context.CommandList.ResourceBarrierTransition(transformContext.Inputs[i], Graphics.GraphicsResourceState.PixelShaderResource); + context.CommandList.ResourceBarrierTransition(transformContext.Inputs[i], Graphics.BarrierLayout.ShaderResource); } transformGroupEffect.SetOutput(output); transformGroupEffect.Draw(context, name: Name); diff --git a/sources/engine/Stride.Rendering/Rendering/Images/ImageEffect.cs b/sources/engine/Stride.Rendering/Rendering/Images/ImageEffect.cs index 8ed654e9bc..b7cd03f986 100644 --- a/sources/engine/Stride.Rendering/Rendering/Images/ImageEffect.cs +++ b/sources/engine/Stride.Rendering/Rendering/Images/ImageEffect.cs @@ -234,16 +234,16 @@ protected virtual void SetRenderTargets(RenderDrawContext context) for (int i = 0; i <= maxInputTextureIndex; ++i) { if (inputTextures[i] != null) - context.CommandList.ResourceBarrierTransition(inputTextures[i], GraphicsResourceState.PixelShaderResource); + context.CommandList.ResourceBarrierTransition(inputTextures[i], BarrierLayout.ShaderResource); } if (outputDepthStencilView != null) - context.CommandList.ResourceBarrierTransition(outputDepthStencilView, GraphicsResourceState.DepthWrite); + context.CommandList.ResourceBarrierTransition(outputDepthStencilView, BarrierLayout.DepthStencilWrite); if (outputRenderTargetView != null) { // Transition render target - context.CommandList.ResourceBarrierTransition(outputRenderTargetView, GraphicsResourceState.RenderTarget); + context.CommandList.ResourceBarrierTransition(outputRenderTargetView, BarrierLayout.RenderTarget); if (outputRenderTargetView.ViewDimension == TextureDimension.TextureCube) { @@ -264,7 +264,7 @@ protected virtual void SetRenderTargets(RenderDrawContext context) { // Transition render targets foreach (var renderTarget in outputRenderTargetViews) - context.CommandList.ResourceBarrierTransition(renderTarget, GraphicsResourceState.RenderTarget); + context.CommandList.ResourceBarrierTransition(renderTarget, BarrierLayout.RenderTarget); context.CommandList.SetRenderTargetsAndViewport(outputDepthStencilView, outputRenderTargetViews); } diff --git a/sources/engine/Stride.Rendering/Rendering/Images/PostProcessingEffects.cs b/sources/engine/Stride.Rendering/Rendering/Images/PostProcessingEffects.cs index 970594a942..82b7372d4f 100644 --- a/sources/engine/Stride.Rendering/Rendering/Images/PostProcessingEffects.cs +++ b/sources/engine/Stride.Rendering/Rendering/Images/PostProcessingEffects.cs @@ -452,7 +452,7 @@ protected override void DrawCore(RenderDrawContext context) colorTransformsGroup.Parameters.Set(LuminanceEffect.LuminanceResult, new LuminanceResult(luminanceEffect.AverageLuminance, luminanceTexture)); if (luminanceTexture != null) - context.CommandList.ResourceBarrierTransition(luminanceTexture, GraphicsResourceState.PixelShaderResource); + context.CommandList.ResourceBarrierTransition(luminanceTexture, BarrierLayout.ShaderResource); } if (BrightFilter.Enabled && (Bloom.Enabled || LightStreak.Enabled || LensFlare.Enabled)) diff --git a/sources/engine/Stride.Rendering/Rendering/Images/RangeConversion/RangeCompressorShader.sdsl b/sources/engine/Stride.Rendering/Rendering/Images/RangeConversion/RangeCompressorShader.sdsl index 0a75198290..f9644ee726 100644 --- a/sources/engine/Stride.Rendering/Rendering/Images/RangeConversion/RangeCompressorShader.sdsl +++ b/sources/engine/Stride.Rendering/Rendering/Images/RangeConversion/RangeCompressorShader.sdsl @@ -20,7 +20,14 @@ namespace Stride.Rendering.Images // http://graphicrants.blogspot.jp/2013/12/tone-mapping.html float3 brianKarisToned = color / (1 + maxComponent / targetRange); - float3 mapped = brianKarisToned; + // Clamp to prevent NaN in RangeDecompressorShader's inverse (division by 1 - maxComponent). + // Result is stored in RGBA16F: values too close to 1.0 may round up to 1.0 in half-float, + // causing division by zero. This is quite brittle — software renderers like SwiftShader + // easily round to 1.0, while GPU hardware needs values close to 1.0 for high HDR precision. + // 2047/2048 is the best compromise: it's the largest half-float value that stays below 1.0. + // TODO: This range compression/decompression approach should be reevaluated to avoid + // relying on half-float precision edge cases. + float3 mapped = min(brianKarisToned, 2047.0f / 2048.0f); // and we don't apply gamma. because of big outlining artefact around [0-1] range objects in front of high [10-80] range emissive objects. // write output for FXAA: diff --git a/sources/engine/Stride.Rendering/Rendering/Images/RangeConversion/RangeDecompressorShader.sdsl b/sources/engine/Stride.Rendering/Rendering/Images/RangeConversion/RangeDecompressorShader.sdsl index 44bc993275..3f8b3de072 100644 --- a/sources/engine/Stride.Rendering/Rendering/Images/RangeConversion/RangeDecompressorShader.sdsl +++ b/sources/engine/Stride.Rendering/Rendering/Images/RangeConversion/RangeDecompressorShader.sdsl @@ -14,7 +14,9 @@ namespace Stride.Rendering.Images // reverse karis tone map: float targetRange = 1.0; float maxComponent = max(max(linearColor.r, linearColor.g), linearColor.b); - float3 reverseKaris = linearColor / (1 - maxComponent / targetRange); + maxComponent = min(maxComponent, 2047.0f / 2048.0f); + float denominator = 1 - maxComponent / targetRange; + float3 reverseKaris = linearColor / denominator; // write output for the rest of the post effects: return float4(reverseKaris, 1.0); diff --git a/sources/engine/Stride.Rendering/Rendering/Images/SphericalHarmonics/SphericalHarmonicsBase.sdsl b/sources/engine/Stride.Rendering/Rendering/Images/SphericalHarmonics/SphericalHarmonicsBase.sdsl index 9f314bb389..a0e6d470f6 100644 --- a/sources/engine/Stride.Rendering/Rendering/Images/SphericalHarmonics/SphericalHarmonicsBase.sdsl +++ b/sources/engine/Stride.Rendering/Rendering/Images/SphericalHarmonics/SphericalHarmonicsBase.sdsl @@ -47,11 +47,11 @@ if(TOrder>2) if(TOrder>3) { - var z3 = pow(z, 3.0); + var z3 = z2 * z; - var x4 = pow(x, 4.0); - var y4 = pow(y, 4.0); - var z4 = pow(z, 4.0); + var x4 = x2 * x2; + var y4 = y2 * y2; + var z4 = z2 * z2; streams.SHBaseValues[ 9] = -sqrt( 70.0/PI64)*y*(3*x2-y2); streams.SHBaseValues[10] = sqrt(105.0/ PI4)*y*x*z; diff --git a/sources/engine/Stride.Rendering/Rendering/RenderSystem.cs b/sources/engine/Stride.Rendering/Rendering/RenderSystem.cs index 8e51f32b03..9c83565ba4 100644 --- a/sources/engine/Stride.Rendering/Rendering/RenderSystem.cs +++ b/sources/engine/Stride.Rendering/Rendering/RenderSystem.cs @@ -382,7 +382,7 @@ public void Draw(RenderDrawContext renderDrawContext, RenderView renderView, Ren else { // Create at most one batch per processor - int batchCount = Math.Min(Environment.ProcessorCount, renderNodeCount); + int batchCount = Math.Min(Dispatcher.MaxDegreeOfParallelism, renderNodeCount); int batchSize = (renderNodeCount + (batchCount - 1)) / batchCount; batchCount = (renderNodeCount + (batchSize - 1)) / batchSize; diff --git a/sources/engine/Stride.Rendering/Rendering/Shadows/LightDirectionalShadowMapRenderer.cs b/sources/engine/Stride.Rendering/Rendering/Shadows/LightDirectionalShadowMapRenderer.cs index 387ff0ff18..2842ece8ef 100644 --- a/sources/engine/Stride.Rendering/Rendering/Shadows/LightDirectionalShadowMapRenderer.cs +++ b/sources/engine/Stride.Rendering/Rendering/Shadows/LightDirectionalShadowMapRenderer.cs @@ -542,6 +542,7 @@ public override void UpdateLightCount(int lightLastCount, int lightCurrentCount) public override void ApplyViewParameters(RenderDrawContext context, ParameterCollection parameters, FastListStruct currentLights) { + shadowMapTexture = null; for (int lightIndex = 0; lightIndex < currentLights.Count; ++lightIndex) { var lightEntry = currentLights[lightIndex]; @@ -576,7 +577,7 @@ public override void ApplyViewParameters(RenderDrawContext context, ParameterCol } } - parameters.Set(shadowMapTextureKey, shadowMapTexture); + parameters.Set(shadowMapTextureKey, shadowMapTexture ?? context.GraphicsDevice.GetSharedDepthTexture()); parameters.Set(shadowMapTextureSizeKey, shadowMapTextureSize); parameters.Set(shadowMapTextureTexelSizeKey, shadowMapTextureTexelSize); parameters.Set(cascadeSplitsKey, cascadeSplits); diff --git a/sources/engine/Stride.Rendering/Rendering/Shadows/LightPointShadowMapRendererCubeMap.cs b/sources/engine/Stride.Rendering/Rendering/Shadows/LightPointShadowMapRendererCubeMap.cs index 81f9d14cdc..ee94eba3fa 100644 --- a/sources/engine/Stride.Rendering/Rendering/Shadows/LightPointShadowMapRendererCubeMap.cs +++ b/sources/engine/Stride.Rendering/Rendering/Shadows/LightPointShadowMapRendererCubeMap.cs @@ -247,6 +247,7 @@ public override void ApplyDrawParameters(RenderDrawContext context, ParameterCol var boundingBox2 = (BoundingBox)boundingBox; bool shadowMapCreated = false; int lightIndex = 0; + shadowMapTexture = null; for (int i = 0; i < currentLights.Count; ++i) { @@ -281,7 +282,7 @@ public override void ApplyDrawParameters(RenderDrawContext context, ParameterCol } } - parameters.Set(shadowMapTextureKey, shadowMapTexture); + parameters.Set(shadowMapTextureKey, shadowMapTexture ?? context.GraphicsDevice.GetSharedDepthTexture()); parameters.Set(shadowMapTextureSizeKey, shadowMapTextureSize); parameters.Set(shadowMapTextureTexelSizeKey, shadowMapTextureTexelSize); diff --git a/sources/engine/Stride.Rendering/Rendering/Shadows/LightPointShadowMapRendererParaboloid.cs b/sources/engine/Stride.Rendering/Rendering/Shadows/LightPointShadowMapRendererParaboloid.cs index 64ced61c1e..19d14967e1 100644 --- a/sources/engine/Stride.Rendering/Rendering/Shadows/LightPointShadowMapRendererParaboloid.cs +++ b/sources/engine/Stride.Rendering/Rendering/Shadows/LightPointShadowMapRendererParaboloid.cs @@ -278,6 +278,7 @@ public override void ApplyDrawParameters(RenderDrawContext context, ParameterCol var boundingBox2 = (BoundingBox)boundingBox; bool shadowMapCreated = false; int lightIndex = 0; + shadowMapTexture = null; for (int i = 0; i < currentLights.Count; ++i) { @@ -307,7 +308,7 @@ public override void ApplyDrawParameters(RenderDrawContext context, ParameterCol } } - parameters.Set(shadowMapTextureKey, shadowMapTexture); + parameters.Set(shadowMapTextureKey, shadowMapTexture ?? context.GraphicsDevice.GetSharedDepthTexture()); parameters.Set(shadowMapTextureSizeKey, shadowMapTextureSize); parameters.Set(shadowMapTextureTexelSizeKey, shadowMapTextureTexelSize); diff --git a/sources/engine/Stride.Rendering/Rendering/Shadows/LightSpotShadowMapRenderer.cs b/sources/engine/Stride.Rendering/Rendering/Shadows/LightSpotShadowMapRenderer.cs index 28164f82c4..c1741d0df1 100644 --- a/sources/engine/Stride.Rendering/Rendering/Shadows/LightSpotShadowMapRenderer.cs +++ b/sources/engine/Stride.Rendering/Rendering/Shadows/LightSpotShadowMapRenderer.cs @@ -215,6 +215,7 @@ public override void ApplyDrawParameters(RenderDrawContext context, ParameterCol var boundingBox2 = (BoundingBox)boundingBox; bool shadowMapCreated = false; int lightIndex = 0; + shadowMapTexture = null; for (int i = 0; i < currentLights.Count; ++i) { @@ -246,7 +247,7 @@ public override void ApplyDrawParameters(RenderDrawContext context, ParameterCol } } - parameters.Set(shadowMapTextureKey, shadowMapTexture); + parameters.Set(shadowMapTextureKey, shadowMapTexture ?? context.GraphicsDevice.GetSharedDepthTexture()); parameters.Set(shadowMapTextureSizeKey, shadowMapTextureSize); parameters.Set(shadowMapTextureTexelSizeKey, shadowMapTextureTexelSize); parameters.Set(worldToShadowCascadeUVsKey, worldToShadowCascadeUV); diff --git a/sources/engine/Stride.Rendering/Rendering/Shadows/ShadowMapAtlasTexture.cs b/sources/engine/Stride.Rendering/Rendering/Shadows/ShadowMapAtlasTexture.cs index 8eec05d4c2..1573cee313 100644 --- a/sources/engine/Stride.Rendering/Rendering/Shadows/ShadowMapAtlasTexture.cs +++ b/sources/engine/Stride.Rendering/Rendering/Shadows/ShadowMapAtlasTexture.cs @@ -59,7 +59,7 @@ public void MarkClearNeeded() public void PrepareAsRenderTarget(CommandList commandList) { // Switch to render target - commandList.ResourceBarrierTransition(Texture, GraphicsResourceState.DepthWrite); + commandList.ResourceBarrierTransition(Texture, BarrierLayout.DepthStencilWrite); if (clearNeeded) { @@ -71,7 +71,7 @@ public void PrepareAsRenderTarget(CommandList commandList) public void PrepareAsShaderResourceView(CommandList commandList) { - commandList.ResourceBarrierTransition(Texture, GraphicsResourceState.PixelShaderResource); + commandList.ResourceBarrierTransition(Texture, BarrierLayout.ShaderResource); } } } diff --git a/sources/engine/Stride.Rendering/Rendering/Shadows/ShadowMapRenderer.cs b/sources/engine/Stride.Rendering/Rendering/Shadows/ShadowMapRenderer.cs index fa61fa39ce..8ee0e8edf8 100644 --- a/sources/engine/Stride.Rendering/Rendering/Shadows/ShadowMapRenderer.cs +++ b/sources/engine/Stride.Rendering/Rendering/Shadows/ShadowMapRenderer.cs @@ -27,6 +27,7 @@ public class ShadowMapRenderer : IShadowMapRenderer private const float ReferenceShadowSize = 1024; private FastListStruct atlases; + private ShadowMapAtlasTexture fallbackAtlas; private readonly List shadowMaps = new List(); @@ -121,9 +122,14 @@ public void Collect(RenderContext context, DictionaryGetDesc(ref constantBufferDesc); if (constantBufferDesc.Type == D3DCBufferType.D3DCTResourceBindInfo) continue; @@ -364,7 +366,7 @@ void UpdateConstantBufferReflection(EffectConstantBufferDescription reflectionCo // // Validates the reflection of a Constant Buffer against the expected description. // - void ValidateConstantBufferReflection(ComPtr constantBufferRaw, + void ValidateConstantBufferReflection(ID3D11ShaderReflectionConstantBuffer* constantBufferRaw, ref ShaderBufferDesc constantBufferRawDesc, EffectConstantBufferDescription constantBuffer, LoggerResult log) @@ -388,18 +390,19 @@ void ValidateConstantBufferReflection(ComPtrGetVariableByIndex(i); SkipInit(out ShaderVariableDesc variableDescription); - variable.GetDesc(ref variableDescription); + variable->GetDesc(ref variableDescription); - // NOTE: To force to call the GetType() of ID3D11ShaderReflectionVariable and not the GetType() of System.Object - var variableType = ToComPtr(variable.Handle->GetType()); + var variableType = variable->GetType(); SkipInit(out ShaderTypeDesc variableTypeDescription); - variableType.GetDesc(ref variableTypeDescription); + variableType->GetDesc(ref variableTypeDescription); var variableName = GetUtf8Span(variableDescription.Name).GetString(); if (variableTypeDescription.Offset != 0) diff --git a/sources/engine/Stride.UI.Tests/Properties/launchSettings.json b/sources/engine/Stride.UI.Tests/Properties/launchSettings.json index 719e25cc64..26edd5bd18 100644 --- a/sources/engine/Stride.UI.Tests/Properties/launchSettings.json +++ b/sources/engine/Stride.UI.Tests/Properties/launchSettings.json @@ -1,13 +1,15 @@ { "profiles": { - "WARP": { + "Software": { "commandName": "Project", - "environmentVariables": { - "STRIDE_GRAPHICS_SOFTWARE_RENDERING": "1" - } + "nativeDebugging": true }, "GPU": { - "commandName": "Project" + "commandName": "Project", + "environmentVariables": { + "STRIDE_TESTS_GPU": "1" + }, + "nativeDebugging": true } } } diff --git a/sources/engine/Stride/Graphics/PixelFormatExtensions.cs b/sources/engine/Stride/Graphics/PixelFormatExtensions.cs index ea7df982ea..c3ced82509 100644 --- a/sources/engine/Stride/Graphics/PixelFormatExtensions.cs +++ b/sources/engine/Stride/Graphics/PixelFormatExtensions.cs @@ -338,6 +338,29 @@ R11G11B10_Float or B8G8R8A8_UNorm or B8G8R8X8_UNorm or B8G8R8A8_Typeless or B8G8R8A8_UNorm_SRgb or B8G8R8X8_Typeless or B8G8R8X8_UNorm_SRgb => true, _ => false, }; + + /// + /// Gets a value indicating if the is an unsigned normalized format. + /// + public bool IsUNorm => format switch + { + R8_UNorm or R8G8_UNorm or R8G8B8A8_UNorm or R8G8B8A8_UNorm_SRgb + or B8G8R8A8_UNorm or B8G8R8A8_UNorm_SRgb or B8G8R8X8_UNorm or B8G8R8X8_UNorm_SRgb + or R16_UNorm or R16G16_UNorm or R16G16B16A16_UNorm + or R10G10B10A2_UNorm => true, + _ => false, + }; + + /// + /// Gets the maximum integer value representable per channel for unsigned normalized formats. + /// For example, 255 for 8-bit UNORM, 65535 for 16-bit UNORM. + /// + public float UNormMaxValue => format switch + { + R16_UNorm or R16G16_UNorm or R16G16B16A16_UNorm => 65535.0f, + R10G10B10A2_UNorm => 1023.0f, + _ => 255.0f, + }; } #region Lookup tables and pre-computed information diff --git a/sources/native/Stride.Native.targets b/sources/native/Stride.Native.targets index 1be6ec3694..79e9afb28a 100644 --- a/sources/native/Stride.Native.targets +++ b/sources/native/Stride.Native.targets @@ -173,23 +173,23 @@ - + - + - + - + - + @@ -272,7 +272,7 @@ - + diff --git a/sources/targets/Stride.UnitTests.Debug.props b/sources/targets/Stride.UnitTests.Debug.props index 8ec0fd5950..a5ba1d73e5 100644 --- a/sources/targets/Stride.UnitTests.Debug.props +++ b/sources/targets/Stride.UnitTests.Debug.props @@ -2,22 +2,5 @@ - - - - - - true - - - - STRIDE_TESTS_CAPTURE_RENDERDOC_ON_ERROR; - $(DefineConstants) - - diff --git a/sources/tools/Stride.Graphics.RenderDocPlugin/RenderDocManager.cs b/sources/tools/Stride.Graphics.RenderDocPlugin/RenderDocManager.cs index 26fa43e1cb..80f6ed5358 100644 --- a/sources/tools/Stride.Graphics.RenderDocPlugin/RenderDocManager.cs +++ b/sources/tools/Stride.Graphics.RenderDocPlugin/RenderDocManager.cs @@ -13,51 +13,63 @@ namespace Stride.Graphics public class RenderDocManager { private const string RenderdocClsid = "{5D6BF029-A6BA-417A-8523-120492B1DCE3}"; - private const string LibraryName = "renderdoc.dll"; + + private static unsafe IntPtr* sharedApiPointers; + // Guard against concurrent captures — RenderDoc currently only supports one active device at a time. + // This can be removed once RenderDoc supports multiple simultaneous device captures. + private static GraphicsDevice activeCaptureDevice; private bool isCaptureStarted; private unsafe IntPtr* apiPointers; - public unsafe bool IsInitialized - { - get { return apiPointers != null; } - } + public unsafe bool IsInitialized => apiPointers != null; // Matching https://github.com/baldurk/renderdoc/blob/master/renderdoc/api/app/renderdoc_app.h public unsafe RenderDocManager() { - var reg = Registry.ClassesRoot.OpenSubKey("CLSID\\" + RenderdocClsid + "\\InprocServer32"); - if (reg == null) + // Only load the RenderDoc library once + if (sharedApiPointers != null) { + apiPointers = sharedApiPointers; return; } - var path = reg.GetValue(null) != null ? reg.GetValue(null).ToString() : null; - if (string.IsNullOrEmpty(path) || !File.Exists(path)) + + // Allow overriding RenderDoc DLL path via environment variable + var path = Environment.GetEnvironmentVariable("STRIDE_RENDERDOC_LIBRARY_PATH"); + + if (string.IsNullOrEmpty(path)) { - return; + var reg = Registry.ClassesRoot.OpenSubKey("CLSID\\" + RenderdocClsid + "\\InprocServer32"); + if (reg == null) + return; + + path = reg.GetValue(null)?.ToString(); } - // Preload the library before using the UnmanagedFunctionPointerAttribute + if (string.IsNullOrEmpty(path) || !File.Exists(path)) + return; + var ptr = LoadLibrary(path); if (ptr == IntPtr.Zero) - { return; - } var getAPIAddress = GetProcAddress(ptr, nameof(RENDERDOC_GetAPI)); if (getAPIAddress == IntPtr.Zero) return; - // Get main entry point to get other function pointers var getAPI = Marshal.GetDelegateForFunctionPointer(getAPIAddress); - // API version 10400 has 25 function pointers - if (!getAPI(RENDERDOC_API_VERSION, ref apiPointers)) + if (!getAPI(RENDERDOC_API_VERSION, ref sharedApiPointers)) return; + + apiPointers = sharedApiPointers; + + // Allow debug/validation output even when RenderDoc is attached + GetMethod(RenderDocAPIFunction.SetCaptureOptionU32)(CaptureOption.DebugOutputMute, 0); } - public unsafe void Initialize(string captureFilePath = null) + public void Initialize(string captureFilePath = null) { var finalLogFilePath = captureFilePath ?? FindAvailablePath(Assembly.GetEntryAssembly().Location); GetMethod(RenderDocAPIFunction.SetCaptureFilePathTemplate)(finalLogFilePath); @@ -71,13 +83,15 @@ public unsafe void Initialize(string captureFilePath = null) public void RemoveHooks() { if (IsInitialized) - { GetMethod(RenderDocAPIFunction.RemoveHooks)(); - } } public void StartFrameCapture(GraphicsDevice graphicsDevice, IntPtr hwndPtr) { + if (activeCaptureDevice is not null) + throw new InvalidOperationException("A RenderDoc capture is already in progress. End or discard the current capture before starting a new one."); + + activeCaptureDevice = graphicsDevice; GetMethod(RenderDocAPIFunction.StartFrameCapture)(GetDevicePointer(graphicsDevice), hwndPtr); isCaptureStarted = true; } @@ -87,8 +101,12 @@ public void EndFrameCapture(GraphicsDevice graphicsDevice, IntPtr hwndPtr) if (!isCaptureStarted) return; + if (graphicsDevice != activeCaptureDevice) + throw new InvalidOperationException("RenderDoc EndFrameCapture called with a different device than StartFrameCapture."); + GetMethod(RenderDocAPIFunction.EndFrameCapture)(GetDevicePointer(graphicsDevice), hwndPtr); isCaptureStarted = false; + activeCaptureDevice = null; } public void DiscardFrameCapture(GraphicsDevice graphicsDevice, IntPtr hwndPtr) @@ -96,8 +114,12 @@ public void DiscardFrameCapture(GraphicsDevice graphicsDevice, IntPtr hwndPtr) if (!isCaptureStarted) return; + if (graphicsDevice != activeCaptureDevice) + throw new InvalidOperationException("RenderDoc DiscardFrameCapture called with a different device than StartFrameCapture."); + GetMethod(RenderDocAPIFunction.DiscardFrameCapture)(GetDevicePointer(graphicsDevice), hwndPtr); isCaptureStarted = false; + activeCaptureDevice = null; } private static unsafe nint GetDevicePointer(GraphicsDevice graphicsDevice) @@ -107,6 +129,9 @@ private static unsafe nint GetDevicePointer(GraphicsDevice graphicsDevice) #if STRIDE_GRAPHICS_API_DIRECT3D11 || STRIDE_GRAPHICS_API_DIRECT3D12 if (graphicsDevice is not null) devicePointer = (nint) GraphicsMarshal.GetNativeDevice(graphicsDevice).Handle; +#elif STRIDE_GRAPHICS_API_VULKAN + // RenderDoc Vulkan capture uses NULL (wildcard) for the device pointer. + // Passing VkInstance.Handle crashes with some ICDs (e.g. SwiftShader). #endif return devicePointer; } @@ -123,96 +148,19 @@ private static string FindAvailablePath(string logFilePath) { var path = filePath; if (i > 0) - { path += i; - } path += ".rdc"; if (!File.Exists(path)) - { return Path.Combine(Path.GetDirectoryName(logFilePath), path); - } } return logFilePath; } private enum KeyButton : uint { - // '0' - '9' matches ASCII values - eRENDERDOC_Key_0 = 0x30, - eRENDERDOC_Key_1 = 0x31, - eRENDERDOC_Key_2 = 0x32, - eRENDERDOC_Key_3 = 0x33, - eRENDERDOC_Key_4 = 0x34, - eRENDERDOC_Key_5 = 0x35, - eRENDERDOC_Key_6 = 0x36, - eRENDERDOC_Key_7 = 0x37, - eRENDERDOC_Key_8 = 0x38, - eRENDERDOC_Key_9 = 0x39, - - // 'A' - 'Z' matches ASCII values - eRENDERDOC_Key_A = 0x41, - eRENDERDOC_Key_B = 0x42, - eRENDERDOC_Key_C = 0x43, - eRENDERDOC_Key_D = 0x44, - eRENDERDOC_Key_E = 0x45, - eRENDERDOC_Key_F = 0x46, - eRENDERDOC_Key_G = 0x47, - eRENDERDOC_Key_H = 0x48, - eRENDERDOC_Key_I = 0x49, - eRENDERDOC_Key_J = 0x4A, - eRENDERDOC_Key_K = 0x4B, - eRENDERDOC_Key_L = 0x4C, - eRENDERDOC_Key_M = 0x4D, - eRENDERDOC_Key_N = 0x4E, - eRENDERDOC_Key_O = 0x4F, - eRENDERDOC_Key_P = 0x50, - eRENDERDOC_Key_Q = 0x51, - eRENDERDOC_Key_R = 0x52, - eRENDERDOC_Key_S = 0x53, - eRENDERDOC_Key_T = 0x54, - eRENDERDOC_Key_U = 0x55, - eRENDERDOC_Key_V = 0x56, - eRENDERDOC_Key_W = 0x57, - eRENDERDOC_Key_X = 0x58, - eRENDERDOC_Key_Y = 0x59, - eRENDERDOC_Key_Z = 0x5A, - - // leave the rest of the ASCII range free - // in case we want to use it later - eRENDERDOC_Key_NonPrintable = 0x100, - - eRENDERDOC_Key_Divide, - eRENDERDOC_Key_Multiply, - eRENDERDOC_Key_Subtract, - eRENDERDOC_Key_Plus, - - eRENDERDOC_Key_F1, - eRENDERDOC_Key_F2, - eRENDERDOC_Key_F3, - eRENDERDOC_Key_F4, - eRENDERDOC_Key_F5, - eRENDERDOC_Key_F6, - eRENDERDOC_Key_F7, - eRENDERDOC_Key_F8, - eRENDERDOC_Key_F9, - eRENDERDOC_Key_F10, - eRENDERDOC_Key_F11, - eRENDERDOC_Key_F12, - - eRENDERDOC_Key_Home, - eRENDERDOC_Key_End, - eRENDERDOC_Key_Insert, - eRENDERDOC_Key_Delete, - eRENDERDOC_Key_PageUp, - eRENDERDOC_Key_PageDn, - - eRENDERDOC_Key_Backspace, - eRENDERDOC_Key_Tab, - eRENDERDOC_Key_PrtScrn, - eRENDERDOC_Key_Pause, - - eRENDERDOC_Key_Max, + eRENDERDOC_Key_F11 = 0x100 + 14, + eRENDERDOC_Key_F12 = 0x100 + 15, }; [Flags] @@ -227,11 +175,28 @@ private enum InAppOverlay : uint eOverlay_None = 0, }; - // API breaking change history: // Version 1 -> 2 - strings changed from wchar_t* to char* (UTF-8) private const int RENDERDOC_API_VERSION = 10400; + private enum CaptureOption : uint + { + AllowVSync = 0, + AllowFullscreen = 1, + APIValidation = 2, + CaptureCallstacks = 3, + CaptureCallstacksOnlyDraws = 4, + DelayForDebugger = 5, + VerifyBufferAccess = 6, + HookIntoChildren = 7, + RefAllResources = 8, + SaveAllInitials = 9, + CaptureAllCmdLists = 10, + DebugOutputMute = 11, + AllowUnsupportedVendorExtensions = 12, + SoftMemoryLimit = 13, + } + private enum RenderDocAPIFunction { GetAPIVersion, @@ -279,6 +244,9 @@ private enum RenderDocAPIFunction [UnmanagedFunctionPointer(CallingConvention.Cdecl, CharSet = CharSet.Ansi)] private unsafe delegate bool RENDERDOC_GetAPI(int version, ref IntPtr* apiPointers); + [UnmanagedFunctionPointer(CallingConvention.Cdecl, CharSet = CharSet.Ansi)] + private delegate bool RENDERDOC_SetCaptureOptionU32(CaptureOption option, uint value); + [UnmanagedFunctionPointer(CallingConvention.Cdecl, CharSet = CharSet.Ansi)] private delegate void RENDERDOC_RemoveHooks(); diff --git a/tests/GPU-TESTING.md b/tests/GPU-TESTING.md new file mode 100644 index 0000000000..75a95180fe --- /dev/null +++ b/tests/GPU-TESTING.md @@ -0,0 +1,83 @@ +# GPU Regression Testing + +Stride uses image comparison tests to validate rendering across graphics APIs. Each test renders a scene, captures a screenshot, and compares it against a reference ("gold") image stored in the `tests/` directory. + +## Graphics APIs and Launch Profiles + +Test projects have two kinds of launch profiles: + +- **GPU profiles** (Direct3D11, Direct3D12, Vulkan) — run on your actual GPU. Results depend on your specific GPU and driver version, so gold images may not match. These are useful for local development and visual inspection, but are not used in CI. +- **Software profiles** (D3D11 WARP, D3D12 WARP, Vulkan SwiftShader) — run on CPU-based software renderers. These produce nearly deterministic output regardless of GPU hardware, making them suitable for CI. Gold images in the repository are generated with these renderers. + +To select a profile, use the `STRIDE_GRAPHICS_API` environment variable or the launch profile in your IDE. + +### Running Tests in Batch (Test Explorer / CLI) + +Tests default to **software rendering** — no configuration needed. Visual Studio Test Explorer and `dotnet test` will use WARP/SwiftShader automatically, matching the gold images in the repository. + +To force tests onto your real GPU instead, use the runsettings file: + +**Visual Studio:** Test > Configure Run Settings > Select Solution Wide runsettings File > `build/GameTests-GPU.runsettings` + +**CLI:** `dotnet test --settings build/GameTests-GPU.runsettings` + +This sets `STRIDE_TESTS_GPU=1`, which skips software rendering. Note that GPU results are hardware-dependent and gold images may not match. + +## Software Renderers + +### Vulkan — SwiftShader + +[SwiftShader](https://github.com/nickersk/SwiftShader) is a CPU-based Vulkan implementation. The `Stride.Dependencies.SwiftShader` NuGet package provides the SwiftShader DLL and ICD JSON for Windows x64. + +Gold images are stored under `tests//Windows.Vulkan/SwiftShader/`. + +SwiftShader is included automatically when building with `StrideGraphicsApi=Vulkan`. The test framework auto-configures the Vulkan loader to use SwiftShader when software rendering is active (the default). + +### Direct3D12 — WARP + +[WARP](https://learn.microsoft.com/en-us/windows/win32/direct3darticles/directx-warp) (Windows Advanced Rasterization Platform) is Microsoft's software rasterizer for Direct3D. It is built into Windows and requires no additional packages. + +Gold images are stored under `tests//Windows.Direct3D11/WARP/`. + +## Generating Gold Images + +Gold images should be generated using the **software renderers** to ensure they are reproducible in CI. Run the tests locally with the software renderer profile and commit the resulting screenshots. + +Important: gold images must be generated with the **same SwiftShader binary** used in CI. The `Stride.Dependencies.SwiftShader` NuGet package is built from source on the CI runner to ensure identical binaries. Using a different SwiftShader build (e.g., from Silk.NET) may produce subtly different images due to compiler flags and CPU-specific floating-point behavior. + +## Dealing with Flaky Tests + +SwiftShader is generally deterministic, but some tests can be flaky due to sub-pixel rasterization differences. Common symptoms: + +- A single edge pixel flickers between two values across runs +- Very thin geometry or text renders slightly differently + +To fix flaky tests: + +1. **Adjust the camera** — nudge the camera position slightly so geometry edges don't land exactly on pixel boundaries. +2. **Avoid sub-pixel edges** — ensure test geometry is large enough that a 1-pixel shift doesn't matter. +3. **Check determinism** — run the test 10+ times locally to confirm it produces identical output every time before committing new gold images. + +## CI Workflow + +The CI workflow (`.github/workflows/test-windows-game.yml`) does the following for Vulkan tests: + +1. Restores NuGet packages with `StrideGraphicsApi=Vulkan` to pull the SwiftShader package +2. Registers the SwiftShader ICD in the Windows registry (the Vulkan loader ignores environment variables under elevated permissions on CI runners) +3. Builds the test projects (the asset compiler also needs SwiftShader for Skybox compilation) +4. Runs the tests, comparing rendered output against the gold images + +## GPU Validation + +Tests automatically assert zero GPU validation errors and warnings after each test run. The Vulkan validation layer (Khronos) and D3D12 debug layer are enabled when running in debug mode. + +If a test triggers a known false positive from the validation layer, you can suppress it with the `[AllowGpuValidationError]` attribute: + +```csharp +[AllowGpuValidationError(GraphicsPlatform.Vulkan, + "substring of the validation error message", + Reason = "Explanation of why this is a false positive")] +public class MyTests : GameTestBase { } +``` + +Prefer fixing the root cause over suppressing errors. diff --git a/tests/Stride.Engine.Tests/Windows.Direct3D11/NVIDIA GeForce RTX 5080/AnimatedModelTests.f1.png b/tests/Stride.Engine.Tests/Windows.Direct3D11/NVIDIA GeForce RTX 5080/AnimatedModelTests.f1.png index c10a440691..6ddebae136 100644 --- a/tests/Stride.Engine.Tests/Windows.Direct3D11/NVIDIA GeForce RTX 5080/AnimatedModelTests.f1.png +++ b/tests/Stride.Engine.Tests/Windows.Direct3D11/NVIDIA GeForce RTX 5080/AnimatedModelTests.f1.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:8b2a35d2198c6ad1ad9540c035314213cce0b5b2215a94d6ef2180979c01fb1e -size 18172 +oid sha256:ce461bd2336e8fe636014e1efdd06973390bbe8e9bdeef1baf554163ee2c2f02 +size 17989 diff --git a/tests/Stride.Engine.Tests/Windows.Direct3D11/NVIDIA GeForce RTX 5080/AnimatedModelTests.f2.png b/tests/Stride.Engine.Tests/Windows.Direct3D11/NVIDIA GeForce RTX 5080/AnimatedModelTests.f2.png index 9f2ec4f6c1..683a39420f 100644 --- a/tests/Stride.Engine.Tests/Windows.Direct3D11/NVIDIA GeForce RTX 5080/AnimatedModelTests.f2.png +++ b/tests/Stride.Engine.Tests/Windows.Direct3D11/NVIDIA GeForce RTX 5080/AnimatedModelTests.f2.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:b181fa4c4034f630ab9ff114bd32e301c2f40ddb67a088c7002826ce87ad9c4f -size 20370 +oid sha256:6278de0d259b4d6738be9a5f0a2caf335f2f6658b824246afd253eca15ce91b5 +size 20316 diff --git a/tests/Stride.Engine.Tests/Windows.Direct3D11/NVIDIA GeForce RTX 5080/AnimatedModelTests.f3.png b/tests/Stride.Engine.Tests/Windows.Direct3D11/NVIDIA GeForce RTX 5080/AnimatedModelTests.f3.png index 5d6ae0083a..8c1301c122 100644 --- a/tests/Stride.Engine.Tests/Windows.Direct3D11/NVIDIA GeForce RTX 5080/AnimatedModelTests.f3.png +++ b/tests/Stride.Engine.Tests/Windows.Direct3D11/NVIDIA GeForce RTX 5080/AnimatedModelTests.f3.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:9b3350f4eeec37fbe31726acb2b725c41210d66bd2e886d452765170acad89cf -size 19961 +oid sha256:fa687c39fc4358dadc7008962c652d9156f87fe68c7172604e518a15251de320 +size 19860 diff --git a/tests/Stride.Engine.Tests/Windows.Direct3D11/NVIDIA GeForce RTX 5080/AnimatedModelTests.f4.png b/tests/Stride.Engine.Tests/Windows.Direct3D11/NVIDIA GeForce RTX 5080/AnimatedModelTests.f4.png index 2aaf25979b..3a61288378 100644 --- a/tests/Stride.Engine.Tests/Windows.Direct3D11/NVIDIA GeForce RTX 5080/AnimatedModelTests.f4.png +++ b/tests/Stride.Engine.Tests/Windows.Direct3D11/NVIDIA GeForce RTX 5080/AnimatedModelTests.f4.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:6bb56258321efd872535c2669ae83aec9587aeaf5352c65d15eeea38217b47a0 -size 35114 +oid sha256:a0ca9cc3df4c7766eb497254f04dccc5b1b13c5ebc068f7fb3618ebed3e35c24 +size 34908 diff --git a/tests/Stride.Engine.Tests/Windows.Direct3D11/NVIDIA GeForce RTX 5080/AnimatedModelTests.f5.png b/tests/Stride.Engine.Tests/Windows.Direct3D11/NVIDIA GeForce RTX 5080/AnimatedModelTests.f5.png index 3143d35fec..0a863dc9dd 100644 --- a/tests/Stride.Engine.Tests/Windows.Direct3D11/NVIDIA GeForce RTX 5080/AnimatedModelTests.f5.png +++ b/tests/Stride.Engine.Tests/Windows.Direct3D11/NVIDIA GeForce RTX 5080/AnimatedModelTests.f5.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:c0065dc4a7e0493bb4decb1acc80cfcf3a27b769d8389e6a712373f29ad75892 -size 24284 +oid sha256:c5e9a5697e9d010e53362bb22376597ae9d8ed9b59e5fc63cd7df034ae657934 +size 24195 diff --git a/tests/Stride.Engine.Tests/Windows.Direct3D11/NVIDIA GeForce RTX 5080/AnimatedModelTests.png b/tests/Stride.Engine.Tests/Windows.Direct3D11/NVIDIA GeForce RTX 5080/AnimatedModelTests.png index 3143d35fec..0a863dc9dd 100644 --- a/tests/Stride.Engine.Tests/Windows.Direct3D11/NVIDIA GeForce RTX 5080/AnimatedModelTests.png +++ b/tests/Stride.Engine.Tests/Windows.Direct3D11/NVIDIA GeForce RTX 5080/AnimatedModelTests.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:c0065dc4a7e0493bb4decb1acc80cfcf3a27b769d8389e6a712373f29ad75892 -size 24284 +oid sha256:c5e9a5697e9d010e53362bb22376597ae9d8ed9b59e5fc63cd7df034ae657934 +size 24195 diff --git a/tests/Stride.Engine.Tests/Windows.Direct3D11/WARP/AnimatedModelTests.f1.png b/tests/Stride.Engine.Tests/Windows.Direct3D11/WARP/AnimatedModelTests.f1.png index a344d79fff..627488a8c9 100644 --- a/tests/Stride.Engine.Tests/Windows.Direct3D11/WARP/AnimatedModelTests.f1.png +++ b/tests/Stride.Engine.Tests/Windows.Direct3D11/WARP/AnimatedModelTests.f1.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:a95de4de876ac514da1ebbc99728469801be341b27f2303871441089ff000287 -size 18257 +oid sha256:dffba0221c18ba672b4cabe8e5a8a5a39f8111026dbd02f3c0837f51ddb20937 +size 18110 diff --git a/tests/Stride.Engine.Tests/Windows.Direct3D11/WARP/AnimatedModelTests.f2.png b/tests/Stride.Engine.Tests/Windows.Direct3D11/WARP/AnimatedModelTests.f2.png index 105922bfc0..0077917d44 100644 --- a/tests/Stride.Engine.Tests/Windows.Direct3D11/WARP/AnimatedModelTests.f2.png +++ b/tests/Stride.Engine.Tests/Windows.Direct3D11/WARP/AnimatedModelTests.f2.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:2b76af03300980ec1f3ddd3a8b0d0d62b6bb63f974f22f34ea50b50dbfa112be -size 20471 +oid sha256:23d855418a321cd4a09ea3eed7a71f94934fa2f44739213c60f084874750d21a +size 20425 diff --git a/tests/Stride.Engine.Tests/Windows.Direct3D11/WARP/AnimatedModelTests.f3.png b/tests/Stride.Engine.Tests/Windows.Direct3D11/WARP/AnimatedModelTests.f3.png index 01bb685523..cc709b1278 100644 --- a/tests/Stride.Engine.Tests/Windows.Direct3D11/WARP/AnimatedModelTests.f3.png +++ b/tests/Stride.Engine.Tests/Windows.Direct3D11/WARP/AnimatedModelTests.f3.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:6fcd28c5c5bf240a201295e74519d1bd7bb82b08386226820192e1f7cbe5261d -size 20048 +oid sha256:adeb524442053738008d4ba27c9a97d565f2e75b4565e02effc33abe9fb93d08 +size 19946 diff --git a/tests/Stride.Engine.Tests/Windows.Direct3D11/WARP/AnimatedModelTests.f4.png b/tests/Stride.Engine.Tests/Windows.Direct3D11/WARP/AnimatedModelTests.f4.png index 3fa2d22cb7..ef5e7d96ae 100644 --- a/tests/Stride.Engine.Tests/Windows.Direct3D11/WARP/AnimatedModelTests.f4.png +++ b/tests/Stride.Engine.Tests/Windows.Direct3D11/WARP/AnimatedModelTests.f4.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:1e984206a5c220f8d4b59782e149819fd45edb4a625fae62819c7cc33e21759e -size 35437 +oid sha256:a00dee41d8a43893b5e96f634a9e8c63289ca3b4ada16064256f4b0b008afa71 +size 35217 diff --git a/tests/Stride.Engine.Tests/Windows.Direct3D11/WARP/AnimatedModelTests.f5.png b/tests/Stride.Engine.Tests/Windows.Direct3D11/WARP/AnimatedModelTests.f5.png index b44d7119d4..7284afcaca 100644 --- a/tests/Stride.Engine.Tests/Windows.Direct3D11/WARP/AnimatedModelTests.f5.png +++ b/tests/Stride.Engine.Tests/Windows.Direct3D11/WARP/AnimatedModelTests.f5.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:41d935a2ba70e2f1bdf550fa6f784cb0bd574249202f20606253980c18a902bc -size 24407 +oid sha256:6052be0a4cbb07fc7cd5c3cee873a670d4cbb45dc8f24344033090814858bed9 +size 24300 diff --git a/tests/Stride.Engine.Tests/Windows.Direct3D11/WARP/AnimatedModelTests.png b/tests/Stride.Engine.Tests/Windows.Direct3D11/WARP/AnimatedModelTests.png index b44d7119d4..7284afcaca 100644 --- a/tests/Stride.Engine.Tests/Windows.Direct3D11/WARP/AnimatedModelTests.png +++ b/tests/Stride.Engine.Tests/Windows.Direct3D11/WARP/AnimatedModelTests.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:41d935a2ba70e2f1bdf550fa6f784cb0bd574249202f20606253980c18a902bc -size 24407 +oid sha256:6052be0a4cbb07fc7cd5c3cee873a670d4cbb45dc8f24344033090814858bed9 +size 24300 diff --git a/tests/Stride.Engine.Tests/Windows.Direct3D12/WARP/SpriteRenderer3DTests.f1.png b/tests/Stride.Engine.Tests/Windows.Direct3D12/WARP/SpriteRenderer3DTests.f1.png new file mode 100644 index 0000000000..d858cb5f91 --- /dev/null +++ b/tests/Stride.Engine.Tests/Windows.Direct3D12/WARP/SpriteRenderer3DTests.f1.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d1b143122e1ec65d646634dd4590d9d0e1af867457486ef099d2839608403822 +size 228465 diff --git a/tests/Stride.Engine.Tests/Windows.Direct3D12/WARP/TesselationTest.png b/tests/Stride.Engine.Tests/Windows.Direct3D12/WARP/TesselationTest.png new file mode 100644 index 0000000000..29df54cd29 --- /dev/null +++ b/tests/Stride.Engine.Tests/Windows.Direct3D12/WARP/TesselationTest.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:6d2528d5d532de4c64fe6cecc7677643ffc9ea28dfa85149c76757f8a123eacb +size 73604 diff --git a/tests/Stride.Engine.Tests/Windows.Vulkan/SwiftShader/AnimatedModelTests.f4.png b/tests/Stride.Engine.Tests/Windows.Vulkan/SwiftShader/AnimatedModelTests.f4.png new file mode 100644 index 0000000000..34f077e00b --- /dev/null +++ b/tests/Stride.Engine.Tests/Windows.Vulkan/SwiftShader/AnimatedModelTests.f4.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a8b536d90686e0ed83d199b49963b4361aabb898900f64bea0e7a6a9b15a1ad4 +size 35297 diff --git a/tests/Stride.Engine.Tests/Windows.Vulkan/SwiftShader/SpriteRenderer2DTests.f1.png b/tests/Stride.Engine.Tests/Windows.Vulkan/SwiftShader/SpriteRenderer2DTests.f1.png new file mode 100644 index 0000000000..8e10f06b17 --- /dev/null +++ b/tests/Stride.Engine.Tests/Windows.Vulkan/SwiftShader/SpriteRenderer2DTests.f1.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:534aa8eef4d5907ffb32eb7474adf51bd17ac48be4033fab301ab585f9b9d082 +size 302020 diff --git a/tests/Stride.Engine.Tests/Windows.Vulkan/SwiftShader/SpriteRenderer3DTests.f1.png b/tests/Stride.Engine.Tests/Windows.Vulkan/SwiftShader/SpriteRenderer3DTests.f1.png new file mode 100644 index 0000000000..c461fc849e --- /dev/null +++ b/tests/Stride.Engine.Tests/Windows.Vulkan/SwiftShader/SpriteRenderer3DTests.f1.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:fc2b79494db4f1aaef5ce2683a3f9d8a9f8bb42b61c4f6a41000aad087a5fd53 +size 227265 diff --git a/tests/Stride.Engine.Tests/Windows.Vulkan/SwiftShader/SpriteRenderer3DTests.png b/tests/Stride.Engine.Tests/Windows.Vulkan/SwiftShader/SpriteRenderer3DTests.png new file mode 100644 index 0000000000..54c271eaee --- /dev/null +++ b/tests/Stride.Engine.Tests/Windows.Vulkan/SwiftShader/SpriteRenderer3DTests.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:de1c74d7a1ed409b5ae4fda491c023e5eba040b7c1a6efdceaeb4aa876437715 +size 266254 diff --git a/tests/Stride.Engine.Tests/Windows.Vulkan/SwiftShader/SpriteTestGame.f1.png b/tests/Stride.Engine.Tests/Windows.Vulkan/SwiftShader/SpriteTestGame.f1.png new file mode 100644 index 0000000000..65ae327023 --- /dev/null +++ b/tests/Stride.Engine.Tests/Windows.Vulkan/SwiftShader/SpriteTestGame.f1.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:ba810222211da07cc7abb803c853fb42f1508ef4e7328bf123aaac97cc737f83 +size 53449 diff --git a/tests/Stride.Engine.Tests/Windows.Vulkan/SwiftShader/SpriteTestGame.f2.png b/tests/Stride.Engine.Tests/Windows.Vulkan/SwiftShader/SpriteTestGame.f2.png new file mode 100644 index 0000000000..34cc5d23bd --- /dev/null +++ b/tests/Stride.Engine.Tests/Windows.Vulkan/SwiftShader/SpriteTestGame.f2.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e225cd861edf140288796623876bc6344daf728054fc050d8276f6ed45e4b811 +size 53349 diff --git a/tests/Stride.Engine.Tests/Windows.Vulkan/SwiftShader/SpriteTestGame.png b/tests/Stride.Engine.Tests/Windows.Vulkan/SwiftShader/SpriteTestGame.png new file mode 100644 index 0000000000..7bb03efcf9 --- /dev/null +++ b/tests/Stride.Engine.Tests/Windows.Vulkan/SwiftShader/SpriteTestGame.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:2430e3a6f7d10f24bebb6266fd07d2fe01951b15cc0f87f1c7867e917374d447 +size 60156 diff --git a/tests/Stride.Graphics.Tests/Windows.Vulkan/SwiftShader/FixedAspectRatioTests.png b/tests/Stride.Graphics.Tests/Windows.Vulkan/SwiftShader/FixedAspectRatioTests.png new file mode 100644 index 0000000000..dd339a103b --- /dev/null +++ b/tests/Stride.Graphics.Tests/Windows.Vulkan/SwiftShader/FixedAspectRatioTests.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:81afdc6d2e19cc8ad17add402124ce612ff0d9fb600517596d19b7df493645be +size 30254 diff --git a/tests/Stride.Graphics.Tests/Windows.Vulkan/SwiftShader/LightingTests.SceneDirectionalLight.png b/tests/Stride.Graphics.Tests/Windows.Vulkan/SwiftShader/LightingTests.SceneDirectionalLight.png new file mode 100644 index 0000000000..5ed8fd84b9 --- /dev/null +++ b/tests/Stride.Graphics.Tests/Windows.Vulkan/SwiftShader/LightingTests.SceneDirectionalLight.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:85387ffe2d91f6e714c10cfc779c588ad8c3cddaa1c3531a8c6c74ab9874792d +size 233456 diff --git a/tests/Stride.Graphics.Tests/Windows.Vulkan/SwiftShader/LightingTests.SceneDirectionalLightShadowFourCascades.png b/tests/Stride.Graphics.Tests/Windows.Vulkan/SwiftShader/LightingTests.SceneDirectionalLightShadowFourCascades.png new file mode 100644 index 0000000000..48b94870f2 --- /dev/null +++ b/tests/Stride.Graphics.Tests/Windows.Vulkan/SwiftShader/LightingTests.SceneDirectionalLightShadowFourCascades.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:83a385b857adab99f313c31aa7dd6420bc819cca836902fd43af64f042cc473d +size 236843 diff --git a/tests/Stride.Graphics.Tests/Windows.Vulkan/SwiftShader/LightingTests.SceneDirectionalLightShadowOneCascade.png b/tests/Stride.Graphics.Tests/Windows.Vulkan/SwiftShader/LightingTests.SceneDirectionalLightShadowOneCascade.png new file mode 100644 index 0000000000..3642fd19a0 --- /dev/null +++ b/tests/Stride.Graphics.Tests/Windows.Vulkan/SwiftShader/LightingTests.SceneDirectionalLightShadowOneCascade.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:80a093b3f8e5abcab0560663e4d3ab00d30737919b9ed83f3db7786a0e1afccb +size 236966 diff --git a/tests/Stride.Graphics.Tests/Windows.Vulkan/SwiftShader/LightingTests.SceneDirectionalLightShadowOneCascadePCF.png b/tests/Stride.Graphics.Tests/Windows.Vulkan/SwiftShader/LightingTests.SceneDirectionalLightShadowOneCascadePCF.png new file mode 100644 index 0000000000..d213bb997d --- /dev/null +++ b/tests/Stride.Graphics.Tests/Windows.Vulkan/SwiftShader/LightingTests.SceneDirectionalLightShadowOneCascadePCF.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e1827677e11e97852bf3ad50af2684dd4d89cb3a473a75af2e6d7a9ecf3a4d92 +size 242315 diff --git a/tests/Stride.Graphics.Tests/Windows.Vulkan/SwiftShader/LightingTests.SceneDirectionalLightShadowOneFourCascade.png b/tests/Stride.Graphics.Tests/Windows.Vulkan/SwiftShader/LightingTests.SceneDirectionalLightShadowOneFourCascade.png new file mode 100644 index 0000000000..55082f434e --- /dev/null +++ b/tests/Stride.Graphics.Tests/Windows.Vulkan/SwiftShader/LightingTests.SceneDirectionalLightShadowOneFourCascade.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:7afba99369844711e99d9ef26d1ab35eaabf087156d6e618f3029f4a8e58d5ff +size 240944 diff --git a/tests/Stride.Graphics.Tests/Windows.Vulkan/SwiftShader/LightingTests.ScenePointLight.png b/tests/Stride.Graphics.Tests/Windows.Vulkan/SwiftShader/LightingTests.ScenePointLight.png new file mode 100644 index 0000000000..5ac5bfdaeb --- /dev/null +++ b/tests/Stride.Graphics.Tests/Windows.Vulkan/SwiftShader/LightingTests.ScenePointLight.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:5b7353d65f32104deb87c0b395e8d09dc2cd7be00875e0ff6861d78a1dd94b90 +size 250613 diff --git a/tests/Stride.Graphics.Tests/Windows.Vulkan/SwiftShader/LightingTests.ScenePointLightShadowCubeMap.png b/tests/Stride.Graphics.Tests/Windows.Vulkan/SwiftShader/LightingTests.ScenePointLightShadowCubeMap.png new file mode 100644 index 0000000000..968d296131 --- /dev/null +++ b/tests/Stride.Graphics.Tests/Windows.Vulkan/SwiftShader/LightingTests.ScenePointLightShadowCubeMap.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:acaadf827abab5e360b32b10448c3ad7ac1d1595bc4a2145a13a5e11544f0dae +size 228495 diff --git a/tests/Stride.Graphics.Tests/Windows.Vulkan/SwiftShader/LightingTests.ScenePointLightShadowParaboloid.png b/tests/Stride.Graphics.Tests/Windows.Vulkan/SwiftShader/LightingTests.ScenePointLightShadowParaboloid.png new file mode 100644 index 0000000000..6a3a51eaee --- /dev/null +++ b/tests/Stride.Graphics.Tests/Windows.Vulkan/SwiftShader/LightingTests.ScenePointLightShadowParaboloid.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:fcd38b06d0c58988b43fe7e52c4f798b0e00433c756bc90ffae728ceedef17a6 +size 219943 diff --git a/tests/Stride.Graphics.Tests/Windows.Vulkan/SwiftShader/LightingTests.SceneTwoDirectionalLightShadowOneCascade.png b/tests/Stride.Graphics.Tests/Windows.Vulkan/SwiftShader/LightingTests.SceneTwoDirectionalLightShadowOneCascade.png new file mode 100644 index 0000000000..5710b18230 --- /dev/null +++ b/tests/Stride.Graphics.Tests/Windows.Vulkan/SwiftShader/LightingTests.SceneTwoDirectionalLightShadowOneCascade.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c407a65649ea3e3de48e69da090e8bfcdf5941baea9e1ad304d8b592265f2a8c +size 249943 diff --git a/tests/Stride.Graphics.Tests/Windows.Vulkan/SwiftShader/MaterialTests.MaterialLayerAAA.png b/tests/Stride.Graphics.Tests/Windows.Vulkan/SwiftShader/MaterialTests.MaterialLayerAAA.png new file mode 100644 index 0000000000..b33cc11fc9 --- /dev/null +++ b/tests/Stride.Graphics.Tests/Windows.Vulkan/SwiftShader/MaterialTests.MaterialLayerAAA.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:04ff608823f3be00562fc2bcd2a724b007d9d5b0bf93cc6faba4e8001e8ee36a +size 143461 diff --git a/tests/Stride.Graphics.Tests/Windows.Vulkan/SwiftShader/MaterialTests.MaterialLayerABA.png b/tests/Stride.Graphics.Tests/Windows.Vulkan/SwiftShader/MaterialTests.MaterialLayerABA.png new file mode 100644 index 0000000000..e6786b7d72 --- /dev/null +++ b/tests/Stride.Graphics.Tests/Windows.Vulkan/SwiftShader/MaterialTests.MaterialLayerABA.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:06a66ec8b2650d676efcc1e1f8e1f6a2a338ba014351d4d483b8aa820cc1b1db +size 149926 diff --git a/tests/Stride.Graphics.Tests/Windows.Vulkan/SwiftShader/MaterialTests.MaterialLayerABC.png b/tests/Stride.Graphics.Tests/Windows.Vulkan/SwiftShader/MaterialTests.MaterialLayerABC.png new file mode 100644 index 0000000000..965b0a847a --- /dev/null +++ b/tests/Stride.Graphics.Tests/Windows.Vulkan/SwiftShader/MaterialTests.MaterialLayerABC.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:3e62e0b2fca10ca0246682b8dda2ec9f0d80bdf2c062e64dcbf8b93eb676c13c +size 154894 diff --git a/tests/Stride.Graphics.Tests/Windows.Vulkan/SwiftShader/MaterialTests.MaterialLayerBBB.png b/tests/Stride.Graphics.Tests/Windows.Vulkan/SwiftShader/MaterialTests.MaterialLayerBBB.png new file mode 100644 index 0000000000..e008a1b747 --- /dev/null +++ b/tests/Stride.Graphics.Tests/Windows.Vulkan/SwiftShader/MaterialTests.MaterialLayerBBB.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:6099108df03c076a6b4174ee3b2e536eb92e0a9d9605bd284515f2511552ce00 +size 146957 diff --git a/tests/Stride.Graphics.Tests/Windows.Vulkan/SwiftShader/MaterialTests.MaterialMetalness.png b/tests/Stride.Graphics.Tests/Windows.Vulkan/SwiftShader/MaterialTests.MaterialMetalness.png new file mode 100644 index 0000000000..c7e4e9efda --- /dev/null +++ b/tests/Stride.Graphics.Tests/Windows.Vulkan/SwiftShader/MaterialTests.MaterialMetalness.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:7207f108d897c822852242d77e426cde42805c204493546a2e8f96f36f248c41 +size 230885 diff --git a/tests/Stride.Graphics.Tests/Windows.Vulkan/SwiftShader/MaterialTests.MaterialSpecular.png b/tests/Stride.Graphics.Tests/Windows.Vulkan/SwiftShader/MaterialTests.MaterialSpecular.png new file mode 100644 index 0000000000..bc40ba3e55 --- /dev/null +++ b/tests/Stride.Graphics.Tests/Windows.Vulkan/SwiftShader/MaterialTests.MaterialSpecular.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f2293210a8befb965d6f39b5de339c835662e742cdc1f30988e49aa0b63901e5 +size 193238 diff --git a/tests/Stride.Graphics.Tests/Windows.Vulkan/SwiftShader/TestBitmapSpriteFont.png b/tests/Stride.Graphics.Tests/Windows.Vulkan/SwiftShader/TestBitmapSpriteFont.png new file mode 100644 index 0000000000..d5dc858b0b --- /dev/null +++ b/tests/Stride.Graphics.Tests/Windows.Vulkan/SwiftShader/TestBitmapSpriteFont.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:968ce5616e02aa3fa09ebf051d20840e7143060930e04264fc3c884b0dc3d43a +size 10647 diff --git a/tests/Stride.Graphics.Tests/Windows.Vulkan/SwiftShader/TestCustomEffect.png b/tests/Stride.Graphics.Tests/Windows.Vulkan/SwiftShader/TestCustomEffect.png new file mode 100644 index 0000000000..491c6a3eba --- /dev/null +++ b/tests/Stride.Graphics.Tests/Windows.Vulkan/SwiftShader/TestCustomEffect.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:ff4d1ee266ba6e55bc3483c87fe9fca68f7424b636d6869cf0d073dbc03ca570 +size 13184 diff --git a/tests/Stride.Graphics.Tests/Windows.Vulkan/SwiftShader/TestDrawQuad.png b/tests/Stride.Graphics.Tests/Windows.Vulkan/SwiftShader/TestDrawQuad.png new file mode 100644 index 0000000000..9ce9fe8754 --- /dev/null +++ b/tests/Stride.Graphics.Tests/Windows.Vulkan/SwiftShader/TestDrawQuad.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:7d46ea58f3312c0cd3a2c24430831aaa0556ff736893fc5ca50ccd5c79881297 +size 27654 diff --git a/tests/Stride.Graphics.Tests/Windows.Vulkan/SwiftShader/TestDynamicSpriteFont.f1.png b/tests/Stride.Graphics.Tests/Windows.Vulkan/SwiftShader/TestDynamicSpriteFont.f1.png new file mode 100644 index 0000000000..78a9b358e7 --- /dev/null +++ b/tests/Stride.Graphics.Tests/Windows.Vulkan/SwiftShader/TestDynamicSpriteFont.f1.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:94e0c487f130cc34d2d8aed358fffc9211102342d2153257c111584dfaf08cad +size 53815 diff --git a/tests/Stride.Graphics.Tests/Windows.Vulkan/SwiftShader/TestDynamicSpriteFont.f2.png b/tests/Stride.Graphics.Tests/Windows.Vulkan/SwiftShader/TestDynamicSpriteFont.f2.png new file mode 100644 index 0000000000..12aea1d3c5 --- /dev/null +++ b/tests/Stride.Graphics.Tests/Windows.Vulkan/SwiftShader/TestDynamicSpriteFont.f2.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:4b1e9e36162d6b3aa7584855f2d33d62faa6c71ff5bec4e62542ad6835d627be +size 59389 diff --git a/tests/Stride.Graphics.Tests/Windows.Vulkan/SwiftShader/TestDynamicSpriteFont.png b/tests/Stride.Graphics.Tests/Windows.Vulkan/SwiftShader/TestDynamicSpriteFont.png new file mode 100644 index 0000000000..dc930a4925 --- /dev/null +++ b/tests/Stride.Graphics.Tests/Windows.Vulkan/SwiftShader/TestDynamicSpriteFont.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a5f9e908cc9be82b2c606340f02f426be7858cf6600dc15f6ffd1ec15e284efd +size 53014 diff --git a/tests/Stride.Graphics.Tests/Windows.Vulkan/SwiftShader/TestDynamicSpriteFontJapanese.png b/tests/Stride.Graphics.Tests/Windows.Vulkan/SwiftShader/TestDynamicSpriteFontJapanese.png new file mode 100644 index 0000000000..722e3c1c40 --- /dev/null +++ b/tests/Stride.Graphics.Tests/Windows.Vulkan/SwiftShader/TestDynamicSpriteFontJapanese.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d530d07ec8a357166e802c5b9bb2e2e8de53bdff64f3f4226c964862f83f6482 +size 102364 diff --git a/tests/Stride.Graphics.Tests/Windows.Vulkan/SwiftShader/TestDynamicSpriteFontVarious.f1.png b/tests/Stride.Graphics.Tests/Windows.Vulkan/SwiftShader/TestDynamicSpriteFontVarious.f1.png new file mode 100644 index 0000000000..72af809a4f --- /dev/null +++ b/tests/Stride.Graphics.Tests/Windows.Vulkan/SwiftShader/TestDynamicSpriteFontVarious.f1.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d9f5109083b3a734bf02c9eedda72141be6325f27f89c527e77a20658c44a0be +size 18206 diff --git a/tests/Stride.Graphics.Tests/Windows.Vulkan/SwiftShader/TestDynamicSpriteFontVarious.f2.png b/tests/Stride.Graphics.Tests/Windows.Vulkan/SwiftShader/TestDynamicSpriteFontVarious.f2.png new file mode 100644 index 0000000000..407f81833f --- /dev/null +++ b/tests/Stride.Graphics.Tests/Windows.Vulkan/SwiftShader/TestDynamicSpriteFontVarious.f2.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c8bfc6e625df7d5a895a7825cba7d5ccabd8f13fd1127fc146e53234e195392f +size 18661 diff --git a/tests/Stride.Graphics.Tests/Windows.Vulkan/SwiftShader/TestDynamicSpriteFontVarious.png b/tests/Stride.Graphics.Tests/Windows.Vulkan/SwiftShader/TestDynamicSpriteFontVarious.png new file mode 100644 index 0000000000..ce431e9f9e --- /dev/null +++ b/tests/Stride.Graphics.Tests/Windows.Vulkan/SwiftShader/TestDynamicSpriteFontVarious.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:db5385858085de4ace3fbcbcf997da288ceb9b9dbc72d9735be3510332a3e42a +size 10239 diff --git a/tests/Stride.Graphics.Tests/Windows.Vulkan/SwiftShader/TestGeometricPrimitives.f1.png b/tests/Stride.Graphics.Tests/Windows.Vulkan/SwiftShader/TestGeometricPrimitives.f1.png new file mode 100644 index 0000000000..d8536f8b37 --- /dev/null +++ b/tests/Stride.Graphics.Tests/Windows.Vulkan/SwiftShader/TestGeometricPrimitives.f1.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e7ac8f22a9c7d1de8f7e9c919eb5a5cccfa4f7dd1e2d549b75b9a2de22c6929c +size 85172 diff --git a/tests/Stride.Graphics.Tests/Windows.Vulkan/SwiftShader/TestGeometricPrimitives.f2.png b/tests/Stride.Graphics.Tests/Windows.Vulkan/SwiftShader/TestGeometricPrimitives.f2.png new file mode 100644 index 0000000000..2fb16b0519 --- /dev/null +++ b/tests/Stride.Graphics.Tests/Windows.Vulkan/SwiftShader/TestGeometricPrimitives.f2.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:2fd028ce437e3da0780ffe330632adccac5a365d62998b572602755e93cc125c +size 82306 diff --git a/tests/Stride.Graphics.Tests/Windows.Vulkan/SwiftShader/TestGeometricPrimitives.png b/tests/Stride.Graphics.Tests/Windows.Vulkan/SwiftShader/TestGeometricPrimitives.png new file mode 100644 index 0000000000..52de8bb238 --- /dev/null +++ b/tests/Stride.Graphics.Tests/Windows.Vulkan/SwiftShader/TestGeometricPrimitives.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c45922bf3976c87ae5e17a5cc2c12318dd292374f21ef735e0c3acddeb0626fa +size 88288 diff --git a/tests/Stride.Graphics.Tests/Windows.Vulkan/SwiftShader/TestImageEffect.png b/tests/Stride.Graphics.Tests/Windows.Vulkan/SwiftShader/TestImageEffect.png new file mode 100644 index 0000000000..a586bc3ab0 --- /dev/null +++ b/tests/Stride.Graphics.Tests/Windows.Vulkan/SwiftShader/TestImageEffect.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:5d01c93567617b320087d0221f400a3f1cd7e26f7315d4d690808ea24ca0a247 +size 469071 diff --git a/tests/Stride.Graphics.Tests/Windows.Vulkan/SwiftShader/TestImageLoad.png b/tests/Stride.Graphics.Tests/Windows.Vulkan/SwiftShader/TestImageLoad.png new file mode 100644 index 0000000000..8f7e66a654 --- /dev/null +++ b/tests/Stride.Graphics.Tests/Windows.Vulkan/SwiftShader/TestImageLoad.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:6dc6228ef4fcfdef46af3a8b9e41936b0ab1b9f7f11694fa007659d8eda69009 +size 140602 diff --git a/tests/Stride.Graphics.Tests/Windows.Vulkan/SwiftShader/TestLightShafts.png b/tests/Stride.Graphics.Tests/Windows.Vulkan/SwiftShader/TestLightShafts.png new file mode 100644 index 0000000000..3a3167585e --- /dev/null +++ b/tests/Stride.Graphics.Tests/Windows.Vulkan/SwiftShader/TestLightShafts.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:7639e6ca4ce4ff927e2241117182145b4f8c6da91a9028ea83e649d580e39795 +size 120223 diff --git a/tests/Stride.Graphics.Tests/Windows.Vulkan/SwiftShader/TestPrecompiledSpriteFont.f1.png b/tests/Stride.Graphics.Tests/Windows.Vulkan/SwiftShader/TestPrecompiledSpriteFont.f1.png new file mode 100644 index 0000000000..2e94f15de9 --- /dev/null +++ b/tests/Stride.Graphics.Tests/Windows.Vulkan/SwiftShader/TestPrecompiledSpriteFont.f1.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:994cd11206f74a5b00a2c04bb6f4988aba173ee108cbe79d8a33e95111dc947b +size 25546 diff --git a/tests/Stride.Graphics.Tests/Windows.Vulkan/SwiftShader/TestPrecompiledSpriteFont.f2.png b/tests/Stride.Graphics.Tests/Windows.Vulkan/SwiftShader/TestPrecompiledSpriteFont.f2.png new file mode 100644 index 0000000000..e989e20b27 --- /dev/null +++ b/tests/Stride.Graphics.Tests/Windows.Vulkan/SwiftShader/TestPrecompiledSpriteFont.f2.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:651360d30281afea675f5630e374899d17d3da14053cd8029fe7ca3049a7839f +size 31025 diff --git a/tests/Stride.Graphics.Tests/Windows.Vulkan/SwiftShader/TestPrecompiledSpriteFont.png b/tests/Stride.Graphics.Tests/Windows.Vulkan/SwiftShader/TestPrecompiledSpriteFont.png new file mode 100644 index 0000000000..7d39122522 --- /dev/null +++ b/tests/Stride.Graphics.Tests/Windows.Vulkan/SwiftShader/TestPrecompiledSpriteFont.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:7f559c697491bd05e03f61bfbcd1104dbd84c25ebe7a1f89d4e6bbdce400a26f +size 24942 diff --git a/tests/Stride.Graphics.Tests/Windows.Vulkan/SwiftShader/TestRenderToTexture.png b/tests/Stride.Graphics.Tests/Windows.Vulkan/SwiftShader/TestRenderToTexture.png new file mode 100644 index 0000000000..a8ef57208b --- /dev/null +++ b/tests/Stride.Graphics.Tests/Windows.Vulkan/SwiftShader/TestRenderToTexture.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:5ce2b4ec89468a8d64c92bf254e93016db63a4777253e2989e7ded41db13e440 +size 56695 diff --git a/tests/Stride.Graphics.Tests/Windows.Vulkan/SwiftShader/TestSpriteBatch.f1.png b/tests/Stride.Graphics.Tests/Windows.Vulkan/SwiftShader/TestSpriteBatch.f1.png new file mode 100644 index 0000000000..4f56882903 --- /dev/null +++ b/tests/Stride.Graphics.Tests/Windows.Vulkan/SwiftShader/TestSpriteBatch.f1.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:cdc6387eb49c9b64c2706bfab8ce4682f7e40f7e220f19d1fc83e88948c5b842 +size 129055 diff --git a/tests/Stride.Graphics.Tests/Windows.Vulkan/SwiftShader/TestSpriteBatch.png b/tests/Stride.Graphics.Tests/Windows.Vulkan/SwiftShader/TestSpriteBatch.png new file mode 100644 index 0000000000..bce796ef83 --- /dev/null +++ b/tests/Stride.Graphics.Tests/Windows.Vulkan/SwiftShader/TestSpriteBatch.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:1c38ff3685f3b609e77d2f0a0d7f9524df2bb9229f42210567c687caa6430a92 +size 130802 diff --git a/tests/Stride.Graphics.Tests/Windows.Vulkan/SwiftShader/TestSpriteBatch3D.f1.png b/tests/Stride.Graphics.Tests/Windows.Vulkan/SwiftShader/TestSpriteBatch3D.f1.png new file mode 100644 index 0000000000..f37e87404d --- /dev/null +++ b/tests/Stride.Graphics.Tests/Windows.Vulkan/SwiftShader/TestSpriteBatch3D.f1.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:858cf04a6536ece5919c28b7eac62b4fb4393d381f709122f0aca2159312631f +size 38284 diff --git a/tests/Stride.Graphics.Tests/Windows.Vulkan/SwiftShader/TestSpriteBatch3D.png b/tests/Stride.Graphics.Tests/Windows.Vulkan/SwiftShader/TestSpriteBatch3D.png new file mode 100644 index 0000000000..c0ba9f64b8 --- /dev/null +++ b/tests/Stride.Graphics.Tests/Windows.Vulkan/SwiftShader/TestSpriteBatch3D.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:6ee03afd20d1085488a7b7cd02e8066086d6cb5c95d522cc1a5d00a79ecc5bd3 +size 34138 diff --git a/tests/Stride.Graphics.Tests/Windows.Vulkan/SwiftShader/TestSpriteBatchResolution.f1.png b/tests/Stride.Graphics.Tests/Windows.Vulkan/SwiftShader/TestSpriteBatchResolution.f1.png new file mode 100644 index 0000000000..6e89d912e5 --- /dev/null +++ b/tests/Stride.Graphics.Tests/Windows.Vulkan/SwiftShader/TestSpriteBatchResolution.f1.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f36e6c11e8bc701457b7f305bafee84fb03f1165d40c6921d7b509c3857978cc +size 20370 diff --git a/tests/Stride.Graphics.Tests/Windows.Vulkan/SwiftShader/TestSpriteBatchResolution.f2.png b/tests/Stride.Graphics.Tests/Windows.Vulkan/SwiftShader/TestSpriteBatchResolution.f2.png new file mode 100644 index 0000000000..9629eeea6a --- /dev/null +++ b/tests/Stride.Graphics.Tests/Windows.Vulkan/SwiftShader/TestSpriteBatchResolution.f2.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:fdb8985d8ef314e4114c526fd2b69fda4593675f8fca9b1491b5e2343e6579a3 +size 80489 diff --git a/tests/Stride.Graphics.Tests/Windows.Vulkan/SwiftShader/TestSpriteBatchResolution.f3.png b/tests/Stride.Graphics.Tests/Windows.Vulkan/SwiftShader/TestSpriteBatchResolution.f3.png new file mode 100644 index 0000000000..1387a3f6b7 --- /dev/null +++ b/tests/Stride.Graphics.Tests/Windows.Vulkan/SwiftShader/TestSpriteBatchResolution.f3.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:796241e1b8996a2e0f11a7eb79ca83f1737e7be41d723af3fccc79e1e30f47a3 +size 20668 diff --git a/tests/Stride.Graphics.Tests/Windows.Vulkan/SwiftShader/TestSpriteBatchResolution.png b/tests/Stride.Graphics.Tests/Windows.Vulkan/SwiftShader/TestSpriteBatchResolution.png new file mode 100644 index 0000000000..e0bf765336 --- /dev/null +++ b/tests/Stride.Graphics.Tests/Windows.Vulkan/SwiftShader/TestSpriteBatchResolution.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:aa385c557bd217995b219f276ac7c2c85613a3bf19b2cd027dc102377410376d +size 34919 diff --git a/tests/Stride.Graphics.Tests/Windows.Vulkan/SwiftShader/TestSpriteBatchToTexture.png b/tests/Stride.Graphics.Tests/Windows.Vulkan/SwiftShader/TestSpriteBatchToTexture.png new file mode 100644 index 0000000000..4e19bbfab9 --- /dev/null +++ b/tests/Stride.Graphics.Tests/Windows.Vulkan/SwiftShader/TestSpriteBatchToTexture.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:0c05dbfb37c282bcd529bc7c5657680432c83ddf5b2dd06d94bd03da40e78c40 +size 53537 diff --git a/tests/Stride.Graphics.Tests/Windows.Vulkan/SwiftShader/TestSpriteFontAlignment.png b/tests/Stride.Graphics.Tests/Windows.Vulkan/SwiftShader/TestSpriteFontAlignment.png new file mode 100644 index 0000000000..5b985ce3f1 --- /dev/null +++ b/tests/Stride.Graphics.Tests/Windows.Vulkan/SwiftShader/TestSpriteFontAlignment.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:1059dea4a22714013f2535e89707c4c50712fcc5c9b7ec128194ed887b488579 +size 22926 diff --git a/tests/Stride.Graphics.Tests/Windows.Vulkan/SwiftShader/TestStaticSpriteFont.f1.png b/tests/Stride.Graphics.Tests/Windows.Vulkan/SwiftShader/TestStaticSpriteFont.f1.png new file mode 100644 index 0000000000..b4e3d1eaf8 --- /dev/null +++ b/tests/Stride.Graphics.Tests/Windows.Vulkan/SwiftShader/TestStaticSpriteFont.f1.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:dc00b23d4c9b474d4d116940528380a29b86dd3753dc771fd532cd9dfd7d1c94 +size 56607 diff --git a/tests/Stride.Graphics.Tests/Windows.Vulkan/SwiftShader/TestStaticSpriteFont.f2.png b/tests/Stride.Graphics.Tests/Windows.Vulkan/SwiftShader/TestStaticSpriteFont.f2.png new file mode 100644 index 0000000000..00e9f43f92 --- /dev/null +++ b/tests/Stride.Graphics.Tests/Windows.Vulkan/SwiftShader/TestStaticSpriteFont.f2.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:046813fb49a1e89a6c3fc53f6d184e4419a4773f02d62ee6b9764b565899245b +size 62625 diff --git a/tests/Stride.Graphics.Tests/Windows.Vulkan/SwiftShader/TestStaticSpriteFont.png b/tests/Stride.Graphics.Tests/Windows.Vulkan/SwiftShader/TestStaticSpriteFont.png new file mode 100644 index 0000000000..0c13c55af8 --- /dev/null +++ b/tests/Stride.Graphics.Tests/Windows.Vulkan/SwiftShader/TestStaticSpriteFont.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:5caa19a9d4d989dc682e03cf159f6ee02536380ae2422c840f6346ca4af4c74c +size 55934 diff --git a/tests/Stride.Graphics.Tests/Windows.Vulkan/SwiftShader/TestTexture.TestLoadDraw(Dds).png b/tests/Stride.Graphics.Tests/Windows.Vulkan/SwiftShader/TestTexture.TestLoadDraw(Dds).png new file mode 100644 index 0000000000..1525c56bd4 --- /dev/null +++ b/tests/Stride.Graphics.Tests/Windows.Vulkan/SwiftShader/TestTexture.TestLoadDraw(Dds).png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e9c3e92d54674e58ecb07107077664555ac351f470e441e14ad63fed33c3fe98 +size 76548 diff --git a/tests/Stride.Graphics.Tests/Windows.Vulkan/SwiftShader/TestTexture.TestLoadDraw(Png).png b/tests/Stride.Graphics.Tests/Windows.Vulkan/SwiftShader/TestTexture.TestLoadDraw(Png).png new file mode 100644 index 0000000000..8e2f85b0ef --- /dev/null +++ b/tests/Stride.Graphics.Tests/Windows.Vulkan/SwiftShader/TestTexture.TestLoadDraw(Png).png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:ac5e46614aa9b1b1a7719e408b45ce1dfca045db3739c69547bb122b3d2f7441 +size 107711 diff --git a/tests/Stride.Graphics.Tests/Windows.Vulkan/SwiftShader/TestTexture.TestLoadDraw(Stride).png b/tests/Stride.Graphics.Tests/Windows.Vulkan/SwiftShader/TestTexture.TestLoadDraw(Stride).png new file mode 100644 index 0000000000..1525c56bd4 --- /dev/null +++ b/tests/Stride.Graphics.Tests/Windows.Vulkan/SwiftShader/TestTexture.TestLoadDraw(Stride).png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e9c3e92d54674e58ecb07107077664555ac351f470e441e14ad63fed33c3fe98 +size 76548 diff --git a/tests/Stride.Graphics.Tests/Windows.Vulkan/SwiftShader/TestTextureSampling.png b/tests/Stride.Graphics.Tests/Windows.Vulkan/SwiftShader/TestTextureSampling.png new file mode 100644 index 0000000000..cf72341023 --- /dev/null +++ b/tests/Stride.Graphics.Tests/Windows.Vulkan/SwiftShader/TestTextureSampling.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:93e269824097bec12802f1f7dc71edb1ace156facb94723486aa97e76330b96d +size 41620 diff --git a/tests/Stride.UI.Tests.Regression/Windows.Vulkan/SwiftShader/BillboardModeTests.png b/tests/Stride.UI.Tests.Regression/Windows.Vulkan/SwiftShader/BillboardModeTests.png new file mode 100644 index 0000000000..1f69f08622 --- /dev/null +++ b/tests/Stride.UI.Tests.Regression/Windows.Vulkan/SwiftShader/BillboardModeTests.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d27775dadab1baaa2f2b14599ef17976734704b5e5313ad2c621057fff40aa30 +size 18219 diff --git a/tests/Stride.UI.Tests.Regression/Windows.Vulkan/SwiftShader/BorderTest.f1.png b/tests/Stride.UI.Tests.Regression/Windows.Vulkan/SwiftShader/BorderTest.f1.png new file mode 100644 index 0000000000..38f0f56687 --- /dev/null +++ b/tests/Stride.UI.Tests.Regression/Windows.Vulkan/SwiftShader/BorderTest.f1.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a0e460e021131525bdbeefd2934b5427ef306eb08606ab898950f0df0ed2cb86 +size 15038 diff --git a/tests/Stride.UI.Tests.Regression/Windows.Vulkan/SwiftShader/BorderTest.f2.png b/tests/Stride.UI.Tests.Regression/Windows.Vulkan/SwiftShader/BorderTest.f2.png new file mode 100644 index 0000000000..f9945d7fe3 --- /dev/null +++ b/tests/Stride.UI.Tests.Regression/Windows.Vulkan/SwiftShader/BorderTest.f2.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:90001f4113e2e51b4f126a10c079f9a9049cb1ae0ddbbe16f3741bac45a28aa7 +size 8986 diff --git a/tests/Stride.UI.Tests.Regression/Windows.Vulkan/SwiftShader/BorderTest.png b/tests/Stride.UI.Tests.Regression/Windows.Vulkan/SwiftShader/BorderTest.png new file mode 100644 index 0000000000..68a21c432d --- /dev/null +++ b/tests/Stride.UI.Tests.Regression/Windows.Vulkan/SwiftShader/BorderTest.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:7238836058f9c55c98361ffe13df0ca598fdb248ec2d363c5fc4e124b61791a3 +size 8113 diff --git a/tests/Stride.UI.Tests.Regression/Windows.Vulkan/SwiftShader/CanvasGridTest.png b/tests/Stride.UI.Tests.Regression/Windows.Vulkan/SwiftShader/CanvasGridTest.png new file mode 100644 index 0000000000..d61a26b6ac --- /dev/null +++ b/tests/Stride.UI.Tests.Regression/Windows.Vulkan/SwiftShader/CanvasGridTest.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c88e7dabdd5d8423d4ecf67ead9a08f813ade1a20eb31e2a54f2a353de1e81ac +size 61304 diff --git a/tests/Stride.UI.Tests.Regression/Windows.Vulkan/SwiftShader/ClickTests.f1.png b/tests/Stride.UI.Tests.Regression/Windows.Vulkan/SwiftShader/ClickTests.f1.png new file mode 100644 index 0000000000..a6b6168a80 --- /dev/null +++ b/tests/Stride.UI.Tests.Regression/Windows.Vulkan/SwiftShader/ClickTests.f1.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:607b1a8a508f415789c28ae04273b10f0577449d59e711db39c80e7f0703104b +size 35811 diff --git a/tests/Stride.UI.Tests.Regression/Windows.Vulkan/SwiftShader/ClickTests.f2.png b/tests/Stride.UI.Tests.Regression/Windows.Vulkan/SwiftShader/ClickTests.f2.png new file mode 100644 index 0000000000..8f6ab8f39c --- /dev/null +++ b/tests/Stride.UI.Tests.Regression/Windows.Vulkan/SwiftShader/ClickTests.f2.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:3feda5d265f84cc15547218c6a245b314b292df69d408d118c5bfe221bc25528 +size 35742 diff --git a/tests/Stride.UI.Tests.Regression/Windows.Vulkan/SwiftShader/ClickTests.f3.png b/tests/Stride.UI.Tests.Regression/Windows.Vulkan/SwiftShader/ClickTests.f3.png new file mode 100644 index 0000000000..daafd8a357 --- /dev/null +++ b/tests/Stride.UI.Tests.Regression/Windows.Vulkan/SwiftShader/ClickTests.f3.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:53329c4ce32e6340c0185e12a88b8c14ab60e4407975e63211678a4d38badd0e +size 37204 diff --git a/tests/Stride.UI.Tests.Regression/Windows.Vulkan/SwiftShader/ClickTests.f4.png b/tests/Stride.UI.Tests.Regression/Windows.Vulkan/SwiftShader/ClickTests.f4.png new file mode 100644 index 0000000000..c426335c00 --- /dev/null +++ b/tests/Stride.UI.Tests.Regression/Windows.Vulkan/SwiftShader/ClickTests.f4.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:1d809eaa51cb5ad4855fd5caa0febe067e466aacbf9003855608388f03b64bbe +size 32108 diff --git a/tests/Stride.UI.Tests.Regression/Windows.Vulkan/SwiftShader/ClickTests.f5.png b/tests/Stride.UI.Tests.Regression/Windows.Vulkan/SwiftShader/ClickTests.f5.png new file mode 100644 index 0000000000..9522bbe70b --- /dev/null +++ b/tests/Stride.UI.Tests.Regression/Windows.Vulkan/SwiftShader/ClickTests.f5.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:3e9780e0618ac81958e7017c2003ede8a75aa774921d4d23e93ce054c90d502f +size 39185 diff --git a/tests/Stride.UI.Tests.Regression/Windows.Vulkan/SwiftShader/ClickTests.f6.png b/tests/Stride.UI.Tests.Regression/Windows.Vulkan/SwiftShader/ClickTests.f6.png new file mode 100644 index 0000000000..a4400efe86 --- /dev/null +++ b/tests/Stride.UI.Tests.Regression/Windows.Vulkan/SwiftShader/ClickTests.f6.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:7f25d1799414ccbb7e5b1f5f5061d1f0bfe6f6a838ef7e40cf9029c68b2a1c13 +size 37986 diff --git a/tests/Stride.UI.Tests.Regression/Windows.Vulkan/SwiftShader/ClickTests.png b/tests/Stride.UI.Tests.Regression/Windows.Vulkan/SwiftShader/ClickTests.png new file mode 100644 index 0000000000..4154739f78 --- /dev/null +++ b/tests/Stride.UI.Tests.Regression/Windows.Vulkan/SwiftShader/ClickTests.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:834f0253737949ecc0ffffcadaa6317da61c099805ce78613979788ffdf0dd93 +size 35729 diff --git a/tests/Stride.UI.Tests.Regression/Windows.Vulkan/SwiftShader/ClippingTest.f1.png b/tests/Stride.UI.Tests.Regression/Windows.Vulkan/SwiftShader/ClippingTest.f1.png new file mode 100644 index 0000000000..566ce695a8 --- /dev/null +++ b/tests/Stride.UI.Tests.Regression/Windows.Vulkan/SwiftShader/ClippingTest.f1.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:6ebddadc92e9838b1926acca53d8e7bb22807687973ce97f36e2faecddcef1e1 +size 200066 diff --git a/tests/Stride.UI.Tests.Regression/Windows.Vulkan/SwiftShader/ClippingTest.f10.png b/tests/Stride.UI.Tests.Regression/Windows.Vulkan/SwiftShader/ClippingTest.f10.png new file mode 100644 index 0000000000..9b468ae5bc --- /dev/null +++ b/tests/Stride.UI.Tests.Regression/Windows.Vulkan/SwiftShader/ClippingTest.f10.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:bbc1a3bbced8d8d845de5260dc892da823d6f107a84e7c74dfd45f841fc92c02 +size 194077 diff --git a/tests/Stride.UI.Tests.Regression/Windows.Vulkan/SwiftShader/ClippingTest.f11.png b/tests/Stride.UI.Tests.Regression/Windows.Vulkan/SwiftShader/ClippingTest.f11.png new file mode 100644 index 0000000000..888aaf2599 --- /dev/null +++ b/tests/Stride.UI.Tests.Regression/Windows.Vulkan/SwiftShader/ClippingTest.f11.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:6aa9d7cb9892a69bacab907a3c54d11803305ca208acb6127a2e28fbc60f3be9 +size 193684 diff --git a/tests/Stride.UI.Tests.Regression/Windows.Vulkan/SwiftShader/ClippingTest.f12.png b/tests/Stride.UI.Tests.Regression/Windows.Vulkan/SwiftShader/ClippingTest.f12.png new file mode 100644 index 0000000000..b5325722c3 --- /dev/null +++ b/tests/Stride.UI.Tests.Regression/Windows.Vulkan/SwiftShader/ClippingTest.f12.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c17af850b686d885fc4cf0935e461f06dde20541c0a0b4321fc370c1d2cf77cf +size 194557 diff --git a/tests/Stride.UI.Tests.Regression/Windows.Vulkan/SwiftShader/ClippingTest.f2.png b/tests/Stride.UI.Tests.Regression/Windows.Vulkan/SwiftShader/ClippingTest.f2.png new file mode 100644 index 0000000000..22dc29bbf4 --- /dev/null +++ b/tests/Stride.UI.Tests.Regression/Windows.Vulkan/SwiftShader/ClippingTest.f2.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:883b8d3875a1fef9403028a96382e48c91afef60416cd3c75ea8dfd188802a88 +size 166234 diff --git a/tests/Stride.UI.Tests.Regression/Windows.Vulkan/SwiftShader/ClippingTest.f3.png b/tests/Stride.UI.Tests.Regression/Windows.Vulkan/SwiftShader/ClippingTest.f3.png new file mode 100644 index 0000000000..c91acb2f1d --- /dev/null +++ b/tests/Stride.UI.Tests.Regression/Windows.Vulkan/SwiftShader/ClippingTest.f3.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:28f2fc98a09a1abe9b6c8f6a94114c69562cda4bdd54583dcf66740a5854724d +size 206964 diff --git a/tests/Stride.UI.Tests.Regression/Windows.Vulkan/SwiftShader/ClippingTest.f4.png b/tests/Stride.UI.Tests.Regression/Windows.Vulkan/SwiftShader/ClippingTest.f4.png new file mode 100644 index 0000000000..566ce695a8 --- /dev/null +++ b/tests/Stride.UI.Tests.Regression/Windows.Vulkan/SwiftShader/ClippingTest.f4.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:6ebddadc92e9838b1926acca53d8e7bb22807687973ce97f36e2faecddcef1e1 +size 200066 diff --git a/tests/Stride.UI.Tests.Regression/Windows.Vulkan/SwiftShader/ClippingTest.f5.png b/tests/Stride.UI.Tests.Regression/Windows.Vulkan/SwiftShader/ClippingTest.f5.png new file mode 100644 index 0000000000..82c822fc45 --- /dev/null +++ b/tests/Stride.UI.Tests.Regression/Windows.Vulkan/SwiftShader/ClippingTest.f5.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:8e2b02476e17ff0b129a597634ceb8ba328090f522781dc4fc7cd7452c866d5e +size 205512 diff --git a/tests/Stride.UI.Tests.Regression/Windows.Vulkan/SwiftShader/ClippingTest.f6.png b/tests/Stride.UI.Tests.Regression/Windows.Vulkan/SwiftShader/ClippingTest.f6.png new file mode 100644 index 0000000000..6ceb69ab3a --- /dev/null +++ b/tests/Stride.UI.Tests.Regression/Windows.Vulkan/SwiftShader/ClippingTest.f6.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:af01167c28fab272f9a0f7084e97b180c819bf13ec284cf595782470a79e1508 +size 189452 diff --git a/tests/Stride.UI.Tests.Regression/Windows.Vulkan/SwiftShader/ClippingTest.f7.png b/tests/Stride.UI.Tests.Regression/Windows.Vulkan/SwiftShader/ClippingTest.f7.png new file mode 100644 index 0000000000..6ceb69ab3a --- /dev/null +++ b/tests/Stride.UI.Tests.Regression/Windows.Vulkan/SwiftShader/ClippingTest.f7.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:af01167c28fab272f9a0f7084e97b180c819bf13ec284cf595782470a79e1508 +size 189452 diff --git a/tests/Stride.UI.Tests.Regression/Windows.Vulkan/SwiftShader/ClippingTest.f8.png b/tests/Stride.UI.Tests.Regression/Windows.Vulkan/SwiftShader/ClippingTest.f8.png new file mode 100644 index 0000000000..43cb471824 --- /dev/null +++ b/tests/Stride.UI.Tests.Regression/Windows.Vulkan/SwiftShader/ClippingTest.f8.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:43957567713d82ff61af2747e5184f5f8be4b068df993d4486e5c7bb4483aab8 +size 192340 diff --git a/tests/Stride.UI.Tests.Regression/Windows.Vulkan/SwiftShader/ClippingTest.f9.png b/tests/Stride.UI.Tests.Regression/Windows.Vulkan/SwiftShader/ClippingTest.f9.png new file mode 100644 index 0000000000..98c9596175 --- /dev/null +++ b/tests/Stride.UI.Tests.Regression/Windows.Vulkan/SwiftShader/ClippingTest.f9.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:95aeec86c4ffddfd7999347016ced4b68545ac675734357ae9a78f301a4075dd +size 187330 diff --git a/tests/Stride.UI.Tests.Regression/Windows.Vulkan/SwiftShader/ClippingTest.png b/tests/Stride.UI.Tests.Regression/Windows.Vulkan/SwiftShader/ClippingTest.png new file mode 100644 index 0000000000..2d515f9691 --- /dev/null +++ b/tests/Stride.UI.Tests.Regression/Windows.Vulkan/SwiftShader/ClippingTest.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:0d59af896aa8536719645ec058746648c92fc378439d934dff8d3f49d13c6399 +size 156959 diff --git a/tests/Stride.UI.Tests.Regression/Windows.Vulkan/SwiftShader/ComplexLayoutTest.f1.png b/tests/Stride.UI.Tests.Regression/Windows.Vulkan/SwiftShader/ComplexLayoutTest.f1.png new file mode 100644 index 0000000000..544548046e --- /dev/null +++ b/tests/Stride.UI.Tests.Regression/Windows.Vulkan/SwiftShader/ComplexLayoutTest.f1.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:8785d0f6c2eb38825208dc75b1911161b203a16134d5bbaa6a3f8debade82360 +size 38697 diff --git a/tests/Stride.UI.Tests.Regression/Windows.Vulkan/SwiftShader/ComplexLayoutTest.f2.png b/tests/Stride.UI.Tests.Regression/Windows.Vulkan/SwiftShader/ComplexLayoutTest.f2.png new file mode 100644 index 0000000000..3873767ee4 --- /dev/null +++ b/tests/Stride.UI.Tests.Regression/Windows.Vulkan/SwiftShader/ComplexLayoutTest.f2.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:45ea23e54d462103db9ef2f50d3612e5c4861713829ebc4cd50994070edd2241 +size 32001 diff --git a/tests/Stride.UI.Tests.Regression/Windows.Vulkan/SwiftShader/ComplexLayoutTest.f3.png b/tests/Stride.UI.Tests.Regression/Windows.Vulkan/SwiftShader/ComplexLayoutTest.f3.png new file mode 100644 index 0000000000..204df520e5 --- /dev/null +++ b/tests/Stride.UI.Tests.Regression/Windows.Vulkan/SwiftShader/ComplexLayoutTest.f3.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:90b9be77b2bfd9b579ccf818a9f3ac3a84d2e408b63b60869d18fcad67a7d352 +size 7759 diff --git a/tests/Stride.UI.Tests.Regression/Windows.Vulkan/SwiftShader/ComplexLayoutTest.f4.png b/tests/Stride.UI.Tests.Regression/Windows.Vulkan/SwiftShader/ComplexLayoutTest.f4.png new file mode 100644 index 0000000000..2738b2aef1 --- /dev/null +++ b/tests/Stride.UI.Tests.Regression/Windows.Vulkan/SwiftShader/ComplexLayoutTest.f4.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:b0322df30f17b7840ff161cf3e0d41aabd6cd8cbd26f20d27c86782fce2cc878 +size 41966 diff --git a/tests/Stride.UI.Tests.Regression/Windows.Vulkan/SwiftShader/ComplexLayoutTest.f5.png b/tests/Stride.UI.Tests.Regression/Windows.Vulkan/SwiftShader/ComplexLayoutTest.f5.png new file mode 100644 index 0000000000..e0b1e7e5db --- /dev/null +++ b/tests/Stride.UI.Tests.Regression/Windows.Vulkan/SwiftShader/ComplexLayoutTest.f5.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:95201bbdda0e9a3bbd7d45ab9329954c7f4e42137ed5e9b1342cd89181230a09 +size 38382 diff --git a/tests/Stride.UI.Tests.Regression/Windows.Vulkan/SwiftShader/ComplexLayoutTest.f6.png b/tests/Stride.UI.Tests.Regression/Windows.Vulkan/SwiftShader/ComplexLayoutTest.f6.png new file mode 100644 index 0000000000..69e72fe3f9 --- /dev/null +++ b/tests/Stride.UI.Tests.Regression/Windows.Vulkan/SwiftShader/ComplexLayoutTest.f6.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c2f4fc766cb469515c0b62eb61f4bed528375bfd182d637af5f065f698ef1a5a +size 38004 diff --git a/tests/Stride.UI.Tests.Regression/Windows.Vulkan/SwiftShader/ComplexLayoutTest.f7.png b/tests/Stride.UI.Tests.Regression/Windows.Vulkan/SwiftShader/ComplexLayoutTest.f7.png new file mode 100644 index 0000000000..2738b2aef1 --- /dev/null +++ b/tests/Stride.UI.Tests.Regression/Windows.Vulkan/SwiftShader/ComplexLayoutTest.f7.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:b0322df30f17b7840ff161cf3e0d41aabd6cd8cbd26f20d27c86782fce2cc878 +size 41966 diff --git a/tests/Stride.UI.Tests.Regression/Windows.Vulkan/SwiftShader/ComplexLayoutTest.png b/tests/Stride.UI.Tests.Regression/Windows.Vulkan/SwiftShader/ComplexLayoutTest.png new file mode 100644 index 0000000000..d9690c4cf6 --- /dev/null +++ b/tests/Stride.UI.Tests.Regression/Windows.Vulkan/SwiftShader/ComplexLayoutTest.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:be9e045489c88faf46a8f2a292a5a9bfa76ba8a81e70000fc43bf64b1a71f8bb +size 41571 diff --git a/tests/Stride.UI.Tests.Regression/Windows.Vulkan/SwiftShader/ContentDecoratorTest.f1.png b/tests/Stride.UI.Tests.Regression/Windows.Vulkan/SwiftShader/ContentDecoratorTest.f1.png new file mode 100644 index 0000000000..ab7c531e86 --- /dev/null +++ b/tests/Stride.UI.Tests.Regression/Windows.Vulkan/SwiftShader/ContentDecoratorTest.f1.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:b1390522552bda19e4a7ee056e4b763139f1aeef9480bb69a8ea50a968e118ef +size 3307 diff --git a/tests/Stride.UI.Tests.Regression/Windows.Vulkan/SwiftShader/ContentDecoratorTest.f2.png b/tests/Stride.UI.Tests.Regression/Windows.Vulkan/SwiftShader/ContentDecoratorTest.f2.png new file mode 100644 index 0000000000..b9a0e27fc7 --- /dev/null +++ b/tests/Stride.UI.Tests.Regression/Windows.Vulkan/SwiftShader/ContentDecoratorTest.f2.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:ab8cfbe2bcf5b18208859e5ef4b28c54228605d227a299d54560b97b016089eb +size 3626 diff --git a/tests/Stride.UI.Tests.Regression/Windows.Vulkan/SwiftShader/ContentDecoratorTest.png b/tests/Stride.UI.Tests.Regression/Windows.Vulkan/SwiftShader/ContentDecoratorTest.png new file mode 100644 index 0000000000..261b03fe2c --- /dev/null +++ b/tests/Stride.UI.Tests.Regression/Windows.Vulkan/SwiftShader/ContentDecoratorTest.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:9db145418a32b2f58eb129ae193f19517849f4f05312cdf37ffa55306accfb73 +size 2950 diff --git a/tests/Stride.UI.Tests.Regression/Windows.Vulkan/SwiftShader/DynamicFontTest.f1.png b/tests/Stride.UI.Tests.Regression/Windows.Vulkan/SwiftShader/DynamicFontTest.f1.png new file mode 100644 index 0000000000..613a94fba3 --- /dev/null +++ b/tests/Stride.UI.Tests.Regression/Windows.Vulkan/SwiftShader/DynamicFontTest.f1.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:b50df28a99feabeae17bb0432c77b7bfe391856f49077a0b4bc4685ffd796caa +size 4069 diff --git a/tests/Stride.UI.Tests.Regression/Windows.Vulkan/SwiftShader/DynamicFontTest.f2.png b/tests/Stride.UI.Tests.Regression/Windows.Vulkan/SwiftShader/DynamicFontTest.f2.png new file mode 100644 index 0000000000..613a94fba3 --- /dev/null +++ b/tests/Stride.UI.Tests.Regression/Windows.Vulkan/SwiftShader/DynamicFontTest.f2.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:b50df28a99feabeae17bb0432c77b7bfe391856f49077a0b4bc4685ffd796caa +size 4069 diff --git a/tests/Stride.UI.Tests.Regression/Windows.Vulkan/SwiftShader/DynamicFontTest.f3.png b/tests/Stride.UI.Tests.Regression/Windows.Vulkan/SwiftShader/DynamicFontTest.f3.png new file mode 100644 index 0000000000..36a43809b5 --- /dev/null +++ b/tests/Stride.UI.Tests.Regression/Windows.Vulkan/SwiftShader/DynamicFontTest.f3.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:4cbb1f4e1548439366460d99022a0deafd778c3df2193ee639c81d26fbe1f493 +size 6829 diff --git a/tests/Stride.UI.Tests.Regression/Windows.Vulkan/SwiftShader/DynamicFontTest.f4.png b/tests/Stride.UI.Tests.Regression/Windows.Vulkan/SwiftShader/DynamicFontTest.f4.png new file mode 100644 index 0000000000..ed4c541c3b --- /dev/null +++ b/tests/Stride.UI.Tests.Regression/Windows.Vulkan/SwiftShader/DynamicFontTest.f4.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:4a088e6a2717ea0e9e8d65ebd6783f64d89b049def7383e206b609d4fe0c9094 +size 3933 diff --git a/tests/Stride.UI.Tests.Regression/Windows.Vulkan/SwiftShader/DynamicFontTest.f5.png b/tests/Stride.UI.Tests.Regression/Windows.Vulkan/SwiftShader/DynamicFontTest.f5.png new file mode 100644 index 0000000000..0bfee1175f --- /dev/null +++ b/tests/Stride.UI.Tests.Regression/Windows.Vulkan/SwiftShader/DynamicFontTest.f5.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:99c3c19659207936131b04c2d992e168e62a283fb5f1d2ff2a86e732b101d95d +size 4096 diff --git a/tests/Stride.UI.Tests.Regression/Windows.Vulkan/SwiftShader/DynamicFontTest.png b/tests/Stride.UI.Tests.Regression/Windows.Vulkan/SwiftShader/DynamicFontTest.png new file mode 100644 index 0000000000..7954c960f8 --- /dev/null +++ b/tests/Stride.UI.Tests.Regression/Windows.Vulkan/SwiftShader/DynamicFontTest.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:567434351d218f36382de94a4a52d58a8e71f6fe67d9cc99b78c4748dc2af577 +size 3116 diff --git a/tests/Stride.UI.Tests.Regression/Windows.Vulkan/SwiftShader/EditTextTest.f1.png b/tests/Stride.UI.Tests.Regression/Windows.Vulkan/SwiftShader/EditTextTest.f1.png new file mode 100644 index 0000000000..6043e78373 --- /dev/null +++ b/tests/Stride.UI.Tests.Regression/Windows.Vulkan/SwiftShader/EditTextTest.f1.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:821273781313039ae6004b639caa87139547e8f473aba126e986dc91333cecb0 +size 7630 diff --git a/tests/Stride.UI.Tests.Regression/Windows.Vulkan/SwiftShader/EditTextTest.f10.png b/tests/Stride.UI.Tests.Regression/Windows.Vulkan/SwiftShader/EditTextTest.f10.png new file mode 100644 index 0000000000..0fccdf72f1 --- /dev/null +++ b/tests/Stride.UI.Tests.Regression/Windows.Vulkan/SwiftShader/EditTextTest.f10.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:0277aa62628adaccb2544672821553eb030aecb34648152a2c06648f1aafa6ed +size 8083 diff --git a/tests/Stride.UI.Tests.Regression/Windows.Vulkan/SwiftShader/EditTextTest.f11.png b/tests/Stride.UI.Tests.Regression/Windows.Vulkan/SwiftShader/EditTextTest.f11.png new file mode 100644 index 0000000000..b1c9d18eb3 --- /dev/null +++ b/tests/Stride.UI.Tests.Regression/Windows.Vulkan/SwiftShader/EditTextTest.f11.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c24e505a758adb6ac3fb5f0664a9529b3c641a0b541b95e2cd3e67cc21822f8e +size 8078 diff --git a/tests/Stride.UI.Tests.Regression/Windows.Vulkan/SwiftShader/EditTextTest.f12.png b/tests/Stride.UI.Tests.Regression/Windows.Vulkan/SwiftShader/EditTextTest.f12.png new file mode 100644 index 0000000000..77a99efc53 --- /dev/null +++ b/tests/Stride.UI.Tests.Regression/Windows.Vulkan/SwiftShader/EditTextTest.f12.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c7941342eed6ee20c7ced20715714b661a320c401769b917020aaa6f7d2a4b14 +size 8082 diff --git a/tests/Stride.UI.Tests.Regression/Windows.Vulkan/SwiftShader/EditTextTest.f13.png b/tests/Stride.UI.Tests.Regression/Windows.Vulkan/SwiftShader/EditTextTest.f13.png new file mode 100644 index 0000000000..f8538fe491 --- /dev/null +++ b/tests/Stride.UI.Tests.Regression/Windows.Vulkan/SwiftShader/EditTextTest.f13.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:495013e6c1eee6171d633df9b094ecab59355e690bb8b839c01d274874d8d919 +size 8288 diff --git a/tests/Stride.UI.Tests.Regression/Windows.Vulkan/SwiftShader/EditTextTest.f14.png b/tests/Stride.UI.Tests.Regression/Windows.Vulkan/SwiftShader/EditTextTest.f14.png new file mode 100644 index 0000000000..17c509517b --- /dev/null +++ b/tests/Stride.UI.Tests.Regression/Windows.Vulkan/SwiftShader/EditTextTest.f14.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:1fe4c08d09bb7fb8c2fd18779390e094df9b84047d18f0c3d1c845632def6d59 +size 8291 diff --git a/tests/Stride.UI.Tests.Regression/Windows.Vulkan/SwiftShader/EditTextTest.f2.png b/tests/Stride.UI.Tests.Regression/Windows.Vulkan/SwiftShader/EditTextTest.f2.png new file mode 100644 index 0000000000..93991cfdf0 --- /dev/null +++ b/tests/Stride.UI.Tests.Regression/Windows.Vulkan/SwiftShader/EditTextTest.f2.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:9dfefebc413fbead15f09387c2bba716dffc7b657b276bafea0bc789a6e95ea4 +size 7795 diff --git a/tests/Stride.UI.Tests.Regression/Windows.Vulkan/SwiftShader/EditTextTest.f3.png b/tests/Stride.UI.Tests.Regression/Windows.Vulkan/SwiftShader/EditTextTest.f3.png new file mode 100644 index 0000000000..a33169483a --- /dev/null +++ b/tests/Stride.UI.Tests.Regression/Windows.Vulkan/SwiftShader/EditTextTest.f3.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c692bfa9ec51c2fc7934dd189fe259ae87fbb51e712b4e7e256bc7831e6a40ab +size 7782 diff --git a/tests/Stride.UI.Tests.Regression/Windows.Vulkan/SwiftShader/EditTextTest.f4.png b/tests/Stride.UI.Tests.Regression/Windows.Vulkan/SwiftShader/EditTextTest.f4.png new file mode 100644 index 0000000000..5c18fb6ef1 --- /dev/null +++ b/tests/Stride.UI.Tests.Regression/Windows.Vulkan/SwiftShader/EditTextTest.f4.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:7d26ddbdfb72a9d80ee69349c2da164da4b1d5c07688bfc0f787bfab682dea77 +size 7931 diff --git a/tests/Stride.UI.Tests.Regression/Windows.Vulkan/SwiftShader/EditTextTest.f5.png b/tests/Stride.UI.Tests.Regression/Windows.Vulkan/SwiftShader/EditTextTest.f5.png new file mode 100644 index 0000000000..98bffb89ba --- /dev/null +++ b/tests/Stride.UI.Tests.Regression/Windows.Vulkan/SwiftShader/EditTextTest.f5.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f0d6da061c3ff387322b8ca32b8c0f927b9f3e4c85093a447c19a2065e048b05 +size 7994 diff --git a/tests/Stride.UI.Tests.Regression/Windows.Vulkan/SwiftShader/EditTextTest.f6.png b/tests/Stride.UI.Tests.Regression/Windows.Vulkan/SwiftShader/EditTextTest.f6.png new file mode 100644 index 0000000000..52b043a18f --- /dev/null +++ b/tests/Stride.UI.Tests.Regression/Windows.Vulkan/SwiftShader/EditTextTest.f6.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:b6b80481473e8e520d81fe5ee35df11918f4526f940bd3130aae9ce2566d31d8 +size 8010 diff --git a/tests/Stride.UI.Tests.Regression/Windows.Vulkan/SwiftShader/EditTextTest.f7.png b/tests/Stride.UI.Tests.Regression/Windows.Vulkan/SwiftShader/EditTextTest.f7.png new file mode 100644 index 0000000000..af7f8c9dec --- /dev/null +++ b/tests/Stride.UI.Tests.Regression/Windows.Vulkan/SwiftShader/EditTextTest.f7.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:1c0bcf930bd810dc0d6e649fa498eec9a0f649fb2374c34938ae70ab804897d4 +size 6157 diff --git a/tests/Stride.UI.Tests.Regression/Windows.Vulkan/SwiftShader/EditTextTest.f8.png b/tests/Stride.UI.Tests.Regression/Windows.Vulkan/SwiftShader/EditTextTest.f8.png new file mode 100644 index 0000000000..4d2fc595ce --- /dev/null +++ b/tests/Stride.UI.Tests.Regression/Windows.Vulkan/SwiftShader/EditTextTest.f8.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f67d385d518d835ff3d212d761576e3d6d7be08fa48063aec5ba3899d4e967c7 +size 8128 diff --git a/tests/Stride.UI.Tests.Regression/Windows.Vulkan/SwiftShader/EditTextTest.f9.png b/tests/Stride.UI.Tests.Regression/Windows.Vulkan/SwiftShader/EditTextTest.f9.png new file mode 100644 index 0000000000..fca726164d --- /dev/null +++ b/tests/Stride.UI.Tests.Regression/Windows.Vulkan/SwiftShader/EditTextTest.f9.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:452a770ee62e46dac6644dc02393fdcb0afe3d36dfa36a890840dc419a2df278 +size 8051 diff --git a/tests/Stride.UI.Tests.Regression/Windows.Vulkan/SwiftShader/EditTextTest.png b/tests/Stride.UI.Tests.Regression/Windows.Vulkan/SwiftShader/EditTextTest.png new file mode 100644 index 0000000000..02940987d0 --- /dev/null +++ b/tests/Stride.UI.Tests.Regression/Windows.Vulkan/SwiftShader/EditTextTest.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a3e58614cc17f6fd3293ad62d9937c31aa019389612539430b742e3e1bbe75f9 +size 7638 diff --git a/tests/Stride.UI.Tests.Regression/Windows.Vulkan/SwiftShader/ImageRegionTest.f1.png b/tests/Stride.UI.Tests.Regression/Windows.Vulkan/SwiftShader/ImageRegionTest.f1.png new file mode 100644 index 0000000000..ac4c45919b --- /dev/null +++ b/tests/Stride.UI.Tests.Regression/Windows.Vulkan/SwiftShader/ImageRegionTest.f1.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c03e4451163d5c2a7e9b1d395f416a7b8158033cbcfd9192156317d3b924b262 +size 9502 diff --git a/tests/Stride.UI.Tests.Regression/Windows.Vulkan/SwiftShader/ImageRegionTest.f2.png b/tests/Stride.UI.Tests.Regression/Windows.Vulkan/SwiftShader/ImageRegionTest.f2.png new file mode 100644 index 0000000000..bf5040f48c --- /dev/null +++ b/tests/Stride.UI.Tests.Regression/Windows.Vulkan/SwiftShader/ImageRegionTest.f2.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:82e18a35416c33fa199af5d3e966a5f8fb9d0abbac11fea72ad593baa739bb71 +size 11652 diff --git a/tests/Stride.UI.Tests.Regression/Windows.Vulkan/SwiftShader/ImageRegionTest.f3.png b/tests/Stride.UI.Tests.Regression/Windows.Vulkan/SwiftShader/ImageRegionTest.f3.png new file mode 100644 index 0000000000..66aff9fb68 --- /dev/null +++ b/tests/Stride.UI.Tests.Regression/Windows.Vulkan/SwiftShader/ImageRegionTest.f3.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:6debf7e26efd54c5bf28329a73a8f686d4abb20d1bba78a635cc3b11e5b7743b +size 9693 diff --git a/tests/Stride.UI.Tests.Regression/Windows.Vulkan/SwiftShader/ImageRegionTest.f4.png b/tests/Stride.UI.Tests.Regression/Windows.Vulkan/SwiftShader/ImageRegionTest.f4.png new file mode 100644 index 0000000000..dd26beb140 --- /dev/null +++ b/tests/Stride.UI.Tests.Regression/Windows.Vulkan/SwiftShader/ImageRegionTest.f4.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:1ae4eb6a8b9b8b864c6701871b06f0ed910fc993141c21d82e3a3d206e9752b4 +size 10039 diff --git a/tests/Stride.UI.Tests.Regression/Windows.Vulkan/SwiftShader/ImageRegionTest.png b/tests/Stride.UI.Tests.Regression/Windows.Vulkan/SwiftShader/ImageRegionTest.png new file mode 100644 index 0000000000..9d0cdc8ac6 --- /dev/null +++ b/tests/Stride.UI.Tests.Regression/Windows.Vulkan/SwiftShader/ImageRegionTest.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:258a14fde3f51b1d59398b10ee78c02d32ec52e2253c8b91c2896f65a7b8e355 +size 36330 diff --git a/tests/Stride.UI.Tests.Regression/Windows.Vulkan/SwiftShader/ImageRotatedTest.png b/tests/Stride.UI.Tests.Regression/Windows.Vulkan/SwiftShader/ImageRotatedTest.png new file mode 100644 index 0000000000..e9c283be4d --- /dev/null +++ b/tests/Stride.UI.Tests.Regression/Windows.Vulkan/SwiftShader/ImageRotatedTest.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:6221858c3cc24f669183e1b384b67c90ed317a28bcba066723977d128ff79958 +size 388719 diff --git a/tests/Stride.UI.Tests.Regression/Windows.Vulkan/SwiftShader/ImageTest.f1.png b/tests/Stride.UI.Tests.Regression/Windows.Vulkan/SwiftShader/ImageTest.f1.png new file mode 100644 index 0000000000..eae56f110b --- /dev/null +++ b/tests/Stride.UI.Tests.Regression/Windows.Vulkan/SwiftShader/ImageTest.f1.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:9c6a01316c77db49248cd683afbecfb8eefcbb1c708e9d56be2d53ccb99f62ca +size 16821 diff --git a/tests/Stride.UI.Tests.Regression/Windows.Vulkan/SwiftShader/ImageTest.f2.png b/tests/Stride.UI.Tests.Regression/Windows.Vulkan/SwiftShader/ImageTest.f2.png new file mode 100644 index 0000000000..f22c9ae0d8 --- /dev/null +++ b/tests/Stride.UI.Tests.Regression/Windows.Vulkan/SwiftShader/ImageTest.f2.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:48a53c8be53797731cacbda26e7c5525b76cdea7c0ad86a468c11384e79aeed1 +size 8373 diff --git a/tests/Stride.UI.Tests.Regression/Windows.Vulkan/SwiftShader/ImageTest.f3.png b/tests/Stride.UI.Tests.Regression/Windows.Vulkan/SwiftShader/ImageTest.f3.png new file mode 100644 index 0000000000..101b7fb624 --- /dev/null +++ b/tests/Stride.UI.Tests.Regression/Windows.Vulkan/SwiftShader/ImageTest.f3.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:155e1601c814fb56bba07a827edfd0124d5ddcc5a7acc631482585d3129c8f65 +size 10331 diff --git a/tests/Stride.UI.Tests.Regression/Windows.Vulkan/SwiftShader/ImageTest.f4.png b/tests/Stride.UI.Tests.Regression/Windows.Vulkan/SwiftShader/ImageTest.f4.png new file mode 100644 index 0000000000..3cabcc6462 --- /dev/null +++ b/tests/Stride.UI.Tests.Regression/Windows.Vulkan/SwiftShader/ImageTest.f4.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:ace5fcabeb18fbfedcdd500204b540be97ad6c933d39c0b04544e5cc175bf54c +size 10564 diff --git a/tests/Stride.UI.Tests.Regression/Windows.Vulkan/SwiftShader/ImageTest.png b/tests/Stride.UI.Tests.Regression/Windows.Vulkan/SwiftShader/ImageTest.png new file mode 100644 index 0000000000..f9cbeafe73 --- /dev/null +++ b/tests/Stride.UI.Tests.Regression/Windows.Vulkan/SwiftShader/ImageTest.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:56e46e02491f554417f7461679988b546f6763318c813a39211e90a6e2f4d47e +size 20994 diff --git a/tests/Stride.UI.Tests.Regression/Windows.Vulkan/SwiftShader/ModalElementTest.f2.png b/tests/Stride.UI.Tests.Regression/Windows.Vulkan/SwiftShader/ModalElementTest.f2.png new file mode 100644 index 0000000000..51bc822ea2 --- /dev/null +++ b/tests/Stride.UI.Tests.Regression/Windows.Vulkan/SwiftShader/ModalElementTest.f2.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:490c93ba66618bcd72de3da02121ccdfbce327979a48596db07e69d267185381 +size 448040 diff --git a/tests/Stride.UI.Tests.Regression/Windows.Vulkan/SwiftShader/ModalElementTest.f3.png b/tests/Stride.UI.Tests.Regression/Windows.Vulkan/SwiftShader/ModalElementTest.f3.png new file mode 100644 index 0000000000..3e018a607c --- /dev/null +++ b/tests/Stride.UI.Tests.Regression/Windows.Vulkan/SwiftShader/ModalElementTest.f3.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:5e10bf5c09ba2e529d394b9a1e9e68e52f1b46a419a99d6a4dc767c311eac505 +size 447309 diff --git a/tests/Stride.UI.Tests.Regression/Windows.Vulkan/SwiftShader/ModalElementTest.f4.png b/tests/Stride.UI.Tests.Regression/Windows.Vulkan/SwiftShader/ModalElementTest.f4.png new file mode 100644 index 0000000000..0c71ea86fb --- /dev/null +++ b/tests/Stride.UI.Tests.Regression/Windows.Vulkan/SwiftShader/ModalElementTest.f4.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:257db588704bf51dc503f421d2623f8c22f460c14e9bc34c6deb2a852d887b56 +size 499844 diff --git a/tests/Stride.UI.Tests.Regression/Windows.Vulkan/SwiftShader/ScrollViewerTest.f1.png b/tests/Stride.UI.Tests.Regression/Windows.Vulkan/SwiftShader/ScrollViewerTest.f1.png new file mode 100644 index 0000000000..b880863eb9 --- /dev/null +++ b/tests/Stride.UI.Tests.Regression/Windows.Vulkan/SwiftShader/ScrollViewerTest.f1.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:9aa2869a74b5ffc3493c37a6917d669164f7a842d893383fee654015649ca46d +size 430236 diff --git a/tests/Stride.UI.Tests.Regression/Windows.Vulkan/SwiftShader/ScrollViewerTest.f10.png b/tests/Stride.UI.Tests.Regression/Windows.Vulkan/SwiftShader/ScrollViewerTest.f10.png new file mode 100644 index 0000000000..2d3a785ae6 --- /dev/null +++ b/tests/Stride.UI.Tests.Regression/Windows.Vulkan/SwiftShader/ScrollViewerTest.f10.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:765dfe0a149a2f8edb8e2dda145d42a4153c61f0636f34e5d7c7566386b1f972 +size 16044 diff --git a/tests/Stride.UI.Tests.Regression/Windows.Vulkan/SwiftShader/ScrollViewerTest.f11.png b/tests/Stride.UI.Tests.Regression/Windows.Vulkan/SwiftShader/ScrollViewerTest.f11.png new file mode 100644 index 0000000000..f24605182e --- /dev/null +++ b/tests/Stride.UI.Tests.Regression/Windows.Vulkan/SwiftShader/ScrollViewerTest.f11.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:b7aa8fbade1e465fcabc6a520d24338f205ca1cf1212a059723f8481cc580f68 +size 16975 diff --git a/tests/Stride.UI.Tests.Regression/Windows.Vulkan/SwiftShader/ScrollViewerTest.f12.png b/tests/Stride.UI.Tests.Regression/Windows.Vulkan/SwiftShader/ScrollViewerTest.f12.png new file mode 100644 index 0000000000..b25f8a2115 --- /dev/null +++ b/tests/Stride.UI.Tests.Regression/Windows.Vulkan/SwiftShader/ScrollViewerTest.f12.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:679c5316c1779cca525b130f6ad97ca0dd2006f12b7d5a73c226db71379931db +size 16027 diff --git a/tests/Stride.UI.Tests.Regression/Windows.Vulkan/SwiftShader/ScrollViewerTest.f13.png b/tests/Stride.UI.Tests.Regression/Windows.Vulkan/SwiftShader/ScrollViewerTest.f13.png new file mode 100644 index 0000000000..3295955054 --- /dev/null +++ b/tests/Stride.UI.Tests.Regression/Windows.Vulkan/SwiftShader/ScrollViewerTest.f13.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:0e2e6b3f24ebca48d4c4fb69edcf29c95d19eaa902e324d64fafde9a202001ac +size 16206 diff --git a/tests/Stride.UI.Tests.Regression/Windows.Vulkan/SwiftShader/ScrollViewerTest.f14.png b/tests/Stride.UI.Tests.Regression/Windows.Vulkan/SwiftShader/ScrollViewerTest.f14.png new file mode 100644 index 0000000000..78df0c52c9 --- /dev/null +++ b/tests/Stride.UI.Tests.Regression/Windows.Vulkan/SwiftShader/ScrollViewerTest.f14.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:2ad28c45ad37ca9817b277f78287fd37ca8b2dca61896c2059bff92f77216450 +size 17812 diff --git a/tests/Stride.UI.Tests.Regression/Windows.Vulkan/SwiftShader/ScrollViewerTest.f15.png b/tests/Stride.UI.Tests.Regression/Windows.Vulkan/SwiftShader/ScrollViewerTest.f15.png new file mode 100644 index 0000000000..c75c352eed --- /dev/null +++ b/tests/Stride.UI.Tests.Regression/Windows.Vulkan/SwiftShader/ScrollViewerTest.f15.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:7ae444dedea991784566e9441292050d399edfab1fde2091dc76028280d12462 +size 20230 diff --git a/tests/Stride.UI.Tests.Regression/Windows.Vulkan/SwiftShader/ScrollViewerTest.f2.png b/tests/Stride.UI.Tests.Regression/Windows.Vulkan/SwiftShader/ScrollViewerTest.f2.png new file mode 100644 index 0000000000..b83e7f2d84 --- /dev/null +++ b/tests/Stride.UI.Tests.Regression/Windows.Vulkan/SwiftShader/ScrollViewerTest.f2.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:2e83149a80ec0dca6ebc64cb46893724aedabf0be83036171799a4ded75e372e +size 428960 diff --git a/tests/Stride.UI.Tests.Regression/Windows.Vulkan/SwiftShader/ScrollViewerTest.f3.png b/tests/Stride.UI.Tests.Regression/Windows.Vulkan/SwiftShader/ScrollViewerTest.f3.png new file mode 100644 index 0000000000..fb0b77635b --- /dev/null +++ b/tests/Stride.UI.Tests.Regression/Windows.Vulkan/SwiftShader/ScrollViewerTest.f3.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:5f37157ce47a43db4b12abd06b5d3662aa0500b17ee450dc718abfcb174afb14 +size 368629 diff --git a/tests/Stride.UI.Tests.Regression/Windows.Vulkan/SwiftShader/ScrollViewerTest.f4.png b/tests/Stride.UI.Tests.Regression/Windows.Vulkan/SwiftShader/ScrollViewerTest.f4.png new file mode 100644 index 0000000000..c213f457b0 --- /dev/null +++ b/tests/Stride.UI.Tests.Regression/Windows.Vulkan/SwiftShader/ScrollViewerTest.f4.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:0946f2fe937c4ebe23fea012e001e78aad19f235d557139b67005205ef70ea5e +size 426058 diff --git a/tests/Stride.UI.Tests.Regression/Windows.Vulkan/SwiftShader/ScrollViewerTest.f5.png b/tests/Stride.UI.Tests.Regression/Windows.Vulkan/SwiftShader/ScrollViewerTest.f5.png new file mode 100644 index 0000000000..1fcfc3e72b --- /dev/null +++ b/tests/Stride.UI.Tests.Regression/Windows.Vulkan/SwiftShader/ScrollViewerTest.f5.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:61fce0cf4d17f6254b98aa70c580ae19483779b8335161ebc421df3f20bf3771 +size 106179 diff --git a/tests/Stride.UI.Tests.Regression/Windows.Vulkan/SwiftShader/ScrollViewerTest.f6.png b/tests/Stride.UI.Tests.Regression/Windows.Vulkan/SwiftShader/ScrollViewerTest.f6.png new file mode 100644 index 0000000000..71a252c5b9 --- /dev/null +++ b/tests/Stride.UI.Tests.Regression/Windows.Vulkan/SwiftShader/ScrollViewerTest.f6.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:843fc3458d692562e38eaf67c405fae0c6dc784091ea821045ffc81ae55b768c +size 435902 diff --git a/tests/Stride.UI.Tests.Regression/Windows.Vulkan/SwiftShader/ScrollViewerTest.f7.png b/tests/Stride.UI.Tests.Regression/Windows.Vulkan/SwiftShader/ScrollViewerTest.f7.png new file mode 100644 index 0000000000..cdfca08c24 --- /dev/null +++ b/tests/Stride.UI.Tests.Regression/Windows.Vulkan/SwiftShader/ScrollViewerTest.f7.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:967b0b43bbbf22b0db7185679b9a10b9d924ae89b724aa2d48e75097aeb8471b +size 180256 diff --git a/tests/Stride.UI.Tests.Regression/Windows.Vulkan/SwiftShader/ScrollViewerTest.f8.png b/tests/Stride.UI.Tests.Regression/Windows.Vulkan/SwiftShader/ScrollViewerTest.f8.png new file mode 100644 index 0000000000..b83e7f2d84 --- /dev/null +++ b/tests/Stride.UI.Tests.Regression/Windows.Vulkan/SwiftShader/ScrollViewerTest.f8.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:2e83149a80ec0dca6ebc64cb46893724aedabf0be83036171799a4ded75e372e +size 428960 diff --git a/tests/Stride.UI.Tests.Regression/Windows.Vulkan/SwiftShader/ScrollViewerTest.png b/tests/Stride.UI.Tests.Regression/Windows.Vulkan/SwiftShader/ScrollViewerTest.png new file mode 100644 index 0000000000..6363bf8a81 --- /dev/null +++ b/tests/Stride.UI.Tests.Regression/Windows.Vulkan/SwiftShader/ScrollViewerTest.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:122fdd75c122593e0bdc84c6f54193408769831ced932b47640f1b5adc0aac28 +size 16634 diff --git a/tests/Stride.UI.Tests.Regression/Windows.Vulkan/SwiftShader/ScrollingTextTest.f1.png b/tests/Stride.UI.Tests.Regression/Windows.Vulkan/SwiftShader/ScrollingTextTest.f1.png new file mode 100644 index 0000000000..3873c8f9e6 --- /dev/null +++ b/tests/Stride.UI.Tests.Regression/Windows.Vulkan/SwiftShader/ScrollingTextTest.f1.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f8c90fd51fab94b1f5c97624fd44514b4cb6e1b2e1783372098fc67afb1d68cd +size 2860 diff --git a/tests/Stride.UI.Tests.Regression/Windows.Vulkan/SwiftShader/ScrollingTextTest.f2.png b/tests/Stride.UI.Tests.Regression/Windows.Vulkan/SwiftShader/ScrollingTextTest.f2.png new file mode 100644 index 0000000000..b1f9166e18 --- /dev/null +++ b/tests/Stride.UI.Tests.Regression/Windows.Vulkan/SwiftShader/ScrollingTextTest.f2.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:cc2b9065d4e4f6f84ba85dca50bf6f771aff10484fdac0f2debf4b4c3b7cb42c +size 2848 diff --git a/tests/Stride.UI.Tests.Regression/Windows.Vulkan/SwiftShader/ScrollingTextTest.f3.png b/tests/Stride.UI.Tests.Regression/Windows.Vulkan/SwiftShader/ScrollingTextTest.f3.png new file mode 100644 index 0000000000..3873c8f9e6 --- /dev/null +++ b/tests/Stride.UI.Tests.Regression/Windows.Vulkan/SwiftShader/ScrollingTextTest.f3.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f8c90fd51fab94b1f5c97624fd44514b4cb6e1b2e1783372098fc67afb1d68cd +size 2860 diff --git a/tests/Stride.UI.Tests.Regression/Windows.Vulkan/SwiftShader/ScrollingTextTest.f4.png b/tests/Stride.UI.Tests.Regression/Windows.Vulkan/SwiftShader/ScrollingTextTest.f4.png new file mode 100644 index 0000000000..a2beabfb6a --- /dev/null +++ b/tests/Stride.UI.Tests.Regression/Windows.Vulkan/SwiftShader/ScrollingTextTest.f4.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:2171ed0ce53645cebfd1363f4899dc7f90f3468f7921a87149f7e01259a149e8 +size 2422 diff --git a/tests/Stride.UI.Tests.Regression/Windows.Vulkan/SwiftShader/ScrollingTextTest.f5.png b/tests/Stride.UI.Tests.Regression/Windows.Vulkan/SwiftShader/ScrollingTextTest.f5.png new file mode 100644 index 0000000000..ad543e3908 --- /dev/null +++ b/tests/Stride.UI.Tests.Regression/Windows.Vulkan/SwiftShader/ScrollingTextTest.f5.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:cf931523288dcedf296630dd8d572decff9ee4979611384e1efbb6abe5b70c83 +size 3045 diff --git a/tests/Stride.UI.Tests.Regression/Windows.Vulkan/SwiftShader/ScrollingTextTest.f6.png b/tests/Stride.UI.Tests.Regression/Windows.Vulkan/SwiftShader/ScrollingTextTest.f6.png new file mode 100644 index 0000000000..4945a8335f --- /dev/null +++ b/tests/Stride.UI.Tests.Regression/Windows.Vulkan/SwiftShader/ScrollingTextTest.f6.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:9226628ee4941e262b6f6ea6b166b3b6e032e1c50c8788943133edd575142dda +size 2987 diff --git a/tests/Stride.UI.Tests.Regression/Windows.Vulkan/SwiftShader/SliderTest.f1.png b/tests/Stride.UI.Tests.Regression/Windows.Vulkan/SwiftShader/SliderTest.f1.png new file mode 100644 index 0000000000..80d6ef9e56 --- /dev/null +++ b/tests/Stride.UI.Tests.Regression/Windows.Vulkan/SwiftShader/SliderTest.f1.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:479c17540efb01bcd701c284ff129508b5e8f92f16a2b4d01214802688651ff6 +size 11773 diff --git a/tests/Stride.UI.Tests.Regression/Windows.Vulkan/SwiftShader/SliderTest.f10.png b/tests/Stride.UI.Tests.Regression/Windows.Vulkan/SwiftShader/SliderTest.f10.png new file mode 100644 index 0000000000..307d1adfe1 --- /dev/null +++ b/tests/Stride.UI.Tests.Regression/Windows.Vulkan/SwiftShader/SliderTest.f10.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:b0c4eef969c8a8561a54d704b8321d6194b09900886a21a1c7e06d61f214cf4f +size 7281 diff --git a/tests/Stride.UI.Tests.Regression/Windows.Vulkan/SwiftShader/SliderTest.f2.png b/tests/Stride.UI.Tests.Regression/Windows.Vulkan/SwiftShader/SliderTest.f2.png new file mode 100644 index 0000000000..4ae8cb213c --- /dev/null +++ b/tests/Stride.UI.Tests.Regression/Windows.Vulkan/SwiftShader/SliderTest.f2.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:6727f45358b45067a115cae3503eeb045751fdc9eeed37257c071c695be6fc2f +size 11259 diff --git a/tests/Stride.UI.Tests.Regression/Windows.Vulkan/SwiftShader/TextBlockTest.f1.png b/tests/Stride.UI.Tests.Regression/Windows.Vulkan/SwiftShader/TextBlockTest.f1.png new file mode 100644 index 0000000000..267f2cc1d7 --- /dev/null +++ b/tests/Stride.UI.Tests.Regression/Windows.Vulkan/SwiftShader/TextBlockTest.f1.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:8cbca36c272d623bb8f7564eba04e8a137bba27e9a84617109bff46f591e3344 +size 5064 diff --git a/tests/Stride.UI.Tests.Regression/Windows.Vulkan/SwiftShader/TextBlockTest.f10.png b/tests/Stride.UI.Tests.Regression/Windows.Vulkan/SwiftShader/TextBlockTest.f10.png new file mode 100644 index 0000000000..7aec3ed24f --- /dev/null +++ b/tests/Stride.UI.Tests.Regression/Windows.Vulkan/SwiftShader/TextBlockTest.f10.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:dba716c7c6042eb8f9ed7d4c17546fd1d2e6f89c615c32c9b4755b540c3ec9cf +size 5177 diff --git a/tests/Stride.UI.Tests.Regression/Windows.Vulkan/SwiftShader/TextBlockTest.f11.png b/tests/Stride.UI.Tests.Regression/Windows.Vulkan/SwiftShader/TextBlockTest.f11.png new file mode 100644 index 0000000000..3beefdc587 --- /dev/null +++ b/tests/Stride.UI.Tests.Regression/Windows.Vulkan/SwiftShader/TextBlockTest.f11.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:3cecf36b81d7619f634f99c259d40204779327e572627ebb6dd06eb612b1beb7 +size 8342 diff --git a/tests/Stride.UI.Tests.Regression/Windows.Vulkan/SwiftShader/TextBlockTest.f12.png b/tests/Stride.UI.Tests.Regression/Windows.Vulkan/SwiftShader/TextBlockTest.f12.png new file mode 100644 index 0000000000..15ee15916a --- /dev/null +++ b/tests/Stride.UI.Tests.Regression/Windows.Vulkan/SwiftShader/TextBlockTest.f12.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:0dead405c629cee97ce1c01fd053a17241804dbe079d93dfcab61f86c726dc8c +size 3677 diff --git a/tests/Stride.UI.Tests.Regression/Windows.Vulkan/SwiftShader/TextBlockTest.f13.png b/tests/Stride.UI.Tests.Regression/Windows.Vulkan/SwiftShader/TextBlockTest.f13.png new file mode 100644 index 0000000000..3beefdc587 --- /dev/null +++ b/tests/Stride.UI.Tests.Regression/Windows.Vulkan/SwiftShader/TextBlockTest.f13.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:3cecf36b81d7619f634f99c259d40204779327e572627ebb6dd06eb612b1beb7 +size 8342 diff --git a/tests/Stride.UI.Tests.Regression/Windows.Vulkan/SwiftShader/TextBlockTest.f14.png b/tests/Stride.UI.Tests.Regression/Windows.Vulkan/SwiftShader/TextBlockTest.f14.png new file mode 100644 index 0000000000..15ee15916a --- /dev/null +++ b/tests/Stride.UI.Tests.Regression/Windows.Vulkan/SwiftShader/TextBlockTest.f14.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:0dead405c629cee97ce1c01fd053a17241804dbe079d93dfcab61f86c726dc8c +size 3677 diff --git a/tests/Stride.UI.Tests.Regression/Windows.Vulkan/SwiftShader/TextBlockTest.f2.png b/tests/Stride.UI.Tests.Regression/Windows.Vulkan/SwiftShader/TextBlockTest.f2.png new file mode 100644 index 0000000000..5198d08a93 --- /dev/null +++ b/tests/Stride.UI.Tests.Regression/Windows.Vulkan/SwiftShader/TextBlockTest.f2.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:4dd102edfbf0b8a25a08eaa74d6dec7a96231e90392e63327c4593e59c53e23d +size 5035 diff --git a/tests/Stride.UI.Tests.Regression/Windows.Vulkan/SwiftShader/TextBlockTest.f3.png b/tests/Stride.UI.Tests.Regression/Windows.Vulkan/SwiftShader/TextBlockTest.f3.png new file mode 100644 index 0000000000..ad3ad102b3 --- /dev/null +++ b/tests/Stride.UI.Tests.Regression/Windows.Vulkan/SwiftShader/TextBlockTest.f3.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:9cb75b5c289884023a8bce3e13d83d1d7bde11e25417ba94d4535d35f94f8f27 +size 5107 diff --git a/tests/Stride.UI.Tests.Regression/Windows.Vulkan/SwiftShader/TextBlockTest.f4.png b/tests/Stride.UI.Tests.Regression/Windows.Vulkan/SwiftShader/TextBlockTest.f4.png new file mode 100644 index 0000000000..367cfdba56 --- /dev/null +++ b/tests/Stride.UI.Tests.Regression/Windows.Vulkan/SwiftShader/TextBlockTest.f4.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:fe4207bd782cb3597274c01ca087ea896fd39efe02e0a5979ff64768a4ec5254 +size 5107 diff --git a/tests/Stride.UI.Tests.Regression/Windows.Vulkan/SwiftShader/TextBlockTest.f5.png b/tests/Stride.UI.Tests.Regression/Windows.Vulkan/SwiftShader/TextBlockTest.f5.png new file mode 100644 index 0000000000..8439ea021b --- /dev/null +++ b/tests/Stride.UI.Tests.Regression/Windows.Vulkan/SwiftShader/TextBlockTest.f5.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:00d62f28a6d86687c244f29b8003d0aaa2ec3e8ed72fa3d28466c6b5a5af716c +size 5113 diff --git a/tests/Stride.UI.Tests.Regression/Windows.Vulkan/SwiftShader/TextBlockTest.f6.png b/tests/Stride.UI.Tests.Regression/Windows.Vulkan/SwiftShader/TextBlockTest.f6.png new file mode 100644 index 0000000000..3c1fb75ce6 --- /dev/null +++ b/tests/Stride.UI.Tests.Regression/Windows.Vulkan/SwiftShader/TextBlockTest.f6.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e39928b801a8b0c5b684b3c5db994c6723e47f291ae45c8f1214ecdef6c067a6 +size 5156 diff --git a/tests/Stride.UI.Tests.Regression/Windows.Vulkan/SwiftShader/TextBlockTest.f7.png b/tests/Stride.UI.Tests.Regression/Windows.Vulkan/SwiftShader/TextBlockTest.f7.png new file mode 100644 index 0000000000..85af66768d --- /dev/null +++ b/tests/Stride.UI.Tests.Regression/Windows.Vulkan/SwiftShader/TextBlockTest.f7.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:4b45ec639012993551ac4c0249331eb3a71439681956ac03cea6bfebde00d47e +size 5128 diff --git a/tests/Stride.UI.Tests.Regression/Windows.Vulkan/SwiftShader/TextBlockTest.f8.png b/tests/Stride.UI.Tests.Regression/Windows.Vulkan/SwiftShader/TextBlockTest.f8.png new file mode 100644 index 0000000000..6449d647d9 --- /dev/null +++ b/tests/Stride.UI.Tests.Regression/Windows.Vulkan/SwiftShader/TextBlockTest.f8.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:2d61f7fbf79b1b356d3d06d7da57b7d0d002fa258368d7a6e584ba042021d36b +size 5168 diff --git a/tests/Stride.UI.Tests.Regression/Windows.Vulkan/SwiftShader/TextBlockTest.f9.png b/tests/Stride.UI.Tests.Regression/Windows.Vulkan/SwiftShader/TextBlockTest.f9.png new file mode 100644 index 0000000000..0303821170 --- /dev/null +++ b/tests/Stride.UI.Tests.Regression/Windows.Vulkan/SwiftShader/TextBlockTest.f9.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:7abcd35b2ffb68032a55e3eeefe710cba724d41518eed6b69c561bb39829eadb +size 5163 diff --git a/tests/Stride.UI.Tests.Regression/Windows.Vulkan/SwiftShader/TextBlockTest.png b/tests/Stride.UI.Tests.Regression/Windows.Vulkan/SwiftShader/TextBlockTest.png new file mode 100644 index 0000000000..cabdc22adb --- /dev/null +++ b/tests/Stride.UI.Tests.Regression/Windows.Vulkan/SwiftShader/TextBlockTest.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:b5e7d58cf9feef8c271ee75612474d10756f7a2acd57dd66dea1f9af997d87e2 +size 5101 diff --git a/tests/Stride.UI.Tests.Regression/Windows.Vulkan/SwiftShader/TextBlockWrappingTest.f1.png b/tests/Stride.UI.Tests.Regression/Windows.Vulkan/SwiftShader/TextBlockWrappingTest.f1.png new file mode 100644 index 0000000000..6b9fa78b5b --- /dev/null +++ b/tests/Stride.UI.Tests.Regression/Windows.Vulkan/SwiftShader/TextBlockWrappingTest.f1.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d8ff85742ccb10f4f2dde1bbde15b4c10699045bb46ea6cf1c75804311ad0eae +size 9594 diff --git a/tests/Stride.UI.Tests.Regression/Windows.Vulkan/SwiftShader/TextBlockWrappingTest.f2.png b/tests/Stride.UI.Tests.Regression/Windows.Vulkan/SwiftShader/TextBlockWrappingTest.f2.png new file mode 100644 index 0000000000..6346bdbe09 --- /dev/null +++ b/tests/Stride.UI.Tests.Regression/Windows.Vulkan/SwiftShader/TextBlockWrappingTest.f2.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:2e1040d972680ed4a40cd7055705c61cc4c35ff6d7f70df8cddcc6599c58cc05 +size 9590 diff --git a/tests/Stride.UI.Tests.Regression/Windows.Vulkan/SwiftShader/TextBlockWrappingTest.f3.png b/tests/Stride.UI.Tests.Regression/Windows.Vulkan/SwiftShader/TextBlockWrappingTest.f3.png new file mode 100644 index 0000000000..6ce146a83b --- /dev/null +++ b/tests/Stride.UI.Tests.Regression/Windows.Vulkan/SwiftShader/TextBlockWrappingTest.f3.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:463e11bdb62b24abcaf4e9b2f2fced658d9f7e82efc72cf0779ae472e2b35116 +size 9601 diff --git a/tests/Stride.UI.Tests.Regression/Windows.Vulkan/SwiftShader/TextBlockWrappingTest.f4.png b/tests/Stride.UI.Tests.Regression/Windows.Vulkan/SwiftShader/TextBlockWrappingTest.f4.png new file mode 100644 index 0000000000..6b9fa78b5b --- /dev/null +++ b/tests/Stride.UI.Tests.Regression/Windows.Vulkan/SwiftShader/TextBlockWrappingTest.f4.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d8ff85742ccb10f4f2dde1bbde15b4c10699045bb46ea6cf1c75804311ad0eae +size 9594 diff --git a/tests/Stride.UI.Tests.Regression/Windows.Vulkan/SwiftShader/TextBlockWrappingTest.f5.png b/tests/Stride.UI.Tests.Regression/Windows.Vulkan/SwiftShader/TextBlockWrappingTest.f5.png new file mode 100644 index 0000000000..6b9fa78b5b --- /dev/null +++ b/tests/Stride.UI.Tests.Regression/Windows.Vulkan/SwiftShader/TextBlockWrappingTest.f5.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d8ff85742ccb10f4f2dde1bbde15b4c10699045bb46ea6cf1c75804311ad0eae +size 9594 diff --git a/tests/Stride.UI.Tests.Regression/Windows.Vulkan/SwiftShader/TextBlockWrappingTest.f6.png b/tests/Stride.UI.Tests.Regression/Windows.Vulkan/SwiftShader/TextBlockWrappingTest.f6.png new file mode 100644 index 0000000000..6b9fa78b5b --- /dev/null +++ b/tests/Stride.UI.Tests.Regression/Windows.Vulkan/SwiftShader/TextBlockWrappingTest.f6.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d8ff85742ccb10f4f2dde1bbde15b4c10699045bb46ea6cf1c75804311ad0eae +size 9594 diff --git a/tests/Stride.UI.Tests.Regression/Windows.Vulkan/SwiftShader/TextBlockWrappingTest.f7.png b/tests/Stride.UI.Tests.Regression/Windows.Vulkan/SwiftShader/TextBlockWrappingTest.f7.png new file mode 100644 index 0000000000..6b9fa78b5b --- /dev/null +++ b/tests/Stride.UI.Tests.Regression/Windows.Vulkan/SwiftShader/TextBlockWrappingTest.f7.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d8ff85742ccb10f4f2dde1bbde15b4c10699045bb46ea6cf1c75804311ad0eae +size 9594 diff --git a/tests/Stride.UI.Tests.Regression/Windows.Vulkan/SwiftShader/TextBlockWrappingTest.f8.png b/tests/Stride.UI.Tests.Regression/Windows.Vulkan/SwiftShader/TextBlockWrappingTest.f8.png new file mode 100644 index 0000000000..30b1767bca --- /dev/null +++ b/tests/Stride.UI.Tests.Regression/Windows.Vulkan/SwiftShader/TextBlockWrappingTest.f8.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:075add9e55c0abd875f4d34d21842dcb0751bb7b288b49bdc70be31ebc091e02 +size 9590 diff --git a/tests/Stride.UI.Tests.Regression/Windows.Vulkan/SwiftShader/TextBlockWrappingTest.f9.png b/tests/Stride.UI.Tests.Regression/Windows.Vulkan/SwiftShader/TextBlockWrappingTest.f9.png new file mode 100644 index 0000000000..a9774e3f28 --- /dev/null +++ b/tests/Stride.UI.Tests.Regression/Windows.Vulkan/SwiftShader/TextBlockWrappingTest.f9.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f797426bce6393c6f967fad0d49d0cc98d983c612f9ec66342a5c43ac1017847 +size 9584 diff --git a/tests/Stride.UI.Tests.Regression/Windows.Vulkan/SwiftShader/TextBlockWrappingTest.png b/tests/Stride.UI.Tests.Regression/Windows.Vulkan/SwiftShader/TextBlockWrappingTest.png new file mode 100644 index 0000000000..570f71c224 --- /dev/null +++ b/tests/Stride.UI.Tests.Regression/Windows.Vulkan/SwiftShader/TextBlockWrappingTest.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:8a8d5c26f58238befb1d7862d5c4ec8b74d7906b31cac6772cf8a0de7c844e71 +size 8030 diff --git a/tests/Stride.UI.Tests.Regression/Windows.Vulkan/SwiftShader/TransparencyTest.f1.png b/tests/Stride.UI.Tests.Regression/Windows.Vulkan/SwiftShader/TransparencyTest.f1.png new file mode 100644 index 0000000000..d44da64a39 --- /dev/null +++ b/tests/Stride.UI.Tests.Regression/Windows.Vulkan/SwiftShader/TransparencyTest.f1.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:4dff181e9b33b792fd9059f77fe30f667859cfa87f76feb159e3d2f9dcfa1ca2 +size 17783 diff --git a/tests/Stride.UI.Tests.Regression/Windows.Vulkan/SwiftShader/TransparencyTest.png b/tests/Stride.UI.Tests.Regression/Windows.Vulkan/SwiftShader/TransparencyTest.png new file mode 100644 index 0000000000..4be23f004b --- /dev/null +++ b/tests/Stride.UI.Tests.Regression/Windows.Vulkan/SwiftShader/TransparencyTest.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:846555da06378cee709563c3a3c764c91661f339d93140706abf59cb76eb9e64 +size 26536 diff --git a/tests/Stride.UI.Tests.Regression/Windows.Vulkan/SwiftShader/UniformGridTest.png b/tests/Stride.UI.Tests.Regression/Windows.Vulkan/SwiftShader/UniformGridTest.png new file mode 100644 index 0000000000..2a85d84439 --- /dev/null +++ b/tests/Stride.UI.Tests.Regression/Windows.Vulkan/SwiftShader/UniformGridTest.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:9b69be21e3eb0635e6d00725ad80cf225cf3c19fa9a6d1fa424df4f8d2095bd6 +size 24378