diff --git a/.github/workflows/build-android.yml b/.github/workflows/build-android.yml index 80600dc4d5..be984fe1db 100644 --- a/.github/workflows/build-android.yml +++ b/.github/workflows/build-android.yml @@ -49,6 +49,7 @@ jobs: - uses: actions/checkout@v4 with: lfs: true + submodules: true - uses: actions/setup-dotnet@v4 with: dotnet-version: '10.0.x' diff --git a/.github/workflows/build-ios.yml b/.github/workflows/build-ios.yml index 3dcc3e8c4c..94257e1de2 100644 --- a/.github/workflows/build-ios.yml +++ b/.github/workflows/build-ios.yml @@ -49,6 +49,7 @@ jobs: - uses: actions/checkout@v4 with: lfs: true + submodules: true - uses: actions/setup-dotnet@v4 with: dotnet-version: '10.0.x' diff --git a/.github/workflows/build-linux-runtime.yml b/.github/workflows/build-linux-runtime.yml index b3c1604246..5099b8ac55 100644 --- a/.github/workflows/build-linux-runtime.yml +++ b/.github/workflows/build-linux-runtime.yml @@ -49,6 +49,7 @@ jobs: - uses: actions/checkout@v4 with: lfs: true + submodules: true - uses: actions/setup-dotnet@v4 with: dotnet-version: '10.0.x' diff --git a/.github/workflows/build-vs-package.yml b/.github/workflows/build-vs-package.yml index 2cc561a42e..bc0ba094c2 100644 --- a/.github/workflows/build-vs-package.yml +++ b/.github/workflows/build-vs-package.yml @@ -32,6 +32,7 @@ jobs: - uses: actions/checkout@v4 with: lfs: true + submodules: true - uses: actions/setup-dotnet@v4 with: dotnet-version: '10.0.x' diff --git a/.github/workflows/build-windows-full.yml b/.github/workflows/build-windows-full.yml index 220c16998d..51d1b95388 100644 --- a/.github/workflows/build-windows-full.yml +++ b/.github/workflows/build-windows-full.yml @@ -44,6 +44,7 @@ jobs: - uses: actions/checkout@v4 with: lfs: true + submodules: true - uses: actions/setup-dotnet@v4 with: dotnet-version: '10.0.x' diff --git a/.github/workflows/build-windows-runtime.yml b/.github/workflows/build-windows-runtime.yml index 88c48d2b23..bbd7ba8727 100644 --- a/.github/workflows/build-windows-runtime.yml +++ b/.github/workflows/build-windows-runtime.yml @@ -60,6 +60,7 @@ jobs: - uses: actions/checkout@v4 with: lfs: true + submodules: true - uses: actions/setup-dotnet@v4 with: dotnet-version: '10.0.x' diff --git a/.github/workflows/dep-freetype.yml b/.github/workflows/dep-freetype.yml index 2b5c02acea..1fc70dc252 100644 --- a/.github/workflows/dep-freetype.yml +++ b/.github/workflows/dep-freetype.yml @@ -227,7 +227,7 @@ jobs: needs: [build-windows, build-linux, build-macos, build-android, build-ios] runs-on: ubuntu-24.04 steps: - - name: Checkout (for version info) + - name: Checkout FreeType (for version info) uses: actions/checkout@v4 with: repository: freetype/freetype @@ -262,9 +262,12 @@ jobs: # Version info COMMIT=$(git -C freetype-src rev-parse HEAD) - printf 'FreeType %s\nRepository: https://github.com/freetype/freetype\nCommit: %s\nBuilt: %s\nWorkflow: %s\n' \ + URL=$(git -C freetype-src config --get remote.origin.url) + printf 'FreeType %s\nRepository: %s\nCommit: %s\nStride: %s/%s @ %s\nBuilt: %s\nWorkflow: %s\n' \ "${{ github.event.inputs.freetype-version }}" \ + "$URL" \ "$COMMIT" \ + "${{ github.server_url }}" "${{ github.repository }}" "${{ github.sha }}" \ "$(date -u +%Y-%m-%d)" \ "${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}" \ > $OUT/VERSION.txt diff --git a/.github/workflows/dep-lavapipe.yml b/.github/workflows/dep-lavapipe.yml index daedf0b054..9da7eed1f6 100644 --- a/.github/workflows/dep-lavapipe.yml +++ b/.github/workflows/dep-lavapipe.yml @@ -1,8 +1,18 @@ name: "Dep: Build & Deploy Lavapipe" -# Skeleton workflow — the full implementation lives on a feature branch. -# This exists on master only so the workflow is triggerable via workflow_dispatch -# with the expected inputs. Checkout the feature branch ref to run the real build. +# Skeleton — not yet exercised. Notes on per-OS friction at the bottom. +# +# Lavapipe is built from Mesa with: +# meson setup _build -Dvulkan-drivers=swrast -Dgallium-drivers=llvmpipe \ +# -Dgallium-extra-hud=false -Dvideo-codecs= \ +# -Dplatforms=windows|x11,wayland|... \ +# -Dbuildtype=release +# +# Outputs (per OS): +# Windows: vulkan_lvp.dll + lvp_icd.x86_64.json (or lvp_icd.json) +# Linux: libvulkan_lvp.so + lvp_icd.x86_64.json +# macOS: libvulkan_lvp.dylib + lvp_icd.x86_64.json +# The ICD JSON's `library_path` may need rewriting to a side-by-side path. on: workflow_dispatch: @@ -20,11 +30,348 @@ on: required: false jobs: - placeholder: - name: Skeleton (run on feature branch for real build) - runs-on: ubuntu-latest + build-windows: + name: Build Lavapipe (Windows x64) + runs-on: windows-2025-vs2026 steps: - - name: Notice + - name: Checkout Stride + uses: actions/checkout@v4 + with: + path: stride-src + sparse-checkout: build/deps/lavapipe + + - name: Clone Mesa + shell: pwsh + run: | + $url = "${{ github.event.inputs.repository || 'https://gitlab.freedesktop.org/mesa/mesa.git' }}" + $ref = "${{ github.event.inputs.ref || 'main' }}" + git init mesa-src + git -C mesa-src remote add origin $url + git -C mesa-src fetch --depth 1 origin $ref + git -C mesa-src checkout FETCH_HEAD + + - name: Install build dependencies + shell: pwsh + run: | + choco install -y winflexbison3 + python -m pip install --upgrade pip + python -m pip install meson ninja mako pyyaml packaging + + # Provides glslangValidator (and headers/loader we don't strictly need). + - name: Install Vulkan SDK + uses: jakoch/install-vulkan-sdk-action@v1 + with: + vulkan_version: 1.4.304.1 + install_runtime: false + cache: true + stripdown: true + + - name: Setup MSVC environment + uses: ilammy/msvc-dev-cmd@v1 + with: + arch: x64 + + # LLVM is statically linked into vulkan_lvp.dll so the output has no host-LLVM + # dependency. We build it once per (LLVM version × MSVC version) and cache. + # Bump the cache key suffix to force a rebuild. + # Split restore/save so the built LLVM persists even if later steps fail. + - name: Restore LLVM cache + id: restore-llvm + uses: actions/cache/restore@v4 + with: + path: llvm-install + key: llvm-21.1.0-msvc2026-static-x64-v1 + + - name: Build LLVM (cache miss) + if: steps.restore-llvm.outputs.cache-hit != 'true' + shell: cmd + run: | + git clone --depth 1 --branch llvmorg-21.1.0 https://github.com/llvm/llvm-project.git + cmake -S llvm-project/llvm -B llvm-build -G Ninja ^ + -DCMAKE_BUILD_TYPE=Release ^ + -DCMAKE_INSTALL_PREFIX=%CD%\llvm-install ^ + -DLLVM_BUILD_LLVM_DYLIB=OFF ^ + -DLLVM_LINK_LLVM_DYLIB=OFF ^ + -DLLVM_ENABLE_PROJECTS= ^ + -DLLVM_TARGETS_TO_BUILD=X86;AArch64 ^ + -DLLVM_ENABLE_ASSERTIONS=OFF ^ + -DLLVM_INCLUDE_TESTS=OFF ^ + -DLLVM_INCLUDE_EXAMPLES=OFF ^ + -DLLVM_INCLUDE_BENCHMARKS=OFF + cmake --build llvm-build --target install + + # Save cache immediately after build, independent of subsequent step outcomes. + - name: Save LLVM cache + if: steps.restore-llvm.outputs.cache-hit != 'true' + uses: actions/cache/save@v4 + with: + path: llvm-install + key: llvm-21.1.0-msvc2026-static-x64-v1 + + - name: Configure Mesa (Lavapipe only, static LLVM) + shell: cmd + working-directory: mesa-src + env: + LLVM_CONFIG: ${{ github.workspace }}\llvm-install\bin\llvm-config.exe + # choco winflexbison3 installs win_flex.exe / win_bison.exe. Mesa's meson + # looks for `flex`/`bison`; these env vars redirect the lookup. + FLEX: win_flex + LEX: win_flex + BISON: win_bison + YACC: win_bison + run: | + set PYTHONUTF8=1 + set PATH=${{ github.workspace }}\llvm-install\bin;%PATH% + meson setup _build ^ + -Dvulkan-drivers=swrast ^ + -Dgallium-drivers=llvmpipe ^ + -Dplatforms=windows ^ + -Dc_args="/wd4189" ^ + -Dcpp_args="/wd4189" ^ + -Dglx=disabled -Degl=disabled ^ + -Dopengl=false -Dgles1=disabled -Dgles2=disabled ^ + -Dgallium-extra-hud=false ^ + -Dvideo-codecs= ^ + -Dllvm=enabled ^ + -Dshared-llvm=disabled ^ + -Dbuildtype=release + + - name: Build vulkan_lvp.dll + shell: cmd + working-directory: mesa-src + run: | + set PYTHONUTF8=1 + meson compile -C _build src/gallium/targets/lavapipe/vulkan_lvp:shared_library + + - name: Collect output + shell: pwsh + run: | + mkdir lavapipe-out + $dll = Get-ChildItem -Recurse mesa-src/_build -Filter vulkan_lvp.dll | Select-Object -First 1 + if (-not $dll) { throw "vulkan_lvp.dll not found in build output" } + Copy-Item $dll.FullName lavapipe-out/ + # ICD manifest with relative library_path. Windows Vulkan loader requires a + # backslash here — forward-slash `./vulkan_lvp.dll` loads the DLL but every + # subsequent Vulkan call returns VK_ERROR_OUT_OF_HOST_MEMORY. + Set-Content -NoNewline -Path lavapipe-out/lvp_icd.json -Value '{"file_format_version":"1.0.0","ICD":{"library_path":".\\vulkan_lvp.dll","api_version":"1.3.0"}}' + + - name: Upload artifact + uses: actions/upload-artifact@v4 + with: + name: lavapipe-win-x64 + path: lavapipe-out/ + + build-linux: + name: Build Lavapipe (Linux x64) + runs-on: ubuntu-24.04 + steps: + - name: Checkout Stride + uses: actions/checkout@v4 + with: + path: stride-src + sparse-checkout: build/deps/lavapipe + + - name: Clone Mesa + run: | + url="${{ github.event.inputs.repository || 'https://gitlab.freedesktop.org/mesa/mesa.git' }}" + ref="${{ github.event.inputs.ref || 'main' }}" + git init mesa-src + git -C mesa-src remote add origin "$url" + git -C mesa-src fetch --depth 1 origin "$ref" + git -C mesa-src checkout FETCH_HEAD + + - name: Install build dependencies + run: | + sudo apt-get update + # Build with x11,wayland Vulkan WSI so swapchains work under Xvfb/WSLg/etc. + # libxcb/libwayland are dlopen'd lazily — headless callers never pay the cost. + # llvm-dev + libpolly-*-dev because llvm-config emits -lPolly -lPollyISL. + sudo apt-get install -y ninja-build pkg-config bison flex glslang-tools \ + llvm-dev libpolly-18-dev libelf-dev libdrm-dev \ + libx11-xcb-dev libxrandr-dev libxext-dev libxfixes-dev libxxf86vm-dev libxdamage-dev \ + libxcb-randr0-dev libxcb-shm0-dev libxcb-present-dev \ + libxcb-dri3-dev libxcb-dri2-0-dev libxcb-glx0-dev libxcb-sync-dev \ + libxshmfence-dev \ + libwayland-dev wayland-protocols libwayland-egl-backend-dev + # Ubuntu 24.04 ships meson 1.3.2; Mesa requires >= 1.4.0 — use pip. + python3 -m pip install --user --upgrade meson mako packaging pyyaml + echo "$HOME/.local/bin" >> $GITHUB_PATH + + - name: Configure & build + working-directory: mesa-src + run: | + meson setup _build \ + -Dvulkan-drivers=swrast -Dgallium-drivers=llvmpipe \ + -Dplatforms=x11,wayland \ + -Dglx=disabled -Degl=disabled \ + -Dopengl=false -Dgles1=disabled -Dgles2=disabled \ + -Dgallium-extra-hud=false -Dvideo-codecs= \ + -Dllvm=enabled -Dshared-llvm=disabled \ + -Dbuildtype=release + ninja -C _build src/gallium/targets/lavapipe/libvulkan_lvp.so + + - name: Collect output + run: | + mkdir -p lavapipe-out + find mesa-src/_build -name "libvulkan_lvp.so*" -exec cp {} lavapipe-out/ \; + # ICD manifest with relative library_path + printf '%s' '{"file_format_version":"1.0.0","ICD":{"library_path":"./libvulkan_lvp.so","api_version":"1.3.0"}}' > lavapipe-out/lvp_icd.json + ls -la lavapipe-out/ + + - name: Upload artifact + uses: actions/upload-artifact@v4 + with: + name: lavapipe-linux-x64 + path: lavapipe-out/ + + build-macos: + name: Build Lavapipe (macOS ARM64) + runs-on: macos-15 + steps: + - name: Checkout Stride + uses: actions/checkout@v4 + with: + path: stride-src + sparse-checkout: build/deps/lavapipe + + - name: Clone Mesa + run: | + url="${{ github.event.inputs.repository || 'https://gitlab.freedesktop.org/mesa/mesa.git' }}" + ref="${{ github.event.inputs.ref || 'main' }}" + git init mesa-src + git -C mesa-src remote add origin "$url" + git -C mesa-src fetch --depth 1 origin "$ref" + git -C mesa-src checkout FETCH_HEAD + + - name: Install build dependencies + run: | + brew install meson ninja pkg-config llvm bison flex glslang + pip3 install --break-system-packages mako packaging pyyaml + + - name: Configure & build + working-directory: mesa-src + env: + PKG_CONFIG_PATH: /opt/homebrew/opt/llvm/lib/pkgconfig:/opt/homebrew/opt/zstd/lib/pkgconfig + PATH: /opt/homebrew/opt/llvm/bin:/opt/homebrew/opt/bison/bin:/opt/homebrew/opt/flex/bin:/opt/homebrew/bin:/usr/local/bin:/usr/bin:/bin + LIBRARY_PATH: /opt/homebrew/opt/zstd/lib:/opt/homebrew/opt/llvm/lib + LDFLAGS: -L/opt/homebrew/opt/zstd/lib -L/opt/homebrew/opt/llvm/lib + run: | + # macOS lavapipe is lightly tested — expect rough edges. + # -Dplatforms=macos exposes Mesa's experimental IOSurface-based WSI so + # VK_KHR_swapchain is available alongside MoltenVK when both ICDs load. + meson setup _build \ + -Dvulkan-drivers=swrast -Dgallium-drivers=llvmpipe \ + -Dplatforms=macos \ + -Dglx=disabled -Degl=disabled \ + -Dopengl=false -Dgles1=disabled -Dgles2=disabled \ + -Dgallium-extra-hud=false -Dvideo-codecs= \ + -Dllvm=enabled -Dshared-llvm=disabled \ + -Dbuildtype=release + ninja -C _build src/gallium/targets/lavapipe/libvulkan_lvp.dylib + + - name: Collect output + run: | + mkdir -p lavapipe-out + find mesa-src/_build -name "libvulkan_lvp*.dylib" -exec cp {} lavapipe-out/ \; + # ICD manifest with relative library_path + printf '%s' '{"file_format_version":"1.0.0","ICD":{"library_path":"./libvulkan_lvp.dylib","api_version":"1.3.0"}}' > lavapipe-out/lvp_icd.json + ls -la lavapipe-out/ + + - name: Upload artifact + uses: actions/upload-artifact@v4 + with: + name: lavapipe-osx-arm64 + path: lavapipe-out/ + + pack: + name: Pack & Publish NuGet + needs: [build-windows, build-linux, build-macos] + runs-on: windows-2025-vs2026 + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Clone Mesa (for commit hash) + shell: pwsh + run: | + $url = "${{ github.event.inputs.repository || 'https://gitlab.freedesktop.org/mesa/mesa.git' }}" + $ref = "${{ github.event.inputs.ref || 'main' }}" + git init mesa-src + git -C mesa-src remote add origin $url + git -C mesa-src fetch --depth 1 origin $ref + git -C mesa-src checkout FETCH_HEAD + + - name: Download Windows artifact + uses: actions/download-artifact@v4 + with: { name: lavapipe-win-x64, path: artifacts/win-x64 } + + - name: Download Linux artifact + uses: actions/download-artifact@v4 + with: { name: lavapipe-linux-x64, path: artifacts/linux-x64 } + + - name: Download macOS artifact + uses: actions/download-artifact@v4 + with: { name: lavapipe-osx-arm64, path: artifacts/osx-arm64 } + + - name: Stage native payload into csproj + shell: pwsh + run: | + $destRoot = "build/deps/lavapipe/runtimes" + foreach ($rid in 'win-x64','linux-x64','osx-arm64') { + $native = "$destRoot/$rid/native" + New-Item -Path $native -ItemType Directory -Force | Out-Null + Copy-Item -Recurse artifacts/$rid/* $native + } + # Note: lvp_icd.json is included but unused — Lavapipe.cs generates + # its own JSON at runtime pointing to an absolute library_path. + + - name: Pack NuGet (dotnet pack) + shell: pwsh + run: | + $commitHash = git -C mesa-src rev-parse --short HEAD + $version = "${{ github.event.inputs.version }}" + if (-not $version) { $version = (Get-Date -Format "yyyy.M.d") } + dotnet pack build/deps/lavapipe/Stride.Dependencies.Lavapipe.csproj ` + -c Release ` + -p:PackageVersion=$version ` + -p:RepositoryCommit=$commitHash ` + -o nupkg + echo "PACKAGE_VERSION=$version" >> $env:GITHUB_ENV + + - name: Upload package artifact + uses: actions/upload-artifact@v4 + with: + name: Stride.Dependencies.Lavapipe.nupkg + path: nupkg/*.nupkg + + publish: + name: Sign & Publish to NuGet.org + needs: pack + runs-on: windows-2025-vs2026 + environment: production + steps: + - name: Install signing tool + run: dotnet tool install sign --tool-path ./sign-tool --version 0.9.0-beta.23127.3 + + - name: Download artifact + uses: actions/download-artifact@v4 + with: + name: Stride.Dependencies.Lavapipe.nupkg + + - name: Sign NuGet package + shell: pwsh + run: | + ./sign-tool/sign code azure-key-vault *.nupkg ` + --description "Stride" ` + --description-url "https://stride3d.net" ` + --publisher-name "Stride" ` + --azure-key-vault-tenant-id "${{ secrets.STRIDE_SIGN_TENANT_ID }}" ` + --azure-key-vault-client-id "${{ secrets.STRIDE_SIGN_CLIENT_ID }}" ` + --azure-key-vault-client-secret "${{ secrets.STRIDE_SIGN_CLIENT_SECRET }}" ` + --azure-key-vault-certificate "${{ secrets.STRIDE_SIGN_KEYVAULT_CERTIFICATE }}" ` + --azure-key-vault-url "https://${{ secrets.STRIDE_SIGN_KEYVAULT_NAME }}.vault.azure.net/" ` + -v Information + + - name: Publish run: | - echo "This is a skeleton. The real build lives on a feature branch." - echo "Trigger this workflow against that branch ref to run the actual build." + dotnet nuget push *.nupkg --api-key "${{ secrets.STRIDE_NUGET_API_KEY }}" --source https://api.nuget.org/v3/index.json --skip-duplicate diff --git a/.github/workflows/dep-spirv-to-dxil.yml b/.github/workflows/dep-spirv-to-dxil.yml index 8988e34d5b..9f6e79c9e2 100644 --- a/.github/workflows/dep-spirv-to-dxil.yml +++ b/.github/workflows/dep-spirv-to-dxil.yml @@ -1,9 +1,5 @@ name: "Dep: Build spirv_to_dxil (Mesa)" -# Skeleton workflow — the full implementation lives on the sdsl-rewrite branch. -# This exists on master only so the workflow is triggerable via workflow_dispatch -# with the expected inputs. Checkout the sdsl-rewrite branch ref to run the real build. - on: workflow_dispatch: inputs: @@ -17,11 +13,117 @@ on: required: false jobs: - placeholder: - name: Skeleton (run on sdsl-rewrite branch for real build) - runs-on: ubuntu-latest + build-windows: + name: Build spirv_to_dxil (Windows x64) + runs-on: windows-2025-vs2026 steps: - - name: Notice + - name: Checkout Stride (for patch file) + uses: actions/checkout@v4 + with: + path: stride-src + sparse-checkout: build/deps/spirv-to-dxil + + - name: Clone Mesa + shell: pwsh + run: | + $url = "${{ github.event.inputs.repository || 'https://gitlab.freedesktop.org/mesa/mesa.git' }}" + $ref = "${{ github.event.inputs.ref || 'main' }}" + git init mesa-src + # Keep LF line endings — our patch is LF, and on Windows runners + # autocrlf=true would otherwise add CR which breaks `git apply`. + git -C mesa-src config core.autocrlf false + git -C mesa-src config core.eol lf + git -C mesa-src remote add origin $url + git -C mesa-src fetch --depth 1 origin $ref + git -C mesa-src checkout FETCH_HEAD + + - name: Apply Stride pipeline patch + shell: pwsh + working-directory: mesa-src + run: | + git apply --3way --verbose "${{ github.workspace }}/stride-src/build/deps/spirv-to-dxil/mesa-pipeline.patch" + + - name: Install build dependencies + shell: pwsh + run: | + python -m pip install --upgrade pip + python -m pip install meson ninja mako pyyaml packaging + + - name: Setup MSVC environment + uses: ilammy/msvc-dev-cmd@v1 + with: + arch: x64 + + - name: Configure Mesa + shell: cmd + working-directory: mesa-src run: | - echo "This is a skeleton. The real build is on the sdsl-rewrite branch." - echo "Trigger this workflow against that branch ref to run the actual build." + set PYTHONUTF8=1 + meson setup _build ^ + --default-library=shared ^ + --buildtype=release ^ + --wrap-mode=default ^ + -Dc_args="/wd4189" ^ + -Dcpp_args="/wd4189" ^ + -Dshared-glapi=disabled ^ + -Dgles1=disabled ^ + -Dgles2=disabled ^ + -Dopengl=false ^ + -Degl=disabled ^ + -Dglx=disabled ^ + -Dvulkan-drivers= ^ + -Dgallium-drivers= ^ + -Dplatforms= ^ + -Dmicrosoft-clc=disabled ^ + -Dspirv-to-dxil=true ^ + -Dbuild-tests=false ^ + -Dllvm=disabled + + - name: Build + shell: cmd + working-directory: mesa-src + run: | + set PYTHONUTF8=1 + meson compile -C _build src/microsoft/spirv_to_dxil/spirv_to_dxil:shared_library + + - name: Install UPX + shell: pwsh + run: | + $ver = "4.2.4" + $url = "https://github.com/upx/upx/releases/download/v${ver}/upx-${ver}-win64.zip" + Invoke-WebRequest -Uri $url -OutFile upx.zip + Expand-Archive upx.zip -DestinationPath upx + echo "$PWD\upx\upx-${ver}-win64" | Out-File -FilePath $env:GITHUB_PATH -Encoding utf8 -Append + + - name: Compress DLL + shell: pwsh + run: | + $dll = Get-ChildItem -Recurse mesa-src/_build -Filter spirv_to_dxil.dll | Select-Object -First 1 + $before = (Get-Item $dll.FullName).Length + upx --best --lzma $dll.FullName + $after = (Get-Item $dll.FullName).Length + Write-Host "Before: $([math]::Round($before/1MB,2)) MB" + Write-Host "After: $([math]::Round($after/1MB,2)) MB" + + - name: Collect DLL + shell: pwsh + run: | + New-Item -ItemType Directory -Force -Path out | Out-Null + $dll = Get-ChildItem -Recurse mesa-src/_build -Filter spirv_to_dxil.dll | Select-Object -First 1 + Copy-Item $dll.FullName out/spirv_to_dxil.dll + $pdb = Get-ChildItem -Recurse mesa-src/_build -Filter spirv_to_dxil.pdb -ErrorAction SilentlyContinue | Select-Object -First 1 + if ($pdb) { Copy-Item $pdb.FullName out/spirv_to_dxil.pdb } + # Record commits for traceability + $mesaCommit = git -C mesa-src rev-parse HEAD + $mesaUrl = git -C mesa-src config --get remote.origin.url + @( + "Mesa: $mesaUrl @ $mesaCommit" + "Stride: ${{ github.server_url }}/${{ github.repository }} @ ${{ github.sha }} (for workflow and build/deps/spirv-to-dxil/mesa-pipeline.patch)" + ) | Out-File out/VERSION.txt + Get-ChildItem out + + - name: Upload artifact + uses: actions/upload-artifact@v4 + with: + name: spirv_to_dxil-win-x64 + path: out/ diff --git a/.github/workflows/dep-spirv-tools.yml b/.github/workflows/dep-spirv-tools.yml index 2803c382aa..0e93300c68 100644 --- a/.github/workflows/dep-spirv-tools.yml +++ b/.github/workflows/dep-spirv-tools.yml @@ -1,8 +1,22 @@ name: "Dep: Build & Deploy SPIRV-Tools" -# Skeleton workflow — the full implementation lives on a feature branch. -# This exists on master only so the workflow is triggerable via workflow_dispatch -# with the expected inputs. Checkout the feature branch ref to run the real build. +# Ships a single combined library — stride_spirv_tools — that bundles +# SPIRV-Tools-static + SPIRV-Tools-opt + a thin C shim (stride_spvopt_shim.cpp), +# re-exporting both the upstream libspirv.h C API (validator, etc.) and our +# stride_spv* optimizer entry points. See build/deps/spirv-tools/CMakeLists.txt. +# +# Upstream: https://github.com/KhronosGroup/SPIRV-Tools +# Dependencies fetched via `python utils/git-sync-deps` (gets SPIRV-Headers). +# +# Output per OS — one DLL to ship per RID: +# Windows: stride_spirv_tools.dll +# Linux: libstride_spirv_tools.so +# macOS: libstride_spirv_tools.dylib +# iOS: libstride_spirv_tools.dylib (cross-compiled on macOS) +# Android: libstride_spirv_tools.so (cross-compiled via NDK) +# +# RIDs covered: win-x64, win-arm64, linux-x64, osx-arm64, ios-arm64, +# android-arm64, android-x64. on: workflow_dispatch: @@ -20,11 +34,327 @@ on: required: false jobs: - placeholder: - name: Skeleton (run on feature branch for real build) - runs-on: ubuntu-latest + # --------------------------------------------------------------------------- + # Windows x64 + # --------------------------------------------------------------------------- + build-windows-x64: + name: Build SPIRV-Tools (Windows x64) + runs-on: windows-2025-vs2026 steps: - - name: Notice + - uses: actions/checkout@v4 + with: { path: stride-src, sparse-checkout: build/deps/spirv-tools } + + - name: Clone SPIRV-Tools + shell: pwsh + run: | + $url = "${{ github.event.inputs.repository || 'https://github.com/KhronosGroup/SPIRV-Tools.git' }}" + $ref = "${{ github.event.inputs.ref || 'main' }}" + git init spirv-tools-src + git -C spirv-tools-src remote add origin $url + git -C spirv-tools-src fetch --depth 1 origin $ref + git -C spirv-tools-src checkout FETCH_HEAD + python spirv-tools-src/utils/git-sync-deps + + - uses: ilammy/msvc-dev-cmd@v1 + with: { arch: x64 } + + - name: Configure & build stride_spirv_tools + shell: cmd + run: | + cmake -S stride-src/build/deps/spirv-tools -B _build -G Ninja ^ + -DCMAKE_BUILD_TYPE=Release ^ + -DSPIRV_TOOLS_SOURCE_DIR=%CD%/spirv-tools-src + cmake --build _build --target stride_spirv_tools + + - name: Collect output + shell: pwsh + run: | + mkdir spirv-tools-out + Copy-Item _build/stride_spirv_tools.dll spirv-tools-out/ + + - uses: actions/upload-artifact@v4 + with: { name: spirv-tools-win-x64, path: spirv-tools-out/ } + + # --------------------------------------------------------------------------- + # Windows ARM64 — same host, MSVC ARM64 cross-compile. + # --------------------------------------------------------------------------- + build-windows-arm64: + name: Build SPIRV-Tools (Windows ARM64) + runs-on: windows-2025-vs2026 + steps: + - uses: actions/checkout@v4 + with: { path: stride-src, sparse-checkout: build/deps/spirv-tools } + + - name: Clone SPIRV-Tools + shell: pwsh + run: | + git init spirv-tools-src + git -C spirv-tools-src remote add origin "${{ github.event.inputs.repository || 'https://github.com/KhronosGroup/SPIRV-Tools.git' }}" + git -C spirv-tools-src fetch --depth 1 origin "${{ github.event.inputs.ref || 'main' }}" + git -C spirv-tools-src checkout FETCH_HEAD + python spirv-tools-src/utils/git-sync-deps + + - uses: ilammy/msvc-dev-cmd@v1 + with: { arch: amd64_arm64 } + + - name: Configure & build stride_spirv_tools + shell: cmd + run: | + cmake -S stride-src/build/deps/spirv-tools -B _build -G Ninja ^ + -DCMAKE_BUILD_TYPE=Release ^ + -DSPIRV_TOOLS_SOURCE_DIR=%CD%/spirv-tools-src + cmake --build _build --target stride_spirv_tools + + - name: Collect output + shell: pwsh + run: | + mkdir spirv-tools-out + Copy-Item _build/stride_spirv_tools.dll spirv-tools-out/ + + - uses: actions/upload-artifact@v4 + with: { name: spirv-tools-win-arm64, path: spirv-tools-out/ } + + # --------------------------------------------------------------------------- + # Linux x64 + # --------------------------------------------------------------------------- + build-linux-x64: + name: Build SPIRV-Tools (Linux x64) + runs-on: ubuntu-24.04 + steps: + - uses: actions/checkout@v4 + with: { path: stride-src, sparse-checkout: build/deps/spirv-tools } + + - name: Clone SPIRV-Tools + run: | + git init spirv-tools-src + git -C spirv-tools-src remote add origin "${{ github.event.inputs.repository || 'https://github.com/KhronosGroup/SPIRV-Tools.git' }}" + git -C spirv-tools-src fetch --depth 1 origin "${{ github.event.inputs.ref || 'main' }}" + git -C spirv-tools-src checkout FETCH_HEAD + python3 spirv-tools-src/utils/git-sync-deps + + - name: Install build dependencies + run: sudo apt-get update && sudo apt-get install -y ninja-build cmake + + - name: Configure & build stride_spirv_tools + run: | + cmake -S stride-src/build/deps/spirv-tools -B _build -G Ninja \ + -DCMAKE_BUILD_TYPE=Release \ + -DSPIRV_TOOLS_SOURCE_DIR=$PWD/spirv-tools-src + cmake --build _build --target stride_spirv_tools + + - name: Collect output + run: | + mkdir -p spirv-tools-out + cp _build/libstride_spirv_tools.so spirv-tools-out/ + + - uses: actions/upload-artifact@v4 + with: { name: spirv-tools-linux-x64, path: spirv-tools-out/ } + + # --------------------------------------------------------------------------- + # macOS ARM64 + # --------------------------------------------------------------------------- + build-macos-arm64: + name: Build SPIRV-Tools (macOS ARM64) + runs-on: macos-15 + steps: + - uses: actions/checkout@v4 + with: { path: stride-src, sparse-checkout: build/deps/spirv-tools } + + - name: Clone SPIRV-Tools + run: | + git init spirv-tools-src + git -C spirv-tools-src remote add origin "${{ github.event.inputs.repository || 'https://github.com/KhronosGroup/SPIRV-Tools.git' }}" + git -C spirv-tools-src fetch --depth 1 origin "${{ github.event.inputs.ref || 'main' }}" + git -C spirv-tools-src checkout FETCH_HEAD + python3 spirv-tools-src/utils/git-sync-deps + + - name: Configure & build stride_spirv_tools + run: | + cmake -S stride-src/build/deps/spirv-tools -B _build -G Ninja \ + -DCMAKE_BUILD_TYPE=Release \ + -DCMAKE_OSX_ARCHITECTURES=arm64 \ + -DSPIRV_TOOLS_SOURCE_DIR=$PWD/spirv-tools-src + cmake --build _build --target stride_spirv_tools + + - name: Collect output + run: | + mkdir -p spirv-tools-out + cp _build/libstride_spirv_tools.dylib spirv-tools-out/ + + - uses: actions/upload-artifact@v4 + with: { name: spirv-tools-osx-arm64, path: spirv-tools-out/ } + + # --------------------------------------------------------------------------- + # iOS ARM64 — cross-compiled on macOS. + # Note: BUILD_SHARED_LIBS on iOS produces dylibs; if App Store requires + # static-only or .framework bundles, revisit once integration lands. + # --------------------------------------------------------------------------- + build-ios-arm64: + name: Build SPIRV-Tools (iOS ARM64) + runs-on: macos-15 + steps: + - uses: actions/checkout@v4 + with: { path: stride-src, sparse-checkout: build/deps/spirv-tools } + + - name: Clone SPIRV-Tools + run: | + git init spirv-tools-src + git -C spirv-tools-src remote add origin "${{ github.event.inputs.repository || 'https://github.com/KhronosGroup/SPIRV-Tools.git' }}" + git -C spirv-tools-src fetch --depth 1 origin "${{ github.event.inputs.ref || 'main' }}" + git -C spirv-tools-src checkout FETCH_HEAD + python3 spirv-tools-src/utils/git-sync-deps + + - name: Configure & build stride_spirv_tools + run: | + # Ninja, not Xcode: SPIRV-Tools' build-version.inc custom command is + # attached to both SPIRV-Tools-shared and SPIRV-Tools-static, which + # the Xcode "new build system" rejects (no common dependency). + # CMAKE_OSX_SYSROOT must be set explicitly because Ninja doesn't + # auto-detect the iOS SDK path like the Xcode generator does. + cmake -S stride-src/build/deps/spirv-tools -B _build -G Ninja \ + -DCMAKE_BUILD_TYPE=Release \ + -DCMAKE_SYSTEM_NAME=iOS \ + -DCMAKE_OSX_ARCHITECTURES=arm64 \ + -DCMAKE_OSX_SYSROOT=iphoneos \ + -DCMAKE_OSX_DEPLOYMENT_TARGET=13.0 \ + -DSPIRV_TOOLS_SOURCE_DIR=$PWD/spirv-tools-src + cmake --build _build --target stride_spirv_tools + + - name: Collect output + run: | + mkdir -p spirv-tools-out + cp _build/libstride_spirv_tools.dylib spirv-tools-out/ + + - uses: actions/upload-artifact@v4 + with: { name: spirv-tools-ios-arm64, path: spirv-tools-out/ } + + # --------------------------------------------------------------------------- + # Android — one job per ABI, cross-compiled via NDK toolchain file. + # --------------------------------------------------------------------------- + build-android: + name: Build SPIRV-Tools (Android ${{ matrix.abi }}) + runs-on: ubuntu-24.04 + strategy: + fail-fast: false + matrix: + include: + - { abi: arm64-v8a, rid: android-arm64 } + - { abi: x86_64, rid: android-x64 } + steps: + - uses: actions/checkout@v4 + with: { path: stride-src, sparse-checkout: build/deps/spirv-tools } + + - name: Clone SPIRV-Tools + run: | + git init spirv-tools-src + git -C spirv-tools-src remote add origin "${{ github.event.inputs.repository || 'https://github.com/KhronosGroup/SPIRV-Tools.git' }}" + git -C spirv-tools-src fetch --depth 1 origin "${{ github.event.inputs.ref || 'main' }}" + git -C spirv-tools-src checkout FETCH_HEAD + python3 spirv-tools-src/utils/git-sync-deps + + - name: Install build dependencies + run: sudo apt-get update && sudo apt-get install -y ninja-build cmake + + - name: Configure & build stride_spirv_tools + run: | + cmake -S stride-src/build/deps/spirv-tools -B _build -G Ninja \ + -DCMAKE_BUILD_TYPE=Release \ + -DCMAKE_TOOLCHAIN_FILE=$ANDROID_NDK_HOME/build/cmake/android.toolchain.cmake \ + -DANDROID_ABI=${{ matrix.abi }} \ + -DANDROID_PLATFORM=android-24 \ + -DSPIRV_TOOLS_SOURCE_DIR=$PWD/spirv-tools-src + cmake --build _build --target stride_spirv_tools + + - name: Collect output + run: | + mkdir -p spirv-tools-out + cp _build/libstride_spirv_tools.so spirv-tools-out/ + + - uses: actions/upload-artifact@v4 + with: + name: spirv-tools-${{ matrix.rid }} + path: spirv-tools-out/ + + # --------------------------------------------------------------------------- + # Pack NuGet from all artifacts. + # --------------------------------------------------------------------------- + pack: + name: Pack & Publish NuGet + needs: + - build-windows-x64 + - build-windows-arm64 + - build-linux-x64 + - build-macos-arm64 + - build-ios-arm64 + - build-android + runs-on: windows-2025-vs2026 + steps: + - uses: actions/checkout@v4 + + - name: Clone SPIRV-Tools (for commit hash) + shell: pwsh + run: | + git init spirv-tools-src + git -C spirv-tools-src remote add origin "${{ github.event.inputs.repository || 'https://github.com/KhronosGroup/SPIRV-Tools.git' }}" + git -C spirv-tools-src fetch --depth 1 origin "${{ github.event.inputs.ref || 'main' }}" + git -C spirv-tools-src checkout FETCH_HEAD + + - name: Download artifacts + uses: actions/download-artifact@v4 + with: { path: artifacts, pattern: spirv-tools-* } + + - name: Stage native payload into csproj + shell: pwsh + run: | + $destRoot = "build/deps/spirv-tools/runtimes" + $rids = 'win-x64','win-arm64','linux-x64','osx-arm64','ios-arm64','android-arm64','android-x64' + foreach ($rid in $rids) { + $native = "$destRoot/$rid/native" + New-Item -Path $native -ItemType Directory -Force | Out-Null + Copy-Item -Recurse "artifacts/spirv-tools-$rid/*" $native + } + + - name: Pack NuGet (dotnet pack) + shell: pwsh + run: | + $commitHash = git -C spirv-tools-src rev-parse --short HEAD + $version = "${{ github.event.inputs.version }}" + if (-not $version) { $version = (Get-Date -Format "yyyy.M.d") } + dotnet pack build/deps/spirv-tools/Stride.Dependencies.SPIRVTools.csproj ` + -c Release ` + -p:PackageVersion=$version ` + -p:RepositoryCommit=$commitHash ` + -o nupkg + + - uses: actions/upload-artifact@v4 + with: { name: Stride.Dependencies.SPIRVTools.nupkg, path: nupkg/*.nupkg } + + publish: + name: Sign & Publish to NuGet.org + needs: pack + runs-on: windows-2025-vs2026 + environment: production + steps: + - name: Install signing tool + run: dotnet tool install sign --tool-path ./sign-tool --version 0.9.0-beta.23127.3 + + - uses: actions/download-artifact@v4 + with: { name: Stride.Dependencies.SPIRVTools.nupkg } + + - name: Sign NuGet package + shell: pwsh + run: | + ./sign-tool/sign code azure-key-vault *.nupkg ` + --description "Stride" ` + --description-url "https://stride3d.net" ` + --publisher-name "Stride" ` + --azure-key-vault-tenant-id "${{ secrets.STRIDE_SIGN_TENANT_ID }}" ` + --azure-key-vault-client-id "${{ secrets.STRIDE_SIGN_CLIENT_ID }}" ` + --azure-key-vault-client-secret "${{ secrets.STRIDE_SIGN_CLIENT_SECRET }}" ` + --azure-key-vault-certificate "${{ secrets.STRIDE_SIGN_KEYVAULT_CERTIFICATE }}" ` + --azure-key-vault-url "https://${{ secrets.STRIDE_SIGN_KEYVAULT_NAME }}.vault.azure.net/" ` + -v Information + + - name: Publish run: | - echo "This is a skeleton. The real build lives on a feature branch." - echo "Trigger this workflow against that branch ref to run the actual build." + dotnet nuget push *.nupkg --api-key "${{ secrets.STRIDE_NUGET_API_KEY }}" --source https://api.nuget.org/v3/index.json --skip-duplicate diff --git a/.github/workflows/dep-swiftshader.yml b/.github/workflows/dep-swiftshader.yml deleted file mode 100644 index e25a92f848..0000000000 --- a/.github/workflows/dep-swiftshader.yml +++ /dev/null @@ -1,214 +0,0 @@ -name: "Dep: Build & Deploy SwiftShader" - -on: - workflow_dispatch: - inputs: - version: - description: NuGet package version (leave empty for date-based) - required: false - -jobs: - build-windows: - name: Build SwiftShader (Windows x64) - runs-on: windows-2025-vs2026 - env: - CMAKE_POLICY_VERSION_MINIMUM: "3.5" - steps: - - name: Checkout - uses: actions/checkout@v4 - - - name: Checkout SwiftShader - uses: actions/checkout@v4 - with: - repository: google/swiftshader - submodules: recursive - path: swiftshader-src - - - name: Build - shell: pwsh - run: | - cmake -S swiftshader-src -B swiftshader-build -Thost=x64 -DSWIFTSHADER_BUILD_TESTS=OFF -DSWIFTSHADER_BUILD_BENCHMARKS=OFF - cmake --build swiftshader-build --config Release --target vk_swiftshader - - - name: Upload build output - uses: actions/upload-artifact@v4 - with: - name: swiftshader-win-x64 - path: | - swiftshader-build/**/vk_swiftshader.dll - swiftshader-build/**/vk_swiftshader_icd.json - - build-linux: - name: Build SwiftShader (Linux x64) - runs-on: ubuntu-24.04 - env: - CMAKE_POLICY_VERSION_MINIMUM: "3.5" - steps: - - name: Checkout - uses: actions/checkout@v4 - - - name: Checkout SwiftShader - uses: actions/checkout@v4 - with: - repository: google/swiftshader - submodules: recursive - path: swiftshader-src - - - name: Build - run: | - cmake -S swiftshader-src -B swiftshader-build -DCMAKE_BUILD_TYPE=Release -DSWIFTSHADER_BUILD_TESTS=OFF -DSWIFTSHADER_BUILD_BENCHMARKS=OFF - cmake --build swiftshader-build --target vk_swiftshader -- -j$(nproc) - - - name: Collect build output - run: | - mkdir -p swiftshader-out - find swiftshader-build -maxdepth 2 -name "libvk_swiftshader.so" -exec cp {} swiftshader-out/ \; - find swiftshader-build -maxdepth 2 -name "vk_swiftshader_icd.json" -exec cp {} swiftshader-out/ \; - - - name: Upload build output - uses: actions/upload-artifact@v4 - with: - name: swiftshader-linux-x64 - path: swiftshader-out/ - - build-macos: - name: Build SwiftShader (macOS ARM64) - runs-on: macos-15 - env: - CMAKE_POLICY_VERSION_MINIMUM: "3.5" - steps: - - name: Checkout - uses: actions/checkout@v4 - - - name: Checkout SwiftShader - uses: actions/checkout@v4 - with: - repository: google/swiftshader - submodules: recursive - path: swiftshader-src - - - name: Build - run: | - cmake -S swiftshader-src -B swiftshader-build -DCMAKE_BUILD_TYPE=Release -DSWIFTSHADER_BUILD_TESTS=OFF -DSWIFTSHADER_BUILD_BENCHMARKS=OFF - cmake --build swiftshader-build --target vk_swiftshader -- -j$(sysctl -n hw.ncpu) - - - name: Collect build output - run: | - mkdir -p swiftshader-out - find swiftshader-build -maxdepth 2 -name "libvk_swiftshader.dylib" -exec cp {} swiftshader-out/ \; - find swiftshader-build -maxdepth 2 -name "vk_swiftshader_icd.json" -exec cp {} swiftshader-out/ \; - - - name: Upload build output - uses: actions/upload-artifact@v4 - with: - name: swiftshader-osx-arm64 - path: swiftshader-out/ - - pack: - name: Pack & Publish NuGet - needs: [build-windows, build-linux, build-macos] - runs-on: windows-2025-vs2026 - steps: - - name: Checkout - uses: actions/checkout@v4 - - - name: Checkout SwiftShader (for commit hash) - uses: actions/checkout@v4 - with: - repository: google/swiftshader - path: swiftshader-src - fetch-depth: 1 - - - name: Download Windows artifact - uses: actions/download-artifact@v4 - with: - name: swiftshader-win-x64 - path: artifacts/win-x64 - - - name: Download Linux artifact - uses: actions/download-artifact@v4 - with: - name: swiftshader-linux-x64 - path: artifacts/linux-x64 - - - name: Download macOS artifact - uses: actions/download-artifact@v4 - with: - name: swiftshader-osx-arm64 - path: artifacts/osx-arm64 - - - name: Prepare package contents - shell: pwsh - run: | - $destDir = "build/deps/swiftshader" - - # Windows - New-Item -Path "$destDir/win-x64" -ItemType Directory -Force | Out-Null - $winDll = Get-ChildItem -Recurse -Path artifacts/win-x64 -Filter vk_swiftshader.dll | Select-Object -First 1 - Copy-Item $winDll.FullName "$destDir/win-x64/" - $winIcd = Get-ChildItem -Recurse -Path artifacts/win-x64 -Filter vk_swiftshader_icd.json | Select-Object -First 1 - Copy-Item $winIcd.FullName "$destDir/win-x64/" - - # Linux - New-Item -Path "$destDir/linux-x64" -ItemType Directory -Force | Out-Null - $linuxSo = Get-ChildItem -Recurse -Path artifacts/linux-x64 -Filter libvk_swiftshader.so | Select-Object -First 1 - Copy-Item $linuxSo.FullName "$destDir/linux-x64/" - $linuxIcd = Get-ChildItem -Recurse -Path artifacts/linux-x64 -Filter vk_swiftshader_icd.json | Select-Object -First 1 - Copy-Item $linuxIcd.FullName "$destDir/linux-x64/" - - # macOS - New-Item -Path "$destDir/osx-arm64" -ItemType Directory -Force | Out-Null - $macDylib = Get-ChildItem -Recurse -Path artifacts/osx-arm64 -Filter libvk_swiftshader.dylib | Select-Object -First 1 - Copy-Item $macDylib.FullName "$destDir/osx-arm64/" - $macIcd = Get-ChildItem -Recurse -Path artifacts/osx-arm64 -Filter vk_swiftshader_icd.json | Select-Object -First 1 - Copy-Item $macIcd.FullName "$destDir/osx-arm64/" - - - name: Pack NuGet - shell: pwsh - run: | - $commitHash = git -C swiftshader-src rev-parse --short HEAD - $version = "${{ github.event.inputs.version }}" - if (-not $version) { $version = (Get-Date -Format "yyyy.M.d") } - nuget pack build/deps/swiftshader/Stride.Dependencies.SwiftShader.nuspec ` - -Version $version ` - -Properties "commit=$commitHash" ` - -OutputDirectory nupkg - echo "PACKAGE_VERSION=$version" >> $env:GITHUB_ENV - - - name: Upload package artifact - uses: actions/upload-artifact@v4 - with: - name: Stride.Dependencies.SwiftShader.nupkg - path: nupkg/*.nupkg - - publish: - name: Sign & Publish to NuGet.org - needs: pack - runs-on: windows-2025-vs2026 - environment: production - steps: - - name: Install signing tool - run: dotnet tool install sign --tool-path ./sign-tool --version 0.9.0-beta.23127.3 - - - name: Download artifact - uses: actions/download-artifact@v4 - with: - name: Stride.Dependencies.SwiftShader.nupkg - - - name: Sign NuGet package - shell: pwsh - run: | - ./sign-tool/sign code azure-key-vault *.nupkg ` - --description "Stride" ` - --description-url "https://stride3d.net" ` - --publisher-name "Stride" ` - --azure-key-vault-tenant-id "${{ secrets.STRIDE_SIGN_TENANT_ID }}" ` - --azure-key-vault-client-id "${{ secrets.STRIDE_SIGN_CLIENT_ID }}" ` - --azure-key-vault-client-secret "${{ secrets.STRIDE_SIGN_CLIENT_SECRET }}" ` - --azure-key-vault-certificate "${{ secrets.STRIDE_SIGN_KEYVAULT_CERTIFICATE }}" ` - --azure-key-vault-url "https://${{ secrets.STRIDE_SIGN_KEYVAULT_NAME }}.vault.azure.net/" ` - -v Information - - - name: Publish - run: | - dotnet nuget push *.nupkg --api-key "${{ secrets.STRIDE_NUGET_API_KEY }}" --source https://api.nuget.org/v3/index.json --skip-duplicate diff --git a/.github/workflows/test-linux-game.yml b/.github/workflows/test-linux-game.yml index afa13f37c1..6a35e8bd01 100644 --- a/.github/workflows/test-linux-game.yml +++ b/.github/workflows/test-linux-game.yml @@ -16,10 +16,6 @@ on: default: Debug type: string -permissions: - checks: write - contents: read - concurrency: group: test-linux-game-${{ github.event.pull_request.number || github.ref }}-${{ github.event.inputs.build-type || inputs.build-type || 'Debug' }} @@ -37,6 +33,7 @@ jobs: - uses: actions/checkout@v4 with: lfs: true + submodules: true - uses: actions/setup-dotnet@v4 with: dotnet-version: '10.0.x' @@ -47,17 +44,24 @@ jobs: install_runtime: true cache: true stripdown: true - - name: Register SwiftShader ICD + - name: Register Lavapipe ICD (Windows host, for asset compilation) shell: pwsh run: | + # Build runs on Windows (CompilerApp needs Windows-only native libs), but produces + # Linux binaries. CompilerApp invokes a local Vulkan device during asset compilation, + # so it needs a Windows-registered ICD here. The win-x64 Lavapipe .dll is shipped + # inside the same NuGet package consumed by the tests; just register it. dotnet restore build\Stride.Tests.Game.GPU.slnf -p:StrideGraphicsApis=Vulkan -p:StrideGraphicsApi=Vulkan - $icd = Get-ChildItem -Recurse -Path "$env:USERPROFILE\.nuget\packages\stride.dependencies.swiftshader" -Filter vk_swiftshader_icd.json -ErrorAction SilentlyContinue | Where-Object { $_.DirectoryName -match 'win-x64' } | Select-Object -First 1 - if ($icd) { + $dll = Get-ChildItem -Recurse -Path "$env:USERPROFILE\.nuget\packages\stride.dependencies.lavapipe" -Filter vulkan_lvp.dll -ErrorAction SilentlyContinue | Where-Object { $_.DirectoryName -match 'win-x64' } | Select-Object -First 1 + if ($dll) { + $icdPath = Join-Path $dll.DirectoryName "lvp_icd.json" + $dllPath = $dll.FullName -replace '\\', '\\\\' + Set-Content -Path $icdPath -Value "{`"file_format_version`":`"1.0.0`",`"ICD`":{`"library_path`":`"$dllPath`",`"api_version`":`"1.3.0`"}}" New-Item -Path "HKLM:\SOFTWARE\Khronos\Vulkan\Drivers" -Force | Out-Null - New-ItemProperty -Path "HKLM:\SOFTWARE\Khronos\Vulkan\Drivers" -Name $icd.FullName -Value 0 -PropertyType DWord -Force | Out-Null - Write-Host "Registered SwiftShader ICD: $($icd.FullName)" + New-ItemProperty -Path "HKLM:\SOFTWARE\Khronos\Vulkan\Drivers" -Name $icdPath -Value 0 -PropertyType DWord -Force | Out-Null + Write-Host "Registered Lavapipe ICD: $icdPath" } else { - Write-Warning "SwiftShader ICD not found in NuGet cache" + Write-Warning "Lavapipe DLL not found in NuGet cache" } - name: Build GPU tests (targeting Linux/Vulkan) run: | @@ -101,7 +105,7 @@ jobs: retention-days: 1 # Non-GPU game tests (run once, no graphics API dependency) - # Note: some tests still create a GraphicsDevice, so libvulkan1 + SwiftShader are needed. + # Note: some tests still create a GraphicsDevice, so libvulkan1 + Lavapipe are needed. Game-Common: name: Test Game Common (${{ github.event.inputs.build-type || inputs.build-type || 'Debug' }}) needs: Build @@ -113,6 +117,7 @@ jobs: - uses: actions/checkout@v4 with: lfs: true + submodules: true - uses: actions/setup-dotnet@v4 with: dotnet-version: '10.0.x' @@ -134,18 +139,23 @@ jobs: # Artifact download doesn't preserve Unix execute permissions find bin/Tests -name "*.bin" -exec chmod +x {} \; find bin/Tests -name "*.so" -exec chmod +x {} \; - - name: Register SwiftShader ICD + - name: Fetch & register Lavapipe ICD (from NuGet cache) run: | - # Find SwiftShader in any test output and create ICD JSON - LIB=$(find bin/Tests -name libvk_swiftshader.so | head -1) + # Pull the lavapipe NuGet package into the user-level cache without + # needing it copied into bin/Tests. A throwaway project pins the package + # into ~/.nuget/packages/stride.dependencies.lavapipe//. + mkdir -p /tmp/lavapipe-fetch + dotnet new classlib -o /tmp/lavapipe-fetch -n LavapipeFetch --force >/dev/null + dotnet add /tmp/lavapipe-fetch/LavapipeFetch.csproj package Stride.Dependencies.Lavapipe + LIB=$(find ~/.nuget/packages/stride.dependencies.lavapipe -path "*/runtimes/linux-x64/native/libvulkan_lvp.so" | head -1) if [ -n "$LIB" ]; then LIB_ABS=$(readlink -f "$LIB") - ICD_JSON="$PWD/vk_swiftshader_icd.json" - echo "{\"file_format_version\":\"1.0.0\",\"ICD\":{\"library_path\":\"$LIB_ABS\",\"api_version\":\"1.1.0\"}}" > "$ICD_JSON" + ICD_JSON="$PWD/lvp_icd.json" + echo "{\"file_format_version\":\"1.0.0\",\"ICD\":{\"library_path\":\"$LIB_ABS\",\"api_version\":\"1.3.0\"}}" > "$ICD_JSON" echo "VK_DRIVER_FILES=$ICD_JSON" >> $GITHUB_ENV - echo "Registered SwiftShader ICD: $ICD_JSON -> $LIB_ABS" + echo "Registered Lavapipe ICD: $ICD_JSON -> $LIB_ABS" else - echo "::warning::SwiftShader libvk_swiftshader.so not found in test binaries" + echo "::warning::Lavapipe libvulkan_lvp.so not found in NuGet cache" fi - name: Test if: always() @@ -157,15 +167,24 @@ jobs: bin/Tests/Stride.Navigation.Tests/Linux/Vulkan/Stride.Navigation.Tests.dll \ bin/Tests/Stride.Particles.Tests/Linux/Vulkan/Stride.Particles.Tests.dll \ --logger:trx --ResultsDirectory:TestResults \ + '--Blame:CollectHangDump;TestTimeout=300000;HangDumpType=Full' \ -- RunConfiguration.MaxCpuCount=1 - name: Publish Test Report if: always() - uses: dorny/test-reporter@v1 + uses: phoenix-actions/test-reporting@v15 with: name: 'Test Report: Linux Game Common' path: TestResults/*.trx reporter: dotnet-trx - token: ${{ secrets.GITHUB_TOKEN }} + output-to: step-summary + list-tests: 'failed' + - name: Upload test results + if: always() + uses: actions/upload-artifact@v4 + with: + name: test-results-linux-common + path: TestResults/ + if-no-files-found: ignore - name: Upload test artifacts if: always() uses: actions/upload-artifact@v4 @@ -174,7 +193,7 @@ jobs: path: tests/local/ if-no-files-found: ignore - # GPU game tests (Vulkan via SwiftShader) + # GPU game tests (Vulkan via Lavapipe) Game-GPU: name: Test Game Vulkan (${{ github.event.inputs.build-type || inputs.build-type || 'Debug' }}) needs: Build @@ -187,13 +206,20 @@ jobs: - uses: actions/checkout@v4 with: lfs: true + submodules: true - uses: actions/setup-dotnet@v4 with: dotnet-version: '10.0.x' - name: Install runtime dependencies run: | sudo apt-get update - sudo apt-get install -y libopenal-dev libfreeimage-dev libvulkan1 + sudo apt-get install -y libopenal-dev libfreeimage-dev libvulkan1 xvfb + - name: Start virtual display + run: | + # Lavapipe's x11 WSI needs a display server to expose VK_KHR_swapchain, + # which Stride's GraphicsDevice requires even for pure offscreen rendering. + Xvfb :99 -screen 0 1920x1080x24 & + echo "DISPLAY=:99" >> $GITHUB_ENV - name: Download test binaries uses: actions/download-artifact@v4 with: @@ -204,17 +230,24 @@ jobs: # Artifact download doesn't preserve Unix execute permissions find bin/Tests -name "*.bin" -exec chmod +x {} \; find bin/Tests -name "*.so" -exec chmod +x {} \; - - name: Register SwiftShader ICD + - name: Fetch & register Lavapipe ICD (from NuGet cache) run: | - LIB=$(find bin/Tests -name libvk_swiftshader.so | head -1) + # The test binaries ship without the 70+ MB vulkan_lvp.so (ExcludeAssets="native"), + # so pull the package into ~/.nuget/packages/ here. Also write an ICD eagerly as a + # safety net — the managed Stride.Dependencies.Lavapipe module initializer would + # otherwise do this at first use, but an explicit export makes CI logs clearer. + mkdir -p /tmp/lavapipe-fetch + dotnet new classlib -o /tmp/lavapipe-fetch -n LavapipeFetch --force >/dev/null + dotnet add /tmp/lavapipe-fetch/LavapipeFetch.csproj package Stride.Dependencies.Lavapipe + LIB=$(find ~/.nuget/packages/stride.dependencies.lavapipe -path "*/runtimes/linux-x64/native/libvulkan_lvp.so" | head -1) if [ -n "$LIB" ]; then LIB_ABS=$(readlink -f "$LIB") - ICD_JSON="$PWD/vk_swiftshader_icd.json" - echo "{\"file_format_version\":\"1.0.0\",\"ICD\":{\"library_path\":\"$LIB_ABS\",\"api_version\":\"1.1.0\"}}" > "$ICD_JSON" + ICD_JSON="$PWD/lvp_icd.json" + echo "{\"file_format_version\":\"1.0.0\",\"ICD\":{\"library_path\":\"$LIB_ABS\",\"api_version\":\"1.3.0\"}}" > "$ICD_JSON" echo "VK_DRIVER_FILES=$ICD_JSON" >> $GITHUB_ENV - echo "Registered SwiftShader ICD: $ICD_JSON -> $LIB_ABS" + echo "Registered Lavapipe ICD: $ICD_JSON -> $LIB_ABS" else - echo "::warning::SwiftShader libvk_swiftshader.so not found in test binaries" + echo "::warning::Lavapipe libvulkan_lvp.so not found in NuGet cache" fi - name: Test if: always() @@ -227,15 +260,24 @@ jobs: bin/Tests/Stride.Physics.Tests/Linux/Vulkan/Stride.Physics.Tests.dll \ bin/Tests/Stride.UI.Tests/Linux/Vulkan/Stride.UI.Tests.dll \ --logger:trx --ResultsDirectory:TestResults \ + '--Blame:CollectHangDump;TestTimeout=300000;HangDumpType=Full' \ -- RunConfiguration.MaxCpuCount=1 - name: Publish Test Report if: always() - uses: dorny/test-reporter@v1 + uses: phoenix-actions/test-reporting@v15 with: name: 'Test Report: Linux Game GPU (Vulkan)' path: TestResults/*.trx reporter: dotnet-trx - token: ${{ secrets.GITHUB_TOKEN }} + output-to: step-summary + list-tests: 'failed' + - name: Upload test results + if: always() + uses: actions/upload-artifact@v4 + with: + name: test-results-linux-vulkan + path: TestResults/ + if-no-files-found: ignore - name: Upload test artifacts if: always() uses: actions/upload-artifact@v4 diff --git a/.github/workflows/test-linux-simple.yml b/.github/workflows/test-linux-simple.yml index adac66f471..80c3941da8 100644 --- a/.github/workflows/test-linux-simple.yml +++ b/.github/workflows/test-linux-simple.yml @@ -16,10 +16,6 @@ on: default: Debug type: string -permissions: - checks: write - contents: read - concurrency: group: test-linux-simple-${{ github.event.pull_request.number || github.ref }}-${{ github.event.inputs.build-type || inputs.build-type || 'Debug' }} @@ -35,6 +31,7 @@ jobs: - uses: actions/checkout@v4 with: lfs: true + submodules: true - uses: actions/setup-dotnet@v4 with: dotnet-version: '10.0.x' @@ -66,6 +63,7 @@ jobs: - uses: actions/checkout@v4 with: lfs: true + submodules: true - uses: actions/setup-dotnet@v4 with: dotnet-version: '10.0.x' @@ -86,12 +84,21 @@ jobs: bin/Tests/Stride.Core.Assets.Quantum.Tests/Linux/Vulkan/Stride.Core.Assets.Quantum.Tests.dll \ bin/Tests/Stride.Core.Quantum.Tests/Linux/Vulkan/Stride.Core.Quantum.Tests.dll \ bin/Tests/Stride.Core.Presentation.Quantum.Tests/Linux/Vulkan/Stride.Core.Presentation.Quantum.Tests.dll \ - --logger:trx --ResultsDirectory:TestResults + --logger:trx --ResultsDirectory:TestResults \ + '--Blame:CollectHangDump;TestTimeout=300000;HangDumpType=Full' - name: Publish Test Report if: always() - uses: dorny/test-reporter@v1 + uses: phoenix-actions/test-reporting@v15 with: name: 'Test Report: Linux Simple' path: TestResults/*.trx reporter: dotnet-trx - token: ${{ secrets.GITHUB_TOKEN }} + output-to: step-summary + list-tests: 'failed' + - name: Upload test results + if: always() + uses: actions/upload-artifact@v4 + with: + name: test-results-linux-simple + path: TestResults/ + if-no-files-found: ignore diff --git a/.github/workflows/test-windows-game.yml b/.github/workflows/test-windows-game.yml index 2900ac397c..c00193f03b 100644 --- a/.github/workflows/test-windows-game.yml +++ b/.github/workflows/test-windows-game.yml @@ -25,10 +25,6 @@ on: default: Debug type: string -permissions: - checks: write - contents: read - concurrency: group: test-windows-game-${{ github.event.pull_request.number || github.ref }}-${{ github.event.inputs.build-type || inputs.build-type || 'Debug' }} @@ -41,6 +37,7 @@ jobs: Game-Common: name: Test Game Common (${{ github.event.inputs.build-type || inputs.build-type || 'Debug' }}) runs-on: windows-2025-vs2026 + timeout-minutes: 20 env: STRIDE_GRAPHICS_SOFTWARE_RENDERING: "1" STRIDE_TESTS_RENDERDOC: "error" @@ -49,6 +46,7 @@ jobs: - uses: actions/checkout@v4 with: lfs: true + submodules: true - uses: actions/setup-dotnet@v4 with: dotnet-version: '10.0.x' @@ -69,15 +67,24 @@ jobs: dotnet test build\Stride.Tests.Game.slnf ` --no-build ` --logger:trx --results-directory TestResults ` + --blame-hang-timeout 5m --blame-hang-dump-type full ` -p:Configuration=${{ github.event.inputs.build-type || inputs.build-type || 'Debug' }} - name: Publish Test Report if: always() - uses: dorny/test-reporter@v1 + uses: phoenix-actions/test-reporting@v15 with: name: 'Test Report: Windows Game Common' path: TestResults/*.trx reporter: dotnet-trx - token: ${{ secrets.GITHUB_TOKEN }} + output-to: step-summary + list-tests: 'failed' + - name: Upload test results + if: always() + uses: actions/upload-artifact@v4 + with: + name: test-results-game-common + path: TestResults/ + if-no-files-found: ignore - name: Upload test artifacts if: always() uses: actions/upload-artifact@v4 @@ -92,6 +99,7 @@ jobs: Game-GraphicsApi: name: Test Game ${{ matrix.graphics-api }} (${{ github.event.inputs.build-type || inputs.build-type || 'Debug' }}) runs-on: windows-2025-vs2026 + timeout-minutes: 20 strategy: fail-fast: false matrix: @@ -100,16 +108,18 @@ jobs: STRIDE_GRAPHICS_SOFTWARE_RENDERING: "1" STRIDE_TESTS_RENDERDOC: "error" STRIDE_MAX_PARALLELISM: "8" - # Crash dump collection (covers 3 crash types): + # Crash dump collection (covers 3 crash types) — all dumps land under TestResults/ + # alongside the .trx logs and the blame-hang dumps from `dotnet test`. 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 + STRIDE_TESTS_CRASH_DUMP_DIR: "${{ github.workspace }}\\TestResults" # 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 + DOTNET_DbgMiniDumpName: "${{ github.workspace }}\\TestResults\\dotnet_%p.dmp" # Type 2: dump path steps: - uses: actions/checkout@v4 with: lfs: true + submodules: true - uses: actions/setup-dotnet@v4 with: dotnet-version: '10.0.x' @@ -119,7 +129,7 @@ jobs: # 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" + $dumpDir = "${{ github.workspace }}\TestResults" 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 @@ -132,25 +142,25 @@ jobs: install_runtime: true cache: true stripdown: true - - name: Register SwiftShader ICD + - name: Register Lavapipe 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) + # CompilerApp needs a Vulkan device at build time (Skybox asset compilation). + # Runtime tests are served by the Lavapipe module initializer; this step is + # only for the pre-runtime CompilerApp invocation, so we register an ICD here. + # Restore first so the package is in the NuGet cache. 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 + $dll = Get-ChildItem -Recurse -Path "$env:USERPROFILE\.nuget\packages\stride.dependencies.lavapipe" -Filter vulkan_lvp.dll -ErrorAction SilentlyContinue | Where-Object { $_.DirectoryName -match 'win-x64' } | Select-Object -First 1 if ($dll) { - $icdPath = Join-Path $dll.DirectoryName "vk_swiftshader_icd.json" + $icdPath = Join-Path $dll.DirectoryName "lvp_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`"}}" + Set-Content -Path $icdPath -Value "{`"file_format_version`":`"1.0.0`",`"ICD`":{`"library_path`":`"$dllPath`",`"api_version`":`"1.3.0`"}}" 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" + Write-Host "Registered Lavapipe ICD: $icdPath" } else { - Write-Warning "SwiftShader DLL not found in NuGet cache" + Write-Warning "Lavapipe DLL not found in NuGet cache" } - name: Build run: | @@ -168,24 +178,26 @@ jobs: dotnet test build\Stride.Tests.Game.GPU.slnf ` --no-build ` --logger:trx --results-directory TestResults ` + --blame-hang-timeout 5m --blame-hang-dump-type full ` -p:Configuration=${{ github.event.inputs.build-type || inputs.build-type || 'Debug' }} ` -p:StrideGraphicsApis=${{ matrix.graphics-api }} ` -p:StrideGraphicsApi=${{ matrix.graphics-api }} ` -- RunConfiguration.MaxCpuCount=1 - name: Publish Test Report if: always() - uses: dorny/test-reporter@v1 + uses: phoenix-actions/test-reporting@v15 with: name: 'Test Report: Windows Game ${{ matrix.graphics-api }}' path: TestResults/*.trx reporter: dotnet-trx - token: ${{ secrets.GITHUB_TOKEN }} + output-to: step-summary + list-tests: 'failed' - name: Collect symbols for crash analysis if: always() shell: pwsh run: | - $dumpDir = "${{ github.workspace }}\crash-dumps" - if (Get-ChildItem $dumpDir -Filter *.dmp -ErrorAction SilentlyContinue) { + $dumpDir = "${{ github.workspace }}\TestResults" + if (Get-ChildItem $dumpDir -Recurse -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 @@ -198,10 +210,10 @@ jobs: name: test-artifacts-game-${{ matrix.graphics-api }} path: tests/local/ if-no-files-found: ignore - - name: Upload crash dumps + - name: Upload test results if: always() uses: actions/upload-artifact@v4 with: - name: crash-dumps-game-${{ matrix.graphics-api }} - path: crash-dumps/ + name: test-results-game-${{ matrix.graphics-api }} + path: TestResults/ if-no-files-found: ignore diff --git a/.github/workflows/test-windows-simple.yml b/.github/workflows/test-windows-simple.yml index efa7503db9..780fb1a615 100644 --- a/.github/workflows/test-windows-simple.yml +++ b/.github/workflows/test-windows-simple.yml @@ -16,10 +16,6 @@ on: default: Debug type: string -permissions: - checks: write - contents: read - concurrency: group: test-windows-simple-${{ github.event.pull_request.number || github.ref }}-${{ github.event.inputs.build-type || inputs.build-type || 'Debug' }} @@ -34,6 +30,7 @@ jobs: - uses: actions/checkout@v4 with: lfs: true + submodules: true - uses: actions/setup-dotnet@v4 with: dotnet-version: '10.0.x' @@ -52,12 +49,21 @@ jobs: dotnet test build\Stride.Tests.Simple.slnf ` --no-build ` --logger:trx --results-directory TestResults ` + --blame-hang-timeout 5m --blame-hang-dump-type full ` -p:Configuration=${{ github.event.inputs.build-type || inputs.build-type || 'Debug' }} - name: Publish Test Report if: always() - uses: dorny/test-reporter@v1 + uses: phoenix-actions/test-reporting@v15 with: name: 'Test Report: Windows Simple' path: TestResults/*.trx reporter: dotnet-trx - token: ${{ secrets.GITHUB_TOKEN }} + output-to: step-summary + list-tests: 'failed' + - name: Upload test results + if: always() + uses: actions/upload-artifact@v4 + with: + name: test-results-simple + path: TestResults/ + if-no-files-found: ignore diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000000..0fcbc01292 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,9 @@ +[submodule "submodules/SpirvHeaders"] + path = build/submodules/SpirvHeaders + url = https://github.com/KhronosGroup/SPIRV-Headers +[submodule "submodules/CppNet8"] + path = build/submodules/CppNet8 + url = https://github.com/ykafia/CppNet/ +[submodule "submodules/SpirvRegistry"] + path = build/submodules/SpirvRegistry + url = https://github.com/KhronosGroup/Registry-Root-SPIR-V diff --git a/build/Stride.Android.sln b/build/Stride.Android.sln new file mode 100644 index 0000000000..fb07069452 --- /dev/null +++ b/build/Stride.Android.sln @@ -0,0 +1,287 @@ +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 18 +VisualStudioVersion = 18.0.11205.157 +MinimumVisualStudioVersion = 18.0 +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "10-CoreRuntime", "10-CoreRuntime", "{2E93E2B5-4500-4E47-9B65-E705218AB578}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "20-StrideRuntime", "20-StrideRuntime", "{4C142567-C42B-40F5-B092-798882190209}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "00-Targets.Private", "00-Targets.Private", "{97978864-95DD-43A6-9159-AA1C881BE99F}" + ProjectSection(SolutionItems) = preProject + ..\sources\native\Stride.Native.targets = ..\sources\native\Stride.Native.targets + ..\sources\targets\Stride.Core.PostSettings.Dependencies.targets = ..\sources\targets\Stride.Core.PostSettings.Dependencies.targets + ..\sources\targets\Stride.Core.props = ..\sources\targets\Stride.Core.props + ..\sources\targets\Stride.Core.targets = ..\sources\targets\Stride.Core.targets + ..\sources\targets\Stride.GraphicsApi.Dev.targets = ..\sources\targets\Stride.GraphicsApi.Dev.targets + ..\sources\targets\Stride.GraphicsApi.PackageReference.targets = ..\sources\targets\Stride.GraphicsApi.PackageReference.targets + ..\sources\targets\Stride.PackageVersion.targets = ..\sources\targets\Stride.PackageVersion.targets + ..\sources\targets\Stride.props = ..\sources\targets\Stride.props + ..\sources\targets\Stride.targets = ..\sources\targets\Stride.targets + ..\sources\targets\Stride.UnitTests.CrossTargeting.targets = ..\sources\targets\Stride.UnitTests.CrossTargeting.targets + ..\sources\targets\Stride.UnitTests.DisableBuild.targets = ..\sources\targets\Stride.UnitTests.DisableBuild.targets + ..\sources\targets\Stride.UnitTests.props = ..\sources\targets\Stride.UnitTests.props + ..\sources\targets\Stride.UnitTests.targets = ..\sources\targets\Stride.UnitTests.targets + EndProjectSection +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "80-Shaders", "80-Shaders", "{10D145AF-C8AE-428F-A80F-CA1B591D0DB2}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "00-Config", "00-Config", "{7662CECF-2A3D-4DBA-AB3D-77FD8536E7A3}" + ProjectSection(SolutionItems) = preProject + ..\sources\shared\SharedAssemblyInfo.cs = ..\sources\shared\SharedAssemblyInfo.cs + EndProjectSection +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Stride.Shared", "Stride.Shared", "{1AC70118-C90F-4EC6-9D8B-C628BDF900F7}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "00-Targets.Build", "00-Targets.Build", "{0B81090E-4066-4723-A658-8AEDBEADE619}" + ProjectSection(SolutionItems) = preProject + Stride.build = Stride.build + Stride.Build.props = Stride.Build.props + Stride.Build.targets = Stride.Build.targets + Stride.Core.Build.props = Stride.Core.Build.props + Stride.Core.Build.targets = Stride.Core.Build.targets + Stride.UnitTests.Build.targets = Stride.UnitTests.Build.targets + EndProjectSection +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Stride.Engine", "..\sources\engine\Stride.Engine\Stride.Engine.csproj", "{C121A566-555E-42B9-9B0A-1696529A9088}" + ProjectSection(ProjectDependencies) = postProject + {F2D52EDB-BC17-4243-B06D-33CD20F87A7F} = {F2D52EDB-BC17-4243-B06D-33CD20F87A7F} + EndProjectSection +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Stride.Graphics", "..\sources\engine\Stride.Graphics\Stride.Graphics.csproj", "{FB06C76A-6BB7-40BE-9AFA-FEC13B045FB5}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Stride.Core.Shaders", "..\sources\shaders\Stride.Core.Shaders\Stride.Core.Shaders.csproj", "{F2D52EDB-BC17-4243-B06D-33CD20F87A7F}" + ProjectSection(ProjectDependencies) = postProject + {5210FB81-B807-49BB-AF0D-31FB6A83A572} = {5210FB81-B807-49BB-AF0D-31FB6A83A572} + EndProjectSection +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Irony", "..\sources\shaders\Irony\Irony.csproj", "{D81F5C91-D7DB-46E5-BC99-49488FB6814C}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Stride.Games", "..\sources\engine\Stride.Games\Stride.Games.csproj", "{42780CBD-3FE7-48E3-BD5B-59945EA20137}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Stride.Core", "..\sources\core\Stride.Core\Stride.Core.csproj", "{0E916AB7-5A6C-4820-8AB1-AA492FE66D68}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Stride.Core.Mathematics", "..\sources\core\Stride.Core.Mathematics\Stride.Core.Mathematics.csproj", "{1677B922-CCF0-44DE-B57E-1CDD3D2B8E8A}" + ProjectSection(ProjectDependencies) = postProject + {5210FB81-B807-49BB-AF0D-31FB6A83A572} = {5210FB81-B807-49BB-AF0D-31FB6A83A572} + EndProjectSection +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Stride.Core.Serialization", "..\sources\core\Stride.Core.Serialization\Stride.Core.Serialization.csproj", "{5210FB81-B807-49BB-AF0D-31FB6A83A572}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Stride.Core.MicroThreading", "..\sources\core\Stride.Core.MicroThreading\Stride.Core.MicroThreading.csproj", "{1320F627-EE43-4115-8E89-19D1753E51F2}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Stride.Core.IO", "..\sources\core\Stride.Core.IO\Stride.Core.IO.csproj", "{1DE01410-22C9-489B-9796-1ADDAB1F64E5}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Stride.Shaders.Parser", "..\sources\engine\Stride.Shaders.Parser\Stride.Shaders.Parser.csproj", "{14A47447-2A24-4ECD-B24D-6571499DCD4C}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Stride.Shaders", "..\sources\shaders\Stride.Shaders\Stride.Shaders.csproj", "{273BDD15-7392-4078-91F0-AF23594A3D7B}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Stride.Audio", "..\sources\engine\Stride.Audio\Stride.Audio.csproj", "{DE042125-C270-4D1D-9270-0759C167567A}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Stride", "..\sources\engine\Stride\Stride.csproj", "{72390339-B2A1-4F61-A800-31ED0975B515}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Stride.Shaders.Compiler", "..\sources\shaders\Stride.Shaders.Compilers\Stride.Shaders.Compilers.csproj", "{E8B3553F-A79F-4E50-B75B-ACEE771C320C}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Stride.Input", "..\sources\engine\Stride.Input\Stride.Input.csproj", "{84DEB606-77ED-49CD-9AED-D2B13C1F5A1E}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Stride.UI", "..\sources\engine\Stride.UI\Stride.UI.csproj", "{BB9DEEEF-F18C-40D8-B016-6434CC71B8C3}" + ProjectSection(ProjectDependencies) = postProject + {C121A566-555E-42B9-9B0A-1696529A9088} = {C121A566-555E-42B9-9B0A-1696529A9088} + EndProjectSection +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Stride.Physics", "..\sources\engine\Stride.Physics\Stride.Physics.csproj", "{DD592516-B341-40FE-9100-1B0FA784A060}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Stride.SpriteStudio.Runtime", "..\sources\engine\Stride.SpriteStudio.Runtime\Stride.SpriteStudio.Runtime.csproj", "{9BC63BEC-F305-451D-BB31-262938EA964D}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Stride.Particles", "..\sources\engine\Stride.Particles\Stride.Particles.csproj", "{F32FDA80-B6DD-47A8-8681-437E2C0D3F31}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Stride.Native", "..\sources\engine\Stride.Native\Stride.Native.csproj", "{1DBBC150-F085-43EF-B41D-27C72D133770}" +EndProject +Project("{D954291E-2A0B-460D-934E-DC6B0785DB48}") = "Stride.Refactor", "..\sources\engine\Stride.Shared\Refactor\Stride.Refactor.shproj", "{B33E576F-2279-4BFC-A438-D9B84343B56B}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Stride.VirtualReality", "..\sources\engine\Stride.VirtualReality\Stride.VirtualReality.csproj", "{53782603-3096-40C2-ABD3-F8F311BAE4BE}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Stride.Navigation", "..\sources\engine\Stride.Navigation\Stride.Navigation.csproj", "{FBE1FA7B-E699-4BB2-9C8F-41F4C9F3F088}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "00-Localization", "00-Localization", "{FC791F56-C1F1-4C41-A193-868D8197F8E2}" + ProjectSection(SolutionItems) = preProject + ..\sources\localization\Stride.Assets.Presentation.pot = ..\sources\localization\Stride.Assets.Presentation.pot + ..\sources\localization\Stride.Core.Assets.Editor.pot = ..\sources\localization\Stride.Core.Assets.Editor.pot + ..\sources\localization\Stride.Core.Presentation.pot = ..\sources\localization\Stride.Core.Presentation.pot + ..\sources\localization\Stride.GameStudio.pot = ..\sources\localization\Stride.GameStudio.pot + EndProjectSection +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "ja", "ja", "{B4EABB0D-E495-405C-B7B1-E2A7A3711AF5}" + ProjectSection(SolutionItems) = preProject + ..\sources\localization\ja\Stride.Assets.Presentation.ja.po = ..\sources\localization\ja\Stride.Assets.Presentation.ja.po + ..\sources\localization\ja\Stride.Core.Assets.Editor.ja.po = ..\sources\localization\ja\Stride.Core.Assets.Editor.ja.po + ..\sources\localization\ja\Stride.Core.Presentation.ja.po = ..\sources\localization\ja\Stride.Core.Presentation.ja.po + ..\sources\localization\ja\Stride.GameStudio.ja.po = ..\sources\localization\ja\Stride.GameStudio.ja.po + EndProjectSection +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Stride.Video", "..\sources\engine\Stride.Video\Stride.Video.csproj", "{DA355C86-866F-4843-9B4D-63A173C750FB}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "fr", "fr", "{62E9A8E4-79AF-4081-84D5-FEC5A0B28598}" + ProjectSection(SolutionItems) = preProject + ..\sources\localization\fr\Stride.Assets.Presentation.fr.po = ..\sources\localization\fr\Stride.Assets.Presentation.fr.po + ..\sources\localization\fr\Stride.Core.Assets.Editor.fr.po = ..\sources\localization\fr\Stride.Core.Assets.Editor.fr.po + ..\sources\localization\fr\Stride.Core.Presentation.fr.po = ..\sources\localization\fr\Stride.Core.Presentation.fr.po + ..\sources\localization\fr\Stride.GameStudio.fr.po = ..\sources\localization\fr\Stride.GameStudio.fr.po + EndProjectSection +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Stride.Rendering", "..\sources\engine\Stride.Rendering\Stride.Rendering.csproj", "{AD4FDC24-B64D-4ED7-91AA-62C9EDA12FA4}" +EndProject +Global + GlobalSection(SharedMSBuildProjectFiles) = preSolution + ..\sources\engine\Stride.Shared\Refactor\Stride.Refactor.projitems*{b33e576f-2279-4bfc-a438-d9b84343b56b}*SharedItemsImports = 13 + ..\sources\engine\Stride.Shared\Refactor\Stride.Refactor.projitems*{c121a566-555e-42b9-9b0a-1696529a9088}*SharedItemsImports = 5 + ..\sources\shared\Stride.Core.ShellHelper\Stride.Core.ShellHelper.projitems*{e8b3553f-a79f-4e50-b75b-acee771c320c}*SharedItemsImports = 5 + ..\sources\engine\Stride.Shared\Refactor\Stride.Refactor.projitems*{fb06c76a-6bb7-40be-9afa-fec13b045fb5}*SharedItemsImports = 5 + EndGlobalSection + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Android = Debug|Android + Release|Android = Release|Android + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {C121A566-555E-42B9-9B0A-1696529A9088}.Debug|Android.ActiveCfg = Debug|Any CPU + {C121A566-555E-42B9-9B0A-1696529A9088}.Debug|Android.Build.0 = Debug|Any CPU + {C121A566-555E-42B9-9B0A-1696529A9088}.Release|Android.ActiveCfg = Release|Any CPU + {C121A566-555E-42B9-9B0A-1696529A9088}.Release|Android.Build.0 = Release|Any CPU + {FB06C76A-6BB7-40BE-9AFA-FEC13B045FB5}.Debug|Android.ActiveCfg = Debug|Any CPU + {FB06C76A-6BB7-40BE-9AFA-FEC13B045FB5}.Debug|Android.Build.0 = Debug|Any CPU + {FB06C76A-6BB7-40BE-9AFA-FEC13B045FB5}.Release|Android.ActiveCfg = Release|Any CPU + {FB06C76A-6BB7-40BE-9AFA-FEC13B045FB5}.Release|Android.Build.0 = Release|Any CPU + {F2D52EDB-BC17-4243-B06D-33CD20F87A7F}.Debug|Android.ActiveCfg = Debug|Any CPU + {F2D52EDB-BC17-4243-B06D-33CD20F87A7F}.Debug|Android.Build.0 = Debug|Any CPU + {F2D52EDB-BC17-4243-B06D-33CD20F87A7F}.Debug|Android.Deploy.0 = Debug|Any CPU + {F2D52EDB-BC17-4243-B06D-33CD20F87A7F}.Release|Android.ActiveCfg = Release|Any CPU + {F2D52EDB-BC17-4243-B06D-33CD20F87A7F}.Release|Android.Build.0 = Release|Any CPU + {F2D52EDB-BC17-4243-B06D-33CD20F87A7F}.Release|Android.Deploy.0 = Release|Any CPU + {D81F5C91-D7DB-46E5-BC99-49488FB6814C}.Debug|Android.ActiveCfg = Debug|Any CPU + {D81F5C91-D7DB-46E5-BC99-49488FB6814C}.Debug|Android.Build.0 = Debug|Any CPU + {D81F5C91-D7DB-46E5-BC99-49488FB6814C}.Debug|Android.Deploy.0 = Debug|Any CPU + {D81F5C91-D7DB-46E5-BC99-49488FB6814C}.Release|Android.ActiveCfg = Release|Any CPU + {D81F5C91-D7DB-46E5-BC99-49488FB6814C}.Release|Android.Build.0 = Release|Any CPU + {D81F5C91-D7DB-46E5-BC99-49488FB6814C}.Release|Android.Deploy.0 = Release|Any CPU + {42780CBD-3FE7-48E3-BD5B-59945EA20137}.Debug|Android.ActiveCfg = Debug|Any CPU + {42780CBD-3FE7-48E3-BD5B-59945EA20137}.Debug|Android.Build.0 = Debug|Any CPU + {42780CBD-3FE7-48E3-BD5B-59945EA20137}.Release|Android.ActiveCfg = Release|Any CPU + {42780CBD-3FE7-48E3-BD5B-59945EA20137}.Release|Android.Build.0 = Release|Any CPU + {0E916AB7-5A6C-4820-8AB1-AA492FE66D68}.Debug|Android.ActiveCfg = Debug|Any CPU + {0E916AB7-5A6C-4820-8AB1-AA492FE66D68}.Debug|Android.Build.0 = Debug|Any CPU + {0E916AB7-5A6C-4820-8AB1-AA492FE66D68}.Release|Android.ActiveCfg = Release|Any CPU + {0E916AB7-5A6C-4820-8AB1-AA492FE66D68}.Release|Android.Build.0 = Release|Any CPU + {1677B922-CCF0-44DE-B57E-1CDD3D2B8E8A}.Debug|Android.ActiveCfg = Debug|Any CPU + {1677B922-CCF0-44DE-B57E-1CDD3D2B8E8A}.Debug|Android.Build.0 = Debug|Any CPU + {1677B922-CCF0-44DE-B57E-1CDD3D2B8E8A}.Release|Android.ActiveCfg = Release|Any CPU + {1677B922-CCF0-44DE-B57E-1CDD3D2B8E8A}.Release|Android.Build.0 = Release|Any CPU + {5210FB81-B807-49BB-AF0D-31FB6A83A572}.Debug|Android.ActiveCfg = Debug|Any CPU + {5210FB81-B807-49BB-AF0D-31FB6A83A572}.Debug|Android.Build.0 = Debug|Any CPU + {5210FB81-B807-49BB-AF0D-31FB6A83A572}.Release|Android.ActiveCfg = Release|Any CPU + {5210FB81-B807-49BB-AF0D-31FB6A83A572}.Release|Android.Build.0 = Release|Any CPU + {1320F627-EE43-4115-8E89-19D1753E51F2}.Debug|Android.ActiveCfg = Debug|Any CPU + {1320F627-EE43-4115-8E89-19D1753E51F2}.Debug|Android.Build.0 = Debug|Any CPU + {1320F627-EE43-4115-8E89-19D1753E51F2}.Release|Android.ActiveCfg = Release|Any CPU + {1320F627-EE43-4115-8E89-19D1753E51F2}.Release|Android.Build.0 = Release|Any CPU + {1DE01410-22C9-489B-9796-1ADDAB1F64E5}.Debug|Android.ActiveCfg = Debug|Any CPU + {1DE01410-22C9-489B-9796-1ADDAB1F64E5}.Debug|Android.Build.0 = Debug|Any CPU + {1DE01410-22C9-489B-9796-1ADDAB1F64E5}.Release|Android.ActiveCfg = Release|Any CPU + {1DE01410-22C9-489B-9796-1ADDAB1F64E5}.Release|Android.Build.0 = Release|Any CPU + {14A47447-2A24-4ECD-B24D-6571499DCD4C}.Debug|Android.ActiveCfg = Debug|Any CPU + {14A47447-2A24-4ECD-B24D-6571499DCD4C}.Debug|Android.Build.0 = Debug|Any CPU + {14A47447-2A24-4ECD-B24D-6571499DCD4C}.Release|Android.ActiveCfg = Release|Any CPU + {14A47447-2A24-4ECD-B24D-6571499DCD4C}.Release|Android.Build.0 = Release|Any CPU + {273BDD15-7392-4078-91F0-AF23594A3D7B}.Debug|Android.ActiveCfg = Debug|Any CPU + {273BDD15-7392-4078-91F0-AF23594A3D7B}.Debug|Android.Build.0 = Debug|Any CPU + {273BDD15-7392-4078-91F0-AF23594A3D7B}.Release|Android.ActiveCfg = Release|Any CPU + {273BDD15-7392-4078-91F0-AF23594A3D7B}.Release|Android.Build.0 = Release|Any CPU + {DE042125-C270-4D1D-9270-0759C167567A}.Debug|Android.ActiveCfg = Debug|Any CPU + {DE042125-C270-4D1D-9270-0759C167567A}.Debug|Android.Build.0 = Debug|Any CPU + {DE042125-C270-4D1D-9270-0759C167567A}.Release|Android.ActiveCfg = Release|Any CPU + {DE042125-C270-4D1D-9270-0759C167567A}.Release|Android.Build.0 = Release|Any CPU + {72390339-B2A1-4F61-A800-31ED0975B515}.Debug|Android.ActiveCfg = Debug|Any CPU + {72390339-B2A1-4F61-A800-31ED0975B515}.Debug|Android.Build.0 = Debug|Any CPU + {72390339-B2A1-4F61-A800-31ED0975B515}.Release|Android.ActiveCfg = Release|Any CPU + {72390339-B2A1-4F61-A800-31ED0975B515}.Release|Android.Build.0 = Release|Any CPU + {E8B3553F-A79F-4E50-B75B-ACEE771C320C}.Debug|Android.ActiveCfg = Debug|Any CPU + {E8B3553F-A79F-4E50-B75B-ACEE771C320C}.Debug|Android.Build.0 = Debug|Any CPU + {E8B3553F-A79F-4E50-B75B-ACEE771C320C}.Release|Android.ActiveCfg = Release|Any CPU + {E8B3553F-A79F-4E50-B75B-ACEE771C320C}.Release|Android.Build.0 = Release|Any CPU + {84DEB606-77ED-49CD-9AED-D2B13C1F5A1E}.Debug|Android.ActiveCfg = Debug|Any CPU + {84DEB606-77ED-49CD-9AED-D2B13C1F5A1E}.Debug|Android.Build.0 = Debug|Any CPU + {84DEB606-77ED-49CD-9AED-D2B13C1F5A1E}.Release|Android.ActiveCfg = Release|Any CPU + {84DEB606-77ED-49CD-9AED-D2B13C1F5A1E}.Release|Android.Build.0 = Release|Any CPU + {BB9DEEEF-F18C-40D8-B016-6434CC71B8C3}.Debug|Android.ActiveCfg = Debug|Any CPU + {BB9DEEEF-F18C-40D8-B016-6434CC71B8C3}.Debug|Android.Build.0 = Debug|Any CPU + {BB9DEEEF-F18C-40D8-B016-6434CC71B8C3}.Release|Android.ActiveCfg = Release|Any CPU + {BB9DEEEF-F18C-40D8-B016-6434CC71B8C3}.Release|Android.Build.0 = Release|Any CPU + {DD592516-B341-40FE-9100-1B0FA784A060}.Debug|Android.ActiveCfg = Debug|Any CPU + {DD592516-B341-40FE-9100-1B0FA784A060}.Debug|Android.Build.0 = Debug|Any CPU + {DD592516-B341-40FE-9100-1B0FA784A060}.Release|Android.ActiveCfg = Release|Any CPU + {DD592516-B341-40FE-9100-1B0FA784A060}.Release|Android.Build.0 = Release|Any CPU + {9BC63BEC-F305-451D-BB31-262938EA964D}.Debug|Android.ActiveCfg = Debug|Any CPU + {9BC63BEC-F305-451D-BB31-262938EA964D}.Debug|Android.Build.0 = Debug|Any CPU + {9BC63BEC-F305-451D-BB31-262938EA964D}.Release|Android.ActiveCfg = Release|Any CPU + {9BC63BEC-F305-451D-BB31-262938EA964D}.Release|Android.Build.0 = Release|Any CPU + {F32FDA80-B6DD-47A8-8681-437E2C0D3F31}.Debug|Android.ActiveCfg = Debug|Any CPU + {F32FDA80-B6DD-47A8-8681-437E2C0D3F31}.Debug|Android.Build.0 = Debug|Any CPU + {F32FDA80-B6DD-47A8-8681-437E2C0D3F31}.Release|Android.ActiveCfg = Release|Any CPU + {F32FDA80-B6DD-47A8-8681-437E2C0D3F31}.Release|Android.Build.0 = Release|Any CPU + {1DBBC150-F085-43EF-B41D-27C72D133770}.Debug|Android.ActiveCfg = Debug|Any CPU + {1DBBC150-F085-43EF-B41D-27C72D133770}.Debug|Android.Build.0 = Debug|Any CPU + {1DBBC150-F085-43EF-B41D-27C72D133770}.Release|Android.ActiveCfg = Release|Any CPU + {1DBBC150-F085-43EF-B41D-27C72D133770}.Release|Android.Build.0 = Release|Any CPU + {53782603-3096-40C2-ABD3-F8F311BAE4BE}.Debug|Android.ActiveCfg = Debug|Any CPU + {53782603-3096-40C2-ABD3-F8F311BAE4BE}.Debug|Android.Build.0 = Debug|Any CPU + {53782603-3096-40C2-ABD3-F8F311BAE4BE}.Release|Android.ActiveCfg = Release|Any CPU + {53782603-3096-40C2-ABD3-F8F311BAE4BE}.Release|Android.Build.0 = Release|Any CPU + {FBE1FA7B-E699-4BB2-9C8F-41F4C9F3F088}.Debug|Android.ActiveCfg = Debug|Any CPU + {FBE1FA7B-E699-4BB2-9C8F-41F4C9F3F088}.Debug|Android.Build.0 = Debug|Any CPU + {FBE1FA7B-E699-4BB2-9C8F-41F4C9F3F088}.Release|Android.ActiveCfg = Release|Any CPU + {FBE1FA7B-E699-4BB2-9C8F-41F4C9F3F088}.Release|Android.Build.0 = Release|Any CPU + {DA355C86-866F-4843-9B4D-63A173C750FB}.Debug|Android.ActiveCfg = Debug|Any CPU + {DA355C86-866F-4843-9B4D-63A173C750FB}.Debug|Android.Build.0 = Debug|Any CPU + {DA355C86-866F-4843-9B4D-63A173C750FB}.Release|Android.ActiveCfg = Release|Any CPU + {DA355C86-866F-4843-9B4D-63A173C750FB}.Release|Android.Build.0 = Release|Any CPU + {AD4FDC24-B64D-4ED7-91AA-62C9EDA12FA4}.Debug|Android.ActiveCfg = Debug|Any CPU + {AD4FDC24-B64D-4ED7-91AA-62C9EDA12FA4}.Debug|Android.Build.0 = Debug|Any CPU + {AD4FDC24-B64D-4ED7-91AA-62C9EDA12FA4}.Release|Android.ActiveCfg = Release|Any CPU + {AD4FDC24-B64D-4ED7-91AA-62C9EDA12FA4}.Release|Android.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(NestedProjects) = preSolution + {1AC70118-C90F-4EC6-9D8B-C628BDF900F7} = {4C142567-C42B-40F5-B092-798882190209} + {C121A566-555E-42B9-9B0A-1696529A9088} = {4C142567-C42B-40F5-B092-798882190209} + {FB06C76A-6BB7-40BE-9AFA-FEC13B045FB5} = {4C142567-C42B-40F5-B092-798882190209} + {F2D52EDB-BC17-4243-B06D-33CD20F87A7F} = {10D145AF-C8AE-428F-A80F-CA1B591D0DB2} + {D81F5C91-D7DB-46E5-BC99-49488FB6814C} = {10D145AF-C8AE-428F-A80F-CA1B591D0DB2} + {42780CBD-3FE7-48E3-BD5B-59945EA20137} = {4C142567-C42B-40F5-B092-798882190209} + {0E916AB7-5A6C-4820-8AB1-AA492FE66D68} = {2E93E2B5-4500-4E47-9B65-E705218AB578} + {1677B922-CCF0-44DE-B57E-1CDD3D2B8E8A} = {2E93E2B5-4500-4E47-9B65-E705218AB578} + {5210FB81-B807-49BB-AF0D-31FB6A83A572} = {2E93E2B5-4500-4E47-9B65-E705218AB578} + {1320F627-EE43-4115-8E89-19D1753E51F2} = {2E93E2B5-4500-4E47-9B65-E705218AB578} + {1DE01410-22C9-489B-9796-1ADDAB1F64E5} = {2E93E2B5-4500-4E47-9B65-E705218AB578} + {14A47447-2A24-4ECD-B24D-6571499DCD4C} = {4C142567-C42B-40F5-B092-798882190209} + {273BDD15-7392-4078-91F0-AF23594A3D7B} = {4C142567-C42B-40F5-B092-798882190209} + {DE042125-C270-4D1D-9270-0759C167567A} = {4C142567-C42B-40F5-B092-798882190209} + {72390339-B2A1-4F61-A800-31ED0975B515} = {4C142567-C42B-40F5-B092-798882190209} + {E8B3553F-A79F-4E50-B75B-ACEE771C320C} = {4C142567-C42B-40F5-B092-798882190209} + {84DEB606-77ED-49CD-9AED-D2B13C1F5A1E} = {4C142567-C42B-40F5-B092-798882190209} + {BB9DEEEF-F18C-40D8-B016-6434CC71B8C3} = {4C142567-C42B-40F5-B092-798882190209} + {DD592516-B341-40FE-9100-1B0FA784A060} = {4C142567-C42B-40F5-B092-798882190209} + {9BC63BEC-F305-451D-BB31-262938EA964D} = {4C142567-C42B-40F5-B092-798882190209} + {F32FDA80-B6DD-47A8-8681-437E2C0D3F31} = {4C142567-C42B-40F5-B092-798882190209} + {1DBBC150-F085-43EF-B41D-27C72D133770} = {4C142567-C42B-40F5-B092-798882190209} + {B33E576F-2279-4BFC-A438-D9B84343B56B} = {1AC70118-C90F-4EC6-9D8B-C628BDF900F7} + {53782603-3096-40C2-ABD3-F8F311BAE4BE} = {4C142567-C42B-40F5-B092-798882190209} + {FBE1FA7B-E699-4BB2-9C8F-41F4C9F3F088} = {4C142567-C42B-40F5-B092-798882190209} + {B4EABB0D-E495-405C-B7B1-E2A7A3711AF5} = {FC791F56-C1F1-4C41-A193-868D8197F8E2} + {DA355C86-866F-4843-9B4D-63A173C750FB} = {4C142567-C42B-40F5-B092-798882190209} + {62E9A8E4-79AF-4081-84D5-FEC5A0B28598} = {FC791F56-C1F1-4C41-A193-868D8197F8E2} + {AD4FDC24-B64D-4ED7-91AA-62C9EDA12FA4} = {4C142567-C42B-40F5-B092-798882190209} + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {FF877973-604D-4EA7-B5F5-A129961F9EF2} + EndGlobalSection +EndGlobal diff --git a/build/Stride.Android.slnf b/build/Stride.Android.slnf index bb99599a8f..3db845c146 100644 --- a/build/Stride.Android.slnf +++ b/build/Stride.Android.slnf @@ -18,15 +18,17 @@ "..\\sources\\engine\\Stride.Particles\\Stride.Particles.csproj", "..\\sources\\engine\\Stride.Physics\\Stride.Physics.csproj", "..\\sources\\engine\\Stride.Rendering\\Stride.Rendering.csproj", - "..\\sources\\engine\\Stride.Shaders\\Stride.Shaders.csproj", - "..\\sources\\engine\\Stride.Shaders.Compiler\\Stride.Shaders.Compiler.csproj", - "..\\sources\\engine\\Stride.Shaders.Parser\\Stride.Shaders.Parser.csproj", "..\\sources\\engine\\Stride.SpriteStudio.Runtime\\Stride.SpriteStudio.Runtime.csproj", "..\\sources\\engine\\Stride.UI\\Stride.UI.csproj", "..\\sources\\engine\\Stride.Video\\Stride.Video.csproj", "..\\sources\\engine\\Stride.VirtualReality\\Stride.VirtualReality.csproj", - "..\\sources\\shaders\\Irony\\Irony.csproj", - "..\\sources\\shaders\\Stride.Core.Shaders\\Stride.Core.Shaders.csproj" + "..\\sources\\shaders\\Stride.Shaders\\Stride.Shaders.csproj", + "..\\sources\\shaders\\Stride.Shaders.Compilers\\Stride.Shaders.Compilers.csproj", + "..\\sources\\shaders\\Stride.Shaders.Parsers\\Stride.Shaders.Parsers.csproj", + "..\\sources\\shaders\\Stride.Shaders.Spirv.Core\\Stride.Shaders.Spirv.Core.csproj", + "..\\sources\\shaders\\Stride.Shaders.Generators\\Stride.Shaders.Generators.csproj", + "..\\sources\\shaders\\Stride.Shaders.Generators.Internal\\Stride.Shaders.Generators.Internal.csproj", + "..\\sources\\shaders\\Stride.Shaders.Spirv.Generators\\Stride.Shaders.Spirv.Generators.csproj" ] } } diff --git a/build/Stride.Build.props b/build/Stride.Build.props new file mode 100644 index 0000000000..75f98464c0 --- /dev/null +++ b/build/Stride.Build.props @@ -0,0 +1,14 @@ + + + + Stride + Windows + Linux + + + Direct3D11 + + + Vulkan + + diff --git a/build/Stride.Runtime.sln b/build/Stride.Runtime.sln new file mode 100644 index 0000000000..8973d3ad2e --- /dev/null +++ b/build/Stride.Runtime.sln @@ -0,0 +1,346 @@ +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.0.31612.314 +MinimumVisualStudioVersion = 16.0 +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "10-CoreRuntime", "10-CoreRuntime", "{2E93E2B5-4500-4E47-9B65-E705218AB578}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "20-StrideRuntime", "20-StrideRuntime", "{4C142567-C42B-40F5-B092-798882190209}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "00-Targets.Private", "00-Targets.Private", "{97978864-95DD-43A6-9159-AA1C881BE99F}" + ProjectSection(SolutionItems) = preProject + ..\sources\targets\Stride.Core.PostSettings.Dependencies.targets = ..\sources\targets\Stride.Core.PostSettings.Dependencies.targets + ..\sources\targets\Stride.Core.props = ..\sources\targets\Stride.Core.props + ..\sources\targets\Stride.Core.targets = ..\sources\targets\Stride.Core.targets + ..\sources\targets\Stride.GraphicsApi.Dev.targets = ..\sources\targets\Stride.GraphicsApi.Dev.targets + ..\sources\targets\Stride.GraphicsApi.PackageReference.targets = ..\sources\targets\Stride.GraphicsApi.PackageReference.targets + ..\sources\native\Stride.Native.targets = ..\sources\native\Stride.Native.targets + ..\sources\targets\Stride.PackageVersion.targets = ..\sources\targets\Stride.PackageVersion.targets + ..\sources\targets\Stride.props = ..\sources\targets\Stride.props + ..\sources\targets\Stride.targets = ..\sources\targets\Stride.targets + ..\sources\targets\Stride.UnitTests.CrossTargeting.targets = ..\sources\targets\Stride.UnitTests.CrossTargeting.targets + ..\sources\targets\Stride.UnitTests.DisableBuild.targets = ..\sources\targets\Stride.UnitTests.DisableBuild.targets + ..\sources\targets\Stride.UnitTests.props = ..\sources\targets\Stride.UnitTests.props + ..\sources\targets\Stride.UnitTests.targets = ..\sources\targets\Stride.UnitTests.targets + EndProjectSection +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "80-Shaders", "80-Shaders", "{10D145AF-C8AE-428F-A80F-CA1B591D0DB2}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "00-Config", "00-Config", "{7662CECF-2A3D-4DBA-AB3D-77FD8536E7A3}" + ProjectSection(SolutionItems) = preProject + ..\sources\shared\SharedAssemblyInfo.cs = ..\sources\shared\SharedAssemblyInfo.cs + EndProjectSection +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "21-StrideRuntime.Tests", "21-StrideRuntime.Tests", "{A7ED9F01-7D78-4381-90A6-D50E51C17250}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "00-Targets.Build", "00-Targets.Build", "{0B81090E-4066-4723-A658-8AEDBEADE619}" + ProjectSection(SolutionItems) = preProject + Stride.build = Stride.build + Stride.Build.props = Stride.Build.props + Stride.Build.targets = Stride.Build.targets + Stride.Core.Build.props = Stride.Core.Build.props + Stride.Core.Build.targets = Stride.Core.Build.targets + Stride.UnitTests.Build.targets = Stride.UnitTests.Build.targets + EndProjectSection +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Stride.Engine", "..\sources\engine\Stride.Engine\Stride.Engine.csproj", "{C121A566-555E-42B9-9B0A-1696529A9088}" + ProjectSection(ProjectDependencies) = postProject + {F2D52EDB-BC17-4243-B06D-33CD20F87A7F} = {F2D52EDB-BC17-4243-B06D-33CD20F87A7F} + EndProjectSection +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Stride.Graphics", "..\sources\engine\Stride.Graphics\Stride.Graphics.csproj", "{FB06C76A-6BB7-40BE-9AFA-FEC13B045FB5}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Stride.Core.Shaders", "..\sources\shaders\Stride.Core.Shaders\Stride.Core.Shaders.csproj", "{F2D52EDB-BC17-4243-B06D-33CD20F87A7F}" + ProjectSection(ProjectDependencies) = postProject + {5210FB81-B807-49BB-AF0D-31FB6A83A572} = {5210FB81-B807-49BB-AF0D-31FB6A83A572} + EndProjectSection +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Irony", "..\sources\shaders\Irony\Irony.csproj", "{D81F5C91-D7DB-46E5-BC99-49488FB6814C}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Stride.Games", "..\sources\engine\Stride.Games\Stride.Games.csproj", "{42780CBD-3FE7-48E3-BD5B-59945EA20137}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Stride.Core", "..\sources\core\Stride.Core\Stride.Core.csproj", "{0E916AB7-5A6C-4820-8AB1-AA492FE66D68}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Stride.Core.Mathematics", "..\sources\core\Stride.Core.Mathematics\Stride.Core.Mathematics.csproj", "{1677B922-CCF0-44DE-B57E-1CDD3D2B8E8A}" + ProjectSection(ProjectDependencies) = postProject + {5210FB81-B807-49BB-AF0D-31FB6A83A572} = {5210FB81-B807-49BB-AF0D-31FB6A83A572} + EndProjectSection +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Stride.Core.Serialization", "..\sources\core\Stride.Core.Serialization\Stride.Core.Serialization.csproj", "{5210FB81-B807-49BB-AF0D-31FB6A83A572}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Stride.Core.MicroThreading", "..\sources\core\Stride.Core.MicroThreading\Stride.Core.MicroThreading.csproj", "{1320F627-EE43-4115-8E89-19D1753E51F2}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Stride.Core.IO", "..\sources\core\Stride.Core.IO\Stride.Core.IO.csproj", "{1DE01410-22C9-489B-9796-1ADDAB1F64E5}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Stride.Shaders.Parser", "..\sources\engine\Stride.Shaders.Parser\Stride.Shaders.Parser.csproj", "{14A47447-2A24-4ECD-B24D-6571499DCD4C}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Stride.Shaders", "..\sources\shaders\Stride.Shaders\Stride.Shaders.csproj", "{273BDD15-7392-4078-91F0-AF23594A3D7B}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Stride.Audio", "..\sources\engine\Stride.Audio\Stride.Audio.csproj", "{DE042125-C270-4D1D-9270-0759C167567A}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Stride", "..\sources\engine\Stride\Stride.csproj", "{72390339-B2A1-4F61-A800-31ED0975B515}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Stride.Shaders.Compiler", "..\sources\shaders\Stride.Shaders.Compilers\Stride.Shaders.Compilers.csproj", "{E8B3553F-A79F-4E50-B75B-ACEE771C320C}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Stride.Input", "..\sources\engine\Stride.Input\Stride.Input.csproj", "{84DEB606-77ED-49CD-9AED-D2B13C1F5A1E}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Stride.UI", "..\sources\engine\Stride.UI\Stride.UI.csproj", "{BB9DEEEF-F18C-40D8-B016-6434CC71B8C3}" + ProjectSection(ProjectDependencies) = postProject + {C121A566-555E-42B9-9B0A-1696529A9088} = {C121A566-555E-42B9-9B0A-1696529A9088} + EndProjectSection +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Stride.Graphics.Regression", "..\sources\engine\Stride.Graphics.Regression\Stride.Graphics.Regression.csproj", "{D002FEB1-00A6-4AB1-A83F-1F253465E64D}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Stride.Physics", "..\sources\engine\Stride.Physics\Stride.Physics.csproj", "{DD592516-B341-40FE-9100-1B0FA784A060}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Stride.SpriteStudio.Runtime", "..\sources\engine\Stride.SpriteStudio.Runtime\Stride.SpriteStudio.Runtime.csproj", "{9BC63BEC-F305-451D-BB31-262938EA964D}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Stride.Particles", "..\sources\engine\Stride.Particles\Stride.Particles.csproj", "{F32FDA80-B6DD-47A8-8681-437E2C0D3F31}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Stride.Games.Testing", "..\sources\engine\Stride.Games.Testing\Stride.Games.Testing.csproj", "{B84ECB15-5E3F-4BD1-AB87-333BAE9B70F9}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Stride.Native", "..\sources\engine\Stride.Native\Stride.Native.csproj", "{1DBBC150-F085-43EF-B41D-27C72D133770}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Stride.VirtualReality", "..\sources\engine\Stride.VirtualReality\Stride.VirtualReality.csproj", "{53782603-3096-40C2-ABD3-F8F311BAE4BE}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Stride.Navigation", "..\sources\engine\Stride.Navigation\Stride.Navigation.csproj", "{FBE1FA7B-E699-4BB2-9C8F-41F4C9F3F088}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "00-Localization", "00-Localization", "{FC791F56-C1F1-4C41-A193-868D8197F8E2}" + ProjectSection(SolutionItems) = preProject + ..\sources\localization\Stride.Assets.Presentation.pot = ..\sources\localization\Stride.Assets.Presentation.pot + ..\sources\localization\Stride.Core.Assets.Editor.pot = ..\sources\localization\Stride.Core.Assets.Editor.pot + ..\sources\localization\Stride.Core.Presentation.pot = ..\sources\localization\Stride.Core.Presentation.pot + ..\sources\localization\Stride.GameStudio.pot = ..\sources\localization\Stride.GameStudio.pot + EndProjectSection +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "ja", "ja", "{B4EABB0D-E495-405C-B7B1-E2A7A3711AF5}" + ProjectSection(SolutionItems) = preProject + ..\sources\localization\ja\Stride.Assets.Presentation.ja.po = ..\sources\localization\ja\Stride.Assets.Presentation.ja.po + ..\sources\localization\ja\Stride.Core.Assets.Editor.ja.po = ..\sources\localization\ja\Stride.Core.Assets.Editor.ja.po + ..\sources\localization\ja\Stride.Core.Presentation.ja.po = ..\sources\localization\ja\Stride.Core.Presentation.ja.po + ..\sources\localization\ja\Stride.GameStudio.ja.po = ..\sources\localization\ja\Stride.GameStudio.ja.po + EndProjectSection +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Stride.Video", "..\sources\engine\Stride.Video\Stride.Video.csproj", "{DA355C86-866F-4843-9B4D-63A173C750FB}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "fr", "fr", "{62E9A8E4-79AF-4081-84D5-FEC5A0B28598}" + ProjectSection(SolutionItems) = preProject + ..\sources\localization\fr\Stride.Assets.Presentation.fr.po = ..\sources\localization\fr\Stride.Assets.Presentation.fr.po + ..\sources\localization\fr\Stride.Core.Assets.Editor.fr.po = ..\sources\localization\fr\Stride.Core.Assets.Editor.fr.po + ..\sources\localization\fr\Stride.Core.Presentation.fr.po = ..\sources\localization\fr\Stride.Core.Presentation.fr.po + ..\sources\localization\fr\Stride.GameStudio.fr.po = ..\sources\localization\fr\Stride.GameStudio.fr.po + EndProjectSection +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Stride.Rendering", "..\sources\engine\Stride.Rendering\Stride.Rendering.csproj", "{AD4FDC24-B64D-4ED7-91AA-62C9EDA12FA4}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {C121A566-555E-42B9-9B0A-1696529A9088}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {C121A566-555E-42B9-9B0A-1696529A9088}.Debug|Any CPU.Build.0 = Debug|Any CPU + {C121A566-555E-42B9-9B0A-1696529A9088}.Debug|Any CPU.Deploy.0 = Debug|Any CPU + {C121A566-555E-42B9-9B0A-1696529A9088}.Release|Any CPU.ActiveCfg = Release|Any CPU + {C121A566-555E-42B9-9B0A-1696529A9088}.Release|Any CPU.Build.0 = Release|Any CPU + {C121A566-555E-42B9-9B0A-1696529A9088}.Release|Any CPU.Deploy.0 = Release|Any CPU + {FB06C76A-6BB7-40BE-9AFA-FEC13B045FB5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {FB06C76A-6BB7-40BE-9AFA-FEC13B045FB5}.Debug|Any CPU.Build.0 = Debug|Any CPU + {FB06C76A-6BB7-40BE-9AFA-FEC13B045FB5}.Debug|Any CPU.Deploy.0 = Debug|Any CPU + {FB06C76A-6BB7-40BE-9AFA-FEC13B045FB5}.Release|Any CPU.ActiveCfg = Release|Any CPU + {FB06C76A-6BB7-40BE-9AFA-FEC13B045FB5}.Release|Any CPU.Build.0 = Release|Any CPU + {FB06C76A-6BB7-40BE-9AFA-FEC13B045FB5}.Release|Any CPU.Deploy.0 = Release|Any CPU + {F2D52EDB-BC17-4243-B06D-33CD20F87A7F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {F2D52EDB-BC17-4243-B06D-33CD20F87A7F}.Debug|Any CPU.Build.0 = Debug|Any CPU + {F2D52EDB-BC17-4243-B06D-33CD20F87A7F}.Debug|Any CPU.Deploy.0 = Debug|Any CPU + {F2D52EDB-BC17-4243-B06D-33CD20F87A7F}.Release|Any CPU.ActiveCfg = Release|Any CPU + {F2D52EDB-BC17-4243-B06D-33CD20F87A7F}.Release|Any CPU.Build.0 = Release|Any CPU + {F2D52EDB-BC17-4243-B06D-33CD20F87A7F}.Release|Any CPU.Deploy.0 = Release|Any CPU + {D81F5C91-D7DB-46E5-BC99-49488FB6814C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {D81F5C91-D7DB-46E5-BC99-49488FB6814C}.Debug|Any CPU.Build.0 = Debug|Any CPU + {D81F5C91-D7DB-46E5-BC99-49488FB6814C}.Debug|Any CPU.Deploy.0 = Debug|Any CPU + {D81F5C91-D7DB-46E5-BC99-49488FB6814C}.Release|Any CPU.ActiveCfg = Release|Any CPU + {D81F5C91-D7DB-46E5-BC99-49488FB6814C}.Release|Any CPU.Build.0 = Release|Any CPU + {D81F5C91-D7DB-46E5-BC99-49488FB6814C}.Release|Any CPU.Deploy.0 = Release|Any CPU + {42780CBD-3FE7-48E3-BD5B-59945EA20137}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {42780CBD-3FE7-48E3-BD5B-59945EA20137}.Debug|Any CPU.Build.0 = Debug|Any CPU + {42780CBD-3FE7-48E3-BD5B-59945EA20137}.Debug|Any CPU.Deploy.0 = Debug|Any CPU + {42780CBD-3FE7-48E3-BD5B-59945EA20137}.Release|Any CPU.ActiveCfg = Release|Any CPU + {42780CBD-3FE7-48E3-BD5B-59945EA20137}.Release|Any CPU.Build.0 = Release|Any CPU + {42780CBD-3FE7-48E3-BD5B-59945EA20137}.Release|Any CPU.Deploy.0 = Release|Any CPU + {0E916AB7-5A6C-4820-8AB1-AA492FE66D68}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {0E916AB7-5A6C-4820-8AB1-AA492FE66D68}.Debug|Any CPU.Build.0 = Debug|Any CPU + {0E916AB7-5A6C-4820-8AB1-AA492FE66D68}.Debug|Any CPU.Deploy.0 = Debug|Any CPU + {0E916AB7-5A6C-4820-8AB1-AA492FE66D68}.Release|Any CPU.ActiveCfg = Release|Any CPU + {0E916AB7-5A6C-4820-8AB1-AA492FE66D68}.Release|Any CPU.Build.0 = Release|Any CPU + {0E916AB7-5A6C-4820-8AB1-AA492FE66D68}.Release|Any CPU.Deploy.0 = Release|Any CPU + {1677B922-CCF0-44DE-B57E-1CDD3D2B8E8A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {1677B922-CCF0-44DE-B57E-1CDD3D2B8E8A}.Debug|Any CPU.Build.0 = Debug|Any CPU + {1677B922-CCF0-44DE-B57E-1CDD3D2B8E8A}.Debug|Any CPU.Deploy.0 = Debug|Any CPU + {1677B922-CCF0-44DE-B57E-1CDD3D2B8E8A}.Release|Any CPU.ActiveCfg = Release|Any CPU + {1677B922-CCF0-44DE-B57E-1CDD3D2B8E8A}.Release|Any CPU.Build.0 = Release|Any CPU + {1677B922-CCF0-44DE-B57E-1CDD3D2B8E8A}.Release|Any CPU.Deploy.0 = Release|Any CPU + {5210FB81-B807-49BB-AF0D-31FB6A83A572}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {5210FB81-B807-49BB-AF0D-31FB6A83A572}.Debug|Any CPU.Build.0 = Debug|Any CPU + {5210FB81-B807-49BB-AF0D-31FB6A83A572}.Debug|Any CPU.Deploy.0 = Debug|Any CPU + {5210FB81-B807-49BB-AF0D-31FB6A83A572}.Release|Any CPU.ActiveCfg = Release|Any CPU + {5210FB81-B807-49BB-AF0D-31FB6A83A572}.Release|Any CPU.Build.0 = Release|Any CPU + {5210FB81-B807-49BB-AF0D-31FB6A83A572}.Release|Any CPU.Deploy.0 = Release|Any CPU + {1320F627-EE43-4115-8E89-19D1753E51F2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {1320F627-EE43-4115-8E89-19D1753E51F2}.Debug|Any CPU.Build.0 = Debug|Any CPU + {1320F627-EE43-4115-8E89-19D1753E51F2}.Debug|Any CPU.Deploy.0 = Debug|Any CPU + {1320F627-EE43-4115-8E89-19D1753E51F2}.Release|Any CPU.ActiveCfg = Release|Any CPU + {1320F627-EE43-4115-8E89-19D1753E51F2}.Release|Any CPU.Build.0 = Release|Any CPU + {1320F627-EE43-4115-8E89-19D1753E51F2}.Release|Any CPU.Deploy.0 = Release|Any CPU + {1DE01410-22C9-489B-9796-1ADDAB1F64E5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {1DE01410-22C9-489B-9796-1ADDAB1F64E5}.Debug|Any CPU.Build.0 = Debug|Any CPU + {1DE01410-22C9-489B-9796-1ADDAB1F64E5}.Debug|Any CPU.Deploy.0 = Debug|Any CPU + {1DE01410-22C9-489B-9796-1ADDAB1F64E5}.Release|Any CPU.ActiveCfg = Release|Any CPU + {1DE01410-22C9-489B-9796-1ADDAB1F64E5}.Release|Any CPU.Build.0 = Release|Any CPU + {1DE01410-22C9-489B-9796-1ADDAB1F64E5}.Release|Any CPU.Deploy.0 = Release|Any CPU + {14A47447-2A24-4ECD-B24D-6571499DCD4C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {14A47447-2A24-4ECD-B24D-6571499DCD4C}.Debug|Any CPU.Build.0 = Debug|Any CPU + {14A47447-2A24-4ECD-B24D-6571499DCD4C}.Debug|Any CPU.Deploy.0 = Debug|Any CPU + {14A47447-2A24-4ECD-B24D-6571499DCD4C}.Release|Any CPU.ActiveCfg = Release|Any CPU + {14A47447-2A24-4ECD-B24D-6571499DCD4C}.Release|Any CPU.Build.0 = Release|Any CPU + {14A47447-2A24-4ECD-B24D-6571499DCD4C}.Release|Any CPU.Deploy.0 = Release|Any CPU + {273BDD15-7392-4078-91F0-AF23594A3D7B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {273BDD15-7392-4078-91F0-AF23594A3D7B}.Debug|Any CPU.Build.0 = Debug|Any CPU + {273BDD15-7392-4078-91F0-AF23594A3D7B}.Debug|Any CPU.Deploy.0 = Debug|Any CPU + {273BDD15-7392-4078-91F0-AF23594A3D7B}.Release|Any CPU.ActiveCfg = Release|Any CPU + {273BDD15-7392-4078-91F0-AF23594A3D7B}.Release|Any CPU.Build.0 = Release|Any CPU + {273BDD15-7392-4078-91F0-AF23594A3D7B}.Release|Any CPU.Deploy.0 = Release|Any CPU + {DE042125-C270-4D1D-9270-0759C167567A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {DE042125-C270-4D1D-9270-0759C167567A}.Debug|Any CPU.Build.0 = Debug|Any CPU + {DE042125-C270-4D1D-9270-0759C167567A}.Debug|Any CPU.Deploy.0 = Debug|Any CPU + {DE042125-C270-4D1D-9270-0759C167567A}.Release|Any CPU.ActiveCfg = Release|Any CPU + {DE042125-C270-4D1D-9270-0759C167567A}.Release|Any CPU.Build.0 = Release|Any CPU + {DE042125-C270-4D1D-9270-0759C167567A}.Release|Any CPU.Deploy.0 = Release|Any CPU + {72390339-B2A1-4F61-A800-31ED0975B515}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {72390339-B2A1-4F61-A800-31ED0975B515}.Debug|Any CPU.Build.0 = Debug|Any CPU + {72390339-B2A1-4F61-A800-31ED0975B515}.Debug|Any CPU.Deploy.0 = Debug|Any CPU + {72390339-B2A1-4F61-A800-31ED0975B515}.Release|Any CPU.ActiveCfg = Release|Any CPU + {72390339-B2A1-4F61-A800-31ED0975B515}.Release|Any CPU.Build.0 = Release|Any CPU + {72390339-B2A1-4F61-A800-31ED0975B515}.Release|Any CPU.Deploy.0 = Release|Any CPU + {E8B3553F-A79F-4E50-B75B-ACEE771C320C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {E8B3553F-A79F-4E50-B75B-ACEE771C320C}.Debug|Any CPU.Build.0 = Debug|Any CPU + {E8B3553F-A79F-4E50-B75B-ACEE771C320C}.Debug|Any CPU.Deploy.0 = Debug|Any CPU + {E8B3553F-A79F-4E50-B75B-ACEE771C320C}.Release|Any CPU.ActiveCfg = Release|Any CPU + {E8B3553F-A79F-4E50-B75B-ACEE771C320C}.Release|Any CPU.Build.0 = Release|Any CPU + {E8B3553F-A79F-4E50-B75B-ACEE771C320C}.Release|Any CPU.Deploy.0 = Release|Any CPU + {84DEB606-77ED-49CD-9AED-D2B13C1F5A1E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {84DEB606-77ED-49CD-9AED-D2B13C1F5A1E}.Debug|Any CPU.Build.0 = Debug|Any CPU + {84DEB606-77ED-49CD-9AED-D2B13C1F5A1E}.Debug|Any CPU.Deploy.0 = Debug|Any CPU + {84DEB606-77ED-49CD-9AED-D2B13C1F5A1E}.Release|Any CPU.ActiveCfg = Release|Any CPU + {84DEB606-77ED-49CD-9AED-D2B13C1F5A1E}.Release|Any CPU.Build.0 = Release|Any CPU + {84DEB606-77ED-49CD-9AED-D2B13C1F5A1E}.Release|Any CPU.Deploy.0 = Release|Any CPU + {BB9DEEEF-F18C-40D8-B016-6434CC71B8C3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {BB9DEEEF-F18C-40D8-B016-6434CC71B8C3}.Debug|Any CPU.Build.0 = Debug|Any CPU + {BB9DEEEF-F18C-40D8-B016-6434CC71B8C3}.Debug|Any CPU.Deploy.0 = Debug|Any CPU + {BB9DEEEF-F18C-40D8-B016-6434CC71B8C3}.Release|Any CPU.ActiveCfg = Release|Any CPU + {BB9DEEEF-F18C-40D8-B016-6434CC71B8C3}.Release|Any CPU.Build.0 = Release|Any CPU + {BB9DEEEF-F18C-40D8-B016-6434CC71B8C3}.Release|Any CPU.Deploy.0 = Release|Any CPU + {D002FEB1-00A6-4AB1-A83F-1F253465E64D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {D002FEB1-00A6-4AB1-A83F-1F253465E64D}.Debug|Any CPU.Build.0 = Debug|Any CPU + {D002FEB1-00A6-4AB1-A83F-1F253465E64D}.Debug|Any CPU.Deploy.0 = Debug|Any CPU + {D002FEB1-00A6-4AB1-A83F-1F253465E64D}.Release|Any CPU.ActiveCfg = Release|Any CPU + {D002FEB1-00A6-4AB1-A83F-1F253465E64D}.Release|Any CPU.Build.0 = Release|Any CPU + {D002FEB1-00A6-4AB1-A83F-1F253465E64D}.Release|Any CPU.Deploy.0 = Release|Any CPU + {DD592516-B341-40FE-9100-1B0FA784A060}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {DD592516-B341-40FE-9100-1B0FA784A060}.Debug|Any CPU.Build.0 = Debug|Any CPU + {DD592516-B341-40FE-9100-1B0FA784A060}.Debug|Any CPU.Deploy.0 = Debug|Any CPU + {DD592516-B341-40FE-9100-1B0FA784A060}.Release|Any CPU.ActiveCfg = Release|Any CPU + {DD592516-B341-40FE-9100-1B0FA784A060}.Release|Any CPU.Build.0 = Release|Any CPU + {DD592516-B341-40FE-9100-1B0FA784A060}.Release|Any CPU.Deploy.0 = Release|Any CPU + {9BC63BEC-F305-451D-BB31-262938EA964D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {9BC63BEC-F305-451D-BB31-262938EA964D}.Debug|Any CPU.Build.0 = Debug|Any CPU + {9BC63BEC-F305-451D-BB31-262938EA964D}.Debug|Any CPU.Deploy.0 = Debug|Any CPU + {9BC63BEC-F305-451D-BB31-262938EA964D}.Release|Any CPU.ActiveCfg = Release|Any CPU + {9BC63BEC-F305-451D-BB31-262938EA964D}.Release|Any CPU.Build.0 = Release|Any CPU + {9BC63BEC-F305-451D-BB31-262938EA964D}.Release|Any CPU.Deploy.0 = Release|Any CPU + {F32FDA80-B6DD-47A8-8681-437E2C0D3F31}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {F32FDA80-B6DD-47A8-8681-437E2C0D3F31}.Debug|Any CPU.Build.0 = Debug|Any CPU + {F32FDA80-B6DD-47A8-8681-437E2C0D3F31}.Debug|Any CPU.Deploy.0 = Debug|Any CPU + {F32FDA80-B6DD-47A8-8681-437E2C0D3F31}.Release|Any CPU.ActiveCfg = Release|Any CPU + {F32FDA80-B6DD-47A8-8681-437E2C0D3F31}.Release|Any CPU.Build.0 = Release|Any CPU + {F32FDA80-B6DD-47A8-8681-437E2C0D3F31}.Release|Any CPU.Deploy.0 = Release|Any CPU + {B84ECB15-5E3F-4BD1-AB87-333BAE9B70F9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {B84ECB15-5E3F-4BD1-AB87-333BAE9B70F9}.Debug|Any CPU.Build.0 = Debug|Any CPU + {B84ECB15-5E3F-4BD1-AB87-333BAE9B70F9}.Debug|Any CPU.Deploy.0 = Debug|Any CPU + {B84ECB15-5E3F-4BD1-AB87-333BAE9B70F9}.Release|Any CPU.ActiveCfg = Release|Any CPU + {B84ECB15-5E3F-4BD1-AB87-333BAE9B70F9}.Release|Any CPU.Build.0 = Release|Any CPU + {B84ECB15-5E3F-4BD1-AB87-333BAE9B70F9}.Release|Any CPU.Deploy.0 = Release|Any CPU + {1DBBC150-F085-43EF-B41D-27C72D133770}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {1DBBC150-F085-43EF-B41D-27C72D133770}.Debug|Any CPU.Build.0 = Debug|Any CPU + {1DBBC150-F085-43EF-B41D-27C72D133770}.Debug|Any CPU.Deploy.0 = Debug|Any CPU + {1DBBC150-F085-43EF-B41D-27C72D133770}.Release|Any CPU.ActiveCfg = Release|Any CPU + {1DBBC150-F085-43EF-B41D-27C72D133770}.Release|Any CPU.Build.0 = Release|Any CPU + {1DBBC150-F085-43EF-B41D-27C72D133770}.Release|Any CPU.Deploy.0 = Release|Any CPU + {53782603-3096-40C2-ABD3-F8F311BAE4BE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {53782603-3096-40C2-ABD3-F8F311BAE4BE}.Debug|Any CPU.Build.0 = Debug|Any CPU + {53782603-3096-40C2-ABD3-F8F311BAE4BE}.Debug|Any CPU.Deploy.0 = Debug|Any CPU + {53782603-3096-40C2-ABD3-F8F311BAE4BE}.Release|Any CPU.ActiveCfg = Release|Any CPU + {53782603-3096-40C2-ABD3-F8F311BAE4BE}.Release|Any CPU.Build.0 = Release|Any CPU + {53782603-3096-40C2-ABD3-F8F311BAE4BE}.Release|Any CPU.Deploy.0 = Release|Any CPU + {FBE1FA7B-E699-4BB2-9C8F-41F4C9F3F088}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {FBE1FA7B-E699-4BB2-9C8F-41F4C9F3F088}.Debug|Any CPU.Build.0 = Debug|Any CPU + {FBE1FA7B-E699-4BB2-9C8F-41F4C9F3F088}.Debug|Any CPU.Deploy.0 = Debug|Any CPU + {FBE1FA7B-E699-4BB2-9C8F-41F4C9F3F088}.Release|Any CPU.ActiveCfg = Release|Any CPU + {FBE1FA7B-E699-4BB2-9C8F-41F4C9F3F088}.Release|Any CPU.Build.0 = Release|Any CPU + {FBE1FA7B-E699-4BB2-9C8F-41F4C9F3F088}.Release|Any CPU.Deploy.0 = Release|Any CPU + {DA355C86-866F-4843-9B4D-63A173C750FB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {DA355C86-866F-4843-9B4D-63A173C750FB}.Debug|Any CPU.Build.0 = Debug|Any CPU + {DA355C86-866F-4843-9B4D-63A173C750FB}.Debug|Any CPU.Deploy.0 = Debug|Any CPU + {DA355C86-866F-4843-9B4D-63A173C750FB}.Release|Any CPU.ActiveCfg = Release|Any CPU + {DA355C86-866F-4843-9B4D-63A173C750FB}.Release|Any CPU.Build.0 = Release|Any CPU + {DA355C86-866F-4843-9B4D-63A173C750FB}.Release|Any CPU.Deploy.0 = Release|Any CPU + {AD4FDC24-B64D-4ED7-91AA-62C9EDA12FA4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {AD4FDC24-B64D-4ED7-91AA-62C9EDA12FA4}.Debug|Any CPU.Build.0 = Debug|Any CPU + {AD4FDC24-B64D-4ED7-91AA-62C9EDA12FA4}.Debug|Any CPU.Deploy.0 = Debug|Any CPU + {AD4FDC24-B64D-4ED7-91AA-62C9EDA12FA4}.Release|Any CPU.ActiveCfg = Release|Any CPU + {AD4FDC24-B64D-4ED7-91AA-62C9EDA12FA4}.Release|Any CPU.Build.0 = Release|Any CPU + {AD4FDC24-B64D-4ED7-91AA-62C9EDA12FA4}.Release|Any CPU.Deploy.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(NestedProjects) = preSolution + {C121A566-555E-42B9-9B0A-1696529A9088} = {4C142567-C42B-40F5-B092-798882190209} + {FB06C76A-6BB7-40BE-9AFA-FEC13B045FB5} = {4C142567-C42B-40F5-B092-798882190209} + {F2D52EDB-BC17-4243-B06D-33CD20F87A7F} = {10D145AF-C8AE-428F-A80F-CA1B591D0DB2} + {D81F5C91-D7DB-46E5-BC99-49488FB6814C} = {10D145AF-C8AE-428F-A80F-CA1B591D0DB2} + {42780CBD-3FE7-48E3-BD5B-59945EA20137} = {4C142567-C42B-40F5-B092-798882190209} + {0E916AB7-5A6C-4820-8AB1-AA492FE66D68} = {2E93E2B5-4500-4E47-9B65-E705218AB578} + {1677B922-CCF0-44DE-B57E-1CDD3D2B8E8A} = {2E93E2B5-4500-4E47-9B65-E705218AB578} + {5210FB81-B807-49BB-AF0D-31FB6A83A572} = {2E93E2B5-4500-4E47-9B65-E705218AB578} + {1320F627-EE43-4115-8E89-19D1753E51F2} = {2E93E2B5-4500-4E47-9B65-E705218AB578} + {1DE01410-22C9-489B-9796-1ADDAB1F64E5} = {2E93E2B5-4500-4E47-9B65-E705218AB578} + {14A47447-2A24-4ECD-B24D-6571499DCD4C} = {4C142567-C42B-40F5-B092-798882190209} + {273BDD15-7392-4078-91F0-AF23594A3D7B} = {4C142567-C42B-40F5-B092-798882190209} + {DE042125-C270-4D1D-9270-0759C167567A} = {4C142567-C42B-40F5-B092-798882190209} + {72390339-B2A1-4F61-A800-31ED0975B515} = {4C142567-C42B-40F5-B092-798882190209} + {E8B3553F-A79F-4E50-B75B-ACEE771C320C} = {4C142567-C42B-40F5-B092-798882190209} + {84DEB606-77ED-49CD-9AED-D2B13C1F5A1E} = {4C142567-C42B-40F5-B092-798882190209} + {BB9DEEEF-F18C-40D8-B016-6434CC71B8C3} = {4C142567-C42B-40F5-B092-798882190209} + {D002FEB1-00A6-4AB1-A83F-1F253465E64D} = {A7ED9F01-7D78-4381-90A6-D50E51C17250} + {DD592516-B341-40FE-9100-1B0FA784A060} = {4C142567-C42B-40F5-B092-798882190209} + {9BC63BEC-F305-451D-BB31-262938EA964D} = {4C142567-C42B-40F5-B092-798882190209} + {F32FDA80-B6DD-47A8-8681-437E2C0D3F31} = {4C142567-C42B-40F5-B092-798882190209} + {B84ECB15-5E3F-4BD1-AB87-333BAE9B70F9} = {A7ED9F01-7D78-4381-90A6-D50E51C17250} + {1DBBC150-F085-43EF-B41D-27C72D133770} = {4C142567-C42B-40F5-B092-798882190209} + {53782603-3096-40C2-ABD3-F8F311BAE4BE} = {4C142567-C42B-40F5-B092-798882190209} + {FBE1FA7B-E699-4BB2-9C8F-41F4C9F3F088} = {4C142567-C42B-40F5-B092-798882190209} + {B4EABB0D-E495-405C-B7B1-E2A7A3711AF5} = {FC791F56-C1F1-4C41-A193-868D8197F8E2} + {DA355C86-866F-4843-9B4D-63A173C750FB} = {4C142567-C42B-40F5-B092-798882190209} + {62E9A8E4-79AF-4081-84D5-FEC5A0B28598} = {FC791F56-C1F1-4C41-A193-868D8197F8E2} + {AD4FDC24-B64D-4ED7-91AA-62C9EDA12FA4} = {4C142567-C42B-40F5-B092-798882190209} + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {FF877973-604D-4EA7-B5F5-A129961F9EF2} + EndGlobalSection + GlobalSection(SharedMSBuildProjectFiles) = preSolution + ..\sources\engine\Stride.Shared\Refactor\Stride.Refactor.projitems*{c121a566-555e-42b9-9b0a-1696529a9088}*SharedItemsImports = 5 + ..\sources\shared\Stride.Core.ShellHelper\Stride.Core.ShellHelper.projitems*{e8b3553f-a79f-4e50-b75b-acee771c320c}*SharedItemsImports = 5 + ..\sources\engine\Stride.Shared\Refactor\Stride.Refactor.projitems*{fb06c76a-6bb7-40be-9afa-fec13b045fb5}*SharedItemsImports = 5 + EndGlobalSection +EndGlobal diff --git a/build/Stride.Runtime.slnf b/build/Stride.Runtime.slnf index 55f5299efd..ecc1a96a82 100644 --- a/build/Stride.Runtime.slnf +++ b/build/Stride.Runtime.slnf @@ -17,18 +17,20 @@ "..\\sources\\engine\\Stride.Particles\\Stride.Particles.csproj", "..\\sources\\engine\\Stride.Physics\\Stride.Physics.csproj", "..\\sources\\engine\\Stride.Rendering\\Stride.Rendering.csproj", - "..\\sources\\engine\\Stride.Shaders.Compiler\\Stride.Shaders.Compiler.csproj", - "..\\sources\\engine\\Stride.Shaders.Parser\\Stride.Shaders.Parser.csproj", - "..\\sources\\engine\\Stride.Shaders\\Stride.Shaders.csproj", "..\\sources\\engine\\Stride.SpriteStudio.Runtime\\Stride.SpriteStudio.Runtime.csproj", "..\\sources\\engine\\Stride.UI\\Stride.UI.csproj", "..\\sources\\engine\\Stride.Video\\Stride.Video.csproj", "..\\sources\\engine\\Stride.VirtualReality\\Stride.VirtualReality.csproj", "..\\sources\\engine\\Stride.Voxels\\Stride.Voxels.csproj", "..\\sources\\engine\\Stride\\Stride.csproj", - "..\\sources\\tools\\Stride.FreeImage\\Stride.FreeImage.csproj", - "..\\sources\\shaders\\Irony\\Irony.csproj", - "..\\sources\\shaders\\Stride.Core.Shaders\\Stride.Core.Shaders.csproj" + "..\\sources\\shaders\\Stride.Shaders\\Stride.Shaders.csproj", + "..\\sources\\shaders\\Stride.Shaders.Compilers\\Stride.Shaders.Compilers.csproj", + "..\\sources\\shaders\\Stride.Shaders.Parsers\\Stride.Shaders.Parsers.csproj", + "..\\sources\\shaders\\Stride.Shaders.Spirv.Core\\Stride.Shaders.Spirv.Core.csproj", + "..\\sources\\shaders\\Stride.Shaders.Generators\\Stride.Shaders.Generators.csproj", + "..\\sources\\shaders\\Stride.Shaders.Generators.Internal\\Stride.Shaders.Generators.Internal.csproj", + "..\\sources\\shaders\\Stride.Shaders.Spirv.Generators\\Stride.Shaders.Spirv.Generators.csproj", + "..\\sources\\tools\\Stride.FreeImage\\Stride.FreeImage.csproj" ] } -} \ No newline at end of file +} diff --git a/build/Stride.Tests.Simple.slnf b/build/Stride.Tests.Simple.slnf index f4e1553025..8b21359c83 100644 --- a/build/Stride.Tests.Simple.slnf +++ b/build/Stride.Tests.Simple.slnf @@ -12,7 +12,6 @@ "..\\sources\\core\\Stride.Core.Yaml.Tests\\Stride.Core.Yaml.Tests.csproj", "..\\sources\\editor\\Stride.Core.Assets.Editor.Tests\\Stride.Core.Assets.Editor.Tests.csproj", "..\\sources\\editor\\Stride.GameStudio.Tests\\Stride.GameStudio.Tests.csproj", - "..\\sources\\engine\\Stride.Shaders.Tests\\Stride.Shaders.Tests.Windows.csproj", "..\\sources\\presentation\\Stride.Core.Presentation.Quantum.Tests\\Stride.Core.Presentation.Quantum.Tests.csproj", "..\\sources\\presentation\\Stride.Core.Presentation.Tests\\Stride.Core.Presentation.Tests.csproj", "..\\sources\\presentation\\Stride.Core.Quantum.Tests\\Stride.Core.Quantum.Tests.csproj" diff --git a/build/Stride.UnitTests.Build.targets b/build/Stride.UnitTests.Build.targets new file mode 100644 index 0000000000..596166fd6e --- /dev/null +++ b/build/Stride.UnitTests.Build.targets @@ -0,0 +1,10 @@ + + + + Vulkan + + + Direct3D11 + + + diff --git a/build/Stride.iOS.sln b/build/Stride.iOS.sln new file mode 100644 index 0000000000..655dc39f22 --- /dev/null +++ b/build/Stride.iOS.sln @@ -0,0 +1,485 @@ +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 18 +VisualStudioVersion = 18.0.11205.157 +MinimumVisualStudioVersion = 18.0 +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "10-CoreRuntime", "10-CoreRuntime", "{2E93E2B5-4500-4E47-9B65-E705218AB578}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "20-StrideRuntime", "20-StrideRuntime", "{4C142567-C42B-40F5-B092-798882190209}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "00-Targets.Private", "00-Targets.Private", "{97978864-95DD-43A6-9159-AA1C881BE99F}" + ProjectSection(SolutionItems) = preProject + ..\sources\native\Stride.Native.targets = ..\sources\native\Stride.Native.targets + ..\sources\targets\Stride.Core.PostSettings.Dependencies.targets = ..\sources\targets\Stride.Core.PostSettings.Dependencies.targets + ..\sources\targets\Stride.Core.props = ..\sources\targets\Stride.Core.props + ..\sources\targets\Stride.Core.targets = ..\sources\targets\Stride.Core.targets + ..\sources\targets\Stride.GraphicsApi.Dev.targets = ..\sources\targets\Stride.GraphicsApi.Dev.targets + ..\sources\targets\Stride.GraphicsApi.PackageReference.targets = ..\sources\targets\Stride.GraphicsApi.PackageReference.targets + ..\sources\targets\Stride.PackageVersion.targets = ..\sources\targets\Stride.PackageVersion.targets + ..\sources\targets\Stride.props = ..\sources\targets\Stride.props + ..\sources\targets\Stride.targets = ..\sources\targets\Stride.targets + ..\sources\targets\Stride.UnitTests.CrossTargeting.targets = ..\sources\targets\Stride.UnitTests.CrossTargeting.targets + ..\sources\targets\Stride.UnitTests.DisableBuild.targets = ..\sources\targets\Stride.UnitTests.DisableBuild.targets + ..\sources\targets\Stride.UnitTests.props = ..\sources\targets\Stride.UnitTests.props + ..\sources\targets\Stride.UnitTests.targets = ..\sources\targets\Stride.UnitTests.targets + EndProjectSection +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "80-Shaders", "80-Shaders", "{10D145AF-C8AE-428F-A80F-CA1B591D0DB2}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "00-Config", "00-Config", "{7662CECF-2A3D-4DBA-AB3D-77FD8536E7A3}" + ProjectSection(SolutionItems) = preProject + ..\sources\shared\SharedAssemblyInfo.cs = ..\sources\shared\SharedAssemblyInfo.cs + EndProjectSection +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Stride.Shared", "Stride.Shared", "{1AC70118-C90F-4EC6-9D8B-C628BDF900F7}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "00-Targets.Build", "00-Targets.Build", "{0B81090E-4066-4723-A658-8AEDBEADE619}" + ProjectSection(SolutionItems) = preProject + Stride.build = Stride.build + Stride.Build.props = Stride.Build.props + Stride.Build.targets = Stride.Build.targets + Stride.Core.Build.props = Stride.Core.Build.props + Stride.Core.Build.targets = Stride.Core.Build.targets + Stride.UnitTests.Build.targets = Stride.UnitTests.Build.targets + EndProjectSection +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Stride.Engine", "..\sources\engine\Stride.Engine\Stride.Engine.csproj", "{C121A566-555E-42B9-9B0A-1696529A9088}" + ProjectSection(ProjectDependencies) = postProject + {F2D52EDB-BC17-4243-B06D-33CD20F87A7F} = {F2D52EDB-BC17-4243-B06D-33CD20F87A7F} + EndProjectSection +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Stride.Graphics", "..\sources\engine\Stride.Graphics\Stride.Graphics.csproj", "{FB06C76A-6BB7-40BE-9AFA-FEC13B045FB5}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Stride.Core.Shaders", "..\sources\shaders\Stride.Core.Shaders\Stride.Core.Shaders.csproj", "{F2D52EDB-BC17-4243-B06D-33CD20F87A7F}" + ProjectSection(ProjectDependencies) = postProject + {5210FB81-B807-49BB-AF0D-31FB6A83A572} = {5210FB81-B807-49BB-AF0D-31FB6A83A572} + EndProjectSection +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Irony", "..\sources\shaders\Irony\Irony.csproj", "{D81F5C91-D7DB-46E5-BC99-49488FB6814C}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Stride.Games", "..\sources\engine\Stride.Games\Stride.Games.csproj", "{42780CBD-3FE7-48E3-BD5B-59945EA20137}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Stride.Core", "..\sources\core\Stride.Core\Stride.Core.csproj", "{0E916AB7-5A6C-4820-8AB1-AA492FE66D68}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Stride.Core.Mathematics", "..\sources\core\Stride.Core.Mathematics\Stride.Core.Mathematics.csproj", "{1677B922-CCF0-44DE-B57E-1CDD3D2B8E8A}" + ProjectSection(ProjectDependencies) = postProject + {5210FB81-B807-49BB-AF0D-31FB6A83A572} = {5210FB81-B807-49BB-AF0D-31FB6A83A572} + EndProjectSection +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Stride.Core.Serialization", "..\sources\core\Stride.Core.Serialization\Stride.Core.Serialization.csproj", "{5210FB81-B807-49BB-AF0D-31FB6A83A572}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Stride.Core.MicroThreading", "..\sources\core\Stride.Core.MicroThreading\Stride.Core.MicroThreading.csproj", "{1320F627-EE43-4115-8E89-19D1753E51F2}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Stride.Core.IO", "..\sources\core\Stride.Core.IO\Stride.Core.IO.csproj", "{1DE01410-22C9-489B-9796-1ADDAB1F64E5}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Stride.Shaders.Parser", "..\sources\engine\Stride.Shaders.Parser\Stride.Shaders.Parser.csproj", "{14A47447-2A24-4ECD-B24D-6571499DCD4C}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Stride.Shaders", "..\sources\shaders\Stride.Shaders\Stride.Shaders.csproj", "{273BDD15-7392-4078-91F0-AF23594A3D7B}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Stride.Audio", "..\sources\engine\Stride.Audio\Stride.Audio.csproj", "{DE042125-C270-4D1D-9270-0759C167567A}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Stride", "..\sources\engine\Stride\Stride.csproj", "{72390339-B2A1-4F61-A800-31ED0975B515}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Stride.Shaders.Compiler", "..\sources\shaders\Stride.Shaders.Compilers\Stride.Shaders.Compilers.csproj", "{E8B3553F-A79F-4E50-B75B-ACEE771C320C}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Stride.Input", "..\sources\engine\Stride.Input\Stride.Input.csproj", "{84DEB606-77ED-49CD-9AED-D2B13C1F5A1E}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Stride.UI", "..\sources\engine\Stride.UI\Stride.UI.csproj", "{BB9DEEEF-F18C-40D8-B016-6434CC71B8C3}" + ProjectSection(ProjectDependencies) = postProject + {C121A566-555E-42B9-9B0A-1696529A9088} = {C121A566-555E-42B9-9B0A-1696529A9088} + EndProjectSection +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Stride.Physics", "..\sources\engine\Stride.Physics\Stride.Physics.csproj", "{DD592516-B341-40FE-9100-1B0FA784A060}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Stride.SpriteStudio.Runtime", "..\sources\engine\Stride.SpriteStudio.Runtime\Stride.SpriteStudio.Runtime.csproj", "{9BC63BEC-F305-451D-BB31-262938EA964D}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Stride.Particles", "..\sources\engine\Stride.Particles\Stride.Particles.csproj", "{F32FDA80-B6DD-47A8-8681-437E2C0D3F31}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Stride.Native", "..\sources\engine\Stride.Native\Stride.Native.csproj", "{1DBBC150-F085-43EF-B41D-27C72D133770}" +EndProject +Project("{D954291E-2A0B-460D-934E-DC6B0785DB48}") = "Stride.Refactor", "..\sources\engine\Stride.Shared\Refactor\Stride.Refactor.shproj", "{B33E576F-2279-4BFC-A438-D9B84343B56B}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Stride.VirtualReality", "..\sources\engine\Stride.VirtualReality\Stride.VirtualReality.csproj", "{53782603-3096-40C2-ABD3-F8F311BAE4BE}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Stride.Navigation", "..\sources\engine\Stride.Navigation\Stride.Navigation.csproj", "{FBE1FA7B-E699-4BB2-9C8F-41F4C9F3F088}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "00-Localization", "00-Localization", "{FC791F56-C1F1-4C41-A193-868D8197F8E2}" + ProjectSection(SolutionItems) = preProject + ..\sources\localization\Stride.Assets.Presentation.pot = ..\sources\localization\Stride.Assets.Presentation.pot + ..\sources\localization\Stride.Core.Assets.Editor.pot = ..\sources\localization\Stride.Core.Assets.Editor.pot + ..\sources\localization\Stride.Core.Presentation.pot = ..\sources\localization\Stride.Core.Presentation.pot + ..\sources\localization\Stride.GameStudio.pot = ..\sources\localization\Stride.GameStudio.pot + EndProjectSection +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "ja", "ja", "{B4EABB0D-E495-405C-B7B1-E2A7A3711AF5}" + ProjectSection(SolutionItems) = preProject + ..\sources\localization\ja\Stride.Assets.Presentation.ja.po = ..\sources\localization\ja\Stride.Assets.Presentation.ja.po + ..\sources\localization\ja\Stride.Core.Assets.Editor.ja.po = ..\sources\localization\ja\Stride.Core.Assets.Editor.ja.po + ..\sources\localization\ja\Stride.Core.Presentation.ja.po = ..\sources\localization\ja\Stride.Core.Presentation.ja.po + ..\sources\localization\ja\Stride.GameStudio.ja.po = ..\sources\localization\ja\Stride.GameStudio.ja.po + EndProjectSection +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Stride.Video", "..\sources\engine\Stride.Video\Stride.Video.csproj", "{DA355C86-866F-4843-9B4D-63A173C750FB}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "fr", "fr", "{62E9A8E4-79AF-4081-84D5-FEC5A0B28598}" + ProjectSection(SolutionItems) = preProject + ..\sources\localization\fr\Stride.Assets.Presentation.fr.po = ..\sources\localization\fr\Stride.Assets.Presentation.fr.po + ..\sources\localization\fr\Stride.Core.Assets.Editor.fr.po = ..\sources\localization\fr\Stride.Core.Assets.Editor.fr.po + ..\sources\localization\fr\Stride.Core.Presentation.fr.po = ..\sources\localization\fr\Stride.Core.Presentation.fr.po + ..\sources\localization\fr\Stride.GameStudio.fr.po = ..\sources\localization\fr\Stride.GameStudio.fr.po + EndProjectSection +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Stride.Rendering", "..\sources\engine\Stride.Rendering\Stride.Rendering.csproj", "{AD4FDC24-B64D-4ED7-91AA-62C9EDA12FA4}" +EndProject +Global + GlobalSection(SharedMSBuildProjectFiles) = preSolution + ..\sources\engine\Stride.Shared\Refactor\Stride.Refactor.projitems*{b33e576f-2279-4bfc-a438-d9b84343b56b}*SharedItemsImports = 13 + ..\sources\engine\Stride.Shared\Refactor\Stride.Refactor.projitems*{c121a566-555e-42b9-9b0a-1696529a9088}*SharedItemsImports = 4 + ..\sources\shared\Stride.Core.ShellHelper\Stride.Core.ShellHelper.projitems*{e8b3553f-a79f-4e50-b75b-acee771c320c}*SharedItemsImports = 4 + ..\sources\engine\Stride.Shared\Refactor\Stride.Refactor.projitems*{fb06c76a-6bb7-40be-9afa-fec13b045fb5}*SharedItemsImports = 4 + EndGlobalSection + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|iPhone = Debug|iPhone + Debug|iPhoneSimulator = Debug|iPhoneSimulator + Release|iPhone = Release|iPhone + Release|iPhoneSimulator = Release|iPhoneSimulator + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {C121A566-555E-42B9-9B0A-1696529A9088}.Debug|iPhone.ActiveCfg = Debug|iPhone + {C121A566-555E-42B9-9B0A-1696529A9088}.Debug|iPhone.Build.0 = Debug|iPhone + {C121A566-555E-42B9-9B0A-1696529A9088}.Debug|iPhone.Deploy.0 = Debug|iPhone + {C121A566-555E-42B9-9B0A-1696529A9088}.Debug|iPhoneSimulator.ActiveCfg = Debug|iPhoneSimulator + {C121A566-555E-42B9-9B0A-1696529A9088}.Debug|iPhoneSimulator.Build.0 = Debug|iPhoneSimulator + {C121A566-555E-42B9-9B0A-1696529A9088}.Debug|iPhoneSimulator.Deploy.0 = Debug|iPhoneSimulator + {C121A566-555E-42B9-9B0A-1696529A9088}.Release|iPhone.ActiveCfg = Release|iPhone + {C121A566-555E-42B9-9B0A-1696529A9088}.Release|iPhone.Build.0 = Release|iPhone + {C121A566-555E-42B9-9B0A-1696529A9088}.Release|iPhone.Deploy.0 = Release|iPhone + {C121A566-555E-42B9-9B0A-1696529A9088}.Release|iPhoneSimulator.ActiveCfg = Release|iPhoneSimulator + {C121A566-555E-42B9-9B0A-1696529A9088}.Release|iPhoneSimulator.Build.0 = Release|iPhoneSimulator + {C121A566-555E-42B9-9B0A-1696529A9088}.Release|iPhoneSimulator.Deploy.0 = Release|iPhoneSimulator + {FB06C76A-6BB7-40BE-9AFA-FEC13B045FB5}.Debug|iPhone.ActiveCfg = Debug|iPhone + {FB06C76A-6BB7-40BE-9AFA-FEC13B045FB5}.Debug|iPhone.Build.0 = Debug|iPhone + {FB06C76A-6BB7-40BE-9AFA-FEC13B045FB5}.Debug|iPhone.Deploy.0 = Debug|iPhone + {FB06C76A-6BB7-40BE-9AFA-FEC13B045FB5}.Debug|iPhoneSimulator.ActiveCfg = Debug|iPhoneSimulator + {FB06C76A-6BB7-40BE-9AFA-FEC13B045FB5}.Debug|iPhoneSimulator.Build.0 = Debug|iPhoneSimulator + {FB06C76A-6BB7-40BE-9AFA-FEC13B045FB5}.Debug|iPhoneSimulator.Deploy.0 = Debug|iPhoneSimulator + {FB06C76A-6BB7-40BE-9AFA-FEC13B045FB5}.Release|iPhone.ActiveCfg = Release|iPhone + {FB06C76A-6BB7-40BE-9AFA-FEC13B045FB5}.Release|iPhone.Build.0 = Release|iPhone + {FB06C76A-6BB7-40BE-9AFA-FEC13B045FB5}.Release|iPhone.Deploy.0 = Release|iPhone + {FB06C76A-6BB7-40BE-9AFA-FEC13B045FB5}.Release|iPhoneSimulator.ActiveCfg = Release|iPhoneSimulator + {FB06C76A-6BB7-40BE-9AFA-FEC13B045FB5}.Release|iPhoneSimulator.Build.0 = Release|iPhoneSimulator + {FB06C76A-6BB7-40BE-9AFA-FEC13B045FB5}.Release|iPhoneSimulator.Deploy.0 = Release|iPhoneSimulator + {F2D52EDB-BC17-4243-B06D-33CD20F87A7F}.Debug|iPhone.ActiveCfg = Debug|iPhone + {F2D52EDB-BC17-4243-B06D-33CD20F87A7F}.Debug|iPhone.Build.0 = Debug|iPhone + {F2D52EDB-BC17-4243-B06D-33CD20F87A7F}.Debug|iPhone.Deploy.0 = Debug|iPhone + {F2D52EDB-BC17-4243-B06D-33CD20F87A7F}.Debug|iPhoneSimulator.ActiveCfg = Debug|iPhoneSimulator + {F2D52EDB-BC17-4243-B06D-33CD20F87A7F}.Debug|iPhoneSimulator.Build.0 = Debug|iPhoneSimulator + {F2D52EDB-BC17-4243-B06D-33CD20F87A7F}.Debug|iPhoneSimulator.Deploy.0 = Debug|iPhoneSimulator + {F2D52EDB-BC17-4243-B06D-33CD20F87A7F}.Release|iPhone.ActiveCfg = Release|iPhone + {F2D52EDB-BC17-4243-B06D-33CD20F87A7F}.Release|iPhone.Build.0 = Release|iPhone + {F2D52EDB-BC17-4243-B06D-33CD20F87A7F}.Release|iPhone.Deploy.0 = Release|iPhone + {F2D52EDB-BC17-4243-B06D-33CD20F87A7F}.Release|iPhoneSimulator.ActiveCfg = Release|iPhoneSimulator + {F2D52EDB-BC17-4243-B06D-33CD20F87A7F}.Release|iPhoneSimulator.Build.0 = Release|iPhoneSimulator + {F2D52EDB-BC17-4243-B06D-33CD20F87A7F}.Release|iPhoneSimulator.Deploy.0 = Release|iPhoneSimulator + {D81F5C91-D7DB-46E5-BC99-49488FB6814C}.Debug|iPhone.ActiveCfg = Debug|iPhone + {D81F5C91-D7DB-46E5-BC99-49488FB6814C}.Debug|iPhone.Build.0 = Debug|iPhone + {D81F5C91-D7DB-46E5-BC99-49488FB6814C}.Debug|iPhone.Deploy.0 = Debug|iPhone + {D81F5C91-D7DB-46E5-BC99-49488FB6814C}.Debug|iPhoneSimulator.ActiveCfg = Debug|iPhoneSimulator + {D81F5C91-D7DB-46E5-BC99-49488FB6814C}.Debug|iPhoneSimulator.Build.0 = Debug|iPhoneSimulator + {D81F5C91-D7DB-46E5-BC99-49488FB6814C}.Debug|iPhoneSimulator.Deploy.0 = Debug|iPhoneSimulator + {D81F5C91-D7DB-46E5-BC99-49488FB6814C}.Release|iPhone.ActiveCfg = Release|iPhone + {D81F5C91-D7DB-46E5-BC99-49488FB6814C}.Release|iPhone.Build.0 = Release|iPhone + {D81F5C91-D7DB-46E5-BC99-49488FB6814C}.Release|iPhone.Deploy.0 = Release|iPhone + {D81F5C91-D7DB-46E5-BC99-49488FB6814C}.Release|iPhoneSimulator.ActiveCfg = Release|iPhoneSimulator + {D81F5C91-D7DB-46E5-BC99-49488FB6814C}.Release|iPhoneSimulator.Build.0 = Release|iPhoneSimulator + {D81F5C91-D7DB-46E5-BC99-49488FB6814C}.Release|iPhoneSimulator.Deploy.0 = Release|iPhoneSimulator + {42780CBD-3FE7-48E3-BD5B-59945EA20137}.Debug|iPhone.ActiveCfg = Debug|iPhone + {42780CBD-3FE7-48E3-BD5B-59945EA20137}.Debug|iPhone.Build.0 = Debug|iPhone + {42780CBD-3FE7-48E3-BD5B-59945EA20137}.Debug|iPhone.Deploy.0 = Debug|iPhone + {42780CBD-3FE7-48E3-BD5B-59945EA20137}.Debug|iPhoneSimulator.ActiveCfg = Debug|iPhoneSimulator + {42780CBD-3FE7-48E3-BD5B-59945EA20137}.Debug|iPhoneSimulator.Build.0 = Debug|iPhoneSimulator + {42780CBD-3FE7-48E3-BD5B-59945EA20137}.Debug|iPhoneSimulator.Deploy.0 = Debug|iPhoneSimulator + {42780CBD-3FE7-48E3-BD5B-59945EA20137}.Release|iPhone.ActiveCfg = Release|iPhone + {42780CBD-3FE7-48E3-BD5B-59945EA20137}.Release|iPhone.Build.0 = Release|iPhone + {42780CBD-3FE7-48E3-BD5B-59945EA20137}.Release|iPhone.Deploy.0 = Release|iPhone + {42780CBD-3FE7-48E3-BD5B-59945EA20137}.Release|iPhoneSimulator.ActiveCfg = Release|iPhoneSimulator + {42780CBD-3FE7-48E3-BD5B-59945EA20137}.Release|iPhoneSimulator.Build.0 = Release|iPhoneSimulator + {42780CBD-3FE7-48E3-BD5B-59945EA20137}.Release|iPhoneSimulator.Deploy.0 = Release|iPhoneSimulator + {0E916AB7-5A6C-4820-8AB1-AA492FE66D68}.Debug|iPhone.ActiveCfg = Debug|iPhone + {0E916AB7-5A6C-4820-8AB1-AA492FE66D68}.Debug|iPhone.Build.0 = Debug|iPhone + {0E916AB7-5A6C-4820-8AB1-AA492FE66D68}.Debug|iPhone.Deploy.0 = Debug|iPhone + {0E916AB7-5A6C-4820-8AB1-AA492FE66D68}.Debug|iPhoneSimulator.ActiveCfg = Debug|iPhoneSimulator + {0E916AB7-5A6C-4820-8AB1-AA492FE66D68}.Debug|iPhoneSimulator.Build.0 = Debug|iPhoneSimulator + {0E916AB7-5A6C-4820-8AB1-AA492FE66D68}.Debug|iPhoneSimulator.Deploy.0 = Debug|iPhoneSimulator + {0E916AB7-5A6C-4820-8AB1-AA492FE66D68}.Release|iPhone.ActiveCfg = Release|iPhone + {0E916AB7-5A6C-4820-8AB1-AA492FE66D68}.Release|iPhone.Build.0 = Release|iPhone + {0E916AB7-5A6C-4820-8AB1-AA492FE66D68}.Release|iPhone.Deploy.0 = Release|iPhone + {0E916AB7-5A6C-4820-8AB1-AA492FE66D68}.Release|iPhoneSimulator.ActiveCfg = Release|iPhoneSimulator + {0E916AB7-5A6C-4820-8AB1-AA492FE66D68}.Release|iPhoneSimulator.Build.0 = Release|iPhoneSimulator + {0E916AB7-5A6C-4820-8AB1-AA492FE66D68}.Release|iPhoneSimulator.Deploy.0 = Release|iPhoneSimulator + {1677B922-CCF0-44DE-B57E-1CDD3D2B8E8A}.Debug|iPhone.ActiveCfg = Debug|iPhone + {1677B922-CCF0-44DE-B57E-1CDD3D2B8E8A}.Debug|iPhone.Build.0 = Debug|iPhone + {1677B922-CCF0-44DE-B57E-1CDD3D2B8E8A}.Debug|iPhone.Deploy.0 = Debug|iPhone + {1677B922-CCF0-44DE-B57E-1CDD3D2B8E8A}.Debug|iPhoneSimulator.ActiveCfg = Debug|iPhoneSimulator + {1677B922-CCF0-44DE-B57E-1CDD3D2B8E8A}.Debug|iPhoneSimulator.Build.0 = Debug|iPhoneSimulator + {1677B922-CCF0-44DE-B57E-1CDD3D2B8E8A}.Debug|iPhoneSimulator.Deploy.0 = Debug|iPhoneSimulator + {1677B922-CCF0-44DE-B57E-1CDD3D2B8E8A}.Release|iPhone.ActiveCfg = Release|iPhone + {1677B922-CCF0-44DE-B57E-1CDD3D2B8E8A}.Release|iPhone.Build.0 = Release|iPhone + {1677B922-CCF0-44DE-B57E-1CDD3D2B8E8A}.Release|iPhone.Deploy.0 = Release|iPhone + {1677B922-CCF0-44DE-B57E-1CDD3D2B8E8A}.Release|iPhoneSimulator.ActiveCfg = Release|iPhoneSimulator + {1677B922-CCF0-44DE-B57E-1CDD3D2B8E8A}.Release|iPhoneSimulator.Build.0 = Release|iPhoneSimulator + {1677B922-CCF0-44DE-B57E-1CDD3D2B8E8A}.Release|iPhoneSimulator.Deploy.0 = Release|iPhoneSimulator + {5210FB81-B807-49BB-AF0D-31FB6A83A572}.Debug|iPhone.ActiveCfg = Debug|iPhone + {5210FB81-B807-49BB-AF0D-31FB6A83A572}.Debug|iPhone.Build.0 = Debug|iPhone + {5210FB81-B807-49BB-AF0D-31FB6A83A572}.Debug|iPhone.Deploy.0 = Debug|iPhone + {5210FB81-B807-49BB-AF0D-31FB6A83A572}.Debug|iPhoneSimulator.ActiveCfg = Debug|iPhoneSimulator + {5210FB81-B807-49BB-AF0D-31FB6A83A572}.Debug|iPhoneSimulator.Build.0 = Debug|iPhoneSimulator + {5210FB81-B807-49BB-AF0D-31FB6A83A572}.Debug|iPhoneSimulator.Deploy.0 = Debug|iPhoneSimulator + {5210FB81-B807-49BB-AF0D-31FB6A83A572}.Release|iPhone.ActiveCfg = Release|iPhone + {5210FB81-B807-49BB-AF0D-31FB6A83A572}.Release|iPhone.Build.0 = Release|iPhone + {5210FB81-B807-49BB-AF0D-31FB6A83A572}.Release|iPhone.Deploy.0 = Release|iPhone + {5210FB81-B807-49BB-AF0D-31FB6A83A572}.Release|iPhoneSimulator.ActiveCfg = Release|iPhoneSimulator + {5210FB81-B807-49BB-AF0D-31FB6A83A572}.Release|iPhoneSimulator.Build.0 = Release|iPhoneSimulator + {5210FB81-B807-49BB-AF0D-31FB6A83A572}.Release|iPhoneSimulator.Deploy.0 = Release|iPhoneSimulator + {1320F627-EE43-4115-8E89-19D1753E51F2}.Debug|iPhone.ActiveCfg = Debug|iPhone + {1320F627-EE43-4115-8E89-19D1753E51F2}.Debug|iPhone.Build.0 = Debug|iPhone + {1320F627-EE43-4115-8E89-19D1753E51F2}.Debug|iPhone.Deploy.0 = Debug|iPhone + {1320F627-EE43-4115-8E89-19D1753E51F2}.Debug|iPhoneSimulator.ActiveCfg = Debug|iPhoneSimulator + {1320F627-EE43-4115-8E89-19D1753E51F2}.Debug|iPhoneSimulator.Build.0 = Debug|iPhoneSimulator + {1320F627-EE43-4115-8E89-19D1753E51F2}.Debug|iPhoneSimulator.Deploy.0 = Debug|iPhoneSimulator + {1320F627-EE43-4115-8E89-19D1753E51F2}.Release|iPhone.ActiveCfg = Release|iPhone + {1320F627-EE43-4115-8E89-19D1753E51F2}.Release|iPhone.Build.0 = Release|iPhone + {1320F627-EE43-4115-8E89-19D1753E51F2}.Release|iPhone.Deploy.0 = Release|iPhone + {1320F627-EE43-4115-8E89-19D1753E51F2}.Release|iPhoneSimulator.ActiveCfg = Release|iPhoneSimulator + {1320F627-EE43-4115-8E89-19D1753E51F2}.Release|iPhoneSimulator.Build.0 = Release|iPhoneSimulator + {1320F627-EE43-4115-8E89-19D1753E51F2}.Release|iPhoneSimulator.Deploy.0 = Release|iPhoneSimulator + {1DE01410-22C9-489B-9796-1ADDAB1F64E5}.Debug|iPhone.ActiveCfg = Debug|iPhone + {1DE01410-22C9-489B-9796-1ADDAB1F64E5}.Debug|iPhone.Build.0 = Debug|iPhone + {1DE01410-22C9-489B-9796-1ADDAB1F64E5}.Debug|iPhone.Deploy.0 = Debug|iPhone + {1DE01410-22C9-489B-9796-1ADDAB1F64E5}.Debug|iPhoneSimulator.ActiveCfg = Debug|iPhoneSimulator + {1DE01410-22C9-489B-9796-1ADDAB1F64E5}.Debug|iPhoneSimulator.Build.0 = Debug|iPhoneSimulator + {1DE01410-22C9-489B-9796-1ADDAB1F64E5}.Debug|iPhoneSimulator.Deploy.0 = Debug|iPhoneSimulator + {1DE01410-22C9-489B-9796-1ADDAB1F64E5}.Release|iPhone.ActiveCfg = Release|iPhone + {1DE01410-22C9-489B-9796-1ADDAB1F64E5}.Release|iPhone.Build.0 = Release|iPhone + {1DE01410-22C9-489B-9796-1ADDAB1F64E5}.Release|iPhone.Deploy.0 = Release|iPhone + {1DE01410-22C9-489B-9796-1ADDAB1F64E5}.Release|iPhoneSimulator.ActiveCfg = Release|iPhoneSimulator + {1DE01410-22C9-489B-9796-1ADDAB1F64E5}.Release|iPhoneSimulator.Build.0 = Release|iPhoneSimulator + {1DE01410-22C9-489B-9796-1ADDAB1F64E5}.Release|iPhoneSimulator.Deploy.0 = Release|iPhoneSimulator + {14A47447-2A24-4ECD-B24D-6571499DCD4C}.Debug|iPhone.ActiveCfg = Debug|iPhone + {14A47447-2A24-4ECD-B24D-6571499DCD4C}.Debug|iPhone.Build.0 = Debug|iPhone + {14A47447-2A24-4ECD-B24D-6571499DCD4C}.Debug|iPhone.Deploy.0 = Debug|iPhone + {14A47447-2A24-4ECD-B24D-6571499DCD4C}.Debug|iPhoneSimulator.ActiveCfg = Debug|iPhoneSimulator + {14A47447-2A24-4ECD-B24D-6571499DCD4C}.Debug|iPhoneSimulator.Build.0 = Debug|iPhoneSimulator + {14A47447-2A24-4ECD-B24D-6571499DCD4C}.Debug|iPhoneSimulator.Deploy.0 = Debug|iPhoneSimulator + {14A47447-2A24-4ECD-B24D-6571499DCD4C}.Release|iPhone.ActiveCfg = Release|iPhone + {14A47447-2A24-4ECD-B24D-6571499DCD4C}.Release|iPhone.Build.0 = Release|iPhone + {14A47447-2A24-4ECD-B24D-6571499DCD4C}.Release|iPhone.Deploy.0 = Release|iPhone + {14A47447-2A24-4ECD-B24D-6571499DCD4C}.Release|iPhoneSimulator.ActiveCfg = Release|iPhoneSimulator + {14A47447-2A24-4ECD-B24D-6571499DCD4C}.Release|iPhoneSimulator.Build.0 = Release|iPhoneSimulator + {14A47447-2A24-4ECD-B24D-6571499DCD4C}.Release|iPhoneSimulator.Deploy.0 = Release|iPhoneSimulator + {273BDD15-7392-4078-91F0-AF23594A3D7B}.Debug|iPhone.ActiveCfg = Debug|iPhone + {273BDD15-7392-4078-91F0-AF23594A3D7B}.Debug|iPhone.Build.0 = Debug|iPhone + {273BDD15-7392-4078-91F0-AF23594A3D7B}.Debug|iPhone.Deploy.0 = Debug|iPhone + {273BDD15-7392-4078-91F0-AF23594A3D7B}.Debug|iPhoneSimulator.ActiveCfg = Debug|iPhoneSimulator + {273BDD15-7392-4078-91F0-AF23594A3D7B}.Debug|iPhoneSimulator.Build.0 = Debug|iPhoneSimulator + {273BDD15-7392-4078-91F0-AF23594A3D7B}.Debug|iPhoneSimulator.Deploy.0 = Debug|iPhoneSimulator + {273BDD15-7392-4078-91F0-AF23594A3D7B}.Release|iPhone.ActiveCfg = Release|iPhone + {273BDD15-7392-4078-91F0-AF23594A3D7B}.Release|iPhone.Build.0 = Release|iPhone + {273BDD15-7392-4078-91F0-AF23594A3D7B}.Release|iPhone.Deploy.0 = Release|iPhone + {273BDD15-7392-4078-91F0-AF23594A3D7B}.Release|iPhoneSimulator.ActiveCfg = Release|iPhoneSimulator + {273BDD15-7392-4078-91F0-AF23594A3D7B}.Release|iPhoneSimulator.Build.0 = Release|iPhoneSimulator + {273BDD15-7392-4078-91F0-AF23594A3D7B}.Release|iPhoneSimulator.Deploy.0 = Release|iPhoneSimulator + {DE042125-C270-4D1D-9270-0759C167567A}.Debug|iPhone.ActiveCfg = Debug|iPhone + {DE042125-C270-4D1D-9270-0759C167567A}.Debug|iPhone.Build.0 = Debug|iPhone + {DE042125-C270-4D1D-9270-0759C167567A}.Debug|iPhone.Deploy.0 = Debug|iPhone + {DE042125-C270-4D1D-9270-0759C167567A}.Debug|iPhoneSimulator.ActiveCfg = Debug|iPhoneSimulator + {DE042125-C270-4D1D-9270-0759C167567A}.Debug|iPhoneSimulator.Build.0 = Debug|iPhoneSimulator + {DE042125-C270-4D1D-9270-0759C167567A}.Debug|iPhoneSimulator.Deploy.0 = Debug|iPhoneSimulator + {DE042125-C270-4D1D-9270-0759C167567A}.Release|iPhone.ActiveCfg = Release|iPhone + {DE042125-C270-4D1D-9270-0759C167567A}.Release|iPhone.Build.0 = Release|iPhone + {DE042125-C270-4D1D-9270-0759C167567A}.Release|iPhone.Deploy.0 = Release|iPhone + {DE042125-C270-4D1D-9270-0759C167567A}.Release|iPhoneSimulator.ActiveCfg = Release|iPhoneSimulator + {DE042125-C270-4D1D-9270-0759C167567A}.Release|iPhoneSimulator.Build.0 = Release|iPhoneSimulator + {DE042125-C270-4D1D-9270-0759C167567A}.Release|iPhoneSimulator.Deploy.0 = Release|iPhoneSimulator + {72390339-B2A1-4F61-A800-31ED0975B515}.Debug|iPhone.ActiveCfg = Debug|iPhone + {72390339-B2A1-4F61-A800-31ED0975B515}.Debug|iPhone.Build.0 = Debug|iPhone + {72390339-B2A1-4F61-A800-31ED0975B515}.Debug|iPhone.Deploy.0 = Debug|iPhone + {72390339-B2A1-4F61-A800-31ED0975B515}.Debug|iPhoneSimulator.ActiveCfg = Debug|iPhoneSimulator + {72390339-B2A1-4F61-A800-31ED0975B515}.Debug|iPhoneSimulator.Build.0 = Debug|iPhoneSimulator + {72390339-B2A1-4F61-A800-31ED0975B515}.Debug|iPhoneSimulator.Deploy.0 = Debug|iPhoneSimulator + {72390339-B2A1-4F61-A800-31ED0975B515}.Release|iPhone.ActiveCfg = Release|iPhone + {72390339-B2A1-4F61-A800-31ED0975B515}.Release|iPhone.Build.0 = Release|iPhone + {72390339-B2A1-4F61-A800-31ED0975B515}.Release|iPhone.Deploy.0 = Release|iPhone + {72390339-B2A1-4F61-A800-31ED0975B515}.Release|iPhoneSimulator.ActiveCfg = Release|iPhoneSimulator + {72390339-B2A1-4F61-A800-31ED0975B515}.Release|iPhoneSimulator.Build.0 = Release|iPhoneSimulator + {72390339-B2A1-4F61-A800-31ED0975B515}.Release|iPhoneSimulator.Deploy.0 = Release|iPhoneSimulator + {E8B3553F-A79F-4E50-B75B-ACEE771C320C}.Debug|iPhone.ActiveCfg = Debug|iPhone + {E8B3553F-A79F-4E50-B75B-ACEE771C320C}.Debug|iPhone.Build.0 = Debug|iPhone + {E8B3553F-A79F-4E50-B75B-ACEE771C320C}.Debug|iPhone.Deploy.0 = Debug|iPhone + {E8B3553F-A79F-4E50-B75B-ACEE771C320C}.Debug|iPhoneSimulator.ActiveCfg = Debug|iPhoneSimulator + {E8B3553F-A79F-4E50-B75B-ACEE771C320C}.Debug|iPhoneSimulator.Build.0 = Debug|iPhoneSimulator + {E8B3553F-A79F-4E50-B75B-ACEE771C320C}.Debug|iPhoneSimulator.Deploy.0 = Debug|iPhoneSimulator + {E8B3553F-A79F-4E50-B75B-ACEE771C320C}.Release|iPhone.ActiveCfg = Release|iPhone + {E8B3553F-A79F-4E50-B75B-ACEE771C320C}.Release|iPhone.Build.0 = Release|iPhone + {E8B3553F-A79F-4E50-B75B-ACEE771C320C}.Release|iPhone.Deploy.0 = Release|iPhone + {E8B3553F-A79F-4E50-B75B-ACEE771C320C}.Release|iPhoneSimulator.ActiveCfg = Release|iPhoneSimulator + {E8B3553F-A79F-4E50-B75B-ACEE771C320C}.Release|iPhoneSimulator.Build.0 = Release|iPhoneSimulator + {E8B3553F-A79F-4E50-B75B-ACEE771C320C}.Release|iPhoneSimulator.Deploy.0 = Release|iPhoneSimulator + {84DEB606-77ED-49CD-9AED-D2B13C1F5A1E}.Debug|iPhone.ActiveCfg = Debug|iPhone + {84DEB606-77ED-49CD-9AED-D2B13C1F5A1E}.Debug|iPhone.Build.0 = Debug|iPhone + {84DEB606-77ED-49CD-9AED-D2B13C1F5A1E}.Debug|iPhone.Deploy.0 = Debug|iPhone + {84DEB606-77ED-49CD-9AED-D2B13C1F5A1E}.Debug|iPhoneSimulator.ActiveCfg = Debug|iPhoneSimulator + {84DEB606-77ED-49CD-9AED-D2B13C1F5A1E}.Debug|iPhoneSimulator.Build.0 = Debug|iPhoneSimulator + {84DEB606-77ED-49CD-9AED-D2B13C1F5A1E}.Debug|iPhoneSimulator.Deploy.0 = Debug|iPhoneSimulator + {84DEB606-77ED-49CD-9AED-D2B13C1F5A1E}.Release|iPhone.ActiveCfg = Release|iPhone + {84DEB606-77ED-49CD-9AED-D2B13C1F5A1E}.Release|iPhone.Build.0 = Release|iPhone + {84DEB606-77ED-49CD-9AED-D2B13C1F5A1E}.Release|iPhone.Deploy.0 = Release|iPhone + {84DEB606-77ED-49CD-9AED-D2B13C1F5A1E}.Release|iPhoneSimulator.ActiveCfg = Release|iPhoneSimulator + {84DEB606-77ED-49CD-9AED-D2B13C1F5A1E}.Release|iPhoneSimulator.Build.0 = Release|iPhoneSimulator + {84DEB606-77ED-49CD-9AED-D2B13C1F5A1E}.Release|iPhoneSimulator.Deploy.0 = Release|iPhoneSimulator + {BB9DEEEF-F18C-40D8-B016-6434CC71B8C3}.Debug|iPhone.ActiveCfg = Debug|iPhone + {BB9DEEEF-F18C-40D8-B016-6434CC71B8C3}.Debug|iPhone.Build.0 = Debug|iPhone + {BB9DEEEF-F18C-40D8-B016-6434CC71B8C3}.Debug|iPhone.Deploy.0 = Debug|iPhone + {BB9DEEEF-F18C-40D8-B016-6434CC71B8C3}.Debug|iPhoneSimulator.ActiveCfg = Debug|iPhoneSimulator + {BB9DEEEF-F18C-40D8-B016-6434CC71B8C3}.Debug|iPhoneSimulator.Build.0 = Debug|iPhoneSimulator + {BB9DEEEF-F18C-40D8-B016-6434CC71B8C3}.Debug|iPhoneSimulator.Deploy.0 = Debug|iPhoneSimulator + {BB9DEEEF-F18C-40D8-B016-6434CC71B8C3}.Release|iPhone.ActiveCfg = Release|iPhone + {BB9DEEEF-F18C-40D8-B016-6434CC71B8C3}.Release|iPhone.Build.0 = Release|iPhone + {BB9DEEEF-F18C-40D8-B016-6434CC71B8C3}.Release|iPhone.Deploy.0 = Release|iPhone + {BB9DEEEF-F18C-40D8-B016-6434CC71B8C3}.Release|iPhoneSimulator.ActiveCfg = Release|iPhoneSimulator + {BB9DEEEF-F18C-40D8-B016-6434CC71B8C3}.Release|iPhoneSimulator.Build.0 = Release|iPhoneSimulator + {BB9DEEEF-F18C-40D8-B016-6434CC71B8C3}.Release|iPhoneSimulator.Deploy.0 = Release|iPhoneSimulator + {DD592516-B341-40FE-9100-1B0FA784A060}.Debug|iPhone.ActiveCfg = Debug|iPhone + {DD592516-B341-40FE-9100-1B0FA784A060}.Debug|iPhone.Build.0 = Debug|iPhone + {DD592516-B341-40FE-9100-1B0FA784A060}.Debug|iPhone.Deploy.0 = Debug|iPhone + {DD592516-B341-40FE-9100-1B0FA784A060}.Debug|iPhoneSimulator.ActiveCfg = Debug|iPhoneSimulator + {DD592516-B341-40FE-9100-1B0FA784A060}.Debug|iPhoneSimulator.Build.0 = Debug|iPhoneSimulator + {DD592516-B341-40FE-9100-1B0FA784A060}.Debug|iPhoneSimulator.Deploy.0 = Debug|iPhoneSimulator + {DD592516-B341-40FE-9100-1B0FA784A060}.Release|iPhone.ActiveCfg = Release|iPhone + {DD592516-B341-40FE-9100-1B0FA784A060}.Release|iPhone.Build.0 = Release|iPhone + {DD592516-B341-40FE-9100-1B0FA784A060}.Release|iPhone.Deploy.0 = Release|iPhone + {DD592516-B341-40FE-9100-1B0FA784A060}.Release|iPhoneSimulator.ActiveCfg = Release|iPhoneSimulator + {DD592516-B341-40FE-9100-1B0FA784A060}.Release|iPhoneSimulator.Build.0 = Release|iPhoneSimulator + {DD592516-B341-40FE-9100-1B0FA784A060}.Release|iPhoneSimulator.Deploy.0 = Release|iPhoneSimulator + {9BC63BEC-F305-451D-BB31-262938EA964D}.Debug|iPhone.ActiveCfg = Debug|iPhone + {9BC63BEC-F305-451D-BB31-262938EA964D}.Debug|iPhone.Build.0 = Debug|iPhone + {9BC63BEC-F305-451D-BB31-262938EA964D}.Debug|iPhone.Deploy.0 = Debug|iPhone + {9BC63BEC-F305-451D-BB31-262938EA964D}.Debug|iPhoneSimulator.ActiveCfg = Debug|iPhoneSimulator + {9BC63BEC-F305-451D-BB31-262938EA964D}.Debug|iPhoneSimulator.Build.0 = Debug|iPhoneSimulator + {9BC63BEC-F305-451D-BB31-262938EA964D}.Debug|iPhoneSimulator.Deploy.0 = Debug|iPhoneSimulator + {9BC63BEC-F305-451D-BB31-262938EA964D}.Release|iPhone.ActiveCfg = Release|iPhone + {9BC63BEC-F305-451D-BB31-262938EA964D}.Release|iPhone.Build.0 = Release|iPhone + {9BC63BEC-F305-451D-BB31-262938EA964D}.Release|iPhone.Deploy.0 = Release|iPhone + {9BC63BEC-F305-451D-BB31-262938EA964D}.Release|iPhoneSimulator.ActiveCfg = Release|iPhoneSimulator + {9BC63BEC-F305-451D-BB31-262938EA964D}.Release|iPhoneSimulator.Build.0 = Release|iPhoneSimulator + {9BC63BEC-F305-451D-BB31-262938EA964D}.Release|iPhoneSimulator.Deploy.0 = Release|iPhoneSimulator + {F32FDA80-B6DD-47A8-8681-437E2C0D3F31}.Debug|iPhone.ActiveCfg = Debug|iPhone + {F32FDA80-B6DD-47A8-8681-437E2C0D3F31}.Debug|iPhone.Build.0 = Debug|iPhone + {F32FDA80-B6DD-47A8-8681-437E2C0D3F31}.Debug|iPhone.Deploy.0 = Debug|iPhone + {F32FDA80-B6DD-47A8-8681-437E2C0D3F31}.Debug|iPhoneSimulator.ActiveCfg = Debug|iPhoneSimulator + {F32FDA80-B6DD-47A8-8681-437E2C0D3F31}.Debug|iPhoneSimulator.Build.0 = Debug|iPhoneSimulator + {F32FDA80-B6DD-47A8-8681-437E2C0D3F31}.Debug|iPhoneSimulator.Deploy.0 = Debug|iPhoneSimulator + {F32FDA80-B6DD-47A8-8681-437E2C0D3F31}.Release|iPhone.ActiveCfg = Release|iPhone + {F32FDA80-B6DD-47A8-8681-437E2C0D3F31}.Release|iPhone.Build.0 = Release|iPhone + {F32FDA80-B6DD-47A8-8681-437E2C0D3F31}.Release|iPhone.Deploy.0 = Release|iPhone + {F32FDA80-B6DD-47A8-8681-437E2C0D3F31}.Release|iPhoneSimulator.ActiveCfg = Release|iPhoneSimulator + {F32FDA80-B6DD-47A8-8681-437E2C0D3F31}.Release|iPhoneSimulator.Build.0 = Release|iPhoneSimulator + {F32FDA80-B6DD-47A8-8681-437E2C0D3F31}.Release|iPhoneSimulator.Deploy.0 = Release|iPhoneSimulator + {1DBBC150-F085-43EF-B41D-27C72D133770}.Debug|iPhone.ActiveCfg = Debug|iPhone + {1DBBC150-F085-43EF-B41D-27C72D133770}.Debug|iPhone.Build.0 = Debug|iPhone + {1DBBC150-F085-43EF-B41D-27C72D133770}.Debug|iPhone.Deploy.0 = Debug|iPhone + {1DBBC150-F085-43EF-B41D-27C72D133770}.Debug|iPhoneSimulator.ActiveCfg = Debug|iPhoneSimulator + {1DBBC150-F085-43EF-B41D-27C72D133770}.Debug|iPhoneSimulator.Build.0 = Debug|iPhoneSimulator + {1DBBC150-F085-43EF-B41D-27C72D133770}.Debug|iPhoneSimulator.Deploy.0 = Debug|iPhoneSimulator + {1DBBC150-F085-43EF-B41D-27C72D133770}.Release|iPhone.ActiveCfg = Release|iPhone + {1DBBC150-F085-43EF-B41D-27C72D133770}.Release|iPhone.Build.0 = Release|iPhone + {1DBBC150-F085-43EF-B41D-27C72D133770}.Release|iPhone.Deploy.0 = Release|iPhone + {1DBBC150-F085-43EF-B41D-27C72D133770}.Release|iPhoneSimulator.ActiveCfg = Release|iPhoneSimulator + {1DBBC150-F085-43EF-B41D-27C72D133770}.Release|iPhoneSimulator.Build.0 = Release|iPhoneSimulator + {1DBBC150-F085-43EF-B41D-27C72D133770}.Release|iPhoneSimulator.Deploy.0 = Release|iPhoneSimulator + {53782603-3096-40C2-ABD3-F8F311BAE4BE}.Debug|iPhone.ActiveCfg = Debug|iPhone + {53782603-3096-40C2-ABD3-F8F311BAE4BE}.Debug|iPhone.Build.0 = Debug|iPhone + {53782603-3096-40C2-ABD3-F8F311BAE4BE}.Debug|iPhone.Deploy.0 = Debug|iPhone + {53782603-3096-40C2-ABD3-F8F311BAE4BE}.Debug|iPhoneSimulator.ActiveCfg = Debug|iPhoneSimulator + {53782603-3096-40C2-ABD3-F8F311BAE4BE}.Debug|iPhoneSimulator.Build.0 = Debug|iPhoneSimulator + {53782603-3096-40C2-ABD3-F8F311BAE4BE}.Debug|iPhoneSimulator.Deploy.0 = Debug|iPhoneSimulator + {53782603-3096-40C2-ABD3-F8F311BAE4BE}.Release|iPhone.ActiveCfg = Release|iPhone + {53782603-3096-40C2-ABD3-F8F311BAE4BE}.Release|iPhone.Build.0 = Release|iPhone + {53782603-3096-40C2-ABD3-F8F311BAE4BE}.Release|iPhone.Deploy.0 = Release|iPhone + {53782603-3096-40C2-ABD3-F8F311BAE4BE}.Release|iPhoneSimulator.ActiveCfg = Release|iPhoneSimulator + {53782603-3096-40C2-ABD3-F8F311BAE4BE}.Release|iPhoneSimulator.Build.0 = Release|iPhoneSimulator + {53782603-3096-40C2-ABD3-F8F311BAE4BE}.Release|iPhoneSimulator.Deploy.0 = Release|iPhoneSimulator + {FBE1FA7B-E699-4BB2-9C8F-41F4C9F3F088}.Debug|iPhone.ActiveCfg = Debug|iPhone + {FBE1FA7B-E699-4BB2-9C8F-41F4C9F3F088}.Debug|iPhone.Build.0 = Debug|iPhone + {FBE1FA7B-E699-4BB2-9C8F-41F4C9F3F088}.Debug|iPhone.Deploy.0 = Debug|iPhone + {FBE1FA7B-E699-4BB2-9C8F-41F4C9F3F088}.Debug|iPhoneSimulator.ActiveCfg = Debug|iPhoneSimulator + {FBE1FA7B-E699-4BB2-9C8F-41F4C9F3F088}.Debug|iPhoneSimulator.Build.0 = Debug|iPhoneSimulator + {FBE1FA7B-E699-4BB2-9C8F-41F4C9F3F088}.Debug|iPhoneSimulator.Deploy.0 = Debug|iPhoneSimulator + {FBE1FA7B-E699-4BB2-9C8F-41F4C9F3F088}.Release|iPhone.ActiveCfg = Release|iPhone + {FBE1FA7B-E699-4BB2-9C8F-41F4C9F3F088}.Release|iPhone.Build.0 = Release|iPhone + {FBE1FA7B-E699-4BB2-9C8F-41F4C9F3F088}.Release|iPhone.Deploy.0 = Release|iPhone + {FBE1FA7B-E699-4BB2-9C8F-41F4C9F3F088}.Release|iPhoneSimulator.ActiveCfg = Release|iPhoneSimulator + {FBE1FA7B-E699-4BB2-9C8F-41F4C9F3F088}.Release|iPhoneSimulator.Build.0 = Release|iPhoneSimulator + {FBE1FA7B-E699-4BB2-9C8F-41F4C9F3F088}.Release|iPhoneSimulator.Deploy.0 = Release|iPhoneSimulator + {DA355C86-866F-4843-9B4D-63A173C750FB}.Debug|iPhone.ActiveCfg = Debug|iPhone + {DA355C86-866F-4843-9B4D-63A173C750FB}.Debug|iPhone.Build.0 = Debug|iPhone + {DA355C86-866F-4843-9B4D-63A173C750FB}.Debug|iPhone.Deploy.0 = Debug|iPhone + {DA355C86-866F-4843-9B4D-63A173C750FB}.Debug|iPhoneSimulator.ActiveCfg = Debug|iPhoneSimulator + {DA355C86-866F-4843-9B4D-63A173C750FB}.Debug|iPhoneSimulator.Build.0 = Debug|iPhoneSimulator + {DA355C86-866F-4843-9B4D-63A173C750FB}.Debug|iPhoneSimulator.Deploy.0 = Debug|iPhoneSimulator + {DA355C86-866F-4843-9B4D-63A173C750FB}.Release|iPhone.ActiveCfg = Release|iPhone + {DA355C86-866F-4843-9B4D-63A173C750FB}.Release|iPhone.Build.0 = Release|iPhone + {DA355C86-866F-4843-9B4D-63A173C750FB}.Release|iPhone.Deploy.0 = Release|iPhone + {DA355C86-866F-4843-9B4D-63A173C750FB}.Release|iPhoneSimulator.ActiveCfg = Release|iPhoneSimulator + {DA355C86-866F-4843-9B4D-63A173C750FB}.Release|iPhoneSimulator.Build.0 = Release|iPhoneSimulator + {DA355C86-866F-4843-9B4D-63A173C750FB}.Release|iPhoneSimulator.Deploy.0 = Release|iPhoneSimulator + {AD4FDC24-B64D-4ED7-91AA-62C9EDA12FA4}.Debug|iPhone.ActiveCfg = Debug|iPhone + {AD4FDC24-B64D-4ED7-91AA-62C9EDA12FA4}.Debug|iPhone.Build.0 = Debug|iPhone + {AD4FDC24-B64D-4ED7-91AA-62C9EDA12FA4}.Debug|iPhone.Deploy.0 = Debug|iPhone + {AD4FDC24-B64D-4ED7-91AA-62C9EDA12FA4}.Debug|iPhoneSimulator.ActiveCfg = Debug|iPhoneSimulator + {AD4FDC24-B64D-4ED7-91AA-62C9EDA12FA4}.Debug|iPhoneSimulator.Build.0 = Debug|iPhoneSimulator + {AD4FDC24-B64D-4ED7-91AA-62C9EDA12FA4}.Debug|iPhoneSimulator.Deploy.0 = Debug|iPhoneSimulator + {AD4FDC24-B64D-4ED7-91AA-62C9EDA12FA4}.Release|iPhone.ActiveCfg = Release|iPhone + {AD4FDC24-B64D-4ED7-91AA-62C9EDA12FA4}.Release|iPhone.Build.0 = Release|iPhone + {AD4FDC24-B64D-4ED7-91AA-62C9EDA12FA4}.Release|iPhone.Deploy.0 = Release|iPhone + {AD4FDC24-B64D-4ED7-91AA-62C9EDA12FA4}.Release|iPhoneSimulator.ActiveCfg = Release|iPhoneSimulator + {AD4FDC24-B64D-4ED7-91AA-62C9EDA12FA4}.Release|iPhoneSimulator.Build.0 = Release|iPhoneSimulator + {AD4FDC24-B64D-4ED7-91AA-62C9EDA12FA4}.Release|iPhoneSimulator.Deploy.0 = Release|iPhoneSimulator + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(NestedProjects) = preSolution + {1AC70118-C90F-4EC6-9D8B-C628BDF900F7} = {4C142567-C42B-40F5-B092-798882190209} + {C121A566-555E-42B9-9B0A-1696529A9088} = {4C142567-C42B-40F5-B092-798882190209} + {FB06C76A-6BB7-40BE-9AFA-FEC13B045FB5} = {4C142567-C42B-40F5-B092-798882190209} + {F2D52EDB-BC17-4243-B06D-33CD20F87A7F} = {10D145AF-C8AE-428F-A80F-CA1B591D0DB2} + {D81F5C91-D7DB-46E5-BC99-49488FB6814C} = {10D145AF-C8AE-428F-A80F-CA1B591D0DB2} + {42780CBD-3FE7-48E3-BD5B-59945EA20137} = {4C142567-C42B-40F5-B092-798882190209} + {0E916AB7-5A6C-4820-8AB1-AA492FE66D68} = {2E93E2B5-4500-4E47-9B65-E705218AB578} + {1677B922-CCF0-44DE-B57E-1CDD3D2B8E8A} = {2E93E2B5-4500-4E47-9B65-E705218AB578} + {5210FB81-B807-49BB-AF0D-31FB6A83A572} = {2E93E2B5-4500-4E47-9B65-E705218AB578} + {1320F627-EE43-4115-8E89-19D1753E51F2} = {2E93E2B5-4500-4E47-9B65-E705218AB578} + {1DE01410-22C9-489B-9796-1ADDAB1F64E5} = {2E93E2B5-4500-4E47-9B65-E705218AB578} + {14A47447-2A24-4ECD-B24D-6571499DCD4C} = {4C142567-C42B-40F5-B092-798882190209} + {273BDD15-7392-4078-91F0-AF23594A3D7B} = {4C142567-C42B-40F5-B092-798882190209} + {DE042125-C270-4D1D-9270-0759C167567A} = {4C142567-C42B-40F5-B092-798882190209} + {72390339-B2A1-4F61-A800-31ED0975B515} = {4C142567-C42B-40F5-B092-798882190209} + {E8B3553F-A79F-4E50-B75B-ACEE771C320C} = {4C142567-C42B-40F5-B092-798882190209} + {84DEB606-77ED-49CD-9AED-D2B13C1F5A1E} = {4C142567-C42B-40F5-B092-798882190209} + {BB9DEEEF-F18C-40D8-B016-6434CC71B8C3} = {4C142567-C42B-40F5-B092-798882190209} + {DD592516-B341-40FE-9100-1B0FA784A060} = {4C142567-C42B-40F5-B092-798882190209} + {9BC63BEC-F305-451D-BB31-262938EA964D} = {4C142567-C42B-40F5-B092-798882190209} + {F32FDA80-B6DD-47A8-8681-437E2C0D3F31} = {4C142567-C42B-40F5-B092-798882190209} + {1DBBC150-F085-43EF-B41D-27C72D133770} = {4C142567-C42B-40F5-B092-798882190209} + {B33E576F-2279-4BFC-A438-D9B84343B56B} = {1AC70118-C90F-4EC6-9D8B-C628BDF900F7} + {53782603-3096-40C2-ABD3-F8F311BAE4BE} = {4C142567-C42B-40F5-B092-798882190209} + {FBE1FA7B-E699-4BB2-9C8F-41F4C9F3F088} = {4C142567-C42B-40F5-B092-798882190209} + {B4EABB0D-E495-405C-B7B1-E2A7A3711AF5} = {FC791F56-C1F1-4C41-A193-868D8197F8E2} + {DA355C86-866F-4843-9B4D-63A173C750FB} = {4C142567-C42B-40F5-B092-798882190209} + {62E9A8E4-79AF-4081-84D5-FEC5A0B28598} = {FC791F56-C1F1-4C41-A193-868D8197F8E2} + {AD4FDC24-B64D-4ED7-91AA-62C9EDA12FA4} = {4C142567-C42B-40F5-B092-798882190209} + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {FF877973-604D-4EA7-B5F5-A129961F9EF2} + EndGlobalSection +EndGlobal diff --git a/build/Stride.iOS.slnf b/build/Stride.iOS.slnf index bb99599a8f..3db845c146 100644 --- a/build/Stride.iOS.slnf +++ b/build/Stride.iOS.slnf @@ -18,15 +18,17 @@ "..\\sources\\engine\\Stride.Particles\\Stride.Particles.csproj", "..\\sources\\engine\\Stride.Physics\\Stride.Physics.csproj", "..\\sources\\engine\\Stride.Rendering\\Stride.Rendering.csproj", - "..\\sources\\engine\\Stride.Shaders\\Stride.Shaders.csproj", - "..\\sources\\engine\\Stride.Shaders.Compiler\\Stride.Shaders.Compiler.csproj", - "..\\sources\\engine\\Stride.Shaders.Parser\\Stride.Shaders.Parser.csproj", "..\\sources\\engine\\Stride.SpriteStudio.Runtime\\Stride.SpriteStudio.Runtime.csproj", "..\\sources\\engine\\Stride.UI\\Stride.UI.csproj", "..\\sources\\engine\\Stride.Video\\Stride.Video.csproj", "..\\sources\\engine\\Stride.VirtualReality\\Stride.VirtualReality.csproj", - "..\\sources\\shaders\\Irony\\Irony.csproj", - "..\\sources\\shaders\\Stride.Core.Shaders\\Stride.Core.Shaders.csproj" + "..\\sources\\shaders\\Stride.Shaders\\Stride.Shaders.csproj", + "..\\sources\\shaders\\Stride.Shaders.Compilers\\Stride.Shaders.Compilers.csproj", + "..\\sources\\shaders\\Stride.Shaders.Parsers\\Stride.Shaders.Parsers.csproj", + "..\\sources\\shaders\\Stride.Shaders.Spirv.Core\\Stride.Shaders.Spirv.Core.csproj", + "..\\sources\\shaders\\Stride.Shaders.Generators\\Stride.Shaders.Generators.csproj", + "..\\sources\\shaders\\Stride.Shaders.Generators.Internal\\Stride.Shaders.Generators.Internal.csproj", + "..\\sources\\shaders\\Stride.Shaders.Spirv.Generators\\Stride.Shaders.Spirv.Generators.csproj" ] } } diff --git a/build/Stride.sln b/build/Stride.sln index 01883bf7ee..0e637d6de7 100644 --- a/build/Stride.sln +++ b/build/Stride.sln @@ -15,8 +15,6 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "00-Config.Private", "00-Con ..\sources\native\Stride.Native.targets = ..\sources\native\Stride.Native.targets EndProjectSection EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "80-Shaders", "80-Shaders", "{10D145AF-C8AE-428F-A80F-CA1B591D0DB2}" -EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "50-Presentation", "50-Presentation", "{75A820AB-0F21-40F2-9448-5D7F495B97A0}" EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Internals", "Internals", "{860946E4-CC77-4FDA-A4FD-3DB2A502A696}" @@ -36,7 +34,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "00-Config", "00-Config", "{ EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Stride.Shared", "Stride.Shared", "{1AC70118-C90F-4EC6-9D8B-C628BDF900F7}" EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "21-StrideRuntime.Tests", "21-StrideRuntime.Tests", "{A7ED9F01-7D78-4381-90A6-D50E51C17250}" +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "25-StrideRuntime.Tests", "25-StrideRuntime.Tests", "{A7ED9F01-7D78-4381-90A6-D50E51C17250}" EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "11-CoreRuntime.Tests", "11-CoreRuntime.Tests", "{22ADD4CD-092E-4ADC-A21E-64CF42230152}" EndProject @@ -59,6 +57,40 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "31-CoreDesign.Tests", "31-C EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "71-StrideAssets.Tests", "71-StrideAssets.Tests", "{A47B451D-3162-410F-BAF7-C650C4B7A4B0}" EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "00-Localization", "00-Localization", "{FC791F56-C1F1-4C41-A193-868D8197F8E2}" + ProjectSection(SolutionItems) = preProject + ..\sources\localization\Stride.Assets.Presentation.pot = ..\sources\localization\Stride.Assets.Presentation.pot + ..\sources\localization\Stride.Core.Assets.Editor.pot = ..\sources\localization\Stride.Core.Assets.Editor.pot + ..\sources\localization\Stride.Core.Presentation.pot = ..\sources\localization\Stride.Core.Presentation.pot + ..\sources\localization\Stride.GameStudio.pot = ..\sources\localization\Stride.GameStudio.pot + EndProjectSection +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "ja", "ja", "{B4EABB0D-E495-405C-B7B1-E2A7A3711AF5}" + ProjectSection(SolutionItems) = preProject + ..\sources\localization\ja\Stride.Assets.Presentation.ja.po = ..\sources\localization\ja\Stride.Assets.Presentation.ja.po + ..\sources\localization\ja\Stride.Core.Assets.Editor.ja.po = ..\sources\localization\ja\Stride.Core.Assets.Editor.ja.po + ..\sources\localization\ja\Stride.Core.Presentation.ja.po = ..\sources\localization\ja\Stride.Core.Presentation.ja.po + ..\sources\localization\ja\Stride.GameStudio.ja.po = ..\sources\localization\ja\Stride.GameStudio.ja.po + EndProjectSection +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "72-StrideSamples", "72-StrideSamples", "{75608B5C-1C03-4B38-810E-14EED5165E59}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "fr", "fr", "{62E9A8E4-79AF-4081-84D5-FEC5A0B28598}" + ProjectSection(SolutionItems) = preProject + ..\sources\localization\fr\Stride.Assets.Presentation.fr.po = ..\sources\localization\fr\Stride.Assets.Presentation.fr.po + ..\sources\localization\fr\Stride.Core.Assets.Editor.fr.po = ..\sources\localization\fr\Stride.Core.Assets.Editor.fr.po + ..\sources\localization\fr\Stride.Core.Presentation.fr.po = ..\sources\localization\fr\Stride.Core.Presentation.fr.po + ..\sources\localization\fr\Stride.GameStudio.fr.po = ..\sources\localization\fr\Stride.GameStudio.fr.po + EndProjectSection +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "VisualStudio", "VisualStudio", "{DF9172C0-DEA3-4DCE-8AF1-39439ACB4BCD}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "NuGetResolver", "NuGetResolver", "{158087CF-AF74-44E9-AA20-A6AEB1E398A9}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Stride.Bepu", "Stride.Bepu", "{DE048114-9AE4-467E-A879-188DC0D88A59}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "22-StrideRuntime.Shaders", "22-StrideRuntime.Shaders", "{7E3ACF35-0CCA-4E88-866B-0F008C5A5580}" +EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Stride.GameStudio", "..\sources\editor\Stride.GameStudio\Stride.GameStudio.csproj", "{2FCA2D8B-B10F-4DCA-9847-4221F74BA586}" ProjectSection(ProjectDependencies) = postProject {040F754C-17F4-4B5F-B974-93F1E39D107F} = {040F754C-17F4-4B5F-B974-93F1E39D107F} @@ -74,14 +106,10 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Stride.Input.Tests.Windows" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Stride.Core.Tests", "..\sources\core\Stride.Core.Tests\Stride.Core.Tests.csproj", "{5AA408BA-E766-453E-B661-E3D7EC46E2A6}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Stride.Core.Shaders", "..\sources\shaders\Stride.Core.Shaders\Stride.Core.Shaders.csproj", "{F2D52EDB-BC17-4243-B06D-33CD20F87A7F}" -EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Stride.Core.Presentation.Wpf", "..\sources\presentation\Stride.Core.Presentation.Wpf\Stride.Core.Presentation.Wpf.csproj", "{47AFCC2E-E9F0-47D6-9D75-9E646546A92B}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Stride.Core.Presentation.Tests", "..\sources\presentation\Stride.Core.Presentation.Tests\Stride.Core.Presentation.Tests.csproj", "{C223FCD7-CDCC-4943-9E11-9C2CC8FA9FC4}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Irony", "..\sources\shaders\Irony\Irony.csproj", "{D81F5C91-D7DB-46E5-BC99-49488FB6814C}" -EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Stride.Games", "..\sources\engine\Stride.Games\Stride.Games.csproj", "{42780CBD-3FE7-48E3-BD5B-59945EA20137}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Stride.TextureConverter", "..\sources\tools\Stride.TextureConverter\Stride.TextureConverter.csproj", "{7F7BFF79-C400-435F-B359-56A2EF8956E0}" @@ -108,17 +136,13 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Stride.Core.MicroThreading" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Stride.Core.IO", "..\sources\core\Stride.Core.IO\Stride.Core.IO.csproj", "{1DE01410-22C9-489B-9796-1ADDAB1F64E5}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Stride.Shaders.Parser", "..\sources\engine\Stride.Shaders.Parser\Stride.Shaders.Parser.csproj", "{14A47447-2A24-4ECD-B24D-6571499DCD4C}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Stride.Shaders", "..\sources\engine\Stride.Shaders\Stride.Shaders.csproj", "{273BDD15-7392-4078-91F0-AF23594A3D7B}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Stride.Shaders", "..\sources\shaders\Stride.Shaders\Stride.Shaders.csproj", "{273BDD15-7392-4078-91F0-AF23594A3D7B}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Stride.Audio", "..\sources\engine\Stride.Audio\Stride.Audio.csproj", "{DE042125-C270-4D1D-9270-0759C167567A}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Stride", "..\sources\engine\Stride\Stride.csproj", "{72390339-B2A1-4F61-A800-31ED0975B515}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Stride.Shaders.Compiler", "..\sources\engine\Stride.Shaders.Compiler\Stride.Shaders.Compiler.csproj", "{E8B3553F-A79F-4E50-B75B-ACEE771C320C}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Stride.Shaders.Tests.Windows", "..\sources\engine\Stride.Shaders.Tests\Stride.Shaders.Tests.Windows.csproj", "{1BE90177-FE4D-4519-839E-7EB7D78AC973}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Stride.Shaders.Compilers", "..\sources\shaders\Stride.Shaders.Compilers\Stride.Shaders.Compilers.csproj", "{E8B3553F-A79F-4E50-B75B-ACEE771C320C}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Stride.Input", "..\sources\engine\Stride.Input\Stride.Input.csproj", "{84DEB606-77ED-49CD-9AED-D2B13C1F5A1E}" EndProject @@ -242,40 +266,14 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Stride.Core.Translation.Pre EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Stride.Core.Translation.Extractor", "..\sources\tools\Stride.Core.Translation.Extractor\Stride.Core.Translation.Extractor.csproj", "{164A5B9A-E684-4B3F-9EF4-B7765FC0A8A1}" EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "00-Localization", "00-Localization", "{FC791F56-C1F1-4C41-A193-868D8197F8E2}" - ProjectSection(SolutionItems) = preProject - ..\sources\localization\Stride.Assets.Presentation.pot = ..\sources\localization\Stride.Assets.Presentation.pot - ..\sources\localization\Stride.Core.Assets.Editor.pot = ..\sources\localization\Stride.Core.Assets.Editor.pot - ..\sources\localization\Stride.Core.Presentation.pot = ..\sources\localization\Stride.Core.Presentation.pot - ..\sources\localization\Stride.GameStudio.pot = ..\sources\localization\Stride.GameStudio.pot - EndProjectSection -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "ja", "ja", "{B4EABB0D-E495-405C-B7B1-E2A7A3711AF5}" - ProjectSection(SolutionItems) = preProject - ..\sources\localization\ja\Stride.Assets.Presentation.ja.po = ..\sources\localization\ja\Stride.Assets.Presentation.ja.po - ..\sources\localization\ja\Stride.Core.Assets.Editor.ja.po = ..\sources\localization\ja\Stride.Core.Assets.Editor.ja.po - ..\sources\localization\ja\Stride.Core.Presentation.ja.po = ..\sources\localization\ja\Stride.Core.Presentation.ja.po - ..\sources\localization\ja\Stride.GameStudio.ja.po = ..\sources\localization\ja\Stride.GameStudio.ja.po - EndProjectSection -EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Stride.Video", "..\sources\engine\Stride.Video\Stride.Video.csproj", "{DA355C86-866F-4843-9B4D-63A173C750FB}" EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "72-StrideSamples", "72-StrideSamples", "{75608B5C-1C03-4B38-810E-14EED5165E59}" -EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Stride.Samples.Tests", "..\samples\Tests\Stride.Samples.Tests.csproj", "{2FC40214-A4AA-45DC-9C93-72ED800C40B0}" EndProject Project("{D954291E-2A0B-460D-934E-DC6B0785DB48}") = "Stride.NuGetResolver.Targets", "..\sources\shared\Stride.NuGetResolver.Targets\Stride.NuGetResolver.Targets.shproj", "{00B72ED7-00E9-47F7-868D-8162027CD068}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Stride.Samples.Templates", "..\sources\editor\Stride.Samples.Templates\Stride.Samples.Templates.csproj", "{040F754C-17F4-4B5F-B974-93F1E39D107F}" EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "fr", "fr", "{62E9A8E4-79AF-4081-84D5-FEC5A0B28598}" - ProjectSection(SolutionItems) = preProject - ..\sources\localization\fr\Stride.Assets.Presentation.fr.po = ..\sources\localization\fr\Stride.Assets.Presentation.fr.po - ..\sources\localization\fr\Stride.Core.Assets.Editor.fr.po = ..\sources\localization\fr\Stride.Core.Assets.Editor.fr.po - ..\sources\localization\fr\Stride.Core.Presentation.fr.po = ..\sources\localization\fr\Stride.Core.Presentation.fr.po - ..\sources\localization\fr\Stride.GameStudio.fr.po = ..\sources\localization\fr\Stride.GameStudio.fr.po - EndProjectSection -EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Stride.Rendering", "..\sources\engine\Stride.Rendering\Stride.Rendering.csproj", "{AD4FDC24-B64D-4ED7-91AA-62C9EDA12FA4}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Stride.Voxels", "..\sources\engine\Stride.Voxels\Stride.Voxels.csproj", "{66BE41FC-FC52-48D0-9C04-BCE8CC393020}" @@ -290,8 +288,6 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Stride.Core.CompilerService EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Stride.Core.CompilerServices.Tests", "..\sources\core\Stride.Core.CompilerServices.Tests\Stride.Core.CompilerServices.Tests.csproj", "{BACD76E5-35D0-4389-9BB9-8743AC4D89DE}" EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "VisualStudio", "VisualStudio", "{DF9172C0-DEA3-4DCE-8AF1-39439ACB4BCD}" -EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Stride.VisualStudio.Commands.Interfaces", "..\sources\tools\Stride.VisualStudio.Commands.Interfaces\Stride.VisualStudio.Commands.Interfaces.csproj", "{09E29A89-A6D7-45C9-B7BA-CA6D643C246F}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Stride.VisualStudio.Commands", "..\sources\tools\Stride.VisualStudio.Commands\Stride.VisualStudio.Commands.csproj", "{A7FC60AE-BB54-47D3-8787-788EEC65AD45}" @@ -300,12 +296,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Stride.NuGetResolver.UI", " EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Stride.NuGetResolver", "..\sources\shared\Stride.NuGetResolver\Stride.NuGetResolver.csproj", "{02FD0BDE-4293-414F-97E6-69FF71105420}" EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "NuGetResolver", "NuGetResolver", "{158087CF-AF74-44E9-AA20-A6AEB1E398A9}" -EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Stride.Core.Presentation", "..\sources\presentation\Stride.Core.Presentation\Stride.Core.Presentation.csproj", "{0C63EF8B-26F9-4511-9FC5-7431DE9657D6}" EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Stride.Bepu", "Stride.Bepu", "{DE048114-9AE4-467E-A879-188DC0D88A59}" -EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Stride.BepuPhysics", "..\sources\engine\Stride.BepuPhysics\Stride.BepuPhysics\Stride.BepuPhysics.csproj", "{3E424688-EC44-4DFB-9FC0-4BB1F0683651}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Stride.BepuPhysics.Debug", "..\sources\engine\Stride.BepuPhysics\Stride.BepuPhysics.Debug\Stride.BepuPhysics.Debug.csproj", "{7715D094-DF59-4D91-BC9A-9A5118039ECB}" @@ -318,6 +310,18 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Stride.FreeImage", "..\sour EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Stride.Editor.CrashReport", "..\sources\editor\Stride.Editor.CrashReport\Stride.Editor.CrashReport.csproj", "{35EC42D8-0A09-41AE-A918-B8C2796061B3}" EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Stride.Shaders.Generators.Internal", "..\sources\shaders\Stride.Shaders.Generators.Internal\Stride.Shaders.Generators.Internal.csproj", "{B8D08AF9-51D1-2B4A-C8CE-307D70E53CCB}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Stride.Shaders.Spirv.Generators", "..\sources\shaders\Stride.Shaders.Spirv.Generators\Stride.Shaders.Spirv.Generators.csproj", "{2618F6D9-685D-FDE1-B467-84BC8C858F51}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Stride.Shaders.Spirv.Core", "..\sources\shaders\Stride.Shaders.Spirv.Core\Stride.Shaders.Spirv.Core.csproj", "{7B8E1D5D-64B3-B768-B2EB-EF2B27DDE2BB}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Stride.Shaders.Tests", "..\sources\shaders\Stride.Shaders.Tests\Stride.Shaders.Tests.csproj", "{137B0DE2-10F3-3496-6A5B-D3FE538BAA7B}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Stride.Shaders.Parsers", "..\sources\shaders\Stride.Shaders.Parsers\Stride.Shaders.Parsers.csproj", "{CF8F1CC5-22B2-FB1B-4D20-288E0A0057D8}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Stride.Shaders.Generators", "..\sources\shaders\Stride.Shaders.Generators\Stride.Shaders.Generators.csproj", "{0920DA18-53C3-8DA4-8BC5-038735B96973}" +EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Stride.Build.Sdk", "Stride.Build.Sdk", "{D26186F8-7158-4A01-9524-EF4F53E0802C}" ProjectSection(SolutionItems) = preProject ..\sources\sdk\Stride.Build.Sdk\Sdk\Sdk.props = ..\sources\sdk\Stride.Build.Sdk\Sdk\Sdk.props @@ -329,7 +333,6 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Stride.Build.Sdk", "Stride. ..\sources\sdk\Stride.Build.Sdk\Sdk\Stride.Frameworks.props = ..\sources\sdk\Stride.Build.Sdk\Sdk\Stride.Frameworks.props ..\sources\sdk\Stride.Build.Sdk\Sdk\Stride.Frameworks.targets = ..\sources\sdk\Stride.Build.Sdk\Sdk\Stride.Frameworks.targets ..\sources\sdk\Stride.Build.Sdk\Sdk\Stride.Graphics.props = ..\sources\sdk\Stride.Build.Sdk\Sdk\Stride.Graphics.props - ..\sources\sdk\Stride.Build.Sdk\Sdk\Stride.Graphics.targets = ..\sources\sdk\Stride.Build.Sdk\Sdk\Stride.Graphics.targets ..\sources\sdk\Stride.Build.Sdk\Sdk\Stride.GraphicsApi.InnerBuild.targets = ..\sources\sdk\Stride.Build.Sdk\Sdk\Stride.GraphicsApi.InnerBuild.targets ..\sources\sdk\Stride.Build.Sdk\Sdk\Stride.NativeBuildMode.props = ..\sources\sdk\Stride.Build.Sdk\Sdk\Stride.NativeBuildMode.props ..\sources\sdk\Stride.Build.Sdk\Sdk\Stride.PackageInfo.targets = ..\sources\sdk\Stride.Build.Sdk\Sdk\Stride.PackageInfo.targets @@ -424,16 +427,6 @@ Global {5AA408BA-E766-453E-B661-E3D7EC46E2A6}.Release|Mixed Platforms.Build.0 = Debug|Any CPU {5AA408BA-E766-453E-B661-E3D7EC46E2A6}.Release|Win32.ActiveCfg = Debug|Any CPU {5AA408BA-E766-453E-B661-E3D7EC46E2A6}.Release|Win32.Build.0 = Debug|Any CPU - {F2D52EDB-BC17-4243-B06D-33CD20F87A7F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {F2D52EDB-BC17-4243-B06D-33CD20F87A7F}.Debug|Any CPU.Build.0 = Debug|Any CPU - {F2D52EDB-BC17-4243-B06D-33CD20F87A7F}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU - {F2D52EDB-BC17-4243-B06D-33CD20F87A7F}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU - {F2D52EDB-BC17-4243-B06D-33CD20F87A7F}.Debug|Win32.ActiveCfg = Debug|Any CPU - {F2D52EDB-BC17-4243-B06D-33CD20F87A7F}.Release|Any CPU.ActiveCfg = Release|Any CPU - {F2D52EDB-BC17-4243-B06D-33CD20F87A7F}.Release|Any CPU.Build.0 = Release|Any CPU - {F2D52EDB-BC17-4243-B06D-33CD20F87A7F}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU - {F2D52EDB-BC17-4243-B06D-33CD20F87A7F}.Release|Mixed Platforms.Build.0 = Release|Any CPU - {F2D52EDB-BC17-4243-B06D-33CD20F87A7F}.Release|Win32.ActiveCfg = Release|Any CPU {47AFCC2E-E9F0-47D6-9D75-9E646546A92B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {47AFCC2E-E9F0-47D6-9D75-9E646546A92B}.Debug|Any CPU.Build.0 = Debug|Any CPU {47AFCC2E-E9F0-47D6-9D75-9E646546A92B}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU @@ -454,16 +447,6 @@ Global {C223FCD7-CDCC-4943-9E11-9C2CC8FA9FC4}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU {C223FCD7-CDCC-4943-9E11-9C2CC8FA9FC4}.Release|Mixed Platforms.Build.0 = Release|Any CPU {C223FCD7-CDCC-4943-9E11-9C2CC8FA9FC4}.Release|Win32.ActiveCfg = Release|Any CPU - {D81F5C91-D7DB-46E5-BC99-49488FB6814C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {D81F5C91-D7DB-46E5-BC99-49488FB6814C}.Debug|Any CPU.Build.0 = Debug|Any CPU - {D81F5C91-D7DB-46E5-BC99-49488FB6814C}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU - {D81F5C91-D7DB-46E5-BC99-49488FB6814C}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU - {D81F5C91-D7DB-46E5-BC99-49488FB6814C}.Debug|Win32.ActiveCfg = Debug|Any CPU - {D81F5C91-D7DB-46E5-BC99-49488FB6814C}.Release|Any CPU.ActiveCfg = Release|Any CPU - {D81F5C91-D7DB-46E5-BC99-49488FB6814C}.Release|Any CPU.Build.0 = Release|Any CPU - {D81F5C91-D7DB-46E5-BC99-49488FB6814C}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU - {D81F5C91-D7DB-46E5-BC99-49488FB6814C}.Release|Mixed Platforms.Build.0 = Release|Any CPU - {D81F5C91-D7DB-46E5-BC99-49488FB6814C}.Release|Win32.ActiveCfg = Release|Any CPU {42780CBD-3FE7-48E3-BD5B-59945EA20137}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {42780CBD-3FE7-48E3-BD5B-59945EA20137}.Debug|Any CPU.Build.0 = Debug|Any CPU {42780CBD-3FE7-48E3-BD5B-59945EA20137}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU @@ -588,16 +571,6 @@ Global {1DE01410-22C9-489B-9796-1ADDAB1F64E5}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU {1DE01410-22C9-489B-9796-1ADDAB1F64E5}.Release|Mixed Platforms.Build.0 = Release|Any CPU {1DE01410-22C9-489B-9796-1ADDAB1F64E5}.Release|Win32.ActiveCfg = Release|Any CPU - {14A47447-2A24-4ECD-B24D-6571499DCD4C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {14A47447-2A24-4ECD-B24D-6571499DCD4C}.Debug|Any CPU.Build.0 = Debug|Any CPU - {14A47447-2A24-4ECD-B24D-6571499DCD4C}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU - {14A47447-2A24-4ECD-B24D-6571499DCD4C}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU - {14A47447-2A24-4ECD-B24D-6571499DCD4C}.Debug|Win32.ActiveCfg = Debug|Any CPU - {14A47447-2A24-4ECD-B24D-6571499DCD4C}.Release|Any CPU.ActiveCfg = Release|Any CPU - {14A47447-2A24-4ECD-B24D-6571499DCD4C}.Release|Any CPU.Build.0 = Release|Any CPU - {14A47447-2A24-4ECD-B24D-6571499DCD4C}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU - {14A47447-2A24-4ECD-B24D-6571499DCD4C}.Release|Mixed Platforms.Build.0 = Release|Any CPU - {14A47447-2A24-4ECD-B24D-6571499DCD4C}.Release|Win32.ActiveCfg = Release|Any CPU {273BDD15-7392-4078-91F0-AF23594A3D7B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {273BDD15-7392-4078-91F0-AF23594A3D7B}.Debug|Any CPU.Build.0 = Debug|Any CPU {273BDD15-7392-4078-91F0-AF23594A3D7B}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU @@ -638,16 +611,6 @@ Global {E8B3553F-A79F-4E50-B75B-ACEE771C320C}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU {E8B3553F-A79F-4E50-B75B-ACEE771C320C}.Release|Mixed Platforms.Build.0 = Release|Any CPU {E8B3553F-A79F-4E50-B75B-ACEE771C320C}.Release|Win32.ActiveCfg = Release|Any CPU - {1BE90177-FE4D-4519-839E-7EB7D78AC973}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {1BE90177-FE4D-4519-839E-7EB7D78AC973}.Debug|Any CPU.Build.0 = Debug|Any CPU - {1BE90177-FE4D-4519-839E-7EB7D78AC973}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU - {1BE90177-FE4D-4519-839E-7EB7D78AC973}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU - {1BE90177-FE4D-4519-839E-7EB7D78AC973}.Debug|Win32.ActiveCfg = Debug|Any CPU - {1BE90177-FE4D-4519-839E-7EB7D78AC973}.Release|Any CPU.ActiveCfg = Release|Any CPU - {1BE90177-FE4D-4519-839E-7EB7D78AC973}.Release|Any CPU.Build.0 = Release|Any CPU - {1BE90177-FE4D-4519-839E-7EB7D78AC973}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU - {1BE90177-FE4D-4519-839E-7EB7D78AC973}.Release|Mixed Platforms.Build.0 = Release|Any CPU - {1BE90177-FE4D-4519-839E-7EB7D78AC973}.Release|Win32.ActiveCfg = Release|Any CPU {84DEB606-77ED-49CD-9AED-D2B13C1F5A1E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {84DEB606-77ED-49CD-9AED-D2B13C1F5A1E}.Debug|Any CPU.Build.0 = Debug|Any CPU {84DEB606-77ED-49CD-9AED-D2B13C1F5A1E}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU @@ -1530,6 +1493,78 @@ Global {35EC42D8-0A09-41AE-A918-B8C2796061B3}.Release|Mixed Platforms.Build.0 = Release|Any CPU {35EC42D8-0A09-41AE-A918-B8C2796061B3}.Release|Win32.ActiveCfg = Release|Any CPU {35EC42D8-0A09-41AE-A918-B8C2796061B3}.Release|Win32.Build.0 = Release|Any CPU + {B8D08AF9-51D1-2B4A-C8CE-307D70E53CCB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {B8D08AF9-51D1-2B4A-C8CE-307D70E53CCB}.Debug|Any CPU.Build.0 = Debug|Any CPU + {B8D08AF9-51D1-2B4A-C8CE-307D70E53CCB}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU + {B8D08AF9-51D1-2B4A-C8CE-307D70E53CCB}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU + {B8D08AF9-51D1-2B4A-C8CE-307D70E53CCB}.Debug|Win32.ActiveCfg = Debug|Any CPU + {B8D08AF9-51D1-2B4A-C8CE-307D70E53CCB}.Debug|Win32.Build.0 = Debug|Any CPU + {B8D08AF9-51D1-2B4A-C8CE-307D70E53CCB}.Release|Any CPU.ActiveCfg = Release|Any CPU + {B8D08AF9-51D1-2B4A-C8CE-307D70E53CCB}.Release|Any CPU.Build.0 = Release|Any CPU + {B8D08AF9-51D1-2B4A-C8CE-307D70E53CCB}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU + {B8D08AF9-51D1-2B4A-C8CE-307D70E53CCB}.Release|Mixed Platforms.Build.0 = Release|Any CPU + {B8D08AF9-51D1-2B4A-C8CE-307D70E53CCB}.Release|Win32.ActiveCfg = Release|Any CPU + {B8D08AF9-51D1-2B4A-C8CE-307D70E53CCB}.Release|Win32.Build.0 = Release|Any CPU + {2618F6D9-685D-FDE1-B467-84BC8C858F51}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {2618F6D9-685D-FDE1-B467-84BC8C858F51}.Debug|Any CPU.Build.0 = Debug|Any CPU + {2618F6D9-685D-FDE1-B467-84BC8C858F51}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU + {2618F6D9-685D-FDE1-B467-84BC8C858F51}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU + {2618F6D9-685D-FDE1-B467-84BC8C858F51}.Debug|Win32.ActiveCfg = Debug|Any CPU + {2618F6D9-685D-FDE1-B467-84BC8C858F51}.Debug|Win32.Build.0 = Debug|Any CPU + {2618F6D9-685D-FDE1-B467-84BC8C858F51}.Release|Any CPU.ActiveCfg = Release|Any CPU + {2618F6D9-685D-FDE1-B467-84BC8C858F51}.Release|Any CPU.Build.0 = Release|Any CPU + {2618F6D9-685D-FDE1-B467-84BC8C858F51}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU + {2618F6D9-685D-FDE1-B467-84BC8C858F51}.Release|Mixed Platforms.Build.0 = Release|Any CPU + {2618F6D9-685D-FDE1-B467-84BC8C858F51}.Release|Win32.ActiveCfg = Release|Any CPU + {2618F6D9-685D-FDE1-B467-84BC8C858F51}.Release|Win32.Build.0 = Release|Any CPU + {7B8E1D5D-64B3-B768-B2EB-EF2B27DDE2BB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {7B8E1D5D-64B3-B768-B2EB-EF2B27DDE2BB}.Debug|Any CPU.Build.0 = Debug|Any CPU + {7B8E1D5D-64B3-B768-B2EB-EF2B27DDE2BB}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU + {7B8E1D5D-64B3-B768-B2EB-EF2B27DDE2BB}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU + {7B8E1D5D-64B3-B768-B2EB-EF2B27DDE2BB}.Debug|Win32.ActiveCfg = Debug|Any CPU + {7B8E1D5D-64B3-B768-B2EB-EF2B27DDE2BB}.Debug|Win32.Build.0 = Debug|Any CPU + {7B8E1D5D-64B3-B768-B2EB-EF2B27DDE2BB}.Release|Any CPU.ActiveCfg = Release|Any CPU + {7B8E1D5D-64B3-B768-B2EB-EF2B27DDE2BB}.Release|Any CPU.Build.0 = Release|Any CPU + {7B8E1D5D-64B3-B768-B2EB-EF2B27DDE2BB}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU + {7B8E1D5D-64B3-B768-B2EB-EF2B27DDE2BB}.Release|Mixed Platforms.Build.0 = Release|Any CPU + {7B8E1D5D-64B3-B768-B2EB-EF2B27DDE2BB}.Release|Win32.ActiveCfg = Release|Any CPU + {7B8E1D5D-64B3-B768-B2EB-EF2B27DDE2BB}.Release|Win32.Build.0 = Release|Any CPU + {137B0DE2-10F3-3496-6A5B-D3FE538BAA7B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {137B0DE2-10F3-3496-6A5B-D3FE538BAA7B}.Debug|Any CPU.Build.0 = Debug|Any CPU + {137B0DE2-10F3-3496-6A5B-D3FE538BAA7B}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU + {137B0DE2-10F3-3496-6A5B-D3FE538BAA7B}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU + {137B0DE2-10F3-3496-6A5B-D3FE538BAA7B}.Debug|Win32.ActiveCfg = Debug|Any CPU + {137B0DE2-10F3-3496-6A5B-D3FE538BAA7B}.Debug|Win32.Build.0 = Debug|Any CPU + {137B0DE2-10F3-3496-6A5B-D3FE538BAA7B}.Release|Any CPU.ActiveCfg = Release|Any CPU + {137B0DE2-10F3-3496-6A5B-D3FE538BAA7B}.Release|Any CPU.Build.0 = Release|Any CPU + {137B0DE2-10F3-3496-6A5B-D3FE538BAA7B}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU + {137B0DE2-10F3-3496-6A5B-D3FE538BAA7B}.Release|Mixed Platforms.Build.0 = Release|Any CPU + {137B0DE2-10F3-3496-6A5B-D3FE538BAA7B}.Release|Win32.ActiveCfg = Release|Any CPU + {137B0DE2-10F3-3496-6A5B-D3FE538BAA7B}.Release|Win32.Build.0 = Release|Any CPU + {CF8F1CC5-22B2-FB1B-4D20-288E0A0057D8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {CF8F1CC5-22B2-FB1B-4D20-288E0A0057D8}.Debug|Any CPU.Build.0 = Debug|Any CPU + {CF8F1CC5-22B2-FB1B-4D20-288E0A0057D8}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU + {CF8F1CC5-22B2-FB1B-4D20-288E0A0057D8}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU + {CF8F1CC5-22B2-FB1B-4D20-288E0A0057D8}.Debug|Win32.ActiveCfg = Debug|Any CPU + {CF8F1CC5-22B2-FB1B-4D20-288E0A0057D8}.Debug|Win32.Build.0 = Debug|Any CPU + {CF8F1CC5-22B2-FB1B-4D20-288E0A0057D8}.Release|Any CPU.ActiveCfg = Release|Any CPU + {CF8F1CC5-22B2-FB1B-4D20-288E0A0057D8}.Release|Any CPU.Build.0 = Release|Any CPU + {CF8F1CC5-22B2-FB1B-4D20-288E0A0057D8}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU + {CF8F1CC5-22B2-FB1B-4D20-288E0A0057D8}.Release|Mixed Platforms.Build.0 = Release|Any CPU + {CF8F1CC5-22B2-FB1B-4D20-288E0A0057D8}.Release|Win32.ActiveCfg = Release|Any CPU + {CF8F1CC5-22B2-FB1B-4D20-288E0A0057D8}.Release|Win32.Build.0 = Release|Any CPU + {0920DA18-53C3-8DA4-8BC5-038735B96973}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {0920DA18-53C3-8DA4-8BC5-038735B96973}.Debug|Any CPU.Build.0 = Debug|Any CPU + {0920DA18-53C3-8DA4-8BC5-038735B96973}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU + {0920DA18-53C3-8DA4-8BC5-038735B96973}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU + {0920DA18-53C3-8DA4-8BC5-038735B96973}.Debug|Win32.ActiveCfg = Debug|Any CPU + {0920DA18-53C3-8DA4-8BC5-038735B96973}.Debug|Win32.Build.0 = Debug|Any CPU + {0920DA18-53C3-8DA4-8BC5-038735B96973}.Release|Any CPU.ActiveCfg = Release|Any CPU + {0920DA18-53C3-8DA4-8BC5-038735B96973}.Release|Any CPU.Build.0 = Release|Any CPU + {0920DA18-53C3-8DA4-8BC5-038735B96973}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU + {0920DA18-53C3-8DA4-8BC5-038735B96973}.Release|Mixed Platforms.Build.0 = Release|Any CPU + {0920DA18-53C3-8DA4-8BC5-038735B96973}.Release|Win32.ActiveCfg = Release|Any CPU + {0920DA18-53C3-8DA4-8BC5-038735B96973}.Release|Win32.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -1539,16 +1574,19 @@ Global {6F473FA6-4F8B-4FBA-AE33-EE5AF997D50C} = {1AE1AC60-5D2F-4CA7-AE20-888F44551185} {4A15BAAD-AA37-4754-A2BF-8D4049211E36} = {1AE1AC60-5D2F-4CA7-AE20-888F44551185} {1AC70118-C90F-4EC6-9D8B-C628BDF900F7} = {4C142567-C42B-40F5-B092-798882190209} + {B4EABB0D-E495-405C-B7B1-E2A7A3711AF5} = {FC791F56-C1F1-4C41-A193-868D8197F8E2} + {62E9A8E4-79AF-4081-84D5-FEC5A0B28598} = {FC791F56-C1F1-4C41-A193-868D8197F8E2} + {DF9172C0-DEA3-4DCE-8AF1-39439ACB4BCD} = {1AE1AC60-5D2F-4CA7-AE20-888F44551185} + {158087CF-AF74-44E9-AA20-A6AEB1E398A9} = {1AE1AC60-5D2F-4CA7-AE20-888F44551185} + {DE048114-9AE4-467E-A879-188DC0D88A59} = {4C142567-C42B-40F5-B092-798882190209} {2FCA2D8B-B10F-4DCA-9847-4221F74BA586} = {5D2D3BE8-9910-45CA-8E45-95660DA4C563} {C121A566-555E-42B9-9B0A-1696529A9088} = {4C142567-C42B-40F5-B092-798882190209} {FB06C76A-6BB7-40BE-9AFA-FEC13B045FB5} = {4C142567-C42B-40F5-B092-798882190209} {A8F8D125-7A22-489F-99BC-9A02F545A17F} = {A7ED9F01-7D78-4381-90A6-D50E51C17250} {01700344-CF44-482C-BEBC-60213B0F844C} = {A7ED9F01-7D78-4381-90A6-D50E51C17250} {5AA408BA-E766-453E-B661-E3D7EC46E2A6} = {22ADD4CD-092E-4ADC-A21E-64CF42230152} - {F2D52EDB-BC17-4243-B06D-33CD20F87A7F} = {10D145AF-C8AE-428F-A80F-CA1B591D0DB2} {47AFCC2E-E9F0-47D6-9D75-9E646546A92B} = {75A820AB-0F21-40F2-9448-5D7F495B97A0} {C223FCD7-CDCC-4943-9E11-9C2CC8FA9FC4} = {52AE329E-B588-40D0-A578-8D0DB1BD83E5} - {D81F5C91-D7DB-46E5-BC99-49488FB6814C} = {10D145AF-C8AE-428F-A80F-CA1B591D0DB2} {42780CBD-3FE7-48E3-BD5B-59945EA20137} = {4C142567-C42B-40F5-B092-798882190209} {7F7BFF79-C400-435F-B359-56A2EF8956E0} = {4A15BAAD-AA37-4754-A2BF-8D4049211E36} {C485CE61-3006-4C99-ACB3-A737F5CEBAE7} = {4A15BAAD-AA37-4754-A2BF-8D4049211E36} @@ -1562,12 +1600,10 @@ Global {CB6C4D8B-906E-4120-8146-09261B8D2885} = {75A820AB-0F21-40F2-9448-5D7F495B97A0} {1320F627-EE43-4115-8E89-19D1753E51F2} = {2E93E2B5-4500-4E47-9B65-E705218AB578} {1DE01410-22C9-489B-9796-1ADDAB1F64E5} = {2E93E2B5-4500-4E47-9B65-E705218AB578} - {14A47447-2A24-4ECD-B24D-6571499DCD4C} = {4C142567-C42B-40F5-B092-798882190209} - {273BDD15-7392-4078-91F0-AF23594A3D7B} = {4C142567-C42B-40F5-B092-798882190209} + {273BDD15-7392-4078-91F0-AF23594A3D7B} = {7E3ACF35-0CCA-4E88-866B-0F008C5A5580} {DE042125-C270-4D1D-9270-0759C167567A} = {4C142567-C42B-40F5-B092-798882190209} {72390339-B2A1-4F61-A800-31ED0975B515} = {4C142567-C42B-40F5-B092-798882190209} - {E8B3553F-A79F-4E50-B75B-ACEE771C320C} = {4C142567-C42B-40F5-B092-798882190209} - {1BE90177-FE4D-4519-839E-7EB7D78AC973} = {A7ED9F01-7D78-4381-90A6-D50E51C17250} + {E8B3553F-A79F-4E50-B75B-ACEE771C320C} = {7E3ACF35-0CCA-4E88-866B-0F008C5A5580} {84DEB606-77ED-49CD-9AED-D2B13C1F5A1E} = {4C142567-C42B-40F5-B092-798882190209} {1E54A9A2-4439-4444-AE57-6D2ED3C0DC47} = {A2A4342E-024B-4063-B10C-1DA96CA3046D} {3E7B5D96-CF71-41EE-8CF0-70D090873390} = {9D5D9861-AE68-429C-8B21-2263F9DA07A1} @@ -1629,12 +1665,10 @@ Global {6A7B231E-36AA-4647-8C1A-FB1540ABC813} = {25F10A0B-7259-404C-86BE-FD2363C92F72} {B686C194-D71D-4FF0-8B4F-F53AFBCD962F} = {75A820AB-0F21-40F2-9448-5D7F495B97A0} {164A5B9A-E684-4B3F-9EF4-B7765FC0A8A1} = {1AE1AC60-5D2F-4CA7-AE20-888F44551185} - {B4EABB0D-E495-405C-B7B1-E2A7A3711AF5} = {FC791F56-C1F1-4C41-A193-868D8197F8E2} {DA355C86-866F-4843-9B4D-63A173C750FB} = {4C142567-C42B-40F5-B092-798882190209} {2FC40214-A4AA-45DC-9C93-72ED800C40B0} = {75608B5C-1C03-4B38-810E-14EED5165E59} {00B72ED7-00E9-47F7-868D-8162027CD068} = {158087CF-AF74-44E9-AA20-A6AEB1E398A9} {040F754C-17F4-4B5F-B974-93F1E39D107F} = {75608B5C-1C03-4B38-810E-14EED5165E59} - {62E9A8E4-79AF-4081-84D5-FEC5A0B28598} = {FC791F56-C1F1-4C41-A193-868D8197F8E2} {AD4FDC24-B64D-4ED7-91AA-62C9EDA12FA4} = {4C142567-C42B-40F5-B092-798882190209} {66BE41FC-FC52-48D0-9C04-BCE8CC393020} = {4C142567-C42B-40F5-B092-798882190209} {D5B023BE-010F-44A8-ABF1-DB6F3BCEA392} = {1AE1AC60-5D2F-4CA7-AE20-888F44551185} @@ -1642,20 +1676,23 @@ Global {806AA078-6070-4BB6-B05B-6EE6B21B1CDE} = {6F473FA6-4F8B-4FBA-AE33-EE5AF997D50C} {D62BBD65-AB1C-41C7-8EC3-88949993C71E} = {2E93E2B5-4500-4E47-9B65-E705218AB578} {BACD76E5-35D0-4389-9BB9-8743AC4D89DE} = {22ADD4CD-092E-4ADC-A21E-64CF42230152} - {DF9172C0-DEA3-4DCE-8AF1-39439ACB4BCD} = {1AE1AC60-5D2F-4CA7-AE20-888F44551185} {09E29A89-A6D7-45C9-B7BA-CA6D643C246F} = {DF9172C0-DEA3-4DCE-8AF1-39439ACB4BCD} {A7FC60AE-BB54-47D3-8787-788EEC65AD45} = {DF9172C0-DEA3-4DCE-8AF1-39439ACB4BCD} {79F7B3CE-A22F-426D-8DAB-2F692F167210} = {158087CF-AF74-44E9-AA20-A6AEB1E398A9} {02FD0BDE-4293-414F-97E6-69FF71105420} = {158087CF-AF74-44E9-AA20-A6AEB1E398A9} - {158087CF-AF74-44E9-AA20-A6AEB1E398A9} = {1AE1AC60-5D2F-4CA7-AE20-888F44551185} {0C63EF8B-26F9-4511-9FC5-7431DE9657D6} = {75A820AB-0F21-40F2-9448-5D7F495B97A0} - {DE048114-9AE4-467E-A879-188DC0D88A59} = {4C142567-C42B-40F5-B092-798882190209} {3E424688-EC44-4DFB-9FC0-4BB1F0683651} = {DE048114-9AE4-467E-A879-188DC0D88A59} {7715D094-DF59-4D91-BC9A-9A5118039ECB} = {DE048114-9AE4-467E-A879-188DC0D88A59} {66EFFDE4-24F0-4E57-9618-0F5577E20A1E} = {6F473FA6-4F8B-4FBA-AE33-EE5AF997D50C} {7B70C783-4085-4702-B3C6-6570FD85CB8F} = {DE048114-9AE4-467E-A879-188DC0D88A59} {03695F9B-10E9-4A10-93AE-6402E46F10B5} = {1AE1AC60-5D2F-4CA7-AE20-888F44551185} {35EC42D8-0A09-41AE-A918-B8C2796061B3} = {5D2D3BE8-9910-45CA-8E45-95660DA4C563} + {B8D08AF9-51D1-2B4A-C8CE-307D70E53CCB} = {7E3ACF35-0CCA-4E88-866B-0F008C5A5580} + {2618F6D9-685D-FDE1-B467-84BC8C858F51} = {7E3ACF35-0CCA-4E88-866B-0F008C5A5580} + {7B8E1D5D-64B3-B768-B2EB-EF2B27DDE2BB} = {7E3ACF35-0CCA-4E88-866B-0F008C5A5580} + {137B0DE2-10F3-3496-6A5B-D3FE538BAA7B} = {7E3ACF35-0CCA-4E88-866B-0F008C5A5580} + {CF8F1CC5-22B2-FB1B-4D20-288E0A0057D8} = {7E3ACF35-0CCA-4E88-866B-0F008C5A5580} + {0920DA18-53C3-8DA4-8BC5-038735B96973} = {7E3ACF35-0CCA-4E88-866B-0F008C5A5580} {D26186F8-7158-4A01-9524-EF4F53E0802C} = {0B81090E-4066-4723-A658-8AEDBEADE619} {8D873BE7-8EF2-4478-B86A-249021D046EB} = {0B81090E-4066-4723-A658-8AEDBEADE619} {E6B11A34-A1DB-41C2-B509-94DACA9D9BDE} = {0B81090E-4066-4723-A658-8AEDBEADE619} @@ -1678,16 +1715,13 @@ Global ..\sources\shared\Stride.NuGetResolver.Targets\Stride.NuGetResolver.Targets.projitems*{75d71310-ecf7-4592-9e35-3fe540040982}*SharedItemsImports = 5 ..\sources\shared\Stride.Core.ShellHelper\Stride.Core.ShellHelper.projitems*{77e2fcc0-4ca6-436c-be6f-9418cb807d45}*SharedItemsImports = 5 ..\sources\shared\Stride.NuGetResolver.Targets\Stride.NuGetResolver.Targets.projitems*{77e2fcc0-4ca6-436c-be6f-9418cb807d45}*SharedItemsImports = 5 - ..\sources\engine\Stride.Shared\Refactor\Stride.Refactor.projitems*{7af4b563-aad3-42ff-b91e-84b9d34d904a}*SharedItemsImports = 5 ..\sources\editor\Stride.PrivacyPolicy\Stride.PrivacyPolicy.projitems*{950badd0-ad5a-4f58-87ec-4adaecbea89b}*SharedItemsImports = 13 ..\sources\editor\Stride.Core.MostRecentlyUsedFiles\Stride.Core.MostRecentlyUsedFiles.projitems*{9ac6d791-811e-4d6a-b08e-93f0093ef268}*SharedItemsImports = 13 ..\sources\shared\Stride.Core.ShellHelper\Stride.Core.ShellHelper.projitems*{a5dc820b-9554-45b6-9677-6a2f902e7787}*SharedItemsImports = 5 ..\sources\shared\Stride.NuGetResolver.Targets\Stride.NuGetResolver.Targets.projitems*{a5dc820b-9554-45b6-9677-6a2f902e7787}*SharedItemsImports = 5 ..\sources\shared\Stride.NuGetResolver.Targets\Stride.NuGetResolver.Targets.projitems*{a7fc60ae-bb54-47d3-8787-788eec65ad45}*SharedItemsImports = 5 - ..\sources\engine\Stride.Shared\Refactor\Stride.Refactor.projitems*{c121a566-555e-42b9-9b0a-1696529a9088}*SharedItemsImports = 5 ..\sources\shared\Stride.NuGetResolver.Targets\Stride.NuGetResolver.Targets.projitems*{e25e7778-0b2f-4a0b-bcd6-1de95320b531}*SharedItemsImports = 5 ..\sources\shared\Stride.Core.ShellHelper\Stride.Core.ShellHelper.projitems*{e8b3553f-a79f-4e50-b75b-acee771c320c}*SharedItemsImports = 5 - ..\sources\engine\Stride.Shared\Refactor\Stride.Refactor.projitems*{fb06c76a-6bb7-40be-9afa-fec13b045fb5}*SharedItemsImports = 5 ..\sources\assets\Stride.Core.Assets.Yaml\Stride.Core.Assets.Yaml.projitems*{fb9ed2c4-94a0-4004-a498-3f29a9d5bb5d}*SharedItemsImports = 13 EndGlobalSection EndGlobal diff --git a/build/deps/lavapipe/Lavapipe.cs b/build/deps/lavapipe/Lavapipe.cs new file mode 100644 index 0000000000..46cc846f53 --- /dev/null +++ b/build/deps/lavapipe/Lavapipe.cs @@ -0,0 +1,98 @@ +using System; +using System.IO; +using System.Linq; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +namespace Stride.Dependencies.Lavapipe; + +/// +/// Auto-configures VK_DRIVER_FILES to point at the packaged lavapipe ICD manifest. +/// +/// +/// The packaged lvp_icd.json uses a relative library_path, so the Vulkan +/// loader resolves vulkan_lvp.dll/libvulkan_lvp.so/libvulkan_lvp.dylib +/// against the manifest's own directory — no runtime rewriting needed. +/// +/// Resolution order: +/// 1. runtimes/<rid>/native/ relative to AppContext.BaseDirectory (normal deployment) +/// 2. Next to this managed DLL (single-file / flat deployment) +/// 3. NuGet package cache (when consumer uses PackageReference ExcludeAssets="runtime") +/// +/// Caller can override by pre-setting VK_DRIVER_FILES before the module loads. +/// +public static class Lavapipe +{ + private const string ManifestName = "lvp_icd.json"; + + [ModuleInitializer] + internal static void AutoInit() + { + if (!string.IsNullOrEmpty(Environment.GetEnvironmentVariable("VK_DRIVER_FILES"))) + return; + TryConfigure(); + } + + /// Force configuration. Returns true if VK_DRIVER_FILES was set. + public static bool TryConfigure() + { + var manifestPath = LocateManifest(); + if (manifestPath is null) + return false; + Environment.SetEnvironmentVariable("VK_DRIVER_FILES", manifestPath); + return true; + } + + static string? LocateManifest() + { + var rid = GetRid(); + var asmLoc = typeof(Lavapipe).Assembly.Location; + var asmDir = string.IsNullOrEmpty(asmLoc) ? null : Path.GetDirectoryName(asmLoc); + + // In-app candidates (no NuGet cache needed) + string?[] candidates = + [ + Path.Combine(AppContext.BaseDirectory, "runtimes", rid, "native", ManifestName), + Path.Combine(AppContext.BaseDirectory, ManifestName), + asmDir is null ? null : Path.Combine(asmDir, ManifestName), + // NuGet-cache layout when this dll is loaded directly from the cache: + // ///lib/net10.0/Stride.Dependencies.Lavapipe.dll + // → manifest at ///runtimes//native/ + asmDir is null ? null : Path.GetFullPath(Path.Combine(asmDir, "..", "..", "runtimes", rid, "native", ManifestName)), + ]; + + foreach (var p in candidates) + { + if (!string.IsNullOrEmpty(p) && File.Exists(p)) + return Path.GetFullPath(p); + } + + // Last-resort: scan the NuGet package cache (handles ExcludeAssets="runtime" + // where natives are not copied to output and this dll also may not be). + var nugetRoot = Environment.GetEnvironmentVariable("NUGET_PACKAGES") + ?? Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), ".nuget", "packages"); + var pkgRoot = Path.Combine(nugetRoot, "stride.dependencies.lavapipe"); + if (Directory.Exists(pkgRoot)) + { + foreach (var verDir in Directory.EnumerateDirectories(pkgRoot).OrderByDescending(d => d)) + { + var p = Path.Combine(verDir, "runtimes", rid, "native", ManifestName); + if (File.Exists(p)) + return p; + } + } + + return null; + } + + static string GetRid() + { + if (OperatingSystem.IsWindows()) + return "win-x64"; + if (OperatingSystem.IsLinux()) + return "linux-x64"; + if (OperatingSystem.IsMacOS()) + return RuntimeInformation.ProcessArchitecture == Architecture.Arm64 ? "osx-arm64" : "osx-x64"; + throw new PlatformNotSupportedException(); + } +} diff --git a/build/deps/lavapipe/Stride.Dependencies.Lavapipe.csproj b/build/deps/lavapipe/Stride.Dependencies.Lavapipe.csproj new file mode 100644 index 0000000000..f386b055e9 --- /dev/null +++ b/build/deps/lavapipe/Stride.Dependencies.Lavapipe.csproj @@ -0,0 +1,25 @@ + + + net10.0 + enable + latest + + Stride.Dependencies.Lavapipe + Stride Contributors + Lavapipe software Vulkan ICD (Mesa/llvmpipe) for Stride GPU testing. Auto-configures VK_DRIVER_FILES on load. + MIT + https://github.com/stride3d/stride + git + + + true + + + + + + + + diff --git a/build/deps/spirv-to-dxil/.gitattributes b/build/deps/spirv-to-dxil/.gitattributes new file mode 100644 index 0000000000..9812ceb1ff --- /dev/null +++ b/build/deps/spirv-to-dxil/.gitattributes @@ -0,0 +1 @@ +*.patch text eol=lf diff --git a/build/deps/spirv-to-dxil/mesa-pipeline.patch b/build/deps/spirv-to-dxil/mesa-pipeline.patch new file mode 100644 index 0000000000..abaf8fb70c --- /dev/null +++ b/build/deps/spirv-to-dxil/mesa-pipeline.patch @@ -0,0 +1,190 @@ +diff --git a/src/microsoft/spirv_to_dxil/spirv_to_dxil.c b/src/microsoft/spirv_to_dxil/spirv_to_dxil.c +index 1a8e6e2dee3..14b0f115dc8 100644 +--- a/src/microsoft/spirv_to_dxil/spirv_to_dxil.c ++++ b/src/microsoft/spirv_to_dxil/spirv_to_dxil.c +@@ -126,3 +126,140 @@ spirv_to_dxil_get_version() + } + return 0; + } ++ ++/* ++ * Stride addition: multi-stage linked compile. ++ * ++ * Compiles all stages of a pipeline together so that dead-code elimination ++ * of inter-stage varyings doesn't break the DXIL I/O signature matching. ++ */ ++bool ++spirv_to_dxil_pipeline(const struct spirv_to_dxil_stage_input *stages, ++ unsigned stage_count, ++ enum dxil_validator_version validator_version_max, ++ const struct dxil_spirv_runtime_conf *conf, ++ const struct dxil_spirv_logger *logger, ++ struct dxil_spirv_object *out_dxils) ++{ ++ if (stage_count == 0 || stage_count > MESA_SHADER_STAGES) ++ return false; ++ ++ glsl_type_singleton_init_or_ref(); ++ ++ struct nir_to_dxil_options opts = { ++ .environment = DXIL_ENVIRONMENT_VULKAN, ++ .shader_model_max = conf->shader_model_max, ++ .validator_version_max = validator_version_max, ++ }; ++ ++ const struct spirv_to_nir_options *spirv_opts = dxil_spirv_nir_get_spirv_options(); ++ nir_shader_compiler_options nir_options; ++ const unsigned supported_bit_sizes = 16 | 32 | 64; ++ dxil_get_nir_compiler_options(&nir_options, conf->shader_model_max, supported_bit_sizes, supported_bit_sizes); ++ nir_options.lower_base_vertex = conf->first_vertex_and_base_instance_mode != DXIL_SPIRV_SYSVAL_TYPE_ZERO; ++ ++ /* Map stage->nir and original input index (to write output back) */ ++ nir_shader *nirs[MESA_SHADER_STAGES] = { NULL }; ++ int input_index_by_stage[MESA_SHADER_STAGES]; ++ for (int i = 0; i < MESA_SHADER_STAGES; ++i) ++ input_index_by_stage[i] = -1; ++ ++ bool success = true; ++ ++ /* Parse all stages */ ++ for (unsigned i = 0; i < stage_count; ++i) { ++ dxil_spirv_shader_stage stage = stages[i].stage; ++ if (stage == DXIL_SPIRV_SHADER_NONE || stage == DXIL_SPIRV_SHADER_KERNEL || ++ stage >= MESA_SHADER_STAGES) { ++ success = false; ++ goto cleanup; ++ } ++ if (nirs[stage]) { ++ /* Duplicate stage */ ++ success = false; ++ goto cleanup; ++ } ++ nir_shader *nir = spirv_to_nir( ++ stages[i].words, stages[i].word_count, NULL, 0, ++ (mesa_shader_stage)stage, stages[i].entry_point_name, ++ spirv_opts, &nir_options); ++ if (!nir) { ++ success = false; ++ goto cleanup; ++ } ++ nir_validate_shader(nir, "Validate SPIR-V to NIR output"); ++ /* Mark all I/O variables as always active so that nir_remove_dead_variables ++ * doesn't strip them. This preserves the I/O signatures across stages, which is ++ * required for D3D12 pipeline state validation (especially for HS<->DS where the ++ * control point and patch constant signatures must match exactly). ++ * ++ * Also keeps HS outputs alive when only read by the patch-constant function ++ * (which the kill pass otherwise treats as unused since they aren't read by DS), ++ * and keeps DS dummy inputs alive (added by Stride to match HS-internal outputs). ++ * ++ * Must happen BEFORE dxil_spirv_nir_prep, since that runs nir_remove_dead_variables ++ * itself and would strip our dummies before they get a chance to be marked. ++ */ ++ nir_foreach_variable_with_modes(var, nir, nir_var_shader_in | nir_var_shader_out) ++ var->data.always_active_io = true; ++ dxil_spirv_nir_prep(nir); ++ nirs[stage] = nir; ++ input_index_by_stage[stage] = (int)i; ++ } ++ ++ /* Run per-stage passes */ ++ struct dxil_spirv_metadata metadata[MESA_SHADER_STAGES] = { 0 }; ++ for (int stage = 0; stage < MESA_SHADER_STAGES; ++stage) { ++ if (!nirs[stage]) ++ continue; ++ dxil_spirv_nir_passes(nirs[stage], conf, &metadata[stage]); ++ } ++ ++ /* Link consecutive stages (later -> earlier). For each stage with a ++ * previous stage present, call dxil_spirv_nir_link. This removes unused ++ * outputs/inputs and reassigns driver_location consistently across stage ++ * boundaries, matching what spirv2dxil.c does. ++ */ ++ for (int cur = MESA_SHADER_FRAGMENT; cur >= MESA_SHADER_VERTEX; --cur) { ++ if (!nirs[cur]) ++ continue; ++ for (int prev = cur - 1; prev >= MESA_SHADER_VERTEX; --prev) { ++ if (!nirs[prev]) ++ continue; ++ struct dxil_spirv_metadata link_meta = { 0 }; ++ dxil_spirv_nir_link(nirs[cur], nirs[prev], conf, &link_meta); ++ break; ++ } ++ } ++ ++ struct dxil_logger logger_inner = { ++ .priv = logger->priv, ++ .log = logger->log, ++ }; ++ ++ /* Emit DXIL for each stage in pipeline order */ ++ for (int stage = 0; stage < MESA_SHADER_STAGES; ++stage) { ++ if (!nirs[stage]) ++ continue; ++ int out_index = input_index_by_stage[stage]; ++ struct blob dxil_blob; ++ if (!nir_to_dxil(nirs[stage], &opts, &logger_inner, &dxil_blob)) { ++ if (dxil_blob.allocated) ++ blob_finish(&dxil_blob); ++ success = false; ++ goto cleanup; ++ } ++ out_dxils[out_index].metadata = metadata[stage]; ++ blob_finish_get_buffer(&dxil_blob, &out_dxils[out_index].binary.buffer, ++ &out_dxils[out_index].binary.size); ++ } ++ ++cleanup: ++ for (int stage = 0; stage < MESA_SHADER_STAGES; ++stage) { ++ if (nirs[stage]) ++ ralloc_free(nirs[stage]); ++ } ++ glsl_type_singleton_decref(); ++ ++ return success; ++} +diff --git a/src/microsoft/spirv_to_dxil/spirv_to_dxil.def b/src/microsoft/spirv_to_dxil/spirv_to_dxil.def +index 62851f2160b..1c6f26c7a3a 100644 +--- a/src/microsoft/spirv_to_dxil/spirv_to_dxil.def ++++ b/src/microsoft/spirv_to_dxil/spirv_to_dxil.def +@@ -2,3 +2,4 @@ EXPORTS + spirv_to_dxil + spirv_to_dxil_free + spirv_to_dxil_get_version ++ spirv_to_dxil_pipeline +diff --git a/src/microsoft/spirv_to_dxil/spirv_to_dxil.h b/src/microsoft/spirv_to_dxil/spirv_to_dxil.h +index 00b18849e41..902716c6221 100644 +--- a/src/microsoft/spirv_to_dxil/spirv_to_dxil.h ++++ b/src/microsoft/spirv_to_dxil/spirv_to_dxil.h +@@ -236,6 +236,31 @@ spirv_to_dxil(const uint32_t *words, size_t word_count, + const struct dxil_spirv_logger *logger, + struct dxil_spirv_object *out_dxil); + ++/** ++ * Input descriptor for a single stage in a linked pipeline compile. ++ */ ++struct spirv_to_dxil_stage_input { ++ const uint32_t *words; ++ size_t word_count; ++ dxil_spirv_shader_stage stage; ++ const char *entry_point_name; ++}; ++ ++/** ++ * Compile all shader stages of a pipeline together, linking between ++ * stages so that unused varyings are stripped consistently and DXIL ++ * I/O signatures match across stage boundaries. ++ * ++ * out_dxils[i] receives the compiled DXIL for stages[i]. ++ */ ++bool ++spirv_to_dxil_pipeline(const struct spirv_to_dxil_stage_input *stages, ++ unsigned stage_count, ++ enum dxil_validator_version validator_version_max, ++ const struct dxil_spirv_runtime_conf *conf, ++ const struct dxil_spirv_logger *logger, ++ struct dxil_spirv_object *out_dxils); ++ + /** + * Free the buffer allocated by spirv_to_dxil. + */ diff --git a/build/deps/spirv-tools/CMakeLists.txt b/build/deps/spirv-tools/CMakeLists.txt new file mode 100644 index 0000000000..2f911ad4c2 --- /dev/null +++ b/build/deps/spirv-tools/CMakeLists.txt @@ -0,0 +1,57 @@ +# Bundles SPIRV-Tools-static + SPIRV-Tools-opt + the C shim into a single +# shared library, stride_spirv_tools, re-exporting both the upstream libspirv.h +# C API and the shim's stride_spv* entry points. +# +# Usage (from dep-spirv-tools.yml): +# cmake -S build/deps/spirv-tools -B _build \ +# -DSPIRV_TOOLS_SOURCE_DIR= +# cmake --build _build + +cmake_minimum_required(VERSION 3.15) +project(stride_spirv_tools LANGUAGES C CXX) + +if(NOT DEFINED SPIRV_TOOLS_SOURCE_DIR) + message(FATAL_ERROR "Set -DSPIRV_TOOLS_SOURCE_DIR to the cloned SPIRV-Tools source tree.") +endif() + +# Force the upstream build to produce static archives we can absorb. +set(BUILD_SHARED_LIBS OFF CACHE BOOL "" FORCE) +set(SPIRV_SKIP_TESTS ON CACHE BOOL "" FORCE) +set(SPIRV_SKIP_EXECUTABLES ON CACHE BOOL "" FORCE) +set(SPIRV_WERROR OFF CACHE BOOL "" FORCE) + +# SPIRV-Tools' SPIRV_TOOLS_EXPORT macro only activates when SPIRV_TOOLS_SHAREDLIB +# is defined; SPIRV_TOOLS_IMPLEMENTATION then flips it from dllimport to dllexport. +# Both must be defined when compiling the static libs so their C API symbols get +# __declspec(dllexport) and end up in our shared output's export table. +add_compile_definitions(SPIRV_TOOLS_IMPLEMENTATION SPIRV_TOOLS_SHAREDLIB) + +add_subdirectory(${SPIRV_TOOLS_SOURCE_DIR} spirv-tools-build EXCLUDE_FROM_ALL) + +add_library(stride_spirv_tools SHARED stride_spvopt_shim.cpp) +set_target_properties(stride_spirv_tools PROPERTIES + CXX_STANDARD 17 + CXX_STANDARD_REQUIRED ON + POSITION_INDEPENDENT_CODE ON + CXX_VISIBILITY_PRESET hidden + VISIBILITY_INLINES_HIDDEN ON) + +target_include_directories(stride_spirv_tools PRIVATE + ${SPIRV_TOOLS_SOURCE_DIR}/include) + +# Whole-archive both static libs so no public symbol gets dropped by dead-code +# stripping. The SPIRV_TOOLS_EXPORT/STRIDE_SPV_EXPORT attributes drive what's +# actually exposed in the final symbol table. +if(WIN32) + target_link_options(stride_spirv_tools PRIVATE + /WHOLEARCHIVE:$ + /WHOLEARCHIVE:$) + target_link_libraries(stride_spirv_tools PRIVATE SPIRV-Tools-static SPIRV-Tools-opt) +elseif(APPLE) + target_link_libraries(stride_spirv_tools PRIVATE + -Wl,-force_load $ + -Wl,-force_load $) +else() + target_link_libraries(stride_spirv_tools PRIVATE + -Wl,--whole-archive SPIRV-Tools-static SPIRV-Tools-opt -Wl,--no-whole-archive) +endif() diff --git a/build/deps/spirv-tools/Stride.Dependencies.SPIRVTools.csproj b/build/deps/spirv-tools/Stride.Dependencies.SPIRVTools.csproj new file mode 100644 index 0000000000..f370ecfa26 --- /dev/null +++ b/build/deps/spirv-tools/Stride.Dependencies.SPIRVTools.csproj @@ -0,0 +1,26 @@ + + + net10.0 + enable + latest + + Stride.Dependencies.SPIRVTools + Stride Contributors + SPIRV-Tools shared libraries (spirv-opt + spirv-val C APIs) for Stride runtime SPIR-V constant folding and validation. + Apache-2.0 + https://github.com/stride3d/stride + git + + true + + + + + + + + + + + + diff --git a/build/deps/spirv-tools/stride_spvopt_shim.cpp b/build/deps/spirv-tools/stride_spvopt_shim.cpp new file mode 100644 index 0000000000..ac408b959c --- /dev/null +++ b/build/deps/spirv-tools/stride_spvopt_shim.cpp @@ -0,0 +1,88 @@ +// C-callable wrappers around spvtools::Optimizer. Upstream SPIRV-Tools exposes +// the optimizer only via optimizer.hpp (C++), which P/Invoke can't reach +// because of name mangling. This shim compiles into stride_spirv_tools and +// exports stable C entry points that the managed bindings call. + +#include +#include +#include +#include +#include +#include + +#if defined(_WIN32) + #define STRIDE_SPV_EXPORT __declspec(dllexport) +#else + #define STRIDE_SPV_EXPORT __attribute__((visibility("default"))) +#endif + +extern "C" { + +STRIDE_SPV_EXPORT void* stride_spvOptimizerCreate(spv_target_env env) { + return static_cast(new spvtools::Optimizer(env)); +} + +STRIDE_SPV_EXPORT void stride_spvOptimizerDestroy(void* optimizer) { + delete static_cast(optimizer); +} + +STRIDE_SPV_EXPORT void stride_spvOptimizerRegisterLegalizationPasses(void* optimizer) { + static_cast(optimizer)->RegisterLegalizationPasses(); +} + +// Same legalization recipe, but with preserve_interface=true so that unused +// Input/Output variables aren't stripped. Needed because Stride runs legalize +// on a merged multi-stage module: dropping an Output in the predecessor stage +// while keeping an Input with the same Location in the successor leaves FXC +// assigning mismatched hardware registers to the same semantic, which D3D11's +// runtime signature check then rejects ("Semantic X defined for mismatched +// hardware registers"). See EffectCompiler.cs for the full picture. +STRIDE_SPV_EXPORT void stride_spvOptimizerRegisterLegalizationPassesPreserveInterface(void* optimizer) { + static_cast(optimizer)->RegisterLegalizationPasses(true); +} + +STRIDE_SPV_EXPORT void stride_spvOptimizerRegisterPerformancePasses(void* optimizer) { + static_cast(optimizer)->RegisterPerformancePasses(); +} + +// Register a single pass by its spirv-opt CLI flag (e.g. "--eliminate-dead-code-aggressive", +// "--scalar-replacement=0"). Returns non-zero on success. Lets managed code assemble +// custom pass pipelines without requiring a new shim export per recipe. +STRIDE_SPV_EXPORT int stride_spvOptimizerRegisterPassFromFlag( + void* optimizer, const char* flag, int preserve_interface) { + return static_cast(optimizer) + ->RegisterPassFromFlag(flag, preserve_interface != 0) ? 1 : 0; +} + +STRIDE_SPV_EXPORT spv_result_t stride_spvOptimizerRun( + void* optimizer, + const uint32_t* input_binary, size_t input_word_count, + uint32_t** output_binary, size_t* output_word_count) { + + auto* opt = static_cast(optimizer); + std::vector result; + if (!opt->Run(input_binary, input_word_count, &result)) { + *output_binary = nullptr; + *output_word_count = 0; + return SPV_ERROR_INTERNAL; + } + // Transfer ownership to caller via malloc so the managed side can pair it + // with stride_spvOptimizerFreeBinary (std::free) — no CRT boundary risk. + const size_t byte_count = result.size() * sizeof(uint32_t); + auto* buffer = static_cast(std::malloc(byte_count)); + if (!buffer) { + *output_binary = nullptr; + *output_word_count = 0; + return SPV_ERROR_OUT_OF_MEMORY; + } + std::memcpy(buffer, result.data(), byte_count); + *output_binary = buffer; + *output_word_count = result.size(); + return SPV_SUCCESS; +} + +STRIDE_SPV_EXPORT void stride_spvOptimizerFreeBinary(uint32_t* binary) { + std::free(binary); +} + +} // extern "C" diff --git a/build/submodules/.editorconfig b/build/submodules/.editorconfig new file mode 100644 index 0000000000..ea009a8fe1 --- /dev/null +++ b/build/submodules/.editorconfig @@ -0,0 +1,13 @@ +# disable diagnostics for CppNet8 files +[CppNet8/**.cs] +dotnet_diagnostic.CS0114.severity = none +dotnet_diagnostic.CS0164.severity = none +dotnet_diagnostic.CS0219.severity = none +dotnet_diagnostic.CS0472.severity = none +dotnet_diagnostic.CS0642.severity = none +dotnet_diagnostic.CS8600.severity = none +dotnet_diagnostic.CS8602.severity = none +dotnet_diagnostic.CS8603.severity = none +dotnet_diagnostic.CS8604.severity = none +dotnet_diagnostic.CS8618.severity = none +dotnet_diagnostic.CS8625.severity = none diff --git a/build/submodules/CppNet8 b/build/submodules/CppNet8 new file mode 160000 index 0000000000..a93fea69ee --- /dev/null +++ b/build/submodules/CppNet8 @@ -0,0 +1 @@ +Subproject commit a93fea69ee9bb51ee355ee2dc0c8d8eea7aaad50 diff --git a/build/submodules/SpirvHeaders b/build/submodules/SpirvHeaders new file mode 160000 index 0000000000..3f17b2af67 --- /dev/null +++ b/build/submodules/SpirvHeaders @@ -0,0 +1 @@ +Subproject commit 3f17b2af6784bfa2c5aa5dbb8e0e74a607dd8b3b diff --git a/build/submodules/SpirvRegistry b/build/submodules/SpirvRegistry new file mode 160000 index 0000000000..a74197a3f0 --- /dev/null +++ b/build/submodules/SpirvRegistry @@ -0,0 +1 @@ +Subproject commit a74197a3f0d5400764ce3bec2880f06e27b7b5d3 diff --git a/build/tools/CompareGold/CompareGold.csproj b/build/tools/CompareGold/CompareGold.csproj deleted file mode 100644 index 03bcd144a1..0000000000 --- a/build/tools/CompareGold/CompareGold.csproj +++ /dev/null @@ -1,12 +0,0 @@ - - - Exe - net10.0 - enable - enable - - false - false - false - - diff --git a/build/tools/CompareGold/Program.cs b/build/tools/CompareGold/Program.cs deleted file mode 100644 index 163e51d78a..0000000000 --- a/build/tools/CompareGold/Program.cs +++ /dev/null @@ -1,489 +0,0 @@ -using System.Collections.Concurrent; -using System.Diagnostics; -using System.Text.Json; - -var builder = WebApplication.CreateBuilder(args); -builder.WebHost.UseUrls("http://localhost:5555"); -builder.Services.AddSingleton(); -var app = builder.Build(); - -// Find Stride root -var strideRoot = FindStrideRoot(AppContext.BaseDirectory) - ?? FindStrideRoot(Directory.GetCurrentDirectory()) - ?? throw new InvalidOperationException("Could not find Stride root (looking for 'tests/' + 'sources/' directories)"); - -var testsDir = Path.Combine(strideRoot, "tests"); -var localDir = Path.Combine(testsDir, "local"); -var ciCacheDir = Path.Combine(Path.GetTempPath(), "stride-compare-gold"); - -var sourceManager = app.Services.GetRequiredService(); - -Console.WriteLine($"Stride root: {strideRoot}"); -Console.WriteLine($"Gold images: {testsDir}"); -Console.WriteLine($"Local output: {localDir}"); -Console.WriteLine($"CI cache: {ciCacheDir}"); -Console.WriteLine(); -Console.WriteLine("CompareGold running at http://localhost:5555"); -Console.WriteLine("Press Ctrl+C to stop."); - -try { Process.Start(new ProcessStartInfo("http://localhost:5555") { UseShellExecute = true }); } -catch { } - -app.UseDefaultFiles(); -app.UseStaticFiles(); - -// Disable caching for gold image responses (they change on promote) -app.Use(async (ctx, next) => -{ - if (ctx.Request.Path.StartsWithSegments("/api/gold") || ctx.Request.Path.StartsWithSegments("/api/thresholds")) - { - ctx.Response.OnStarting(() => - { - ctx.Response.Headers.CacheControl = "no-store"; - ctx.Response.Headers.Remove("ETag"); - ctx.Response.Headers.Remove("Last-Modified"); - return Task.CompletedTask; - }); - } - await next(); -}); - -// Check gh CLI availability once at startup -bool ghAvailable = false; -string ghError = "gh CLI not found. Install from https://cli.github.com/"; -try -{ - var ghCheck = Process.Start(new ProcessStartInfo { FileName = "gh", Arguments = "auth status", RedirectStandardError = true, RedirectStandardOutput = true, UseShellExecute = false }); - if (ghCheck != null) - { - ghCheck.WaitForExit(5000); - ghAvailable = ghCheck.ExitCode == 0; - if (!ghAvailable) - ghError = "gh CLI not authenticated. Run: gh auth login"; - } -} -catch { } -Console.WriteLine(ghAvailable ? "GitHub CLI: authenticated" : $"GitHub CLI: {ghError}"); - -// === Gold API === - -app.MapGet("/api/suites", () => -{ - var suites = new HashSet(); - if (Directory.Exists(testsDir)) - foreach (var dir in Directory.GetDirectories(testsDir)) - { - var name = Path.GetFileName(dir); - if (name != "local") suites.Add(name); - } - // Also from sources - foreach (var src in sourceManager.GetAll()) - if (Directory.Exists(src.Path)) - foreach (var dir in Directory.GetDirectories(src.Path)) - suites.Add(Path.GetFileName(dir)); - return suites.OrderBy(s => s); -}); - -app.MapGet("/api/platforms", (string suite) => -{ - var platforms = new HashSet(); - CollectPlatforms(Path.Combine(testsDir, suite), platforms); - foreach (var src in sourceManager.GetAll()) - CollectPlatforms(Path.Combine(src.Path, suite), platforms); - return platforms.OrderBy(p => p); -}); - -app.MapGet("/api/gold/images", (string suite, string platform) => -{ - var parts = platform.Split('/', 2); - if (parts.Length != 2) return Results.BadRequest("Platform format: Platform/Device"); - var dir = Path.Combine(testsDir, suite, parts[0], parts[1]); - var primary = ListPngNames(dir); - var primarySet = new HashSet(primary); - - // Find fallback gold from other platforms (matching test framework behavior) - var seen = new HashSet(primarySet); - var fallbacks = new List(); - var suiteDir = Path.Combine(testsDir, suite); - if (Directory.Exists(suiteDir)) - { - foreach (var pDir in Directory.GetDirectories(suiteDir)) - { - var pName = Path.GetFileName(pDir); - if (pName == "local") continue; - foreach (var dDir in Directory.GetDirectories(pDir)) - { - var fallbackPlatform = $"{pName}/{Path.GetFileName(dDir)}"; - if (fallbackPlatform == platform) continue; - foreach (var f in Directory.GetFiles(dDir, "*.png")) - { - var name = Path.GetFileName(f); - if (seen.Add(name)) - fallbacks.Add(new { Name = name, FallbackPlatform = fallbackPlatform }); - } - } - } - } - - return Results.Ok(new - { - Images = primary.Select(n => new { Name = n, FallbackPlatform = (string?)null }), - Fallbacks = fallbacks - }); -}); - -app.MapGet("/api/gold/image", (string suite, string platform, string name) => -{ - // Try exact platform first, then fallback across all platforms - var parts = platform.Split('/', 2); - if (parts.Length != 2) return Results.BadRequest("Invalid platform"); - var filePath = Path.Combine(testsDir, suite, parts[0], parts[1], name); - if (File.Exists(filePath)) return Results.File(filePath, "image/png"); - - // Fallback: search all platforms in this suite - var suiteDir = Path.Combine(testsDir, suite); - if (Directory.Exists(suiteDir)) - foreach (var pDir in Directory.GetDirectories(suiteDir)) - foreach (var dDir in Directory.GetDirectories(pDir)) - { - var candidate = Path.Combine(dDir, name); - if (File.Exists(candidate)) return Results.File(candidate, "image/png"); - } - return Results.NotFound(); -}); - -// Thresholds -app.MapGet("/api/thresholds", (string suite) => -{ - var path = Path.Combine(testsDir, suite, "thresholds.jsonc"); - if (!File.Exists(path)) return Results.Ok(Array.Empty()); - var jsonc = File.ReadAllText(path); - // Strip // comments - var json = System.Text.RegularExpressions.Regex.Replace(jsonc, @"//.*?$", "", System.Text.RegularExpressions.RegexOptions.Multiline); - var rules = JsonSerializer.Deserialize(json); - return Results.Ok(rules); -}); - -// Also list all gold platforms that have a given image (for fallback info) -app.MapGet("/api/gold/all", (string suite, string name) => -{ - var results = new List(); - var suiteDir = Path.Combine(testsDir, suite); - if (!Directory.Exists(suiteDir)) return Results.Ok(results); - foreach (var pDir in Directory.GetDirectories(suiteDir)) - { - if (Path.GetFileName(pDir) == "local") continue; - foreach (var dDir in Directory.GetDirectories(pDir)) - if (File.Exists(Path.Combine(dDir, name))) - results.Add(new { Platform = $"{Path.GetFileName(pDir)}/{Path.GetFileName(dDir)}" }); - } - return Results.Ok(results); -}); - -// === Source API === - -app.MapGet("/api/sources", () => sourceManager.GetAll().Select(s => new { s.Id, s.Type, s.Label })); - -app.MapPost("/api/sources/add-local", () => -{ - if (!Directory.Exists(localDir)) - return Results.BadRequest("tests/local/ does not exist"); - var src = sourceManager.Add("local", "Local", localDir); - return Results.Ok(new { src.Id, src.Label }); -}); - -app.MapPost("/api/sources/add-ci", async (HttpRequest request) => -{ - if (!ghAvailable) return Results.BadRequest(ghError); - var body = await JsonSerializer.DeserializeAsync(request.Body); - if (body == null || string.IsNullOrEmpty(body.RunId)) return Results.BadRequest("runId required"); - - var artifactName = body.ArtifactName ?? "test-artifacts-linux-vulkan"; - var destDir = Path.Combine(ciCacheDir, body.RunId); - var markerFile = Path.Combine(destDir, $".downloaded_{artifactName}"); - - // Skip if already downloaded - if (File.Exists(markerFile)) - { - var cachedSrc = sourceManager.GetAll().FirstOrDefault(s => s.Path == destDir); - var cachedLabel = !string.IsNullOrEmpty(body.Label) ? body.Label : $"CI #{body.RunId[..Math.Min(5, body.RunId.Length)]}"; - cachedSrc ??= sourceManager.Add("ci", cachedLabel, destDir); - return Results.Ok(new { cachedSrc.Id, Label = cachedSrc.Label }); - } - - // Download to a temp dir first, then merge into the cache dir - var tmpDir = destDir + $"_tmp_{artifactName.GetHashCode():x}"; - if (Directory.Exists(tmpDir)) - Directory.Delete(tmpDir, true); - Directory.CreateDirectory(tmpDir); - - // Download this artifact - var proc = Process.Start(new ProcessStartInfo - { - FileName = "gh", - Arguments = $"run download {body.RunId} --repo stride3d/stride --name {artifactName} --dir \"{tmpDir}\"", - RedirectStandardOutput = true, - RedirectStandardError = true, - UseShellExecute = false - }); - if (proc != null) - { - await proc.WaitForExitAsync(); - if (proc.ExitCode != 0) - { - var err = await proc.StandardError.ReadToEndAsync(); - if (Directory.Exists(tmpDir)) Directory.Delete(tmpDir, true); - return Results.Problem($"gh failed for {artifactName}: {err}"); - } - } - - // Merge temp into cache dir (overwrite existing files) - Directory.CreateDirectory(destDir); - foreach (var file in Directory.GetFiles(tmpDir, "*", SearchOption.AllDirectories)) - { - var relativePath = Path.GetRelativePath(tmpDir, file); - var destFile = Path.Combine(destDir, relativePath); - Directory.CreateDirectory(Path.GetDirectoryName(destFile)!); - File.Copy(file, destFile, overwrite: true); - } - if (Directory.Exists(tmpDir)) Directory.Delete(tmpDir, true); - - // Mark as downloaded so we skip next time - File.WriteAllText(markerFile, DateTime.UtcNow.ToString("o")); - - // Reuse existing source for same run, or create new - var label = $"CI #{body.RunId[..Math.Min(5, body.RunId.Length)]}"; - if (!string.IsNullOrEmpty(body.Label)) label = body.Label; - var existing = sourceManager.GetAll().FirstOrDefault(s => s.Path == destDir); - var src = existing ?? sourceManager.Add("ci", label, destDir); - return Results.Ok(new { src.Id, src.Label }); -}); - -app.MapPost("/api/sources/add-folder", async (HttpRequest request) => -{ - var body = await JsonSerializer.DeserializeAsync(request.Body); - if (body == null || string.IsNullOrEmpty(body.Path)) return Results.BadRequest("path required"); - if (!Directory.Exists(body.Path)) return Results.BadRequest("Directory does not exist"); - var label = body.Label ?? Path.GetFileName(body.Path); - var src = sourceManager.Add("folder", label, body.Path); - return Results.Ok(new { src.Id, src.Label }); -}); - -app.MapDelete("/api/sources/{id}", (string id) => -{ - sourceManager.Remove(id); - return Results.Ok(); -}); - -app.MapGet("/api/source/{id}/images", (string id, string suite, string platform) => -{ - var src = sourceManager.Get(id); - if (src == null) return Results.NotFound(); - var parts = platform.Split('/', 2); - if (parts.Length != 2) return Results.BadRequest("Invalid platform"); - var dir = Path.Combine(src.Path, suite, parts[0], parts[1]); - return Results.Ok(ListPngs(dir)); -}); - -app.MapGet("/api/source/{id}/image", (string id, string suite, string platform, string name) => -{ - var src = sourceManager.Get(id); - if (src == null) return Results.NotFound(); - return ServeImage(src.Path, suite, platform, name); -}); - -// === CI Runs API === - -app.MapGet("/api/ci/status", () => Results.Ok(new { Available = ghAvailable, Error = ghAvailable ? null : ghError })); - -app.MapGet("/api/ci/artifacts", async (string runId) => -{ - if (!ghAvailable) return Results.BadRequest(ghError); - var proc = Process.Start(new ProcessStartInfo - { - FileName = "gh", - Arguments = $"api repos/stride3d/stride/actions/runs/{runId}/artifacts --jq \".artifacts[] | {{name: .name, size_in_bytes: .size_in_bytes, expired: .expired}}\"", - RedirectStandardOutput = true, - RedirectStandardError = true, - UseShellExecute = false - }); - if (proc == null) return Results.Problem("Could not start gh"); - var output = await proc.StandardOutput.ReadToEndAsync(); - await proc.WaitForExitAsync(); - var artifacts = output.Split('\n', StringSplitOptions.RemoveEmptyEntries) - .Select(line => { try { return JsonSerializer.Deserialize(line); } catch { return default; } }) - .Where(j => j.ValueKind != JsonValueKind.Undefined) - .ToList(); - return Results.Ok(artifacts); -}); - -app.MapGet("/api/ci/runs", async (string? branch, int? limit) => -{ - if (!ghAvailable) return Results.BadRequest(ghError); - var lim = limit ?? 10; - var branchArg = !string.IsNullOrEmpty(branch) ? $"--branch {branch}" : ""; - var proc = Process.Start(new ProcessStartInfo - { - FileName = "gh", - Arguments = $"api repos/stride3d/stride/actions/runs --jq \".workflow_runs[:{ lim}] | .[] | {{id: .id, head_sha: .head_sha, head_branch: .head_branch, created_at: .created_at, conclusion: .conclusion, name: .name}}\" {branchArg}", - RedirectStandardOutput = true, - RedirectStandardError = true, - UseShellExecute = false - }); - if (proc == null) return Results.Problem("Could not start gh"); - var output = await proc.StandardOutput.ReadToEndAsync(); - await proc.WaitForExitAsync(); - // Parse JSONL output - var runs = output.Split('\n', StringSplitOptions.RemoveEmptyEntries) - .Select(line => { try { return JsonSerializer.Deserialize(line); } catch { return default; } }) - .Where(j => j.ValueKind != JsonValueKind.Undefined) - .ToList(); - return Results.Ok(runs); -}); - -// === Promote API === - -app.MapPost("/api/promote", async (HttpRequest request) => -{ - var body = await JsonSerializer.DeserializeAsync(request.Body); - if (body == null) return Results.BadRequest("Invalid body"); - - var src = sourceManager.Get(body.SourceId); - if (src == null) return Results.BadRequest("Source not found"); - - var parts = body.Platform.Split('/', 2); - if (parts.Length != 2) return Results.BadRequest("Invalid platform"); - - var srcDir = Path.Combine(src.Path, body.Suite, parts[0], parts[1]); - var goldDir = Path.Combine(testsDir, body.Suite, parts[0], parts[1]); - Directory.CreateDirectory(goldDir); - - int promoted = 0; - var details = new List(); - foreach (var name in body.Names) - { - var srcFile = Path.Combine(srcDir, name); - var dstFile = Path.Combine(goldDir, name); - if (File.Exists(srcFile)) - { - File.Copy(srcFile, dstFile, overwrite: true); - promoted++; - details.Add(new { Name = name, Src = srcFile, Dst = dstFile, SrcSize = new FileInfo(srcFile).Length, DstSize = new FileInfo(dstFile).Length }); - } - else - { - details.Add(new { Name = name, Src = srcFile, Dst = dstFile, Error = "Source file not found" }); - } - } - Console.WriteLine($"Promote: {promoted}/{body.Names.Length} from {srcDir} -> {goldDir}"); - foreach (var d in details) Console.WriteLine($" {d}"); - return Results.Ok(new { Promoted = promoted, Details = details }); -}); - -app.Run(); - -// === Helpers === - -static string? FindStrideRoot(string startDir) -{ - var dir = startDir; - while (dir != null) - { - if (Directory.Exists(Path.Combine(dir, "tests")) && Directory.Exists(Path.Combine(dir, "sources"))) - return dir; - dir = Path.GetDirectoryName(dir); - } - return null; -} - -static void CollectPlatforms(string suiteDir, HashSet platforms) -{ - if (!Directory.Exists(suiteDir)) return; - foreach (var pDir in Directory.GetDirectories(suiteDir)) - foreach (var dDir in Directory.GetDirectories(pDir)) - platforms.Add($"{Path.GetFileName(pDir)}/{Path.GetFileName(dDir)}"); -} - -static List ListPngNames(string dir) -{ - if (!Directory.Exists(dir)) return []; - return Directory.GetFiles(dir, "*.png") - .Select(Path.GetFileName) - .ToList()!; -} - -static List ListPngs(string dir) -{ - if (!Directory.Exists(dir)) return []; - return Directory.GetFiles(dir, "*.png") - .Select(f => (object)new { Name = Path.GetFileName(f) }) - .ToList(); -} - - -static IResult ServeImage(string baseDir, string suite, string platform, string name) -{ - var parts = platform.Split('/', 2); - if (parts.Length != 2) return Results.BadRequest("Invalid platform"); - var filePath = Path.Combine(baseDir, suite, parts[0], parts[1], name); - if (!File.Exists(filePath)) return Results.NotFound(); - return Results.File(filePath, "image/png"); -} - -// === Models === - -record CiDownloadRequest -{ - [System.Text.Json.Serialization.JsonPropertyName("runId")] - public string RunId { get; set; } = ""; - [System.Text.Json.Serialization.JsonPropertyName("artifactName")] - public string? ArtifactName { get; set; } - [System.Text.Json.Serialization.JsonPropertyName("label")] - public string? Label { get; set; } -} -record FolderRequest -{ - [System.Text.Json.Serialization.JsonPropertyName("path")] - public string Path { get; set; } = ""; - [System.Text.Json.Serialization.JsonPropertyName("label")] - public string? Label { get; set; } -} -record PromoteRequest -{ - [System.Text.Json.Serialization.JsonPropertyName("sourceId")] - public string SourceId { get; set; } = ""; - [System.Text.Json.Serialization.JsonPropertyName("suite")] - public string Suite { get; set; } = ""; - [System.Text.Json.Serialization.JsonPropertyName("platform")] - public string Platform { get; set; } = ""; - [System.Text.Json.Serialization.JsonPropertyName("names")] - public string[] Names { get; set; } = []; -} - -// === Source Manager === - -class Source -{ - public string Id { get; set; } = ""; - public string Type { get; set; } = ""; // "local", "ci", "folder" - public string Label { get; set; } = ""; - public string Path { get; set; } = ""; -} - -class SourceManager -{ - private readonly ConcurrentDictionary _sources = new(); - private int _nextId = 1; - - public Source Add(string type, string label, string path) - { - var id = $"src-{Interlocked.Increment(ref _nextId)}"; - var src = new Source { Id = id, Type = type, Label = label, Path = path }; - _sources[id] = src; - return src; - } - - public Source? Get(string id) => _sources.GetValueOrDefault(id); - public void Remove(string id) => _sources.TryRemove(id, out _); - public IEnumerable GetAll() => _sources.Values; -} diff --git a/build/tools/CompareGold/wwwroot/app.js b/build/tools/CompareGold/wwwroot/app.js deleted file mode 100644 index 08a8e93535..0000000000 --- a/build/tools/CompareGold/wwwroot/app.js +++ /dev/null @@ -1,1288 +0,0 @@ -// === State === -let allSuites = []; -let currentPlatform = ''; -let sources = []; // [{id, type, label}] -let sourceDefs = []; // tracks how sources were added for persistence -let suiteData = {}; // {suite: {gold: [{name}], sourceImages: {srcId: [{name}]}}} -let expanded = new Set(); // "suite:name" — fully loaded and visible -let loading = new Set(); // "suite:name" — loading in progress -let selected = new Set(); // "suite:name" -let collapsedSuites = new Set(); -let compareLeft = {}; // {"suite:name": "gold:" or "src:"} -let compareRight = {}; // {"suite:name": "gold:" or "src:"} -let cellStats = {}; // {`${sourceId}:${suite}:${name}`: stats} - -// === Init === -async function init() { - const res = await fetch('/api/suites'); - allSuites = await res.json(); - - // Collect all platforms across all suites - const allPlatforms = new Set(); - for (const suite of allSuites) { - const pRes = await fetch(`/api/platforms?suite=${enc(suite)}`); - (await pRes.json()).forEach(p => allPlatforms.add(p)); - } - const platforms = [...allPlatforms].sort(); - const sel = document.getElementById('platformSelect'); - sel.innerHTML = platforms.map(p => ``).join(''); - currentPlatform = platforms[0] || ''; - sel.onchange = onPlatformChange; - - document.getElementById('statusFilter').onchange = () => render(); - await reload(); -} - -async function onPlatformChange() { - currentPlatform = document.getElementById('platformSelect').value; - await reload(); -} - -async function reload() { - if (!currentPlatform) return; - suiteData = {}; - for (const suite of allSuites) { - const gRes = await fetch(`/api/gold/images?suite=${enc(suite)}&platform=${enc(currentPlatform)}`); - const gData = await gRes.json(); - // Merge primary + fallback gold, tagging fallbacks - const gold = [ - ...(gData.images || []).map(g => ({ name: g.name, fallback: null })), - ...(gData.fallbacks || []).map(g => ({ name: g.name, fallback: g.fallbackPlatform })) - ]; - const srcImgs = {}; - for (const src of sources) { - const sRes = await fetch(`/api/source/${src.id}/images?suite=${enc(suite)}&platform=${enc(currentPlatform)}`); - srcImgs[src.id] = await sRes.json(); - } - // Load thresholds for this suite - const tRes = await fetch(`/api/thresholds?suite=${enc(suite)}`); - const thresholdRules = tRes.ok ? await tRes.json() : []; - // Only include suite if it has any images - if (gold.length > 0 || Object.values(srcImgs).some(imgs => imgs.length > 0)) - suiteData[suite] = { gold, sourceImages: srcImgs, thresholdRules }; - } - cellStats = {}; - render(); -} - -// === Sources === -async function addLocalSource() { - const res = await fetch('/api/sources/add-local', { method: 'POST' }); - if (!res.ok) { alert(await res.text()); return; } - const src = await res.json(); - sources.push(src); - sourceDefs.push({ type: 'local' }); - await reload(); -} - -function showCiModal() { - document.getElementById('ciModal').style.display = 'flex'; - selectedCiRun = null; - document.getElementById('ciRunId').value = ''; - document.getElementById('ciDownloadBtn').disabled = true; - loadCiRuns(); -} -function closeCiModal() { document.getElementById('ciModal').style.display = 'none'; } - -async function downloadCiRun() { - const runId = String(document.getElementById('ciRunId').value).trim(); - if (!runId) { alert('Enter or select a run ID'); return; } - - const artifacts = selectedCiArtifacts.size > 0 ? [...selectedCiArtifacts] : ['test-artifacts-linux-vulkan']; - - const btn = document.getElementById('ciDownloadBtn'); - btn.disabled = true; - - try { - for (let i = 0; i < artifacts.length; i++) { - btn.textContent = `Downloading ${i + 1}/${artifacts.length}...`; - const res = await fetch('/api/sources/add-ci', { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ runId: String(runId), artifactName: artifacts[i], label: `CI #${String(runId).substring(0, 5)}` }) - }); - if (!res.ok) { alert(`Failed to download ${artifacts[i]}: ${await res.text()}`); continue; } - const src = await res.json(); - // Only add if not already in sources list - if (!sources.find(s => s.id === src.id)) { - sources.push(src); - sourceDefs.push({ type: 'ci', runId: String(runId), artifactName: artifacts[i], label: src.label }); - } - } - closeCiModal(); - await reload(); - } finally { - btn.textContent = 'Download & Add'; - btn.disabled = false; - } -} - -async function removeSource(id) { - const idx = sources.findIndex(s => s.id === id); - await fetch(`/api/sources/${id}`, { method: 'DELETE' }); - sources = sources.filter(s => s.id !== id); - if (idx >= 0) sourceDefs.splice(idx, 1); - cellStats = {}; - expanded.clear(); - selected.clear(); - compareLeft = {}; - compareRight = {}; - if (sources.length === 0) { - suiteData = {}; - render(); - } else { - await reload(); - } -} - -// === Render === -function render() { - renderSourceTags(); - renderPromoteSourceSelect(); - renderTable(); -} - -function renderSourceTags() { - const el = document.getElementById('sourceTags'); - el.innerHTML = sources.map(s => - `
- ${esc(s.label)} - × -
` - ).join(''); -} - -function renderPromoteSourceSelect() { - const sel = document.getElementById('promoteSource'); - sel.innerHTML = `` + - sources.map(s => ``).join(''); -} - -function buildSuiteImages(suite) { - const data = suiteData[suite]; - if (!data) return []; - const allNames = new Set(); - data.gold.forEach(g => allNames.add(g.name)); - for (const srcId in data.sourceImages) - data.sourceImages[srcId].forEach(s => allNames.add(s.name)); - - return [...allNames].sort(naturalSort).map(name => { - const goldEntry = data.gold.find(g => g.name === name); - const hasGold = !!goldEntry; - const goldFallback = goldEntry?.fallback || null; - const sourcesWithImage = {}; - for (const src of sources) - sourcesWithImage[src.id] = (data.sourceImages[src.id] || []).some(s => s.name === name); - let status = 'pass'; - if (!hasGold && Object.values(sourcesWithImage).some(v => v)) status = 'new'; - else if (hasGold && Object.values(sourcesWithImage).some(v => v)) { - // Check all sources — fail if ANY source fails the threshold - let anyFail = false; - let anyPending = false; - for (const src of sources) { - if (!sourcesWithImage[src.id]) continue; - const stats = cellStats[`${src.id}:${suite}:${name}`]; - if (!stats) { anyPending = true; computeCellStats(src.id, suite, name); continue; } - const result = checkCellThreshold(suite, name, stats); - if (!result.passed) { anyFail = true; } - } - status = anyFail ? 'fail' : anyPending ? 'pending' : 'pass'; - } - return { suite, name, hasGold, goldFallback, sourcesWithImage, status }; - }); -} - -function renderTable() { - const filter = document.getElementById('statusFilter').value; - - // Render header - const thead = document.getElementById('thead'); - thead.innerHTML = ` - - Test - Gold - ${sources.map(s => `${esc(s.label)}`).join('')} - `; - - const tbody = document.getElementById('tbody'); - tbody.innerHTML = ''; - let totalVisible = 0; - - const search = (document.getElementById('searchFilter')?.value || '').toLowerCase(); - - for (const suite of Object.keys(suiteData).sort()) { - let images = buildSuiteImages(suite); - if (filter) images = images.filter(i => i.status === filter || (filter === 'fail' && i.status === 'pending')); - if (search) images = images.filter(i => i.name.toLowerCase().includes(search)); - if (images.length === 0) continue; - - const sortMode = document.getElementById('sortSelect')?.value || 'name'; - if (sortMode === 'diff') { - images.sort((a, b) => { - const aMax = getMaxDiffForImage(a) ?? -1; - const bMax = getMaxDiffForImage(b) ?? -1; - return bMax - aMax; // worst first - }); - } - - const failCount = images.filter(i => i.status === 'fail' || i.status === 'new' || i.status === 'pending').length; - const isCollapsed = collapsedSuites.has(suite); - const shortSuite = suite.replace('Stride.', '').replace('.Tests', '').replace('.Regression', ''); - - // Suite header row - const suiteKeys = images.map(i => `${i.suite}:${i.name}`); - const allSelected = suiteKeys.every(k => selected.has(k)); - const someSelected = !allSelected && suiteKeys.some(k => selected.has(k)); - const suiteTr = document.createElement('tr'); - suiteTr.className = 'suite-row'; - suiteTr.dataset.kbKey = suite; - suiteTr.innerHTML = ` - - - ${isCollapsed ? '▶' : '▼'} - ${esc(shortSuite)} - ${images.length} tests - ${failCount > 0 ? `${failCount} failing` : ''} - `; - suiteTr.onclick = () => { toggleSuite(suite); }; - tbody.appendChild(suiteTr); - // Set indeterminate state (can't do via HTML attribute) - if (someSelected) suiteTr.querySelector('input[type=checkbox]').indeterminate = true; - - if (isCollapsed) continue; - - for (const img of images) { - totalVisible++; - const key = `${img.suite}:${img.name}`; - const isExp = expanded.has(key); - const isLoading = loading.has(key); - const isSel = selected.has(key); - - const tr = document.createElement('tr'); - tr.className = `row ${isExp ? 'expanded' : ''}`; - tr.dataset.kbKey = key; - // Gold thumbnail - let goldThumb = ''; - if (img.hasGold && sources.length > 0) { - const thumbUrl = `/api/gold/image?suite=${enc(img.suite)}&platform=${enc(currentPlatform)}&name=${enc(img.name)}`; - goldThumb = `
`; - } - - let cells = ` - - ${isExp ? '▼' : '▶'} ${esc(img.name)}${isLoading ? ' ' : ''} - ${img.hasGold ? (img.goldFallback ? 'fb' : 'ref') : '—'}${img.hasGold ? ` ${esc(img.goldFallback || currentPlatform)}` : ''}${goldThumb}`; - - const activeRef = compareRight[key] || `src:${getSourceForKey(key)}`; - for (const src of sources) { - const has = img.sourcesWithImage[src.id]; - const isActive = activeRef === `src:${src.id}`; - const statsKey = `${src.id}:${img.suite}:${img.name}`; - const stats = cellStats[statsKey]; - let cellHtml; - if (!has) { - cellHtml = ''; - } else if (!img.hasGold) { - cellHtml = '○ new'; - } else if (stats) { - const result = checkCellThreshold(img.suite, img.name, stats); - const cls = result.passed ? 'pass' : 'fail'; - const brief = formatThresholdBrief(result); - cellHtml = `${cls === 'pass' ? '✓' : '✗'} ${brief}`; - } else { - cellHtml = `...`; - computeCellStats(src.id, img.suite, img.name); - } - // Show source thumbnail + diff canvas for failing/new items - if (has) { - const thumbSrc = `/api/source/${src.id}/image?suite=${enc(img.suite)}&platform=${enc(currentPlatform)}&name=${enc(img.name)}`; - if (img.hasGold) { - const thumbId = `thumb-${css(src.id)}-${css(key)}`; - cellHtml += `
`; - // Queue thumbnail diff computation - requestAnimationFrame(() => computeThumbDiff(img.suite, img.name, src.id, thumbId)); - } else { - cellHtml += `
`; - } - } - cells += `${cellHtml}`; - } - - tr.innerHTML = cells; - tr.onmousedown = (e) => { tr._clickX = e.clientX; tr._clickY = e.clientY; }; - tr.onclick = (e) => { - if (Math.abs(e.clientX - tr._clickX) > 3 || Math.abs(e.clientY - tr._clickY) > 3) return; - toggleExpand(key); - }; - tbody.appendChild(tr); - - if (isExp) { - const detailTr = document.createElement('tr'); - const colspan = 3 + sources.length; - detailTr.innerHTML = ` -
-
Loading...
-
- `; - tbody.appendChild(detailTr); - loadDetail(img.suite, img.name); - } - } - } - document.getElementById('emptyMsg').style.display = totalVisible === 0 ? 'block' : 'none'; - updateSelectedCount(); -} - -function toggleSuite(suite) { - if (collapsedSuites.has(suite)) collapsedSuites.delete(suite); - else collapsedSuites.add(suite); - render(); -} - -// === Detail === -const detailVersion = {}; // track version to discard stale loads -const preloaded = {}; // cached images from startExpand - -function resolveImageRef(ref, suite, name) { - if (!ref) return null; - - if (ref.startsWith('gold:')) { - const plat = ref.slice(5); - return `/api/gold/image?suite=${enc(suite)}&platform=${enc(plat)}&name=${enc(name)}`; - } - if (ref.startsWith('src:')) { - const srcId = ref.slice(4); - return `/api/source/${srcId}/image?suite=${enc(suite)}&platform=${enc(currentPlatform)}&name=${enc(name)}`; - } - return null; -} - -function buildRefOptions(goldPlatforms, selectedRef) { - let html = ''; - if (goldPlatforms.length === 0) html += ''; - for (const p of goldPlatforms) - html += ``; - html += ''; - if (sources.length > 0) { - html += ''; - for (const s of sources) - html += ``; - html += ''; - } - return html; -} - -function pickDefaultLeft(goldPlatforms) { - const best = pickBestGoldPlatform(goldPlatforms, currentPlatform); - return best ? `gold:${best}` : (sources[0] ? `src:${sources[0].id}` : ''); -} - -function pickDefaultRight(img) { - // Prefer first source that has this image - const src = sources.find(s => img ? img.sourcesWithImage?.[s.id] : false); - if (src) return `src:${src.id}`; - return sources[0] ? `src:${sources[0].id}` : ''; -} - -async function loadDetail(suite, name) { - const key = `${suite}:${name}`; - const id = css(key); - - // Use preloaded data if available (from startExpand) - const cached = preloaded[key]; - if (cached) { - delete preloaded[key]; - fillDetail(id, key, suite, name, cached); - return; - } - - const ver = (detailVersion[key] || 0) + 1; - detailVersion[key] = ver; - - const data = suiteData[suite]; - const img = data ? buildSuiteImages(suite).find(i => i.name === name) : null; - - // Fetch gold platforms - const goldPlatforms = await fetch(`/api/gold/all?suite=${enc(suite)}&name=${enc(name)}`).then(r => r.json()).catch(() => []); - if (detailVersion[key] !== ver) return; - - // Resolve left and right refs - const leftRef = compareLeft[key] || pickDefaultLeft(goldPlatforms); - const rightRef = compareRight[key] || pickDefaultRight(img); - const leftUrl = resolveImageRef(leftRef, suite, name); - const rightUrl = resolveImageRef(rightRef, suite, name); - - // Load images in parallel - const [leftImg, rightImg] = await Promise.all([ - leftUrl ? loadImg(leftUrl).catch(() => null) : null, - rightUrl ? loadImg(rightUrl).catch(() => null) : null, - ]); - if (detailVersion[key] !== ver) return; - - // If no container yet (loading from startExpand), transition to expanded - if (!document.getElementById(`images-${id}`)) { - loading.delete(key); - expanded.add(key); - preloaded[key] = { ver, leftImg, rightImg, goldPlatforms, leftRef, rightRef, img }; - render(); // re-render creates the container, loadDetail re-enters and uses preloaded - return; - } - - fillDetail(id, key, suite, name, { ver, leftImg, rightImg, goldPlatforms, leftRef, rightRef, img }); -} - -function fillDetail(id, key, suite, name, { ver, leftImg, rightImg, goldPlatforms, leftRef, rightRef, img }) { - detailVersion[key] = ver; - const container = document.getElementById(`images-${id}`); - if (!container) return; - - // Build dropdowns - const leftOpts = buildRefOptions(goldPlatforms, leftRef); - const rightOpts = buildRefOptions(goldPlatforms, rightRef); - const leftSelHtml = ``; - const rightSelHtml = ``; - - // Build DOM - let html = `
`; - html += `
${leftSelHtml}
`; - if (leftImg) html += `
`; - else html += `
No image
`; - html += `
`; - html += `
${rightSelHtml}
`; - if (rightImg) html += `
`; - else html += `
No image
`; - html += `
`; - html += `
Diff
`; - if (leftImg && rightImg) html += `
`; - else html += `
`; - html += `
`; - html += '
'; - html += `
Ctrl+Scroll to zoom · Drag to pan ·
`; - container.innerHTML = html; - - if (leftImg) drawToCanvas(document.getElementById(`left-${id}`), leftImg); - if (rightImg) drawToCanvas(document.getElementById(`right-${id}`), rightImg); - if (leftImg && rightImg) { - const canvas = document.getElementById(`diff-${id}`); - const stats = computeImageDiff(leftImg, rightImg, canvas); - // Resolve threshold for this image - const data = suiteData[suite]; - const rules = data?.thresholdRules || []; - const [platApi, device] = currentPlatform.split('/'); - const dotIdx = platApi?.indexOf('.') ?? -1; - const plat = dotIdx >= 0 ? platApi.substring(0, dotIdx) : platApi; - const api = dotIdx >= 0 ? platApi.substring(dotIdx + 1) : null; - const allow = resolveThreshold(rules, name, plat, api, device); - const thresholdResult = stats.pixelDiffs ? checkThreshold(stats.pixelDiffs, allow) : null; - const statsEl = document.getElementById(`stats-${id}`); - if (statsEl) statsEl.innerHTML = formatStats(stats, thresholdResult); - } - initZoomGroup(id); - - // Compute compact stats for gold options vs the other side - const otherImg = rightImg || leftImg; - if (otherImg && goldPlatforms.length > 0) { - const leftSel = container.querySelector(`select`); - const rightSel = container.querySelectorAll(`select`)[1]; - for (const sel of [leftSel, rightSel]) { - if (!sel) continue; - const otherRef = sel === leftSel ? rightRef : leftRef; - const otherImgForStats = sel === leftSel ? rightImg : leftImg; - if (!otherImgForStats) continue; - for (const opt of sel.options) { - if (!opt.value.startsWith('gold:')) continue; - const plat = opt.value.slice(5); - loadImg(`/api/gold/image?suite=${enc(suite)}&platform=${enc(plat)}&name=${enc(name)}`).then(gImg => { - if (detailVersion[key] !== ver) return; - const tmpCanvas = new OffscreenCanvas(gImg.width, gImg.height); - const s = computeImageDiff(gImg, otherImgForStats, tmpCanvas); - opt.textContent = `${plat} (d=${s.maxDiff} px=${s.diffPixels})`; - }).catch(() => {}); - } - } - } -} - -// === Background cell stats === -const statsQueue = new Set(); -let statsRunning = false; - -function computeCellStats(srcId, suite, name) { - const key = `${srcId}:${suite}:${name}`; - if (cellStats[key] || statsQueue.has(key)) return; - statsQueue.add(key); - if (!statsRunning) runStatsQueue(); -} - -async function runStatsQueue() { - statsRunning = true; - while (statsQueue.size > 0) { - const key = statsQueue.values().next().value; - statsQueue.delete(key); - const parts = key.split(':'); - const srcId = parts[0]; - const suite = parts[1]; - const name = parts.slice(2).join(':'); - - try { - - const goldUrl = `/api/gold/image?suite=${enc(suite)}&platform=${enc(currentPlatform)}&name=${enc(name)}`; - const srcUrl = `/api/source/${srcId}/image?suite=${enc(suite)}&platform=${enc(currentPlatform)}&name=${enc(name)}`; - - const [goldImg, srcImg] = await Promise.all([loadImage(goldUrl), loadImage(srcUrl)]); - const canvas = new OffscreenCanvas(goldImg.width, goldImg.height); - const stats = computeImageDiff(goldImg, srcImg, canvas); - cellStats[key] = stats; - // Update just the cell inline instead of re-rendering everything - updateCellInline(key, stats); - } catch (e) { - // Skip failed comparisons - } - await new Promise(r => setTimeout(r, 10)); - } - statsRunning = false; - // Re-render to update suite badges and filter counts now that all stats are available - render(); -} - -function checkCellThreshold(suite, name, stats) { - const data = suiteData[suite]; - const rules = data?.thresholdRules || []; - const [platApi, device] = currentPlatform.split('/'); - const dotIdx = platApi?.indexOf('.') ?? -1; - const plat = dotIdx >= 0 ? platApi.substring(0, dotIdx) : platApi; - const api = dotIdx >= 0 ? platApi.substring(dotIdx + 1) : null; - const allow = resolveThreshold(rules, name, plat, api, device); - if (stats.pixelDiffs) return checkThreshold(stats.pixelDiffs, allow); - // Fallback for stats without pixelDiffs - return { passed: stats.diffPixels === 0, details: [] }; -} - -function updateCellInline(key, stats) { - const el = document.querySelector(`[data-stats-key="${CSS.escape(key)}"]`); - if (!el) return; - const parts = key.split(':'); - const suite = parts[1]; - const name = parts.slice(2).join(':'); - const result = checkCellThreshold(suite, name, stats); - const cls = result.passed ? 'pass' : 'fail'; - el.className = `cell ${cls}`; - el.removeAttribute('style'); - el.removeAttribute('data-stats-key'); - const brief = formatThresholdBrief(result); - el.textContent = `${cls === 'pass' ? '✓' : '✗'} ${brief}`; -} - -async function computeThumbDiff(suite, name, srcId, canvasId) { - try { - - const goldUrl = `/api/gold/image?suite=${enc(suite)}&platform=${enc(currentPlatform)}&name=${enc(name)}`; - const srcUrl = `/api/source/${srcId}/image?suite=${enc(suite)}&platform=${enc(currentPlatform)}&name=${enc(name)}`; - const [goldImg, srcImg] = await Promise.all([loadImage(goldUrl), loadImage(srcUrl)]); - const canvas = document.getElementById(canvasId); - if (!canvas) return; - const stats = computeImageDiff(goldImg, srcImg, canvas); - // Also cache stats - cellStats[`${srcId}:${suite}:${name}`] = stats; - } catch { } -} - -function loadImage(url) { - return new Promise((resolve, reject) => { - const img = new Image(); - img.crossOrigin = 'anonymous'; - img.onload = () => resolve(img); - img.onerror = reject; - img.src = url; - }); -} - -// === Actions === -function toggleExpand(key) { - if (expanded.has(key)) { expanded.delete(key); loading.delete(key); render(); } - else if (loading.has(key)) { loading.delete(key); render(); } - else startExpand(key); -} - -function expandWith(key, srcId) { - if (expanded.has(key)) { expanded.delete(key); loading.delete(key); render(); } - else if (loading.has(key)) { loading.delete(key); render(); } - else { compareRight[key] = `src:${srcId}`; startExpand(key); } -} - -function startExpand(key) { - loading.add(key); - render(); - const [suite, name] = [key.substring(0, key.indexOf(':')), key.substring(key.indexOf(':') + 1)]; - loadDetail(suite, name); -} - -function setActiveSource(key, srcId) { - compareRight[key] = `src:${srcId}`; - // Update active-source highlight inline - const row = document.querySelector(`tr.row[data-kb-key="${CSS.escape(key)}"]`); - if (row) { - row.querySelectorAll('td[onclick]').forEach(td => { - const match = td.getAttribute('onclick')?.match(/setActiveSource\('[^']*','([^']*)'\)/); - td.classList.toggle('active-source', match && match[1] === srcId); - }); - } - // If expanded, also update the detail view - if (expanded.has(key)) { - const [suite, name] = [key.substring(0, key.indexOf(':')), key.substring(key.indexOf(':') + 1)]; - loadDetail(suite, name); - } -} - -function switchDetailSide(key, side, value) { - if (side === 'left') compareLeft[key] = value; - else compareRight[key] = value; - const [suite, name] = [key.substring(0, key.indexOf(':')), key.substring(key.indexOf(':') + 1)]; - loadDetail(suite, name); - // Update active-source underline on the table row - if (side === 'right') { - const row = document.querySelector(`tr.row[data-kb-key="${CSS.escape(key)}"]`); - if (row) { - const activeSrcId = value.startsWith('src:') ? value.slice(4) : null; - const srcCells = row.querySelectorAll('td[onclick]'); - srcCells.forEach(td => { - const match = td.getAttribute('onclick')?.match(/expandWith\('[^']*','([^']*)'\)/); - td.classList.toggle('active-source', match && match[1] === activeSrcId); - }); - } - } -} - - -function toggleSelect(name) { - if (selected.has(name)) selected.delete(name); - else selected.add(name); - render(); -} - -function toggleSelectSuite(suite, checked) { - const filter = document.getElementById('statusFilter').value; - let images = buildSuiteImages(suite); - if (filter) images = images.filter(i => i.status === filter); - images.forEach(i => { - const key = `${i.suite}:${i.name}`; - if (checked) selected.add(key); else selected.delete(key); - }); - render(); -} - -function toggleSelectAll() { - const checked = document.getElementById('selectAll').checked; - const filter = document.getElementById('statusFilter').value; - for (const suite of Object.keys(suiteData)) { - let images = buildSuiteImages(suite); - if (filter) images = images.filter(i => i.status === filter || (filter === 'fail' && i.status === 'pending')); - images.forEach(i => { - const key = `${i.suite}:${i.name}`; - if (checked) selected.add(key); else selected.delete(key); - }); - } - render(); -} - -function getMaxDiffForImage(img) { - for (const src of sources) { - const stats = cellStats[`${src.id}:${img.suite}:${img.name}`]; - if (stats) return stats.maxDiff; - } - return null; -} - -function selectAllFailing() { - for (const suite of Object.keys(suiteData)) { - const images = buildSuiteImages(suite); - images.filter(i => i.status === 'fail' || i.status === 'new').forEach(i => selected.add(`${i.suite}:${i.name}`)); - } - render(); -} - -function expandAllFailing() { - for (const suite of Object.keys(suiteData)) { - const images = buildSuiteImages(suite); - images.filter(i => i.status === 'fail' || i.status === 'new').forEach(i => expanded.add(`${i.suite}:${i.name}`)); - } - render(); -} - -function collapseAll() { expanded.clear(); render(); } - -function getSourceForKey(key) { - // Use the right-side source from the detail view, or fall back to first source with the image - const ref = compareRight[key]; - if (ref && ref.startsWith('src:')) return ref.slice(4); - const [suite, name] = [key.substring(0, key.indexOf(':')), key.substring(key.indexOf(':') + 1)]; - const data = suiteData[suite]; - if (data) { - for (const s of sources) - if ((data.sourceImages[s.id] || []).some(i => i.name === name)) return s.id; - } - return sources[0]?.id || null; -} - -async function promoteSelected() { - if (selected.size === 0) return; - const mode = document.getElementById('promoteSource').value; - - // Group by suite+source - const groups = {}; // {`${srcId}:${suite}`: {srcId, suite, names[]}} - for (const key of selected) { - const [suite, name] = [key.substring(0, key.indexOf(':')), key.substring(key.indexOf(':') + 1)]; - const srcId = mode === '__active__' ? getSourceForKey(key) : mode; - if (!srcId) continue; - const gkey = `${srcId}:${suite}`; - if (!groups[gkey]) groups[gkey] = { srcId, suite, names: [] }; - groups[gkey].names.push(name); - } - - const srcLabels = [...new Set(Object.values(groups).map(g => sources.find(s => s.id === g.srcId)?.label || g.srcId))]; - if (!confirm(`Promote ${selected.size} image(s) from ${srcLabels.join(', ')} to gold?`)) return; - - let totalPromoted = 0; - for (const { srcId, suite, names } of Object.values(groups)) { - const res = await fetch('/api/promote', { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ sourceId: srcId, suite, platform: currentPlatform, names }) - }); - const result = await res.json(); - console.log('Promote result:', result); - totalPromoted += result.promoted; - } - alert(`Promoted ${totalPromoted} image(s).`); - selected.clear(); - cellStats = {}; - compareLeft = {}; - compareRight = {}; - await reload(); -} - -function updateSelectedCount() { - document.getElementById('selectedCount').textContent = selected.size; -} - -// === Utils === -function isSoftwareRenderer(platform) { - const p = platform.toLowerCase(); - return p.includes('swiftshader') || p.includes('warp'); -} - -function pickBestGoldPlatform(platforms, currentPlatform) { - if (!platforms || platforms.length === 0) return currentPlatform; - // Exact match - const exact = platforms.find(p => p.platform === currentPlatform); - if (exact) return exact.platform; - // Same class (software/hardware) - const currentIsSw = isSoftwareRenderer(currentPlatform); - const sameClass = platforms.find(p => isSoftwareRenderer(p.platform) === currentIsSw); - if (sameClass) return sameClass.platform; - // Any - return platforms[0].platform; -} - -function enc(s) { return encodeURIComponent(s); } -function css(s) { return s.replace(/[^a-zA-Z0-9]/g, '_'); } -function esc(s) { return s.replace(/&/g, '&').replace(//g, '>').replace(/"/g, '"'); } -function loadImg(url) { - return new Promise((resolve, reject) => { - const img = new Image(); - img.crossOrigin = 'anonymous'; - img.onload = () => resolve(img); - img.onerror = reject; - img.src = url; - }); -} -function drawToCanvas(canvas, img) { - canvas.width = img.naturalWidth; - canvas.height = img.naturalHeight; - canvas.getContext('2d').drawImage(img, 0, 0); -} -function naturalSort(a, b) { return a.localeCompare(b, undefined, { numeric: true, sensitivity: 'base' }); } - -// === CI Modal === -let ciRuns = []; -let selectedCiRun = null; - -async function loadCiRuns() { - document.getElementById('ciLoading').style.display = 'block'; - document.getElementById('ciRunsList').innerHTML = ''; - try { - // Check gh status first - const statusRes = await fetch('/api/ci/status'); - const status = await statusRes.json(); - if (!status.available) { - document.getElementById('ciLoading').innerHTML = `⚠ ${status.error}`; - return; - } - const res = await fetch('/api/ci/runs?limit=30'); - const allRuns = await res.json(); - // Deduplicate by SHA — keep one per commit, use the CI workflow (name "CI") - const seen = new Map(); - for (const run of allRuns) { - const sha = run.head_sha ?? ''; - const name = run.name ?? ''; - if (!seen.has(sha) || name === 'CI') - seen.set(sha, run); - } - ciRuns = [...seen.values()].slice(0, 15); - console.log('CI runs:', ciRuns); - renderCiRuns(); - } catch (e) { - document.getElementById('ciLoading').textContent = 'Failed to load runs. Is gh CLI authenticated?'; - } -} - -function renderCiRuns() { - document.getElementById('ciLoading').style.display = 'none'; - const list = document.getElementById('ciRunsList'); - list.innerHTML = ciRuns.map(run => { - const id = run.id ?? run.Id; - const branch = run.head_branch ?? run.HeadBranch ?? ''; - const sha = (run.head_sha ?? run.HeadSha ?? '').substring(0, 7); - const date = run.created_at ?? run.CreatedAt ?? ''; - const conclusion = run.conclusion ?? run.Conclusion ?? ''; - const ago = timeAgo(date); - const wfName = run.name ?? ''; - const statusIcon = conclusion === 'success' ? '✓' : conclusion === 'failure' ? '✗' : conclusion === 'cancelled' ? '⊘' : '○'; - return `
-
#${id} ${esc(branch)} ${sha}
-
${esc(wfName)} ${ago} ${statusIcon}
-
`; - }).join(''); -} - -let ciArtifacts = []; -let selectedCiArtifacts = new Set(); - -async function selectCiRun(id) { - selectedCiRun = id; - document.getElementById('ciRunId').value = String(id); - renderCiRuns(); - - // Load artifacts for this run - const listEl = document.getElementById('ciArtifactsList'); - listEl.innerHTML = ' Loading artifacts...'; - try { - const res = await fetch(`/api/ci/artifacts?runId=${id}`); - ciArtifacts = await res.json(); - // Auto-select test-related artifacts - selectedCiArtifacts = new Set( - ciArtifacts - .filter(a => { - const name = (a.name ?? a.Name ?? '').toLowerCase(); - return name.includes('test-artifacts') && !(a.expired ?? a.Expired ?? false); - }) - .map(a => a.name ?? a.Name) - ); - renderCiArtifacts(); - } catch (e) { - listEl.innerHTML = `Failed to load artifacts: ${e.message}`; - console.error('Artifact load error:', e); - } - document.getElementById('ciDownloadBtn').disabled = selectedCiArtifacts.size === 0; -} - -function renderCiArtifacts() { - const listEl = document.getElementById('ciArtifactsList'); - const testArtifacts = ciArtifacts.filter(a => (a.name ?? '').startsWith('test-artifacts')); - if (testArtifacts.length === 0) { - listEl.innerHTML = 'No test artifacts found for this run'; - document.getElementById('ciDownloadBtn').disabled = true; - return; - } - listEl.innerHTML = testArtifacts.map(a => { - const name = a.name ?? a.Name; - const size = a.size_in_bytes ?? a.SizeInBytes ?? 0; - const sizeMB = (size / 1048576).toFixed(1); - const checked = selectedCiArtifacts.has(name); - const expired = a.expired ?? a.Expired ?? false; - return ``; - }).join(''); - document.getElementById('ciDownloadBtn').disabled = selectedCiArtifacts.size === 0; -} - -function toggleCiArtifact(name, checked) { - if (checked) selectedCiArtifacts.add(name); - else selectedCiArtifacts.delete(name); - document.getElementById('ciDownloadBtn').disabled = selectedCiArtifacts.size === 0; -} - -function timeAgo(dateStr) { - if (!dateStr) return ''; - const d = new Date(dateStr); - const now = new Date(); - const diff = (now - d) / 1000; - if (diff < 60) return 'just now'; - if (diff < 3600) return Math.floor(diff / 60) + 'm ago'; - if (diff < 86400) return Math.floor(diff / 3600) + 'h ago'; - return Math.floor(diff / 86400) + 'd ago'; -} - -// === Synchronized Zoom/Pan === -const zoomState = {}; // {groupId: {scale, panX, panY}} - -function initZoomGroup(groupId) { - zoomState[groupId] = { scale: 1, panX: 0, panY: 0 }; - const group = document.getElementById(`zoomgroup-${groupId}`); - if (!group) return; - - const containers = group.querySelectorAll('.zoom-container'); - - containers.forEach(container => { - // Wheel zoom - container.addEventListener('wheel', (e) => { - if (!e.ctrlKey) return; // plain scroll passes through; Ctrl+scroll zooms - e.preventDefault(); - const state = zoomState[groupId]; - const rect = container.getBoundingClientRect(); - const mx = e.clientX - rect.left; - const my = e.clientY - rect.top; - - const oldScale = state.scale; - const delta = e.deltaY > 0 ? 0.8 : 1.25; - state.scale = Math.max(0.5, Math.min(20, state.scale * delta)); - - // Zoom toward mouse position - state.panX = mx - (mx - state.panX) * (state.scale / oldScale); - state.panY = my - (my - state.panY) * (state.scale / oldScale); - - applyZoom(groupId); - }, { passive: false }); - - // Drag pan - let dragging = false, startX, startY, startPanX, startPanY; - container.addEventListener('mousedown', (e) => { - if (e.button !== 0) return; - dragging = true; - startX = e.clientX; - startY = e.clientY; - const state = zoomState[groupId]; - startPanX = state.panX; - startPanY = state.panY; - container.classList.add('dragging'); - e.preventDefault(); - }); - window.addEventListener('mousemove', (e) => { - if (!dragging) return; - const state = zoomState[groupId]; - state.panX = startPanX + (e.clientX - startX); - state.panY = startPanY + (e.clientY - startY); - applyZoom(groupId); - }); - window.addEventListener('mouseup', () => { - if (dragging) { - dragging = false; - container.classList.remove('dragging'); - } - }); - }); -} - -function applyZoom(groupId) { - const state = zoomState[groupId]; - const group = document.getElementById(`zoomgroup-${groupId}`); - if (!group) return; - group.querySelectorAll('.zoom-inner').forEach(inner => { - inner.style.transform = `translate(${state.panX}px, ${state.panY}px) scale(${state.scale})`; - }); -} - -function resetZoom(groupId) { - zoomState[groupId] = { scale: 1, panX: 0, panY: 0 }; - applyZoom(groupId); -} - -// === Pixel Inspector === -const piZoomSize = 7; // 7x7 pixel grid -const piScale = 12; // each pixel drawn at 12x12 - -document.addEventListener('mousemove', (e) => { - const img = e.target.closest('img.thumb, canvas.thumb, .image-box img, .image-box canvas'); - if (!img) { document.getElementById('pixelInspector').style.display = 'none'; return; } - - const rect = img.getBoundingClientRect(); - const scaleX = (img.naturalWidth || img.width) / rect.width; - const scaleY = (img.naturalHeight || img.height) / rect.height; - const px = Math.floor((e.clientX - rect.left) * scaleX); - const py = Math.floor((e.clientY - rect.top) * scaleY); - - if (px < 0 || py < 0 || px >= (img.naturalWidth || img.width) || py >= (img.naturalHeight || img.height)) { - document.getElementById('pixelInspector').style.display = 'none'; - return; - } - - // Find which detail panel this image belongs to - const detail = img.closest('.detail') || img.closest('td'); - if (!detail) return; - - // Collect all images in the same row/detail (gold + sources) - const allImages = detail.closest('tr')?.parentElement?.querySelectorAll('img, canvas') || []; - // Filter to full-size images in the same detail panel, or thumbnails in the same row - const relatedImages = []; - const detailEl = img.closest('.detail'); - if (detailEl) { - detailEl.querySelectorAll('img, canvas').forEach(el => relatedImages.push(el)); - } else { - // Thumbnail mode — find images in sibling tds - const row = img.closest('tr'); - if (row) row.querySelectorAll('img.thumb, canvas.thumb').forEach(el => relatedImages.push(el)); - } - - // Build inspector content - let html = ''; - for (const ri of relatedImages) { - const lblEl = ri.closest('.image-box')?.querySelector('.lbl'); - const sel = lblEl?.querySelector('select'); - const label = sel ? sel.options[sel.selectedIndex]?.text?.split(' (')[0] || '' : lblEl?.textContent || ''; - const w = ri.naturalWidth || ri.width; - const h = ri.naturalHeight || ri.height; - if (w === 0 || h === 0) continue; - - // Draw zoomed region - const canvas = document.createElement('canvas'); - canvas.width = piZoomSize * piScale; - canvas.height = piZoomSize * piScale; - const ctx = canvas.getContext('2d'); - - // Get pixel data from the image - const tmpCanvas = new OffscreenCanvas(w, h); - const tmpCtx = tmpCanvas.getContext('2d'); - tmpCtx.drawImage(ri, 0, 0); - - const half = Math.floor(piZoomSize / 2); - - // Draw zoomed pixels, clamping to image bounds - ctx.fillStyle = '#111'; - ctx.fillRect(0, 0, canvas.width, canvas.height); - let cr = 0, cg = 0, cb = 0, ca = 0; - for (let dy = -half; dy <= half; dy++) { - for (let dx = -half; dx <= half; dx++) { - const sx = px + dx, sy = py + dy; - if (sx >= 0 && sx < w && sy >= 0 && sy < h) { - const pxData = tmpCtx.getImageData(sx, sy, 1, 1).data; - ctx.fillStyle = `rgb(${pxData[0]},${pxData[1]},${pxData[2]})`; - ctx.fillRect((dx + half) * piScale, (dy + half) * piScale, piScale, piScale); - if (dx === 0 && dy === 0) { cr = pxData[0]; cg = pxData[1]; cb = pxData[2]; ca = pxData[3]; } - } - } - } - // Draw center crosshair - ctx.strokeStyle = '#fff'; - ctx.lineWidth = 1; - ctx.strokeRect(half * piScale, half * piScale, piScale, piScale); - - const r = cr, g = cg, b = cb, a = ca; - - html += `
-
${esc(label)}
- -
X:${px} Y:${py}
-
#${r.toString(16).padStart(2,'0')}${g.toString(16).padStart(2,'0')}${b.toString(16).padStart(2,'0')}${a<255?a.toString(16).padStart(2,'0'):''}
-
R: ${String(r).padStart(3)} (${(r/255).toFixed(3)})
-
G: ${String(g).padStart(3)} (${(g/255).toFixed(3)})
-
B: ${String(b).padStart(3)} (${(b/255).toFixed(3)})
-
A: ${String(a).padStart(3)} (${(a/255).toFixed(3)})
-
`; - } - - const inspector = document.getElementById('pixelInspector'); - document.getElementById('piContent').innerHTML = html; - inspector.style.display = 'block'; -}); - -document.addEventListener('mouseleave', () => { - document.getElementById('pixelInspector').style.display = 'none'; -}); - -// === Keyboard Navigation === -let kbFocusKey = null; // persists across re-renders - -document.addEventListener('keydown', (e) => { - if (e.target.tagName === 'INPUT' || e.target.tagName === 'SELECT' || e.target.tagName === 'TEXTAREA') return; - const rows = [...document.querySelectorAll('tr.suite-row, tr.row')]; - if (rows.length === 0) return; - - const focusedIdx = kbFocusKey != null ? rows.findIndex(r => r.dataset.kbKey === kbFocusKey) : -1; - - if (e.key === 'ArrowDown' || e.key === 'j') { - e.preventDefault(); - const next = Math.min(focusedIdx + 1, rows.length - 1); - kbSetFocus(rows, next); - } else if (e.key === 'ArrowUp' || e.key === 'k') { - e.preventDefault(); - const prev = focusedIdx <= 0 ? 0 : focusedIdx - 1; - kbSetFocus(rows, prev); - } else if (e.key === 'ArrowRight' || e.key === 'l') { - e.preventDefault(); - if (focusedIdx >= 0) kbExpand(rows[focusedIdx]); - } else if (e.key === 'ArrowLeft' || e.key === 'h') { - e.preventDefault(); - if (focusedIdx >= 0) kbCollapse(rows[focusedIdx]); - } else if (e.key === ' ') { - e.preventDefault(); - if (focusedIdx >= 0) { - const cb = rows[focusedIdx].querySelector('input[type=checkbox]'); - if (cb) cb.click(); - } - } -}); - -function kbSetFocus(rows, idx) { - rows.forEach(r => r.classList.remove('kb-focus')); - if (idx >= 0 && idx < rows.length) { - rows[idx].classList.add('kb-focus'); - rows[idx].scrollIntoView({ block: 'nearest' }); - kbFocusKey = rows[idx].dataset.kbKey || null; - } -} - -function kbExpand(row) { - const key = row.dataset.kbKey; - if (!key) return; - if (row.classList.contains('suite-row')) { - if (!collapsedSuites.has(key)) { - // Already expanded — move to first child - const rows = [...document.querySelectorAll('tr.suite-row, tr.row')]; - const idx = rows.indexOf(row); - if (idx >= 0 && idx + 1 < rows.length && rows[idx + 1].classList.contains('row')) { - kbSetFocus(rows, idx + 1); - return; - } - } - collapsedSuites.delete(key); - render(); - } else if (!expanded.has(key) && !loading.has(key)) { - startExpand(key); - } -} - -function kbCollapse(row) { - const key = row.dataset.kbKey; - if (!key) return; - if (row.classList.contains('suite-row')) { - if (collapsedSuites.has(key)) return; // already collapsed, nowhere to go - collapsedSuites.add(key); - } else if (expanded.has(key) || loading.has(key)) { - expanded.delete(key); - loading.delete(key); - } else { - // Already collapsed test row — move to parent suite - const rows = [...document.querySelectorAll('tr.suite-row, tr.row')]; - const idx = rows.indexOf(row); - for (let i = idx - 1; i >= 0; i--) { - if (rows[i].classList.contains('suite-row')) { - kbSetFocus(rows, i); - return; - } - } - return; - } - render(); -} - -// Restore kb focus after render -const _origRender2 = render; -render = function() { - _origRender2(); - if (kbFocusKey) { - const row = document.querySelector(`[data-kb-key="${CSS.escape(kbFocusKey)}"]`); - if (row) row.classList.add('kb-focus'); - } -}; - -// === Persistence === -function saveState() { - try { - localStorage.setItem('compareGold', JSON.stringify({ - platform: currentPlatform, - expanded: [...expanded], - collapsedSuites: [...collapsedSuites], - statusFilter: document.getElementById('statusFilter')?.value || '', - sort: document.getElementById('sortSelect')?.value || 'name', - search: document.getElementById('searchFilter')?.value || '', - savedSources: sourceDefs, - })); - } catch {} -} - -function restoreState() { - try { - const data = JSON.parse(localStorage.getItem('compareGold') || '{}'); - if (data.platform) currentPlatform = data.platform; - if (data.expanded) data.expanded.forEach(k => expanded.add(k)); - if (data.collapsedSuites) data.collapsedSuites.forEach(k => collapsedSuites.add(k)); - if (data.statusFilter !== undefined) { - const sel = document.getElementById('statusFilter'); - if (sel) sel.value = data.statusFilter; - } - if (data.sort) { - const sel = document.getElementById('sortSelect'); - if (sel) sel.value = data.sort; - } - if (data.search) { - const el = document.getElementById('searchFilter'); - if (el) el.value = data.search; - } - if (data.savedSources) savedSourceDefs = data.savedSources; - } catch {} -} - -let savedSourceDefs = null; -async function restoreSources() { - if (!savedSourceDefs || savedSourceDefs.length === 0) return; - for (const def of savedSourceDefs) { - try { - let res, src; - if (def.type === 'local') { - res = await fetch('/api/sources/add-local', { method: 'POST' }); - } else if (def.type === 'ci') { - res = await fetch('/api/sources/add-ci', { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ runId: def.runId, artifactName: def.artifactName, label: def.label }) - }); - } - if (res && res.ok) { - src = await res.json(); - if (!sources.find(s => s.id === src.id)) { - sources.push(src); - sourceDefs.push(def); - } - } - } catch {} - } - savedSourceDefs = null; -} - -// Hook render to auto-save -const _origRender = render; -render = function() { _origRender(); saveState(); }; - -// === Start === -restoreState(); -init().then(async () => { - // Restore platform selection after init populates the dropdown - if (currentPlatform) { - const sel = document.getElementById('platformSelect'); - if (sel && [...sel.options].some(o => o.value === currentPlatform)) { - sel.value = currentPlatform; - } - } - // Restore saved sources, or auto-add local - if (savedSourceDefs && savedSourceDefs.length > 0) { - await restoreSources(); - await reload(); - } else { - await addLocalSource().catch(() => {}); - } -}); diff --git a/build/tools/CompareGold/wwwroot/style.css b/build/tools/CompareGold/wwwroot/style.css deleted file mode 100644 index cbf92f4224..0000000000 --- a/build/tools/CompareGold/wwwroot/style.css +++ /dev/null @@ -1,129 +0,0 @@ -* { box-sizing: border-box; margin: 0; padding: 0; } -body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', system-ui, sans-serif; background: #1a1a2e; color: #e0e0e0; font-size: 13px; } - -header { background: #16213e; padding: 12px 20px; display: flex; align-items: center; gap: 16px; border-bottom: 1px solid #333; } -header h1 { font-size: 18px; color: #4fc3f7; } -header .subtitle { color: #666; font-size: 12px; } - -.panel { padding: 12px 20px; background: #1a1a2e; border-bottom: 1px solid #333; } -.panel-row { display: flex; gap: 12px; align-items: center; flex-wrap: wrap; margin-bottom: 8px; } -.panel-row:last-child { margin-bottom: 0; } - -select, input[type=text], button { padding: 6px 10px; border-radius: 4px; border: 1px solid #444; background: #2a2a4a; color: #e0e0e0; font-size: 13px; } -button { cursor: pointer; background: #4fc3f7; color: #000; font-weight: 600; border: none; } -button:hover { background: #29b6f6; } -button.promote { background: #66bb6a; } -button.promote:hover { background: #43a047; } -button.danger { background: #ef5350; } -button.danger:hover { background: #c62828; } -button.secondary { background: #555; color: #ddd; } -button.secondary:hover { background: #666; } -label { color: #999; } - -/* Source tags */ -.source-tags { display: flex; gap: 8px; flex-wrap: wrap; } -.source-tag { display: inline-flex; align-items: center; gap: 6px; padding: 4px 10px; border-radius: 12px; background: #2a4a6a; font-size: 12px; } -.source-tag .remove { cursor: pointer; color: #888; font-size: 14px; } -.source-tag .remove:hover { color: #ef5350; } -.source-tag.local { background: #2a4a3a; } -.source-tag.ci { background: #4a2a5a; } - -/* Table */ -.table-wrap { padding: 0 20px 20px; overflow-x: auto; } -table { width: 100%; border-collapse: collapse; margin-top: 12px; } -th { text-align: left; padding: 8px 10px; border-bottom: 2px solid #333; font-size: 11px; color: #888; text-transform: uppercase; letter-spacing: 0.5px; cursor: pointer; white-space: nowrap; } -th:hover { color: #4fc3f7; } -th.right { text-align: right; } -td { padding: 6px 10px; border-bottom: 1px solid #222244; vertical-align: top; } -tr.row { cursor: pointer; } -tr.row .thumb, tr.row .cell, tr.row .cb { user-select: none; } -tr.row:hover { background: #222244; } -tr.row.expanded { background: #1a2a4a; } -tr.row.kb-focus, tr.suite-row.kb-focus { outline: 1px solid #4fc3f7; outline-offset: -1px; } -td.active-source { background: #1a3a5c; box-shadow: inset 0 0 0 1px #4fc3f7aa; } - -tr.suite-row { cursor: pointer; background: #12122a; } -tr.suite-row:hover { background: #1a1a3a; } -tr.suite-row td { padding: 10px 12px; border-bottom: 2px solid #333; } -.suite-toggle { margin-right: 6px; color: #888; } -.suite-badge { font-size: 11px; color: #888; background: #2a2a4a; padding: 2px 8px; border-radius: 8px; margin-left: 8px; } -.suite-badge.fail { color: #ef5350; background: #3a1a1a; } - -.cb { width: 24px; text-align: center; } -.cb input { cursor: pointer; } - -/* Cell status */ -.cell { text-align: center; font-size: 12px; font-weight: 600; white-space: nowrap; padding: 2px 6px; border-radius: 3px; } -.cell.pass { color: #66bb6a; } -.cell.fail { color: #ef5350; } -.cell.new { color: #ffa726; } -.cell.miss { color: #555; } -.cell.ref { color: #888; } - -/* Expand panel */ -.detail { background: #16213e; padding: 16px; border-bottom: 1px solid #333; } -.image-box .lbl select { font-size: 11px; padding: 2px 4px; max-width: 100%; } -.images-row { display: flex; gap: 12px; margin: 12px 0; align-items: flex-start; } -.image-box { flex: 1; text-align: center; min-width: 0; } -.image-box img, .image-box canvas { max-width: 100%; border: 1px solid #333; background: #000; image-rendering: pixelated; } - -.zoom-container { overflow: hidden; position: relative; cursor: grab; background: #0a0a1a; border: 1px solid #333; border-radius: 4px; image-rendering: pixelated; } -.zoom-container.dragging { cursor: grabbing; } -.zoom-inner { - transform-origin: 0 0; - will-change: transform; - image-rendering: pixelated; - image-rendering: -moz-crisp-edges; - image-rendering: crisp-edges; -} -.zoom-inner img, .zoom-inner canvas { - image-rendering: pixelated; - image-rendering: -moz-crisp-edges; - image-rendering: crisp-edges; - -ms-interpolation-mode: nearest-neighbor; -} -.zoom-controls { font-size: 11px; color: #666; margin-top: 4px; } -.zoom-controls button { font-size: 11px; padding: 2px 6px; margin-right: 4px; } - -.thumb-row { display: flex; gap: 4px; margin-top: 4px; } -.thumb { width: 80px; height: 48px; object-fit: contain; border: 1px solid #333; background: #000; image-rendering: pixelated; cursor: pointer; } -.thumb:hover { border-color: #4fc3f7; } -.image-box .lbl { font-size: 11px; color: #888; margin-bottom: 4px; min-height: 24px; display: flex; align-items: center; justify-content: center; } - -.stats { font-size: 12px; color: #aaa; margin-top: 8px; line-height: 1.8; } -.stats b { color: #e0e0e0; } -.stats .histogram { font-family: monospace; } - -.mode-sel { margin-bottom: 8px; } -.mode-sel label { margin-right: 12px; cursor: pointer; } -.mode-sel input { margin-right: 3px; } - -.empty-msg { text-align: center; padding: 60px; color: #555; } - -/* Pixel Inspector */ -.pixel-inspector { - position: fixed; top: 10px; right: 10px; z-index: 50; - background: #1a1a2eee; border: 1px solid #444; border-radius: 8px; - padding: 10px; min-width: 200px; pointer-events: none; - box-shadow: 0 4px 20px rgba(0,0,0,0.5); -} -.pi-title { font-size: 11px; color: #888; margin-bottom: 6px; text-transform: uppercase; letter-spacing: 0.5px; } -.pi-entry { display: inline-block; margin-right: 12px; margin-bottom: 8px; vertical-align: top; } -.pi-label { font-size: 10px; color: #666; margin-bottom: 2px; } -.pi-zoom { image-rendering: pixelated; border: 1px solid #333; display: block; } -.pi-coords { font-size: 11px; color: #aaa; font-family: monospace; margin-top: 4px; } -.pi-rgb { font-size: 11px; font-family: monospace; white-space: pre; } - -/* Modal */ -.modal-overlay { position: fixed; top: 0; left: 0; right: 0; bottom: 0; background: rgba(0,0,0,0.7); display: flex; align-items: center; justify-content: center; z-index: 100; } -.modal { background: #1a1a2e; border: 1px solid #444; border-radius: 8px; padding: 20px; min-width: 400px; max-width: 600px; } -.modal h3 { margin-bottom: 12px; color: #4fc3f7; } -.modal .actions { margin-top: 16px; display: flex; gap: 8px; justify-content: flex-end; } -.ci-runs-list { max-height: 300px; overflow-y: auto; margin: 8px 0; } -.ci-run { padding: 8px; border: 1px solid #333; border-radius: 4px; margin-bottom: 4px; cursor: pointer; display: flex; justify-content: space-between; } -.ci-run:hover { background: #222244; } -.ci-run.selected { background: #1a3a5c; border-color: #4fc3f7; } -.ci-run .meta { color: #888; font-size: 11px; } - -.spinner { display: inline-block; width: 16px; height: 16px; border: 2px solid #444; border-top-color: #4fc3f7; border-radius: 50%; animation: spin 0.8s linear infinite; vertical-align: middle; margin-right: 6px; } -@keyframes spin { to { transform: rotate(360deg); } } diff --git a/build/tools/Stride.CompareGold/Program.cs b/build/tools/Stride.CompareGold/Program.cs new file mode 100644 index 0000000000..d4ad14346d --- /dev/null +++ b/build/tools/Stride.CompareGold/Program.cs @@ -0,0 +1,589 @@ +using System.Collections.Concurrent; +using System.Diagnostics; +using System.Text.Json; + +var builder = WebApplication.CreateBuilder(args); +builder.WebHost.UseUrls("http://localhost:5555"); +builder.Services.AddSingleton(); +var app = builder.Build(); + +// Find Stride root +var strideRoot = FindStrideRoot(AppContext.BaseDirectory) + ?? FindStrideRoot(Directory.GetCurrentDirectory()) + ?? throw new InvalidOperationException("Could not find Stride root (looking for 'tests/' + 'sources/' directories)"); + +var testsDir = Path.Combine(strideRoot, "tests"); +var localDir = Path.Combine(testsDir, "local"); +var ciCacheDir = Path.Combine(Path.GetTempPath(), "stride-compare-gold"); + +var sourceManager = app.Services.GetRequiredService(); + +Console.WriteLine($"Stride root: {strideRoot}"); +Console.WriteLine($"Gold images: {testsDir}"); +Console.WriteLine($"Local output: {localDir}"); +Console.WriteLine($"CI cache: {ciCacheDir}"); +Console.WriteLine(); +Console.WriteLine("CompareGold running at http://localhost:5555"); +Console.WriteLine("Press Ctrl+C to stop."); + +try { Process.Start(new ProcessStartInfo("http://localhost:5555") { UseShellExecute = true }); } +catch { } + +app.UseDefaultFiles(); +app.UseStaticFiles(); + +// Disable caching for gold image responses (they change on promote) +app.Use(async (ctx, next) => +{ + if (ctx.Request.Path.StartsWithSegments("/api/gold") || ctx.Request.Path.StartsWithSegments("/api/thresholds")) + { + ctx.Response.OnStarting(() => + { + ctx.Response.Headers.CacheControl = "no-store"; + ctx.Response.Headers.Remove("ETag"); + ctx.Response.Headers.Remove("Last-Modified"); + return Task.CompletedTask; + }); + } + await next(); +}); + +// Check gh CLI availability once at startup +bool ghAvailable = false; +string ghError = "gh CLI not found. Install from https://cli.github.com/"; +try +{ + var ghCheck = Process.Start(new ProcessStartInfo { FileName = "gh", Arguments = "auth status", RedirectStandardError = true, RedirectStandardOutput = true, UseShellExecute = false }); + if (ghCheck != null) + { + ghCheck.WaitForExit(5000); + ghAvailable = ghCheck.ExitCode == 0; + if (!ghAvailable) + ghError = "gh CLI not authenticated. Run: gh auth login"; + } +} +catch { } +Console.WriteLine(ghAvailable ? "GitHub CLI: authenticated" : $"GitHub CLI: {ghError}"); + +// === Info API === + +app.MapGet("/api/info", () => new { StrideRoot = strideRoot }); + +// === Gold API === + +app.MapGet("/api/suites", () => +{ + var suites = new HashSet(); + if (Directory.Exists(testsDir)) + foreach (var dir in Directory.GetDirectories(testsDir)) + { + var name = Path.GetFileName(dir); + if (name != "local") suites.Add(name); + } + // Also from sources + foreach (var src in sourceManager.GetAll()) + if (Directory.Exists(src.Path)) + foreach (var dir in Directory.GetDirectories(src.Path)) + suites.Add(Path.GetFileName(dir)); + return suites.OrderBy(s => s); +}); + +app.MapGet("/api/platforms", (string suite) => +{ + var platforms = new HashSet(); + CollectPlatforms(Path.Combine(testsDir, suite), platforms); + foreach (var src in sourceManager.GetAll()) + CollectPlatforms(Path.Combine(src.Path, suite), platforms); + return platforms.OrderBy(p => p); +}); + +app.MapGet("/api/gold/images", (string suite, string platform) => +{ + var parts = platform.Split('/', 2); + if (parts.Length != 2) return Results.BadRequest("Platform format: Platform/Device"); + var dir = Path.Combine(testsDir, suite, parts[0], parts[1]); + var primary = ListPngNames(dir); + var primarySet = new HashSet(primary); + + // Pick the best fallback gold for display. We mirror Graphics.Regression's + // any-match semantics for pass/fail on the client — this score only decides + // which gold the UI shows by default. + var requestedApi = parts[0]; + var requestedDevice = parts[1]; + var requestedIsSw = IsSoftwareRenderer(requestedDevice); + var fallbackBest = new Dictionary(); + var suiteDir = Path.Combine(testsDir, suite); + if (Directory.Exists(suiteDir)) + { + foreach (var pDir in Directory.GetDirectories(suiteDir)) + { + var pName = Path.GetFileName(pDir); + if (pName == "local") continue; + foreach (var dDir in Directory.GetDirectories(pDir)) + { + var device = Path.GetFileName(dDir); + var fallbackPlatform = $"{pName}/{device}"; + if (fallbackPlatform == platform) continue; + var candidateIsSw = IsSoftwareRenderer(device); + var score = ScoreFallback(pName, device, candidateIsSw, requestedApi, requestedDevice, requestedIsSw); + foreach (var f in Directory.GetFiles(dDir, "*.png")) + { + var name = Path.GetFileName(f); + if (primarySet.Contains(name)) continue; + if (!fallbackBest.TryGetValue(name, out var existing) || score > existing.score) + fallbackBest[name] = (fallbackPlatform, score); + } + } + } + } + var fallbacks = fallbackBest.Select(kv => (object)new { Name = kv.Key, FallbackPlatform = kv.Value.platform }).ToList(); + + return Results.Ok(new + { + Images = primary.Select(n => new { Name = n, FallbackPlatform = (string?)null }), + Fallbacks = fallbacks + }); +}); + +app.MapGet("/api/gold/image", (string suite, string platform, string name) => +{ + // Try exact platform first, then fallback across all platforms + var parts = platform.Split('/', 2); + if (parts.Length != 2) return Results.BadRequest("Invalid platform"); + var filePath = Path.Combine(testsDir, suite, parts[0], parts[1], name); + if (File.Exists(filePath)) return Results.File(filePath, "image/png"); + + // Fallback: search all platforms in this suite, preferring closest match + var suiteDir = Path.Combine(testsDir, suite); + if (Directory.Exists(suiteDir)) + { + var requestedApi = parts[0]; + var requestedDevice = parts[1]; + var requestedIsSw = IsSoftwareRenderer(requestedDevice); + string? bestPath = null; + int bestScore = -1; + foreach (var pDir in Directory.GetDirectories(suiteDir)) + { + var pName = Path.GetFileName(pDir); + if (pName == "local") continue; + foreach (var dDir in Directory.GetDirectories(pDir)) + { + var candidate = Path.Combine(dDir, name); + if (!File.Exists(candidate)) continue; + var device = Path.GetFileName(dDir); + var candidateIsSw = IsSoftwareRenderer(device); + var score = ScoreFallback(pName, device, candidateIsSw, requestedApi, requestedDevice, requestedIsSw); + if (score > bestScore) { bestScore = score; bestPath = candidate; } + } + } + if (bestPath != null) return Results.File(bestPath, "image/png"); + } + return Results.NotFound(); +}); + +// Thresholds +app.MapGet("/api/thresholds", (string suite) => +{ + var path = Path.Combine(testsDir, suite, "thresholds.jsonc"); + if (!File.Exists(path)) return Results.Ok(Array.Empty()); + var jsonc = File.ReadAllText(path); + // Strip // comments + var json = System.Text.RegularExpressions.Regex.Replace(jsonc, @"//.*?$", "", System.Text.RegularExpressions.RegexOptions.Multiline); + var rules = JsonSerializer.Deserialize(json); + return Results.Ok(rules); +}); + +// Also list all gold platforms that have a given image (for fallback info) +app.MapGet("/api/gold/all", (string suite, string name) => +{ + var results = new List(); + var suiteDir = Path.Combine(testsDir, suite); + if (!Directory.Exists(suiteDir)) return Results.Ok(results); + foreach (var pDir in Directory.GetDirectories(suiteDir)) + { + if (Path.GetFileName(pDir) == "local") continue; + foreach (var dDir in Directory.GetDirectories(pDir)) + if (File.Exists(Path.Combine(dDir, name))) + results.Add(new { Platform = $"{Path.GetFileName(pDir)}/{Path.GetFileName(dDir)}" }); + } + return Results.Ok(results); +}); + +// === Source API === + +app.MapGet("/api/sources", () => sourceManager.GetAll().Select(s => new { s.Id, s.Type, s.Label })); + +app.MapPost("/api/sources/add-local", () => +{ + if (!Directory.Exists(localDir)) + return Results.BadRequest("tests/local/ does not exist"); + var src = sourceManager.Add("local", "Local", localDir); + return Results.Ok(new { src.Id, src.Label }); +}); + +app.MapPost("/api/sources/add-ci", async (HttpRequest request) => +{ + if (!ghAvailable) return Results.BadRequest(ghError); + var body = await JsonSerializer.DeserializeAsync(request.Body); + if (body == null || string.IsNullOrEmpty(body.RunId)) return Results.BadRequest("runId required"); + + var artifactName = body.ArtifactName ?? "test-artifacts-linux-vulkan"; + var destDir = Path.Combine(ciCacheDir, body.RunId); + var markerFile = Path.Combine(destDir, $".downloaded_{artifactName}"); + + // Skip if already downloaded + if (File.Exists(markerFile)) + { + var cachedSrc = sourceManager.GetAll().FirstOrDefault(s => s.Path == destDir); + var cachedLabel = !string.IsNullOrEmpty(body.Label) ? body.Label : $"CI #{body.RunId[..Math.Min(5, body.RunId.Length)]}"; + cachedSrc ??= sourceManager.Add("ci", cachedLabel, destDir); + return Results.Ok(new { cachedSrc.Id, Label = cachedSrc.Label }); + } + + // Download to a temp dir first, then merge into the cache dir + var tmpDir = destDir + $"_tmp_{artifactName.GetHashCode():x}"; + if (Directory.Exists(tmpDir)) + Directory.Delete(tmpDir, true); + Directory.CreateDirectory(tmpDir); + + // Download this artifact + var proc = Process.Start(new ProcessStartInfo + { + FileName = "gh", + Arguments = $"run download {body.RunId} --repo stride3d/stride --name {artifactName} --dir \"{tmpDir}\"", + RedirectStandardOutput = true, + RedirectStandardError = true, + UseShellExecute = false + }); + if (proc != null) + { + await proc.WaitForExitAsync(); + if (proc.ExitCode != 0) + { + var err = await proc.StandardError.ReadToEndAsync(); + if (Directory.Exists(tmpDir)) Directory.Delete(tmpDir, true); + return Results.Problem($"gh failed for {artifactName}: {err}"); + } + } + + // Merge temp into cache dir (overwrite existing files) + Directory.CreateDirectory(destDir); + foreach (var file in Directory.GetFiles(tmpDir, "*", SearchOption.AllDirectories)) + { + var relativePath = Path.GetRelativePath(tmpDir, file); + var destFile = Path.Combine(destDir, relativePath); + Directory.CreateDirectory(Path.GetDirectoryName(destFile)!); + File.Copy(file, destFile, overwrite: true); + } + if (Directory.Exists(tmpDir)) Directory.Delete(tmpDir, true); + + // Mark as downloaded so we skip next time + File.WriteAllText(markerFile, DateTime.UtcNow.ToString("o")); + + // Reuse existing source for same run, or create new + var label = $"CI #{body.RunId[..Math.Min(5, body.RunId.Length)]}"; + if (!string.IsNullOrEmpty(body.Label)) label = body.Label; + var existing = sourceManager.GetAll().FirstOrDefault(s => s.Path == destDir); + var src = existing ?? sourceManager.Add("ci", label, destDir); + return Results.Ok(new { src.Id, src.Label }); +}); + +app.MapPost("/api/sources/add-folder", async (HttpRequest request) => +{ + var body = await JsonSerializer.DeserializeAsync(request.Body); + if (body == null || string.IsNullOrEmpty(body.Path)) return Results.BadRequest("path required"); + if (!Directory.Exists(body.Path)) return Results.BadRequest("Directory does not exist"); + var label = body.Label ?? Path.GetFileName(body.Path); + var src = sourceManager.Add("folder", label, body.Path); + return Results.Ok(new { src.Id, src.Label }); +}); + +app.MapDelete("/api/sources/{id}", (string id) => +{ + sourceManager.Remove(id); + return Results.Ok(); +}); + +app.MapGet("/api/source/{id}/images", (string id, string suite, string platform) => +{ + var src = sourceManager.Get(id); + if (src == null) return Results.NotFound(); + var parts = platform.Split('/', 2); + if (parts.Length != 2) return Results.BadRequest("Invalid platform"); + var dir = Path.Combine(src.Path, suite, parts[0], parts[1]); + return Results.Ok(ListPngs(dir)); +}); + +app.MapGet("/api/source/{id}/image", (string id, string suite, string platform, string name) => +{ + var src = sourceManager.Get(id); + if (src == null) return Results.NotFound(); + return ServeImage(src.Path, suite, platform, name); +}); + +// === CI Runs API === + +app.MapGet("/api/ci/status", () => Results.Ok(new { Available = ghAvailable, Error = ghAvailable ? null : ghError })); + +app.MapGet("/api/ci/artifacts", async (string runId) => +{ + if (!ghAvailable) return Results.BadRequest(ghError); + var proc = Process.Start(new ProcessStartInfo + { + FileName = "gh", + Arguments = $"api repos/stride3d/stride/actions/runs/{runId}/artifacts --jq \".artifacts[] | {{name: .name, size_in_bytes: .size_in_bytes, expired: .expired}}\"", + RedirectStandardOutput = true, + RedirectStandardError = true, + UseShellExecute = false + }); + if (proc == null) return Results.Problem("Could not start gh"); + var output = await proc.StandardOutput.ReadToEndAsync(); + await proc.WaitForExitAsync(); + var artifacts = output.Split('\n', StringSplitOptions.RemoveEmptyEntries) + .Select(line => { try { return JsonSerializer.Deserialize(line); } catch { return default; } }) + .Where(j => j.ValueKind != JsonValueKind.Undefined) + .ToList(); + return Results.Ok(artifacts); +}); + +app.MapGet("/api/ci/runs", async (string? branch, int? limit) => +{ + if (!ghAvailable) return Results.BadRequest(ghError); + var lim = limit ?? 10; + var branchArg = !string.IsNullOrEmpty(branch) ? $"--branch {branch}" : ""; + var proc = Process.Start(new ProcessStartInfo + { + FileName = "gh", + Arguments = $"api repos/stride3d/stride/actions/runs --jq \".workflow_runs[:{ lim}] | .[] | {{id: .id, head_sha: .head_sha, head_branch: .head_branch, created_at: .created_at, conclusion: .conclusion, name: .name}}\" {branchArg}", + RedirectStandardOutput = true, + RedirectStandardError = true, + UseShellExecute = false + }); + if (proc == null) return Results.Problem("Could not start gh"); + var output = await proc.StandardOutput.ReadToEndAsync(); + await proc.WaitForExitAsync(); + // Parse JSONL output + var runs = output.Split('\n', StringSplitOptions.RemoveEmptyEntries) + .Select(line => { try { return JsonSerializer.Deserialize(line); } catch { return default; } }) + .Where(j => j.ValueKind != JsonValueKind.Undefined) + .ToList(); + return Results.Ok(runs); +}); + +// === Promote API === + +app.MapPost("/api/promote", async (HttpRequest request) => +{ + var body = await JsonSerializer.DeserializeAsync(request.Body); + if (body == null) return Results.BadRequest("Invalid body"); + + var src = sourceManager.Get(body.SourceId); + if (src == null) return Results.BadRequest("Source not found"); + + var parts = body.Platform.Split('/', 2); + if (parts.Length != 2) return Results.BadRequest("Invalid platform"); + + var srcDir = Path.Combine(src.Path, body.Suite, parts[0], parts[1]); + var goldDir = Path.Combine(testsDir, body.Suite, parts[0], parts[1]); + Directory.CreateDirectory(goldDir); + + int promoted = 0; + var details = new List(); + foreach (var name in body.Names) + { + var srcFile = Path.Combine(srcDir, name); + var dstFile = Path.Combine(goldDir, name); + if (File.Exists(srcFile)) + { + File.Copy(srcFile, dstFile, overwrite: true); + promoted++; + details.Add(new { Name = name, Src = srcFile, Dst = dstFile, SrcSize = new FileInfo(srcFile).Length, DstSize = new FileInfo(dstFile).Length }); + } + else + { + details.Add(new { Name = name, Src = srcFile, Dst = dstFile, Error = "Source file not found" }); + } + } + Console.WriteLine($"Promote: {promoted}/{body.Names.Length} from {srcDir} -> {goldDir}"); + foreach (var d in details) Console.WriteLine($" {d}"); + return Results.Ok(new { Promoted = promoted, Details = details }); +}); + +app.MapPost("/api/gold/delete", async (HttpRequest request) => +{ + var body = await JsonSerializer.DeserializeAsync(request.Body); + if (body == null) return Results.BadRequest("Invalid body"); + + var parts = body.Platform.Split('/', 2); + if (parts.Length != 2) return Results.BadRequest("Invalid platform"); + + var goldDir = Path.Combine(testsDir, body.Suite, parts[0], parts[1]); + int deleted = 0; + foreach (var name in body.Names) + { + var file = Path.Combine(goldDir, name); + if (File.Exists(file)) + { + File.Delete(file); + deleted++; + Console.WriteLine($" Deleted: {file}"); + } + } + Console.WriteLine($"Delete gold: {deleted}/{body.Names.Length} from {goldDir}"); + return Results.Ok(new { Deleted = deleted }); +}); + +app.Run(); + +// === Helpers === + +static string? FindStrideRoot(string startDir) +{ + var dir = startDir; + while (dir != null) + { + if (Directory.Exists(Path.Combine(dir, "tests")) && Directory.Exists(Path.Combine(dir, "sources"))) + return dir; + dir = Path.GetDirectoryName(dir); + } + return null; +} + +static void CollectPlatforms(string suiteDir, HashSet platforms) +{ + if (!Directory.Exists(suiteDir)) return; + foreach (var pDir in Directory.GetDirectories(suiteDir)) + foreach (var dDir in Directory.GetDirectories(pDir)) + platforms.Add($"{Path.GetFileName(pDir)}/{Path.GetFileName(dDir)}"); +} + +static List ListPngNames(string dir) +{ + if (!Directory.Exists(dir)) return []; + return Directory.GetFiles(dir, "*.png") + .Select(Path.GetFileName) + .ToList()!; +} + +static List ListPngs(string dir) +{ + if (!Directory.Exists(dir)) return []; + return Directory.GetFiles(dir, "*.png") + .Select(f => (object)new { Name = Path.GetFileName(f) }) + .ToList(); +} + + +static string GetGfxApi(string platformApi) +{ + // "Windows.Direct3D11" → "Direct3D11", "Linux.Vulkan" → "Vulkan" + var dot = platformApi.IndexOf('.'); + return dot >= 0 ? platformApi[(dot + 1)..] : platformApi; +} + +static string GetOS(string platformApi) +{ + // "Windows.Direct3D11" → "Windows", "Linux.Vulkan" → "Linux" + var dot = platformApi.IndexOf('.'); + return dot >= 0 ? platformApi[..dot] : platformApi; +} + +static int ScoreFallback(string candidatePlatApi, string candidateDevice, bool candidateIsSw, + string requestedPlatApi, string requestedDevice, bool requestedIsSw) +{ + // Higher = closer. Tiers, roughly in order of importance: + // exact OS+API > same OS > same gfx API across OS > same device/renderer > + // same renderer class (SW/HW). + // Same-device (e.g. both WARP, both Lavapipe) matters so D3D12/WARP picks + // D3D11/WARP over Windows.Vulkan/Lavapipe when they'd otherwise tie on OS. + int score = 0; + if (candidatePlatApi == requestedPlatApi) score += 16; + if (GetOS(candidatePlatApi) == GetOS(requestedPlatApi)) score += 8; + if (GetGfxApi(candidatePlatApi) == GetGfxApi(requestedPlatApi)) score += 4; + if (string.Equals(candidateDevice, requestedDevice, StringComparison.OrdinalIgnoreCase)) score += 2; + if (candidateIsSw == requestedIsSw) score += 1; + return score; +} + +static bool IsSoftwareRenderer(string device) +{ + var d = device.ToLowerInvariant(); + return d.Contains("warp") || d.Contains("swiftshader") || d.Contains("lavapipe") || d.Contains("llvmpipe"); +} + +static IResult ServeImage(string baseDir, string suite, string platform, string name) +{ + var parts = platform.Split('/', 2); + if (parts.Length != 2) return Results.BadRequest("Invalid platform"); + var filePath = Path.Combine(baseDir, suite, parts[0], parts[1], name); + if (!File.Exists(filePath)) return Results.NotFound(); + return Results.File(filePath, "image/png"); +} + +// === Models === + +record CiDownloadRequest +{ + [System.Text.Json.Serialization.JsonPropertyName("runId")] + public string RunId { get; set; } = ""; + [System.Text.Json.Serialization.JsonPropertyName("artifactName")] + public string? ArtifactName { get; set; } + [System.Text.Json.Serialization.JsonPropertyName("label")] + public string? Label { get; set; } +} +record FolderRequest +{ + [System.Text.Json.Serialization.JsonPropertyName("path")] + public string Path { get; set; } = ""; + [System.Text.Json.Serialization.JsonPropertyName("label")] + public string? Label { get; set; } +} +record PromoteRequest +{ + [System.Text.Json.Serialization.JsonPropertyName("sourceId")] + public string SourceId { get; set; } = ""; + [System.Text.Json.Serialization.JsonPropertyName("suite")] + public string Suite { get; set; } = ""; + [System.Text.Json.Serialization.JsonPropertyName("platform")] + public string Platform { get; set; } = ""; + [System.Text.Json.Serialization.JsonPropertyName("names")] + public string[] Names { get; set; } = []; +} + +record DeleteGoldRequest +{ + [System.Text.Json.Serialization.JsonPropertyName("suite")] + public string Suite { get; set; } = ""; + [System.Text.Json.Serialization.JsonPropertyName("platform")] + public string Platform { get; set; } = ""; + [System.Text.Json.Serialization.JsonPropertyName("names")] + public string[] Names { get; set; } = []; +} + +// === Source Manager === + +class Source +{ + public string Id { get; set; } = ""; + public string Type { get; set; } = ""; // "local", "ci", "folder" + public string Label { get; set; } = ""; + public string Path { get; set; } = ""; +} + +class SourceManager +{ + private readonly ConcurrentDictionary _sources = new(); + private int _nextId = 1; + + public Source Add(string type, string label, string path) + { + var id = $"src-{Interlocked.Increment(ref _nextId)}"; + var src = new Source { Id = id, Type = type, Label = label, Path = path }; + _sources[id] = src; + return src; + } + + public Source? Get(string id) => _sources.GetValueOrDefault(id); + public void Remove(string id) => _sources.TryRemove(id, out _); + public IEnumerable GetAll() => _sources.Values; +} diff --git a/build/tools/Stride.CompareGold/Stride.CompareGold.csproj b/build/tools/Stride.CompareGold/Stride.CompareGold.csproj new file mode 100644 index 0000000000..97f4313a40 --- /dev/null +++ b/build/tools/Stride.CompareGold/Stride.CompareGold.csproj @@ -0,0 +1,13 @@ + + + Exe + net10.0 + enable + enable + + false + false + false + wwwroot\favicon.ico + + diff --git a/build/tools/Stride.CompareGold/wwwroot/app.js b/build/tools/Stride.CompareGold/wwwroot/app.js new file mode 100644 index 0000000000..e7d9ab1435 --- /dev/null +++ b/build/tools/Stride.CompareGold/wwwroot/app.js @@ -0,0 +1,1699 @@ +// === State === +let allSuites = []; +let currentPlatform = ''; +let sources = []; // [{id, type, label}] +let sourceDefs = []; // tracks how sources were added for persistence +let suiteData = {}; // {suite: {gold: [{name}], sourceImages: {srcId: [{name}]}}} +let focusedKey = null; // "suite:name" — currently selected row shown in bottom detail pane +let loading = new Set(); // "suite:name" — loading in progress +let selected = new Set(); // "suite:name" +let collapsedSuites = new Set(); +let compareLeft = {}; // {"suite:name": "gold:" or "src:"} +let compareRight = {}; // {"suite:name": "gold:" or "src:"} +let cellStats = {}; // {`${sourceId}:${suite}:${name}`: stats} + +// === Init === +async function init() { + // Show Stride root path + try { + const infoRes = await fetch('/api/info'); + const info = await infoRes.json(); + document.getElementById('strideRoot').textContent = info.strideRoot; + } catch {} + + const res = await fetch('/api/suites'); + allSuites = await res.json(); + + // Collect all platforms across all suites + const allPlatforms = new Set(); + for (const suite of allSuites) { + const pRes = await fetch(`/api/platforms?suite=${enc(suite)}`); + (await pRes.json()).forEach(p => allPlatforms.add(p)); + } + const platforms = [...allPlatforms].sort(); + const sel = document.getElementById('platformSelect'); + sel.innerHTML = platforms.map(p => ``).join(''); + // Use platform from restoreState() if valid, otherwise default to first + if (!currentPlatform || !platforms.includes(currentPlatform)) + currentPlatform = platforms[0] || ''; + sel.value = currentPlatform; + sel.onchange = onPlatformChange; + + document.getElementById('statusFilter').onchange = () => render(); + await reload(); +} + +async function onPlatformChange() { + currentPlatform = document.getElementById('platformSelect').value; + await reload(); +} + +async function reload() { + if (!currentPlatform) return; + suiteData = {}; + for (const suite of allSuites) { + const gRes = await fetch(`/api/gold/images?suite=${enc(suite)}&platform=${enc(currentPlatform)}`); + const gData = await gRes.json(); + // Merge primary + fallback gold, tagging fallbacks + const gold = [ + ...(gData.images || []).map(g => ({ name: g.name, fallback: null })), + ...(gData.fallbacks || []).map(g => ({ name: g.name, fallback: g.fallbackPlatform })) + ]; + const srcImgs = {}; + for (const src of sources) { + const sRes = await fetch(`/api/source/${src.id}/images?suite=${enc(suite)}&platform=${enc(currentPlatform)}`); + srcImgs[src.id] = await sRes.json(); + } + // Load thresholds for this suite + const tRes = await fetch(`/api/thresholds?suite=${enc(suite)}`); + const thresholdRules = tRes.ok ? await tRes.json() : []; + // Only include suite if it has any images + if (gold.length > 0 || Object.values(srcImgs).some(imgs => imgs.length > 0)) + suiteData[suite] = { gold, sourceImages: srcImgs, thresholdRules }; + } + cellStats = {}; + resetAltGoldState(); + render(); +} + +// === Sources === +async function addLocalSource() { + const res = await fetch('/api/sources/add-local', { method: 'POST' }); + if (!res.ok) { alert(await res.text()); return; } + const src = await res.json(); + sources.push(src); + sourceDefs.push({ type: 'local' }); + await reload(); +} + +function showCiModal() { + document.getElementById('ciModal').style.display = 'flex'; + selectedCiRun = null; + document.getElementById('ciRunId').value = ''; + document.getElementById('ciDownloadBtn').disabled = true; + loadCiRuns(); +} +function closeCiModal() { document.getElementById('ciModal').style.display = 'none'; } + +async function downloadCiRun() { + const runId = String(document.getElementById('ciRunId').value).trim(); + if (!runId) { alert('Enter or select a run ID'); return; } + + const artifacts = selectedCiArtifacts.size > 0 ? [...selectedCiArtifacts] : ['test-artifacts-linux-vulkan']; + + const btn = document.getElementById('ciDownloadBtn'); + btn.disabled = true; + + try { + for (let i = 0; i < artifacts.length; i++) { + btn.textContent = `Downloading ${i + 1}/${artifacts.length}...`; + const res = await fetch('/api/sources/add-ci', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ runId: String(runId), artifactName: artifacts[i], label: `CI #${String(runId).substring(0, 5)}` }) + }); + if (!res.ok) { alert(`Failed to download ${artifacts[i]}: ${await res.text()}`); continue; } + const src = await res.json(); + // Only add if not already in sources list + if (!sources.find(s => s.id === src.id)) { + sources.push(src); + sourceDefs.push({ type: 'ci', runId: String(runId), artifactName: artifacts[i], label: src.label }); + } + } + closeCiModal(); + await reload(); + } finally { + btn.textContent = 'Download & Add'; + btn.disabled = false; + } +} + +async function removeSource(id) { + const idx = sources.findIndex(s => s.id === id); + await fetch(`/api/sources/${id}`, { method: 'DELETE' }); + sources = sources.filter(s => s.id !== id); + if (idx >= 0) sourceDefs.splice(idx, 1); + cellStats = {}; + resetAltGoldState(); + focusedKey = null; + selected.clear(); + compareLeft = {}; + compareRight = {}; + if (sources.length === 0) { + suiteData = {}; + render(); + } else { + await reload(); + } +} + +// === Render === +function render() { + renderSourceTags(); + renderPromoteSourceSelect(); + renderTable(); + updateActionCounts(); + // If the focused row is no longer visible (platform switch, filter, search, etc.), + // clear the detail pane so it doesn't keep showing a stale selection. + if (focusedKey && !document.querySelector(`tr.row[data-kb-key="${CSS.escape(focusedKey)}"]`)) { + focusedKey = null; + kbFocusKey = null; + renderDetailPane(null); + } +} + +function renderSourceTags() { + const el = document.getElementById('sourceTags'); + el.innerHTML = sources.map(s => + `
+ ${esc(s.label)} + × +
` + ).join(''); +} + +function renderPromoteSourceSelect() { + const sel = document.getElementById('promoteSource'); + sel.innerHTML = `` + + sources.map(s => ``).join(''); +} + +function buildSuiteImages(suite) { + const data = suiteData[suite]; + if (!data) return []; + const allNames = new Set(); + data.gold.forEach(g => allNames.add(g.name)); + for (const srcId in data.sourceImages) + data.sourceImages[srcId].forEach(s => allNames.add(s.name)); + + return [...allNames].sort(naturalSort).map(name => { + const goldEntry = data.gold.find(g => g.name === name); + const hasGold = !!goldEntry; + const goldFallback = goldEntry?.fallback || null; + const sourcesWithImage = {}; + for (const src of sources) + sourcesWithImage[src.id] = (data.sourceImages[src.id] || []).some(s => s.name === name); + let status = 'pass'; + if (!hasGold && Object.values(sourcesWithImage).some(v => v)) status = 'new'; + else if (hasGold && Object.values(sourcesWithImage).some(v => v)) { + // Per source: fail only when every gold in the suite fails — mirrors + // Graphics.Regression's any-match semantics. The alternate-gold check + // is lazy (kicked off when the preferred fails), so a cell whose + // preferred gold failed stays "pending" until that completes. + let anyFail = false; + let anyPending = false; + for (const src of sources) { + if (!sourcesWithImage[src.id]) continue; + const stats = cellStats[`${src.id}:${suite}:${name}`]; + if (!stats) { anyPending = true; computeCellStats(src.id, suite, name); continue; } + const r = isCellPassing(src.id, suite, name, stats); + if (r === null) anyPending = true; + else if (!r) anyFail = true; + } + status = anyFail ? 'fail' : anyPending ? 'pending' : 'pass'; + } + return { suite, name, hasGold, goldFallback, sourcesWithImage, status }; + }); +} + +function renderTable() { + const filter = document.getElementById('statusFilter').value; + + // Render header + const thead = document.getElementById('thead'); + thead.innerHTML = ` + + Test + Gold + ${sources.map(s => `${esc(s.label)}`).join('')} + `; + + const tbody = document.getElementById('tbody'); + tbody.innerHTML = ''; + let totalVisible = 0; + + const search = (document.getElementById('searchFilter')?.value || '').toLowerCase(); + + for (const suite of Object.keys(suiteData).sort()) { + let images = buildSuiteImages(suite); + if (filter) images = images.filter(i => i.status === filter || (filter === 'fail' && i.status === 'pending')); + if (search) images = images.filter(i => i.name.toLowerCase().includes(search)); + if (images.length === 0) continue; + + const sortMode = document.getElementById('sortSelect')?.value || 'name'; + if (sortMode === 'diff') { + images.sort((a, b) => { + const aMax = getMaxDiffForImage(a) ?? -1; + const bMax = getMaxDiffForImage(b) ?? -1; + return bMax - aMax; // worst first + }); + } + + const failCount = images.filter(i => i.status === 'fail' || i.status === 'new').length; + const pendingCount = images.filter(i => i.status === 'pending').length; + const isCollapsed = collapsedSuites.has(suite); + const shortSuite = suite.replace('Stride.', '').replace('.Tests', '').replace('.Regression', ''); + + // Suite header row + const suiteKeys = images.map(i => `${i.suite}:${i.name}`); + const allSelected = suiteKeys.every(k => selected.has(k)); + const someSelected = !allSelected && suiteKeys.some(k => selected.has(k)); + const suiteTr = document.createElement('tr'); + suiteTr.className = 'suite-row'; + suiteTr.dataset.kbKey = suite; + suiteTr.innerHTML = ` + + + ${isCollapsed ? '▶' : '▼'} + ${esc(shortSuite)} + ${images.length} tests + 0 ? '' : 'style="display:none"'}>${failCount} failing 0 ? '' : 'style="display:none"'}>${pendingCount} pending + `; + suiteTr.onclick = () => { toggleSuite(suite); }; + tbody.appendChild(suiteTr); + // Set indeterminate state (can't do via HTML attribute) + if (someSelected) suiteTr.querySelector('input[type=checkbox]').indeterminate = true; + + if (isCollapsed) continue; + + for (const img of images) { + totalVisible++; + const key = `${img.suite}:${img.name}`; + + const tr = document.createElement('tr'); + tr.className = `row${focusedKey === key ? ' kb-focus' : ''}`; + tr.dataset.kbKey = key; + tr.innerHTML = buildRowCells(img, key); + tr.onmousedown = (e) => { tr._clickX = e.clientX; tr._clickY = e.clientY; }; + tr.onclick = (e) => { + if (Math.abs(e.clientX - tr._clickX) > 3 || Math.abs(e.clientY - tr._clickY) > 3) return; + focusRow(key); + }; + tbody.appendChild(tr); + } + } + document.getElementById('emptyMsg').style.display = totalVisible === 0 ? 'block' : 'none'; + updateSelectedCount(); +} + +function buildRowCells(img, key) { + const isLoading = loading.has(key); + const isSel = selected.has(key); + + let goldThumb = ''; + if (img.hasGold && sources.length > 0) { + const thumbUrl = `/api/gold/image?suite=${enc(img.suite)}&platform=${enc(currentPlatform)}&name=${enc(img.name)}`; + goldThumb = `
`; + } + + let cells = ` + + ${esc(img.name)}${isLoading ? ' ' : ''}${img.status === 'fail' ? `${fixableVia[key] ? 'failing (fixable)' : 'failing'}` : img.status === 'new' ? 'new' : img.status === 'pending' ? '...' : ''} + ${img.hasGold ? (img.goldFallback ? 'fb' : 'ref') : '—'}${img.hasGold ? ` ${esc(img.goldFallback || currentPlatform)}` : ''}${goldThumb}`; + + const activeRef = compareRight[key] || `src:${getSourceForKey(key)}`; + for (const src of sources) { + const has = img.sourcesWithImage[src.id]; + const isActive = activeRef === `src:${src.id}`; + const statsKey = `${src.id}:${img.suite}:${img.name}`; + const stats = cellStats[statsKey]; + let cellHtml; + if (!has) { + cellHtml = ''; + } else if (!img.hasGold) { + cellHtml = '○ new'; + } else if (stats) { + const result = checkCellThreshold(img.suite, img.name, stats); + const cls = result.passed ? 'pass' : 'fail'; + const brief = formatThresholdBrief(result); + cellHtml = `${cls === 'pass' ? '✓' : '✗'} ${brief}`; + } else { + cellHtml = `...`; + computeCellStats(src.id, img.suite, img.name); + } + if (has) { + const thumbSrc = `/api/source/${src.id}/image?suite=${enc(img.suite)}&platform=${enc(currentPlatform)}&name=${enc(img.name)}`; + if (img.hasGold) { + const thumbId = `thumb-${css(src.id)}-${css(key)}`; + cellHtml += `
`; + requestAnimationFrame(() => computeThumbDiff(img.suite, img.name, src.id, thumbId)); + } else { + cellHtml += `
`; + } + } + cells += `${cellHtml}`; + } + return cells; +} + +function renderRow(key) { + const suite = key.substring(0, key.indexOf(':')); + const data = suiteData[suite]; + if (!data) return; + const images = buildSuiteImages(suite); + const img = images.find(i => `${i.suite}:${i.name}` === key); + if (!img) return; + + const existingTr = document.querySelector(`tr.row[data-kb-key="${CSS.escape(key)}"]`); + if (!existingTr) return; + + existingTr.className = `row${focusedKey === key ? ' kb-focus' : ''}`; + existingTr.innerHTML = buildRowCells(img, key); + existingTr.onmousedown = (e) => { existingTr._clickX = e.clientX; existingTr._clickY = e.clientY; }; + existingTr.onclick = (e) => { + if (Math.abs(e.clientX - existingTr._clickX) > 3 || Math.abs(e.clientY - existingTr._clickY) > 3) return; + focusRow(key); + }; + updateSelectedCount(); + saveState(); +} + +function toggleSuite(suite) { + if (collapsedSuites.has(suite)) collapsedSuites.delete(suite); + else collapsedSuites.add(suite); + render(); +} + +// === Detail === +const detailVersion = {}; // track version to discard stale loads + +function resolveImageRef(ref, suite, name) { + if (!ref) return null; + + if (ref.startsWith('gold:')) { + const plat = ref.slice(5); + return `/api/gold/image?suite=${enc(suite)}&platform=${enc(plat)}&name=${enc(name)}`; + } + if (ref.startsWith('src:')) { + const srcId = ref.slice(4); + return `/api/source/${srcId}/image?suite=${enc(suite)}&platform=${enc(currentPlatform)}&name=${enc(name)}`; + } + return null; +} + +function buildRefOptions(goldPlatforms, selectedRef) { + let html = ''; + if (goldPlatforms.length === 0) html += ''; + for (const p of goldPlatforms) + html += ``; + html += ''; + if (sources.length > 0) { + html += ''; + for (const s of sources) + html += ``; + html += ''; + } + return html; +} + +function pickDefaultLeft(goldPlatforms, img) { + // Match the gold the table row shows: primary if it exists for currentPlatform, + // otherwise the fallback the backend resolved. Only fall back to the scoring + // heuristic when the row has no gold at all. + if (img?.hasGold) { + const plat = img.goldFallback || currentPlatform; + if (goldPlatforms.some(p => p.platform === plat)) return `gold:${plat}`; + } + const best = pickBestGoldPlatform(goldPlatforms, currentPlatform); + return best ? `gold:${best}` : (sources[0] ? `src:${sources[0].id}` : ''); +} + +function pickDefaultRight(img) { + // Prefer first source that has this image + const src = sources.find(s => img ? img.sourcesWithImage?.[s.id] : false); + if (src) return `src:${src.id}`; + return sources[0] ? `src:${sources[0].id}` : ''; +} + +async function loadDetail(suite, name) { + const key = `${suite}:${name}`; + const id = css(key); + + const ver = (detailVersion[key] || 0) + 1; + detailVersion[key] = ver; + + const data = suiteData[suite]; + const img = data ? buildSuiteImages(suite).find(i => i.name === name) : null; + + // Fetch gold platforms + const goldPlatforms = await fetch(`/api/gold/all?suite=${enc(suite)}&name=${enc(name)}`).then(r => r.json()).catch(() => []); + if (detailVersion[key] !== ver) return; + + // Resolve left and right refs + const leftHadUserChoice = compareLeft[key] != null; + const rightHadUserChoice = compareRight[key] != null; + const leftRef = compareLeft[key] || pickDefaultLeft(goldPlatforms, img); + const rightRef = compareRight[key] || pickDefaultRight(img); + const leftUrl = resolveImageRef(leftRef, suite, name); + const rightUrl = resolveImageRef(rightRef, suite, name); + + // Load images in parallel + const [leftImg, rightImg] = await Promise.all([ + leftUrl ? loadImg(leftUrl).catch(() => null) : null, + rightUrl ? loadImg(rightUrl).catch(() => null) : null, + ]); + if (detailVersion[key] !== ver) return; + + fillDetail(id, key, suite, name, { ver, leftImg, rightImg, goldPlatforms, leftRef, rightRef, img, leftHadUserChoice, rightHadUserChoice }); +} + +async function fillDetail(id, key, suite, name, { ver, leftImg, rightImg, goldPlatforms, leftRef, rightRef, img, leftHadUserChoice, rightHadUserChoice }) { + detailVersion[key] = ver; + const container = document.getElementById(`images-${id}`); + if (!container) return; + + // Build dropdowns + const leftOpts = buildRefOptions(goldPlatforms, leftRef); + const rightOpts = buildRefOptions(goldPlatforms, rightRef); + const leftSelHtml = ``; + const rightSelHtml = ``; + + // Build DOM + let html = `
`; + html += `
${leftSelHtml}
`; + if (leftImg) html += `
`; + else html += `
No image
`; + html += `
`; + html += `
${rightSelHtml}
`; + if (rightImg) html += `
`; + else html += `
No image
`; + html += `
`; + html += `
Diff
`; + if (leftImg && rightImg) html += `
`; + else html += `
`; + html += `
`; + html += '
'; + html += ``; + container.innerHTML = html; + + if (leftImg) drawToCanvas(document.getElementById(`left-${id}`), leftImg); + if (rightImg) drawToCanvas(document.getElementById(`right-${id}`), rightImg); + if (leftImg && rightImg) { + const canvas = document.getElementById(`diff-${id}`); + const stats = computeImageDiff(leftImg, rightImg, canvas); + // Resolve threshold for this image + const data = suiteData[suite]; + const rules = data?.thresholdRules || []; + const [platApi, device] = currentPlatform.split('/'); + const dotIdx = platApi?.indexOf('.') ?? -1; + const plat = dotIdx >= 0 ? platApi.substring(0, dotIdx) : platApi; + const api = dotIdx >= 0 ? platApi.substring(dotIdx + 1) : null; + const allow = resolveThreshold(rules, name, plat, api, device); + const thresholdResult = stats.pixelDiffs ? checkThreshold(stats.pixelDiffs, allow) : null; + const statsEl = document.getElementById(`stats-${id}`); + if (statsEl) statsEl.innerHTML = formatStats(stats, thresholdResult); + } + initZoomGroup(id); + updatePaneBounds(leftImg || rightImg); + + // Compute compact stats for gold options vs the other side + const otherImg = rightImg || leftImg; + if (otherImg && goldPlatforms.length > 0) { + const leftSel = container.querySelector(`select`); + const rightSel = container.querySelectorAll(`select`)[1]; + for (const sel of [leftSel, rightSel]) { + if (!sel) continue; + const isLeft = sel === leftSel; + const hadUserChoice = isLeft ? leftHadUserChoice : rightHadUserChoice; + const otherRef = isLeft ? rightRef : leftRef; + const otherImgForStats = isLeft ? rightImg : leftImg; + if (!otherImgForStats) continue; + const goldOpts = [...sel.options].filter(o => o.value.startsWith('gold:')); + // Compute diffs for all gold options, then auto-select best passing one + const optResults = []; + await Promise.all(goldOpts.map(async (opt) => { + const plat = opt.value.slice(5); + try { + const gImg = await loadImg(`/api/gold/image?suite=${enc(suite)}&platform=${enc(plat)}&name=${enc(name)}`); + if (detailVersion[key] !== ver) return; + const tmpCanvas = new OffscreenCanvas(gImg.width, gImg.height); + const s = computeImageDiff(gImg, otherImgForStats, tmpCanvas); + const result = checkCellThreshold(suite, name, s); + const icon = result.passed ? '\u2713' : '\u2717'; + opt.textContent = `${icon} ${plat} (d=${s.maxDiff} px=${s.diffPixels})`; + opt.dataset.passed = result.passed ? '1' : '0'; + opt.style.color = result.passed ? '#4caf50' : '#f44336'; + optResults.push({ opt, result, diffPixels: s.diffPixels }); + } catch {} + })); + // If currently selected gold fails, auto-switch to best passing one — + // but only when the user hasn't explicitly picked this side, otherwise + // we'd override their choice every time loadDetail re-renders. + if (!hadUserChoice) { + const selOpt = sel.options[sel.selectedIndex]; + if (selOpt?.dataset.passed === '0') { + const passing = optResults.filter(r => r.result.passed).sort((a, b) => a.diffPixels - b.diffPixels); + if (passing.length > 0) { + passing[0].opt.selected = true; + sel.dispatchEvent(new Event('change')); + } + } + } + const updateSelColor = () => { + const so = sel.options[sel.selectedIndex]; + sel.style.color = so?.style.color || ''; + }; + updateSelColor(); + sel.addEventListener('change', updateSelColor); + } + } +} + +// === Background cell stats === +const statsQueue = new Set(); +let statsRunning = false; +// "srcId:suite:name" → { checked: bool, passingPlatform: string|null }. +// Populated once the preferred gold has been checked against alternates. +// Drives the framework-style "any gold passes → cell passes" verdict. +const altGoldStatus = {}; +// "suite:name" → { platform } when the row has a primary gold at currentPlatform +// that fails while some alternate passes. Used by Delete Gold to clean up the +// now-unneeded primary. +const fixableVia = {}; + +function resetAltGoldState() { + Object.keys(altGoldStatus).forEach(k => delete altGoldStatus[k]); + Object.keys(fixableVia).forEach(k => delete fixableVia[k]); +} + +function computeCellStats(srcId, suite, name) { + const key = `${srcId}:${suite}:${name}`; + if (cellStats[key] || statsQueue.has(key)) return; + statsQueue.add(key); + if (!statsRunning) runStatsQueue(); +} + +async function runStatsQueue() { + statsRunning = true; + const BATCH = 8; + while (statsQueue.size > 0) { + const batch = []; + for (const key of statsQueue) { + batch.push(key); + if (batch.length >= BATCH) break; + } + batch.forEach(k => statsQueue.delete(k)); + + await Promise.all(batch.map(async (key) => { + const parts = key.split(':'); + const srcId = parts[0]; + const suite = parts[1]; + const name = parts.slice(2).join(':'); + try { + const goldUrl = `/api/gold/image?suite=${enc(suite)}&platform=${enc(currentPlatform)}&name=${enc(name)}`; + const srcUrl = `/api/source/${srcId}/image?suite=${enc(suite)}&platform=${enc(currentPlatform)}&name=${enc(name)}`; + const [goldImg, srcImg] = await Promise.all([loadImg(goldUrl), loadImg(srcUrl)]); + const canvas = new OffscreenCanvas(goldImg.width, goldImg.height); + const stats = computeImageDiff(goldImg, srcImg, canvas); + cellStats[key] = stats; + updateCellInline(key, stats); + // If the preferred gold fails, scan the rest of the suite — the + // framework passes the test if any gold matches, so we do too. + const result = checkCellThreshold(suite, name, stats); + if (!result.passed) { + await checkAlternateGold(srcId, suite, name, srcImg); + // Re-render cell and row tag with the final verdict (may flip to pass). + updateCellInline(key, stats); + scheduleCountUpdate(); + } + } catch (e) { + // Skip failed comparisons + } + })); + } + statsRunning = false; + // Final count/badge update (no full render — everything was updated inline) + updateActionCounts(); + updateSuiteBadges(); +} + +function checkCellThreshold(suite, name, stats) { + const data = suiteData[suite]; + const rules = data?.thresholdRules || []; + const [platApi, device] = currentPlatform.split('/'); + const dotIdx = platApi?.indexOf('.') ?? -1; + const plat = dotIdx >= 0 ? platApi.substring(0, dotIdx) : platApi; + const api = dotIdx >= 0 ? platApi.substring(dotIdx + 1) : null; + const allow = resolveThreshold(rules, name, plat, api, device); + if (stats.pixelDiffs) return checkThreshold(stats.pixelDiffs, allow); + // Fallback for stats without pixelDiffs + return { passed: stats.diffPixels === 0, details: [] }; +} + +// Returns true (pass), false (fail), or null (pending) — pending means the +// preferred gold failed but the alternate-gold scan hasn't finished yet. +function isCellPassing(srcId, suite, name, stats) { + if (checkCellThreshold(suite, name, stats).passed) return true; + // Graphics.Regression only enumerates fallbacks when the primary gold for + // the current platform doesn't exist. If it does exist, that single file is + // the sole judge — a passing alternate doesn't rescue a failing primary. + const goldEntry = suiteData[suite]?.gold.find(g => g.name === name); + if (goldEntry && goldEntry.fallback == null) return false; + const alt = altGoldStatus[`${srcId}:${suite}:${name}`]; + if (!alt || !alt.checked) return null; + return alt.passingPlatform != null; +} + +function updateCellInline(key, stats) { + const el = document.querySelector(`[data-stats-key="${CSS.escape(key)}"]`); + const parts = key.split(':'); + const srcId = parts[0]; + const suite = parts[1]; + const name = parts.slice(2).join(':'); + const result = checkCellThreshold(suite, name, stats); + const passing = isCellPassing(srcId, suite, name, stats); + const cls = passing === true ? 'pass' : passing === false ? 'fail' : 'pending'; + if (el) { + el.className = `cell ${cls}`; + el.removeAttribute('style'); + el.removeAttribute('data-stats-key'); + const icon = passing === true ? '✓' : passing === false ? '✗' : '…'; + const brief = formatThresholdBrief(result); + const viaAlt = passing === true && !result.passed ? ' (via alt)' : ''; + el.innerHTML = `${icon} ${brief}${viaAlt}`; + } + // Update the row tag once all sources for this image are resolved + const rowKey = `${suite}:${name}`; + const data = suiteData[suite]; + if (!data) return; + let anyFail = false, anyPending = false; + for (const src of sources) { + if (!(data.sourceImages[src.id] || []).some(s => s.name === name)) continue; + const s = cellStats[`${src.id}:${suite}:${name}`]; + if (!s) { anyPending = true; continue; } + const r = isCellPassing(src.id, suite, name, s); + if (r === null) anyPending = true; + else if (!r) anyFail = true; + } + const tagEl = document.querySelector(`[data-row-tag="${CSS.escape(rowKey)}"]`); + if (tagEl) { + if (anyFail) { + const label = fixableVia[rowKey] ? 'failing (fixable)' : 'failing'; + tagEl.innerHTML = `${label}`; + } else if (anyPending) tagEl.innerHTML = '...'; + else tagEl.innerHTML = ''; + } + if (!anyFail && !anyPending) { + // Hide row if it no longer matches the active filter + const filter = document.getElementById('statusFilter').value; + if (filter && filter !== 'pass') { + const tr = document.querySelector(`tr.row[data-kb-key="${CSS.escape(rowKey)}"]`); + if (tr) { + tr.style.display = 'none'; + const next = tr.nextElementSibling; + if (next && !next.classList.contains('row') && !next.classList.contains('suite-row')) + next.style.display = 'none'; + } + } + scheduleCountUpdate(); + } +} + +async function checkAlternateGold(srcId, suite, name, srcImg) { + const key = `${srcId}:${suite}:${name}`; + if (altGoldStatus[key]?.checked) return; + let passingPlatform = null; + try { + const platforms = await fetch(`/api/gold/all?suite=${enc(suite)}&name=${enc(name)}`).then(r => r.json()); + for (const p of platforms) { + if (p.platform === currentPlatform) continue; + try { + const gImg = await loadImg(`/api/gold/image?suite=${enc(suite)}&platform=${enc(p.platform)}&name=${enc(name)}`); + const canvas = new OffscreenCanvas(gImg.width, gImg.height); + const s = computeImageDiff(gImg, srcImg, canvas); + if (checkCellThreshold(suite, name, s).passed) { + passingPlatform = p.platform; + break; + } + } catch {} + } + } catch {} + altGoldStatus[key] = { checked: true, passingPlatform }; + // Record a "fixable" hit only when the primary gold lives at currentPlatform + // — deleting a fallback path would either be a no-op or hurt other platforms. + const goldEntry = suiteData[suite]?.gold.find(g => g.name === name); + if (passingPlatform && goldEntry && goldEntry.fallback == null) { + const fixKey = `${suite}:${name}`; + fixableVia[fixKey] = { platform: passingPlatform, goldFallback: currentPlatform }; + } +} + +async function computeThumbDiff(suite, name, srcId, canvasId) { + try { + + const goldUrl = `/api/gold/image?suite=${enc(suite)}&platform=${enc(currentPlatform)}&name=${enc(name)}`; + const srcUrl = `/api/source/${srcId}/image?suite=${enc(suite)}&platform=${enc(currentPlatform)}&name=${enc(name)}`; + const [goldImg, srcImg] = await Promise.all([loadImg(goldUrl), loadImg(srcUrl)]); + const canvas = document.getElementById(canvasId); + if (!canvas) return; + const stats = computeImageDiff(goldImg, srcImg, canvas); + // Also cache stats + cellStats[`${srcId}:${suite}:${name}`] = stats; + } catch { } +} + + +// === Actions === +function focusRow(key) { + if (!key) { + focusedKey = null; + kbFocusKey = null; + document.querySelectorAll('tr.kb-focus').forEach(r => r.classList.remove('kb-focus')); + renderDetailPane(null); + saveState(); + return; + } + focusedKey = key; + kbFocusKey = key; + // Update border on rows without full re-render + document.querySelectorAll('tr.kb-focus').forEach(r => r.classList.remove('kb-focus')); + const row = document.querySelector(`tr[data-kb-key="${CSS.escape(key)}"]`); + if (row) { + row.classList.add('kb-focus'); + row.scrollIntoView({ block: 'nearest' }); + } + renderDetailPane(key); + saveState(); +} + +function renderDetailPane(key) { + const pane = document.getElementById('detailPaneContent'); + if (!pane) return; + if (!key) { + pane.innerHTML = `
No selection
`; + return; + } + const colon = key.indexOf(':'); + const suite = key.substring(0, colon); + const name = key.substring(colon + 1); + const id = css(key); + pane.innerHTML = `
Loading...
`; + loadDetail(suite, name); +} + +function setActiveSource(key, srcId) { + compareRight[key] = `src:${srcId}`; + // Update active-source highlight inline + const row = document.querySelector(`tr.row[data-kb-key="${CSS.escape(key)}"]`); + if (row) { + row.querySelectorAll('td[onclick]').forEach(td => { + const match = td.getAttribute('onclick')?.match(/setActiveSource\('[^']*','([^']*)'\)/); + td.classList.toggle('active-source', match && match[1] === srcId); + }); + } + // If this is the focused row, refresh the detail pane + if (focusedKey === key) { + const [suite, name] = [key.substring(0, key.indexOf(':')), key.substring(key.indexOf(':') + 1)]; + loadDetail(suite, name); + } +} + +function switchDetailSide(key, side, value) { + if (side === 'left') compareLeft[key] = value; + else compareRight[key] = value; + const [suite, name] = [key.substring(0, key.indexOf(':')), key.substring(key.indexOf(':') + 1)]; + loadDetail(suite, name); + // Update active-source underline on the table row + if (side === 'right') { + const row = document.querySelector(`tr.row[data-kb-key="${CSS.escape(key)}"]`); + if (row) { + const activeSrcId = value.startsWith('src:') ? value.slice(4) : null; + const srcCells = row.querySelectorAll('td[onclick]'); + srcCells.forEach(td => { + const match = td.getAttribute('onclick')?.match(/setActiveSource\('[^']*','([^']*)'\)/); + td.classList.toggle('active-source', match && match[1] === activeSrcId); + }); + } + } +} + + +function toggleSelect(name) { + if (selected.has(name)) selected.delete(name); + else selected.add(name); + updateSelectedCount(); + saveState(); +} + +function toggleSelectSuite(suite, checked) { + const filter = document.getElementById('statusFilter').value; + let images = buildSuiteImages(suite); + if (filter) images = images.filter(i => i.status === filter || (filter === 'fail' && i.status === 'pending')); + images.forEach(i => { + const key = `${i.suite}:${i.name}`; + if (checked) selected.add(key); else selected.delete(key); + }); + syncCheckboxes(); + updateSelectedCount(); + saveState(); +} + +function toggleSelectAll() { + const cb = document.getElementById('selectAll'); + const checked = cb.checked; + const filter = document.getElementById('statusFilter').value; + for (const suite of Object.keys(suiteData)) { + let images = buildSuiteImages(suite); + if (filter) images = images.filter(i => i.status === filter || (filter === 'fail' && i.status === 'pending')); + images.forEach(i => { + const key = `${i.suite}:${i.name}`; + if (checked) selected.add(key); else selected.delete(key); + }); + } + syncCheckboxes(); + updateSelectedCount(); + saveState(); +} + +function getMaxDiffForImage(img) { + for (const src of sources) { + const stats = cellStats[`${src.id}:${img.suite}:${img.name}`]; + if (stats) return stats.maxDiff; + } + return null; +} + +function selectAllFailing() { + selected.clear(); + for (const suite of Object.keys(suiteData)) { + const images = buildSuiteImages(suite); + images.filter(i => i.status === 'fail' || i.status === 'new').forEach(i => selected.add(`${i.suite}:${i.name}`)); + } + syncCheckboxes(); + updateSelectedCount(); + saveState(); +} + +function selectFixable() { + // With the framework's any-match semantics, "fixable" rows now pass (via an + // alternate gold) while still carrying a stale primary gold at the current + // platform. Selecting them lets the user clean up those redundant primaries. + selected.clear(); + for (const suite of Object.keys(suiteData)) { + for (const img of buildSuiteImages(suite)) { + const fixKey = `${img.suite}:${img.name}`; + if (fixableVia[fixKey]) selected.add(fixKey); + } + } + syncCheckboxes(); + updateSelectedCount(); + saveState(); +} + +async function deleteSelectedGold() { + if (selected.size === 0) return alert('No images selected.'); + // Group fixable images by the platform whose gold should be deleted + const toDelete = {}; + for (const key of selected) { + const fix = fixableVia[key]; + if (!fix) continue; + const [suite, ...nameParts] = key.split(':'); + const name = nameParts.join(':'); + // Delete the gold for the current platform (the failing one) + const k = `${suite}|${fix.goldFallback}`; + if (!toDelete[k]) toDelete[k] = { suite, platform: fix.goldFallback, names: [] }; + toDelete[k].names.push(name); + } + const entries = Object.values(toDelete); + if (entries.length === 0) return alert('No fixable images selected. Select images that show a green checkmark hint.'); + const total = entries.reduce((s, e) => s + e.names.length, 0); + if (!confirm(`Delete ${total} device-specific gold image(s)? They will fall back to a passing alternate.`)) return; + let totalDeleted = 0; + for (const entry of entries) { + const res = await fetch('/api/gold/delete', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ suite: entry.suite, platform: entry.platform, names: entry.names }) + }); + const result = await res.json(); + totalDeleted += result.deleted; + } + alert(`Deleted ${totalDeleted} gold image(s).`); + selected.clear(); + cellStats = {}; + resetAltGoldState(); + await reload(); +} + +function getSourceForKey(key) { + // Use the right-side source from the detail view, or fall back to first source with the image + const ref = compareRight[key]; + if (ref && ref.startsWith('src:')) return ref.slice(4); + const [suite, name] = [key.substring(0, key.indexOf(':')), key.substring(key.indexOf(':') + 1)]; + const data = suiteData[suite]; + if (data) { + for (const s of sources) + if ((data.sourceImages[s.id] || []).some(i => i.name === name)) return s.id; + } + return sources[0]?.id || null; +} + +async function promoteSelected() { + if (selected.size === 0) return; + const mode = document.getElementById('promoteSource').value; + + // Group by suite+source + const groups = {}; // {`${srcId}:${suite}`: {srcId, suite, names[]}} + for (const key of selected) { + const [suite, name] = [key.substring(0, key.indexOf(':')), key.substring(key.indexOf(':') + 1)]; + const srcId = mode === '__active__' ? getSourceForKey(key) : mode; + if (!srcId) continue; + const gkey = `${srcId}:${suite}`; + if (!groups[gkey]) groups[gkey] = { srcId, suite, names: [] }; + groups[gkey].names.push(name); + } + + const srcLabels = [...new Set(Object.values(groups).map(g => sources.find(s => s.id === g.srcId)?.label || g.srcId))]; + if (!confirm(`Promote ${selected.size} image(s) from ${srcLabels.join(', ')} to gold?`)) return; + + let totalPromoted = 0; + for (const { srcId, suite, names } of Object.values(groups)) { + const res = await fetch('/api/promote', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ sourceId: srcId, suite, platform: currentPlatform, names }) + }); + const result = await res.json(); + console.log('Promote result:', result); + totalPromoted += result.promoted; + } + alert(`Promoted ${totalPromoted} image(s).`); + selected.clear(); + cellStats = {}; + resetAltGoldState(); + compareLeft = {}; + compareRight = {}; + await reload(); +} + +function syncCheckboxes() { + // Sync individual row checkboxes + document.querySelectorAll('tr.row[data-kb-key]').forEach(tr => { + const key = tr.dataset.kbKey; + const cb = tr.querySelector('input[type=checkbox]'); + if (cb) cb.checked = selected.has(key); + }); + // Sync suite-level checkboxes + document.querySelectorAll('tr.suite-row[data-kb-key]').forEach(tr => { + const suite = tr.dataset.kbKey; + const cb = tr.querySelector('input[type=checkbox]'); + if (!cb) return; + const images = buildSuiteImages(suite); + const filter = document.getElementById('statusFilter').value; + const filtered = filter ? images.filter(i => i.status === filter || (filter === 'fail' && i.status === 'pending')) : images; + const keys = filtered.map(i => `${i.suite}:${i.name}`); + const allSel = keys.length > 0 && keys.every(k => selected.has(k)); + const someSel = !allSel && keys.some(k => selected.has(k)); + cb.checked = allSel; + cb.indeterminate = someSel; + }); +} + +function updateSelectedCount() { + document.getElementById('selectedCount').textContent = selected.size; + document.getElementById('selectedCount2').textContent = selected.size; +} + +let _countUpdateTimer = null; +function scheduleCountUpdate() { + if (_countUpdateTimer) return; + _countUpdateTimer = setTimeout(() => { + _countUpdateTimer = null; + updateActionCounts(); + updateSuiteBadges(); + }, 300); +} + +function updateSuiteBadges() { + for (const suite of Object.keys(suiteData)) { + const images = buildSuiteImages(suite); + const failCount = images.filter(i => i.status === 'fail' || i.status === 'new').length; + const pendingCount = images.filter(i => i.status === 'pending').length; + const failEl = document.querySelector(`[data-suite-fail="${CSS.escape(suite)}"]`); + const pendEl = document.querySelector(`[data-suite-pending="${CSS.escape(suite)}"]`); + if (failEl) { failEl.textContent = `${failCount} failing`; failEl.style.display = failCount > 0 ? '' : 'none'; } + if (pendEl) { pendEl.textContent = `${pendingCount} pending`; pendEl.style.display = pendingCount > 0 ? '' : 'none'; } + } +} + +function updateActionCounts() { + let failCount = 0, fixableCount = 0; + for (const suite of Object.keys(suiteData)) { + const images = buildSuiteImages(suite); + for (const i of images) { + if (i.status === 'fail' || i.status === 'new') failCount++; + if (fixableVia[`${i.suite}:${i.name}`]) fixableCount++; + } + } + document.getElementById('failingCount').textContent = failCount; + document.getElementById('fixableCount').textContent = fixableCount; +} + +// === Utils === +function isSoftwareRenderer(platform) { + const p = platform.toLowerCase(); + return p.includes('swiftshader') || p.includes('warp'); +} + +function getGfxApi(platform) { + // "Windows.Direct3D11/WARP" → "Direct3D11", "Linux.Vulkan/SwiftShader" → "Vulkan" + const platApi = platform.split('/')[0]; + const dot = platApi.indexOf('.'); + return dot >= 0 ? platApi.substring(dot + 1) : platApi; +} + +function getOS(platform) { + const platApi = platform.split('/')[0]; + const dot = platApi.indexOf('.'); + return dot >= 0 ? platApi.substring(0, dot) : platApi; +} + +function scoreFallback(candidate, requested) { + // Mirrors Program.cs ScoreFallback: exact > same OS > same gfx API > + // same device (WARP↔WARP, Lavapipe↔Lavapipe) > same renderer class. + const cPlatApi = candidate.split('/')[0]; + const rPlatApi = requested.split('/')[0]; + const cDevice = candidate.split('/')[1] || ''; + const rDevice = requested.split('/')[1] || ''; + let score = 0; + if (cPlatApi === rPlatApi) score += 16; + if (getOS(cPlatApi) === getOS(rPlatApi)) score += 8; + if (getGfxApi(cPlatApi) === getGfxApi(rPlatApi)) score += 4; + if (cDevice.toLowerCase() === rDevice.toLowerCase()) score += 2; + if (isSoftwareRenderer(candidate) === isSoftwareRenderer(requested)) score += 1; + return score; +} + +function pickBestGoldPlatform(platforms, currentPlatform) { + if (!platforms || platforms.length === 0) return currentPlatform; + // Exact match + const exact = platforms.find(p => p.platform === currentPlatform); + if (exact) return exact.platform; + let best = null, bestScore = -1; + for (const p of platforms) { + const score = scoreFallback(p.platform, currentPlatform); + if (score > bestScore) { bestScore = score; best = p.platform; } + } + return best || platforms[0].platform; +} + +function enc(s) { return encodeURIComponent(s); } +function css(s) { return s.replace(/[^a-zA-Z0-9]/g, '_'); } +function esc(s) { return s.replace(/&/g, '&').replace(//g, '>').replace(/"/g, '"'); } +function loadImg(url) { + return new Promise((resolve, reject) => { + const img = new Image(); + img.crossOrigin = 'anonymous'; + img.onload = () => resolve(img); + img.onerror = reject; + img.src = url; + }); +} +function drawToCanvas(canvas, img) { + canvas.width = img.naturalWidth; + canvas.height = img.naturalHeight; + canvas.getContext('2d').drawImage(img, 0, 0); +} +function naturalSort(a, b) { return a.localeCompare(b, undefined, { numeric: true, sensitivity: 'base' }); } + +// === CI Modal === +let ciRuns = []; +let selectedCiRun = null; + +async function loadCiRuns() { + document.getElementById('ciLoading').style.display = 'block'; + document.getElementById('ciRunsList').innerHTML = ''; + try { + // Check gh status first + const statusRes = await fetch('/api/ci/status'); + const status = await statusRes.json(); + if (!status.available) { + document.getElementById('ciLoading').innerHTML = `⚠ ${status.error}`; + return; + } + const res = await fetch('/api/ci/runs?limit=30'); + const allRuns = await res.json(); + // Deduplicate by SHA — keep one per commit, use the CI workflow (name "CI") + const seen = new Map(); + for (const run of allRuns) { + const sha = run.head_sha ?? ''; + const name = run.name ?? ''; + if (!seen.has(sha) || name === 'CI') + seen.set(sha, run); + } + ciRuns = [...seen.values()].slice(0, 15); + console.log('CI runs:', ciRuns); + renderCiRuns(); + } catch (e) { + document.getElementById('ciLoading').textContent = 'Failed to load runs. Is gh CLI authenticated?'; + } +} + +function renderCiRuns() { + document.getElementById('ciLoading').style.display = 'none'; + const list = document.getElementById('ciRunsList'); + list.innerHTML = ciRuns.map(run => { + const id = run.id ?? run.Id; + const branch = run.head_branch ?? run.HeadBranch ?? ''; + const sha = (run.head_sha ?? run.HeadSha ?? '').substring(0, 7); + const date = run.created_at ?? run.CreatedAt ?? ''; + const conclusion = run.conclusion ?? run.Conclusion ?? ''; + const ago = timeAgo(date); + const wfName = run.name ?? ''; + const statusIcon = conclusion === 'success' ? '✓' : conclusion === 'failure' ? '✗' : conclusion === 'cancelled' ? '⊘' : '○'; + return `
+
#${id} ${esc(branch)} ${sha}
+
${esc(wfName)} ${ago} ${statusIcon}
+
`; + }).join(''); +} + +let ciArtifacts = []; +let selectedCiArtifacts = new Set(); + +async function selectCiRun(id) { + selectedCiRun = id; + document.getElementById('ciRunId').value = String(id); + renderCiRuns(); + + // Load artifacts for this run + const listEl = document.getElementById('ciArtifactsList'); + listEl.innerHTML = ' Loading artifacts...'; + try { + const res = await fetch(`/api/ci/artifacts?runId=${id}`); + ciArtifacts = await res.json(); + // Auto-select test-related artifacts + selectedCiArtifacts = new Set( + ciArtifacts + .filter(a => { + const name = (a.name ?? a.Name ?? '').toLowerCase(); + return name.includes('test-artifacts') && !(a.expired ?? a.Expired ?? false); + }) + .map(a => a.name ?? a.Name) + ); + renderCiArtifacts(); + } catch (e) { + listEl.innerHTML = `Failed to load artifacts: ${e.message}`; + console.error('Artifact load error:', e); + } + document.getElementById('ciDownloadBtn').disabled = selectedCiArtifacts.size === 0; +} + +function renderCiArtifacts() { + const listEl = document.getElementById('ciArtifactsList'); + const testArtifacts = ciArtifacts.filter(a => (a.name ?? '').startsWith('test-artifacts')); + if (testArtifacts.length === 0) { + listEl.innerHTML = 'No test artifacts found for this run'; + document.getElementById('ciDownloadBtn').disabled = true; + return; + } + listEl.innerHTML = testArtifacts.map(a => { + const name = a.name ?? a.Name; + const size = a.size_in_bytes ?? a.SizeInBytes ?? 0; + const sizeMB = (size / 1048576).toFixed(1); + const checked = selectedCiArtifacts.has(name); + const expired = a.expired ?? a.Expired ?? false; + return ``; + }).join(''); + document.getElementById('ciDownloadBtn').disabled = selectedCiArtifacts.size === 0; +} + +function toggleCiArtifact(name, checked) { + if (checked) selectedCiArtifacts.add(name); + else selectedCiArtifacts.delete(name); + document.getElementById('ciDownloadBtn').disabled = selectedCiArtifacts.size === 0; +} + +function timeAgo(dateStr) { + if (!dateStr) return ''; + const d = new Date(dateStr); + const now = new Date(); + const diff = (now - d) / 1000; + if (diff < 60) return 'just now'; + if (diff < 3600) return Math.floor(diff / 60) + 'm ago'; + if (diff < 86400) return Math.floor(diff / 3600) + 'h ago'; + return Math.floor(diff / 86400) + 'd ago'; +} + +// === Synchronized Zoom/Pan === +const zoomState = {}; // {groupId: {scale, panX, panY}} + +function initZoomGroup(groupId) { + zoomState[groupId] = { scale: 1, panX: 0, panY: 0 }; + const group = document.getElementById(`zoomgroup-${groupId}`); + if (!group) return; + + const containers = group.querySelectorAll('.zoom-container'); + + containers.forEach(container => { + // Wheel zoom (scroll anywhere over the image area) + container.addEventListener('wheel', (e) => { + e.preventDefault(); + const state = zoomState[groupId]; + const rect = container.getBoundingClientRect(); + const mx = e.clientX - rect.left; + const my = e.clientY - rect.top; + + const oldScale = state.scale; + const delta = e.deltaY > 0 ? 0.8 : 1.25; + state.scale = Math.max(0.5, Math.min(20, state.scale * delta)); + + // Zoom toward mouse position + state.panX = mx - (mx - state.panX) * (state.scale / oldScale); + state.panY = my - (my - state.panY) * (state.scale / oldScale); + + applyZoom(groupId); + }, { passive: false }); + + // Drag pan + let dragging = false, startX, startY, startPanX, startPanY; + container.addEventListener('mousedown', (e) => { + if (e.button !== 0) return; + dragging = true; + startX = e.clientX; + startY = e.clientY; + const state = zoomState[groupId]; + startPanX = state.panX; + startPanY = state.panY; + container.classList.add('dragging'); + e.preventDefault(); + }); + window.addEventListener('mousemove', (e) => { + if (!dragging) return; + const state = zoomState[groupId]; + state.panX = startPanX + (e.clientX - startX); + state.panY = startPanY + (e.clientY - startY); + applyZoom(groupId); + }); + window.addEventListener('mouseup', () => { + if (dragging) { + dragging = false; + container.classList.remove('dragging'); + } + }); + }); +} + +function applyZoom(groupId) { + const state = zoomState[groupId]; + const group = document.getElementById(`zoomgroup-${groupId}`); + if (!group) return; + group.querySelectorAll('.zoom-inner').forEach(inner => { + inner.style.transform = `translate(${state.panX}px, ${state.panY}px) scale(${state.scale})`; + }); +} + +function resetZoom(groupId) { + zoomState[groupId] = { scale: 1, panX: 0, panY: 0 }; + applyZoom(groupId); +} + +// === Pixel Inspector === +const piZoomSize = 7; // 7x7 pixel grid +const piScale = 12; // each pixel drawn at 12x12 + +document.addEventListener('mousemove', (e) => { + const img = e.target.closest('img.thumb, canvas.thumb, .image-box img, .image-box canvas'); + if (!img) { document.getElementById('pixelInspector').style.display = 'none'; return; } + + const rect = img.getBoundingClientRect(); + const scaleX = (img.naturalWidth || img.width) / rect.width; + const scaleY = (img.naturalHeight || img.height) / rect.height; + const px = Math.floor((e.clientX - rect.left) * scaleX); + const py = Math.floor((e.clientY - rect.top) * scaleY); + + if (px < 0 || py < 0 || px >= (img.naturalWidth || img.width) || py >= (img.naturalHeight || img.height)) { + document.getElementById('pixelInspector').style.display = 'none'; + return; + } + + // Find which detail panel this image belongs to + const detail = img.closest('.detail') || img.closest('td'); + if (!detail) return; + + // Collect all images in the same row/detail (gold + sources) + const allImages = detail.closest('tr')?.parentElement?.querySelectorAll('img, canvas') || []; + // Filter to full-size images in the same detail panel, or thumbnails in the same row + const relatedImages = []; + const detailEl = img.closest('.detail'); + if (detailEl) { + detailEl.querySelectorAll('img, canvas').forEach(el => relatedImages.push(el)); + } else { + // Thumbnail mode — find images in sibling tds + const row = img.closest('tr'); + if (row) row.querySelectorAll('img.thumb, canvas.thumb').forEach(el => relatedImages.push(el)); + } + + // Build inspector content + let html = ''; + // Collect full-size entries in order [left, right, diff]; delta is shown only on the diff entry, + // computed as right − left (so the third column reports the inter-image difference). + let leftRGBA = null, rightRGBA = null, entryIdx = 0; + for (const ri of relatedImages) { + const lblEl = ri.closest('.image-box')?.querySelector('.lbl'); + const sel = lblEl?.querySelector('select'); + const label = sel ? sel.options[sel.selectedIndex]?.text?.split(' (')[0] || '' : lblEl?.textContent || ''; + const w = ri.naturalWidth || ri.width; + const h = ri.naturalHeight || ri.height; + if (w === 0 || h === 0) continue; + + // Draw zoomed region + const canvas = document.createElement('canvas'); + canvas.width = piZoomSize * piScale; + canvas.height = piZoomSize * piScale; + const ctx = canvas.getContext('2d'); + + // Get pixel data from the image + const tmpCanvas = new OffscreenCanvas(w, h); + const tmpCtx = tmpCanvas.getContext('2d'); + tmpCtx.drawImage(ri, 0, 0); + + const half = Math.floor(piZoomSize / 2); + + // Draw zoomed pixels, clamping to image bounds + ctx.fillStyle = '#111'; + ctx.fillRect(0, 0, canvas.width, canvas.height); + let cr = 0, cg = 0, cb = 0, ca = 0; + for (let dy = -half; dy <= half; dy++) { + for (let dx = -half; dx <= half; dx++) { + const sx = px + dx, sy = py + dy; + if (sx >= 0 && sx < w && sy >= 0 && sy < h) { + const pxData = tmpCtx.getImageData(sx, sy, 1, 1).data; + ctx.fillStyle = `rgb(${pxData[0]},${pxData[1]},${pxData[2]})`; + ctx.fillRect((dx + half) * piScale, (dy + half) * piScale, piScale, piScale); + if (dx === 0 && dy === 0) { cr = pxData[0]; cg = pxData[1]; cb = pxData[2]; ca = pxData[3]; } + } + } + } + // Draw center crosshair + ctx.strokeStyle = '#fff'; + ctx.lineWidth = 1; + ctx.strokeRect(half * piScale, half * piScale, piScale, piScale); + + const r = cr, g = cg, b = cb, a = ca; + + // Capture left/right RGBA so the diff entry can report right − left. + if (entryIdx === 0) leftRGBA = { r, g, b, a }; + else if (entryIdx === 1) rightRGBA = { r, g, b, a }; + + // Body rows: columns 1 and 2 show absolute RGB; column 3 shows Δ(right − left) only. + let bodyHtml; + if (entryIdx >= 2 && leftRGBA && rightRGBA) { + const dR = rightRGBA.r - leftRGBA.r; + const dG = rightRGBA.g - leftRGBA.g; + const dB = rightRGBA.b - leftRGBA.b; + const dA = rightRGBA.a - leftRGBA.a; + const fmt = (v) => (v > 0 ? '+' : '') + v; + const cls = (v) => v !== 0 ? 'pi-delta-nz' : ''; + bodyHtml = + `
 
` + // spacer to align with the #hex row on columns 1/2 + `
ΔR: ${fmt(dR)}
` + + `
ΔG: ${fmt(dG)}
` + + `
ΔB: ${fmt(dB)}
` + + `
ΔA: ${fmt(dA)}
`; + } else { + bodyHtml = + `
#${r.toString(16).padStart(2,'0')}${g.toString(16).padStart(2,'0')}${b.toString(16).padStart(2,'0')}${a<255?a.toString(16).padStart(2,'0'):''}
` + + `
R: ${String(r).padStart(3)} (${(r/255).toFixed(3)})
` + + `
G: ${String(g).padStart(3)} (${(g/255).toFixed(3)})
` + + `
B: ${String(b).padStart(3)} (${(b/255).toFixed(3)})
` + + `
A: ${String(a).padStart(3)} (${(a/255).toFixed(3)})
`; + } + + html += `
+
${esc(label)}
+ +
X:${px} Y:${py}
+ ${bodyHtml} +
`; + entryIdx++; + } + + const inspector = document.getElementById('pixelInspector'); + document.getElementById('piContent').innerHTML = html; + inspector.style.display = 'block'; +}); + +document.addEventListener('mouseleave', () => { + document.getElementById('pixelInspector').style.display = 'none'; +}); + +// === Keyboard Navigation === +let kbFocusKey = null; // persists across re-renders + +document.addEventListener('keydown', (e) => { + if (e.target.tagName === 'INPUT' || e.target.tagName === 'SELECT' || e.target.tagName === 'TEXTAREA') return; + const rows = [...document.querySelectorAll('tr.suite-row, tr.row')]; + if (rows.length === 0) return; + + const focusedIdx = kbFocusKey != null ? rows.findIndex(r => r.dataset.kbKey === kbFocusKey) : -1; + + if (e.key === 'ArrowDown' || e.key === 'j') { + e.preventDefault(); + const next = Math.min(focusedIdx + 1, rows.length - 1); + kbSetFocus(rows, next); + } else if (e.key === 'ArrowUp' || e.key === 'k') { + e.preventDefault(); + const prev = focusedIdx <= 0 ? 0 : focusedIdx - 1; + kbSetFocus(rows, prev); + } else if (e.key === 'ArrowRight' || e.key === 'l') { + e.preventDefault(); + if (focusedIdx >= 0) kbExpand(rows[focusedIdx]); + } else if (e.key === 'ArrowLeft' || e.key === 'h') { + e.preventDefault(); + if (focusedIdx >= 0) kbCollapse(rows[focusedIdx]); + } else if (e.key === ' ') { + e.preventDefault(); + if (focusedIdx >= 0) { + const cb = rows[focusedIdx].querySelector('input[type=checkbox]'); + if (cb) cb.click(); + } + } +}); + +function kbSetFocus(rows, idx) { + rows.forEach(r => r.classList.remove('kb-focus')); + if (idx >= 0 && idx < rows.length) { + rows[idx].classList.add('kb-focus'); + rows[idx].scrollIntoView({ block: 'nearest' }); + const key = rows[idx].dataset.kbKey || null; + kbFocusKey = key; + // Drive the detail pane when focus lands on a test row (not the suite header). + if (rows[idx].classList.contains('row')) { + focusedKey = key; + renderDetailPane(key); + } + saveState(); + } +} + +function kbExpand(row) { + const key = row.dataset.kbKey; + if (!key) return; + if (row.classList.contains('suite-row')) { + if (!collapsedSuites.has(key)) { + // Already expanded — move to first child + const rows = [...document.querySelectorAll('tr.suite-row, tr.row')]; + const idx = rows.indexOf(row); + if (idx >= 0 && idx + 1 < rows.length && rows[idx + 1].classList.contains('row')) { + kbSetFocus(rows, idx + 1); + return; + } + } + collapsedSuites.delete(key); + render(); + } +} + +function kbCollapse(row) { + const key = row.dataset.kbKey; + if (!key) return; + if (row.classList.contains('suite-row')) { + if (collapsedSuites.has(key)) return; // already collapsed, nowhere to go + collapsedSuites.add(key); + } else { + // Test row — move to parent suite + const rows = [...document.querySelectorAll('tr.suite-row, tr.row')]; + const idx = rows.indexOf(row); + for (let i = idx - 1; i >= 0; i--) { + if (rows[i].classList.contains('suite-row')) { + kbSetFocus(rows, i); + return; + } + } + return; + } + render(); +} + +// Restore kb focus after render +const _origRender2 = render; +render = function() { + _origRender2(); + if (kbFocusKey) { + const row = document.querySelector(`[data-kb-key="${CSS.escape(kbFocusKey)}"]`); + if (row) row.classList.add('kb-focus'); + } +}; + +// === Persistence === +function saveState() { + try { + localStorage.setItem('compareGold', JSON.stringify({ + platform: currentPlatform, + selectedKey: focusedKey, + collapsedSuites: [...collapsedSuites], + statusFilter: document.getElementById('statusFilter')?.value || '', + sort: document.getElementById('sortSelect')?.value || 'name', + search: document.getElementById('searchFilter')?.value || '', + savedSources: sourceDefs, + detailPaneH: document.documentElement.style.getPropertyValue('--detail-pane-h') || '', + })); + } catch {} +} + +function restoreState() { + try { + const data = JSON.parse(localStorage.getItem('compareGold') || '{}'); + if (data.platform) currentPlatform = data.platform; + if (data.selectedKey) { focusedKey = data.selectedKey; kbFocusKey = data.selectedKey; } + if (data.collapsedSuites) data.collapsedSuites.forEach(k => collapsedSuites.add(k)); + if (data.statusFilter !== undefined) { + const sel = document.getElementById('statusFilter'); + if (sel) sel.value = data.statusFilter; + } + if (data.sort) { + const sel = document.getElementById('sortSelect'); + if (sel) sel.value = data.sort; + } + if (data.search) { + const el = document.getElementById('searchFilter'); + if (el) el.value = data.search; + } + if (data.savedSources) savedSourceDefs = data.savedSources; + if (data.detailPaneH) document.documentElement.style.setProperty('--detail-pane-h', data.detailPaneH); + } catch {} +} + +let savedSourceDefs = null; +async function restoreSources() { + if (!savedSourceDefs || savedSourceDefs.length === 0) return; + for (const def of savedSourceDefs) { + try { + let res, src; + if (def.type === 'local') { + res = await fetch('/api/sources/add-local', { method: 'POST' }); + } else if (def.type === 'ci') { + res = await fetch('/api/sources/add-ci', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ runId: def.runId, artifactName: def.artifactName, label: def.label }) + }); + } + if (res && res.ok) { + src = await res.json(); + if (!sources.find(s => s.id === src.id)) { + sources.push(src); + sourceDefs.push(def); + } + } + } catch {} + } + savedSourceDefs = null; +} + +// Hook render to auto-save +const _origRender = render; +render = function() { _origRender(); saveState(); }; + +// === Detail pane resize === +// Hard floor: keep images usable; updatePaneBounds() recomputes ceiling from loaded image aspect. +const PANE_MIN_H = 160; +let paneMaxH = null; + +function updatePaneBounds(refImg) { + const pane = document.getElementById('detailPane'); + if (!pane) return; + const container = pane.querySelector('.zoom-container'); + if (!refImg || !container) { paneMaxH = null; return; } + const aw = refImg.naturalWidth || refImg.width; + const ah = refImg.naturalHeight || refImg.height; + if (!aw || !ah) { paneMaxH = null; return; } + // Image-box width at full pane width. Add non-image overhead (handle, padding, label, footer, margins). + const boxW = container.offsetWidth; + const maxImgH = Math.ceil(boxW * ah / aw); + const overhead = pane.offsetHeight - container.offsetHeight; + const winCap = window.innerHeight - 120; + paneMaxH = Math.min(winCap, Math.max(PANE_MIN_H, maxImgH + overhead)); + // Clamp current height if it exceeds the new bounds. + const cur = pane.offsetHeight; + const clamped = Math.max(PANE_MIN_H, Math.min(paneMaxH, cur)); + if (clamped !== cur) { + document.documentElement.style.setProperty('--detail-pane-h', clamped + 'px'); + saveState(); + } +} + +(function initPaneResize() { + const handle = document.getElementById('detailPaneHandle'); + if (!handle) return; + handle.addEventListener('mousedown', (e) => { + e.preventDefault(); + handle.classList.add('dragging'); + const startY = e.clientY; + const startH = document.getElementById('detailPane').offsetHeight; + const onMove = (ev) => { + const dy = startY - ev.clientY; + const cap = paneMaxH != null ? paneMaxH : (window.innerHeight - 120); + const h = Math.max(PANE_MIN_H, Math.min(cap, startH + dy)); + document.documentElement.style.setProperty('--detail-pane-h', h + 'px'); + }; + const onUp = () => { + handle.classList.remove('dragging'); + document.removeEventListener('mousemove', onMove); + document.removeEventListener('mouseup', onUp); + saveState(); + }; + document.addEventListener('mousemove', onMove); + document.addEventListener('mouseup', onUp); + }); +})(); + +// === Start === +restoreState(); +init().then(async () => { + // Restore platform selection after init populates the dropdown + if (currentPlatform) { + const sel = document.getElementById('platformSelect'); + if (sel && [...sel.options].some(o => o.value === currentPlatform)) { + sel.value = currentPlatform; + } + } + // Restore saved sources, or auto-add local + if (savedSourceDefs && savedSourceDefs.length > 0) { + await restoreSources(); + await reload(); + } else { + await addLocalSource().catch(() => {}); + } + // Re-hydrate the detail pane with the restored selection (if it still exists). + if (focusedKey) { + const row = document.querySelector(`tr.row[data-kb-key="${CSS.escape(focusedKey)}"]`); + if (row) { + row.classList.add('kb-focus'); + row.scrollIntoView({ block: 'nearest' }); + renderDetailPane(focusedKey); + } else { + focusedKey = null; + renderDetailPane(null); + } + } else { + renderDetailPane(null); + } +}); diff --git a/build/tools/CompareGold/wwwroot/diff.js b/build/tools/Stride.CompareGold/wwwroot/diff.js similarity index 100% rename from build/tools/CompareGold/wwwroot/diff.js rename to build/tools/Stride.CompareGold/wwwroot/diff.js diff --git a/build/tools/Stride.CompareGold/wwwroot/favicon.ico b/build/tools/Stride.CompareGold/wwwroot/favicon.ico new file mode 100644 index 0000000000..f2c5b30f89 Binary files /dev/null and b/build/tools/Stride.CompareGold/wwwroot/favicon.ico differ diff --git a/build/tools/CompareGold/wwwroot/index.html b/build/tools/Stride.CompareGold/wwwroot/index.html similarity index 79% rename from build/tools/CompareGold/wwwroot/index.html rename to build/tools/Stride.CompareGold/wwwroot/index.html index ff921aac69..91eca98d99 100644 --- a/build/tools/CompareGold/wwwroot/index.html +++ b/build/tools/Stride.CompareGold/wwwroot/index.html @@ -2,6 +2,7 @@ + CompareGold — Stride Image Comparison @@ -9,6 +10,7 @@

CompareGold

Stride Image Comparison Tool +
@@ -25,9 +27,6 @@

CompareGold

- - -
Sources: @@ -35,8 +34,10 @@

CompareGold

- + + +
@@ -49,6 +50,14 @@

CompareGold

Select a suite and platform, then add a source to compare.
+ +
+
+
+
No selection
+
+
+