The lavapipe job did the thing it was built to do on its first attempt.
It accepted a Cpu device at API 1.4, created a device and read pixels
back, captured a real frame, and exited 4 when a feature was forced
unsupported. Three other things were red, and none of them were the
Vulkan backend.
The shader-freshness step aborted for two separate Linux faults in the
compiler tool. Disposing the Silk.NET API container unloads the native
module, and dlclose-ing libshaderc_shared.so leaves glslang's
process-level teardown running against unmapped code. Bisected with a
four-mode probe on Ubuntu 24.04: GetApi, CompilerInitialize and
CompilerRelease each exit 0, and adding only the container Dispose turns
the exit into SIGSEGV. That is the 134 CI reported. shaderc's own handles
are still released; the container is not, because the module's lifetime
is the process's and the process is one statement from returning.
Separately, a portable dotnet build leaves the native under
runtimes/linux-x64/native/ and makes reaching it Silk.NET's probing
problem, which it solved on a local Ubuntu 24.04 and did not solve on the
runner. The script now publishes the tool for the host RID, so the native
sits beside the assembly where AppContext.BaseDirectory finds it, and
checks for it by name so a regression says which file is missing rather
than which names failed.
With both fixed, the question section 5.5.20 left open has an answer:
Linux shaderc and Windows shaderc agree byte-for-byte at the pinned Silk
2.23.0. Eighteen of eighteen .spv identical, manifest identical. The byte
comparison stays a byte comparison.
The Windows leg of portable-headless was running sudo apt-get. That step
is older than this campaign - it is red in the 2026-07-27 main run too -
and it was misplaced rather than mis-conditioned. Nothing in that job
opens a display or links GL, and the graphical jobs that do call xvfb-run
take it from the runner image, so the step is deleted rather than
guarded. Every remaining step in the two-operating-system matrix is pwsh;
every bash step now lives in an ubuntu-only job.
The last failure was ours in a quieter way. WaitForCharacterLogOff-
Confirmation expressed its deadline only as a CancellationTokenSource,
whose timeout is published from a thread-pool timer callback, so on a
saturated pool the token stays unsignalled past the deadline while the
loop keeps draining items that are already queued. That is the case the
method exists to bound. Reproduced by pinning the suite to two CPUs on
Linux, which failed 2 of 6 where four CPUs and sixteen were clean, and
where CI failed 3 of 3. The drain now reads the deadline off the
monotonic clock as well; the token still bounds the asynchronous wait.
Ten of ten clean under the same pin. The test is untouched. Filed as
Release build green. App tests 4,152 / 3 skipped against the same 4,152 /
3 measured at base 32f9bcfa. Core.Net 600 / 600.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
460 lines
20 KiB
YAML
460 lines
20 KiB
YAML
name: Headless portability
|
|
|
|
on:
|
|
pull_request:
|
|
paths:
|
|
- ".github/workflows/headless-portability.yml"
|
|
- "AcDream.slnx"
|
|
- "src/AcDream.Core/**"
|
|
- "src/AcDream.Core.Net/**"
|
|
- "src/AcDream.Content/**"
|
|
- "src/AcDream.Plugin.Abstractions/**"
|
|
- "src/AcDream.Runtime/**"
|
|
- "src/AcDream.Headless/**"
|
|
- "src/AcDream.App/**"
|
|
- "src/AcDream.UI.Abstractions/**"
|
|
- "src/AcDream.UI.ImGui/**"
|
|
- "tests/AcDream.Core.Tests/**"
|
|
- "tests/AcDream.Core.Net.Tests/**"
|
|
- "tests/AcDream.Content.Tests/**"
|
|
- "tests/AcDream.Runtime.Tests/**"
|
|
- "tests/AcDream.Headless.Tests/**"
|
|
- "tests/AcDream.App.Tests/**"
|
|
- "tests/AcDream.UI.Abstractions.Tests/**"
|
|
- "tools/ShaderCompiler/**"
|
|
- "tools/compile-shaders.ps1"
|
|
push:
|
|
paths:
|
|
- ".github/workflows/headless-portability.yml"
|
|
- "AcDream.slnx"
|
|
- "src/AcDream.Core/**"
|
|
- "src/AcDream.Core.Net/**"
|
|
- "src/AcDream.Content/**"
|
|
- "src/AcDream.Plugin.Abstractions/**"
|
|
- "src/AcDream.Runtime/**"
|
|
- "src/AcDream.Headless/**"
|
|
- "src/AcDream.App/**"
|
|
- "src/AcDream.UI.Abstractions/**"
|
|
- "src/AcDream.UI.ImGui/**"
|
|
- "tests/AcDream.Core.Tests/**"
|
|
- "tests/AcDream.Core.Net.Tests/**"
|
|
- "tests/AcDream.Content.Tests/**"
|
|
- "tests/AcDream.Runtime.Tests/**"
|
|
- "tests/AcDream.Headless.Tests/**"
|
|
- "tests/AcDream.App.Tests/**"
|
|
- "tests/AcDream.UI.Abstractions.Tests/**"
|
|
- "tools/ShaderCompiler/**"
|
|
- "tools/compile-shaders.ps1"
|
|
workflow_dispatch:
|
|
|
|
permissions:
|
|
contents: read
|
|
|
|
jobs:
|
|
portable-headless:
|
|
strategy:
|
|
fail-fast: false
|
|
matrix:
|
|
os: [windows-latest, ubuntu-latest]
|
|
runs-on: ${{ matrix.os }}
|
|
|
|
steps:
|
|
- name: Checkout
|
|
uses: actions/checkout@v6
|
|
|
|
- name: Install .NET 10
|
|
uses: actions/setup-dotnet@v4
|
|
with:
|
|
dotnet-version: "10.0.x"
|
|
|
|
# No apt step here on purpose. This job's whole claim is that the closure
|
|
# below is presentation-free: it builds Plugin.Abstractions, Core,
|
|
# Core.Net, Content, Runtime and Headless, runs their tests, and invokes
|
|
# the Headless CLI. Nothing in it opens a display, links GL, or calls
|
|
# xvfb-run, so an "install the graphical smoke dependencies" step here was
|
|
# both unnecessary and, being unconditional `sudo apt-get` in a
|
|
# two-operating-system matrix, fatal on the windows-latest leg (exit 127,
|
|
# `sudo: command not found`). The graphical jobs that do use xvfb-run and
|
|
# jq run only on ubuntu-latest and take both from the runner image.
|
|
- name: Build presentation-free closure
|
|
shell: pwsh
|
|
run: |
|
|
$projects = @(
|
|
"src/AcDream.Plugin.Abstractions/AcDream.Plugin.Abstractions.csproj",
|
|
"src/AcDream.Core/AcDream.Core.csproj",
|
|
"src/AcDream.Core.Net/AcDream.Core.Net.csproj",
|
|
"src/AcDream.Content/AcDream.Content.csproj",
|
|
"src/AcDream.Runtime/AcDream.Runtime.csproj",
|
|
"src/AcDream.Headless/AcDream.Headless.csproj"
|
|
)
|
|
foreach ($project in $projects) {
|
|
dotnet build $project -c Release
|
|
if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }
|
|
}
|
|
|
|
# AcDream.Core.Tests still contains historical App integration fixtures,
|
|
# so it is deliberately not a member of this no-App restore lane. Core is
|
|
# built directly above and exercised through every portable downstream
|
|
# test project below; the complete solution lane retains Core.Tests.
|
|
- name: Test presentation-free closure
|
|
shell: pwsh
|
|
run: |
|
|
$projects = @(
|
|
"tests/AcDream.Core.Net.Tests/AcDream.Core.Net.Tests.csproj",
|
|
"tests/AcDream.Content.Tests/AcDream.Content.Tests.csproj",
|
|
"tests/AcDream.Runtime.Tests/AcDream.Runtime.Tests.csproj",
|
|
"tests/AcDream.Headless.Tests/AcDream.Headless.Tests.csproj"
|
|
)
|
|
foreach ($project in $projects) {
|
|
dotnet test $project -c Release
|
|
if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }
|
|
}
|
|
|
|
- name: Verify CLI without connecting
|
|
shell: pwsh
|
|
run: |
|
|
dotnet run --project src/AcDream.Headless/AcDream.Headless.csproj -c Release -- --help
|
|
if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }
|
|
Set-Content -LiteralPath headless-k0.json -Value '{"version":1,"sessions":[]}'
|
|
dotnet run --project src/AcDream.Headless/AcDream.Headless.csproj -c Release -- validate --config headless-k0.json
|
|
if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }
|
|
|
|
linux-graphical:
|
|
runs-on: ubuntu-latest
|
|
|
|
steps:
|
|
- name: Checkout
|
|
uses: actions/checkout@v6
|
|
|
|
- name: Install .NET 10
|
|
uses: actions/setup-dotnet@v4
|
|
with:
|
|
dotnet-version: "10.0.x"
|
|
|
|
- name: Build and publish Linux graphical client
|
|
shell: pwsh
|
|
run: |
|
|
dotnet build src/AcDream.App/AcDream.App.csproj -c Release -r linux-x64
|
|
if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }
|
|
dotnet publish src/AcDream.App/AcDream.App.csproj `
|
|
-c Release `
|
|
-r linux-x64 `
|
|
--self-contained false `
|
|
-o artifacts/acdream-linux-x64
|
|
if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }
|
|
|
|
- name: Test portable graphical boundary
|
|
shell: pwsh
|
|
run: |
|
|
dotnet test `
|
|
tests/AcDream.UI.Abstractions.Tests/AcDream.UI.Abstractions.Tests.csproj `
|
|
-c Release
|
|
if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }
|
|
dotnet test `
|
|
tests/AcDream.App.Tests/AcDream.App.Tests.csproj `
|
|
-c Release `
|
|
--filter "FullyQualifiedName~LinuxMonotonicFramePacingWaiterTests|FullyQualifiedName~LinuxPlatformBoundaryTests|FullyQualifiedName~GraphicalHostPlatformServicesTests|FullyQualifiedName~GraphicalLegacyConfigurationMigratorTests|FullyQualifiedName~GraphicalWindowBackendSelectionTests|FullyQualifiedName~GraphicalCapabilityRequirementsTests|FullyQualifiedName~StudioWindowTests"
|
|
if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }
|
|
|
|
- name: Verify Linux package contract
|
|
shell: bash
|
|
run: |
|
|
set -euo pipefail
|
|
root=artifacts/acdream-linux-x64
|
|
test -x "$root/AcDream.App"
|
|
test -f "$root/AcDream.App.dll"
|
|
test -f "$root/libglfw.so.3"
|
|
test -f "$root/libopenal.so"
|
|
test -f "$root/libcimgui.so"
|
|
test -f "$root/Rendering/Shaders/mesh_modern.vert"
|
|
test -f "$root/plugins/AcDream.Plugins.Smoke/AcDream.Plugins.Smoke.dll"
|
|
test -f "$root/plugins/AcDream.Plugins.Smoke/plugin.json"
|
|
|
|
test "$(grep -RIl --include='*.cs' 'LibraryImport(\"kernel32.dll\"' \
|
|
src/AcDream.App | wc -l)" -eq 1
|
|
grep -q 'WindowsHighResolutionFramePacingWaiter.cs' < <(
|
|
grep -RIl --include='*.cs' 'LibraryImport(\"kernel32.dll\"' \
|
|
src/AcDream.App)
|
|
! grep -RIn --include='*.cs' 'LocalApplicationData' \
|
|
src/AcDream.App src/AcDream.UI.Abstractions
|
|
! grep -RIn --include='*.cs' \
|
|
'WindowsHighResolutionFramePacingWaiter.Create()' \
|
|
src/AcDream.App \
|
|
--exclude='FramePacingWaiterFactory.cs' \
|
|
--exclude='WindowsHighResolutionFramePacingWaiter.cs'
|
|
|
|
- name: Verify actionable unsupported-driver gate
|
|
shell: bash
|
|
run: |
|
|
set -euo pipefail
|
|
root=artifacts/acdream-linux-x64
|
|
report=artifacts/linux-x11-capabilities.json
|
|
set +e
|
|
ACDREAM_DISPLAY_PROTOCOL=x11 \
|
|
xvfb-run -a "$root/AcDream.App" \
|
|
ui-studio /tmp/not-needed \
|
|
--mockup \
|
|
--capability-report "$report"
|
|
code=$?
|
|
set -e
|
|
|
|
test "$code" -eq 4
|
|
test "$(jq -r '.ActiveDisplayProtocol' "$report")" = X11
|
|
test "$(jq -r '.Lifecycle.ShutdownComplete' "$report")" = true
|
|
test "$(jq -r '.Lifecycle.OwnedWindowCount' "$report")" -eq 0
|
|
test "$(jq -r '.Lifecycle.OwnedGlApiCount' "$report")" -eq 0
|
|
test "$(jq -r '.Lifecycle.OwnedInputContextCount' "$report")" -eq 0
|
|
jq -e '.SupportFailures | length > 0' "$report"
|
|
|
|
# Campaign V slice V9. The GL job above proves the unsupported-driver gate
|
|
# fires on Mesa's software OpenGL; this one proves the Vulkan backend does the
|
|
# opposite on Mesa's software Vulkan. lavapipe passes the capability gate that
|
|
# llvmpipe-GL cannot, because mandatory GL_ARB_bindless_texture has no llvmpipe
|
|
# implementation while every Vulkan feature acdream requires is core 1.3 or a
|
|
# descriptor-indexing feature lavapipe implements. That makes this the first CI
|
|
# job in the project's history that renders a frame.
|
|
#
|
|
# NOT DONE HERE, deliberately: a GL-versus-Vulkan pixel comparison. Two
|
|
# independent reasons, either of which alone is disqualifying. First, the GL
|
|
# job never produces a frame at all: it asserts exit code 4, so there is no
|
|
# left-hand side. Second, even if llvmpipe-GL could run, the probe harness
|
|
# renders synthetic verification scenes rather than the world, and the world
|
|
# needs retail DATs that CI does not have and cannot be given. The real
|
|
# GL-versus-Vulkan differential is V7's, on the developer machine, against the
|
|
# DATs, with both clocks pinned. See plan section 5.5.20.
|
|
linux-vulkan:
|
|
runs-on: ubuntu-latest
|
|
|
|
# Redirect the portable per-user roots into the workspace so the capability
|
|
# report, which the app writes to its own diagnostics directory rather than
|
|
# to a path a caller chooses, lands somewhere collectable. Slice L0 made
|
|
# these XDG-driven precisely so a host could place them.
|
|
env:
|
|
XDG_CONFIG_HOME: ${{ github.workspace }}/artifacts/xdg/config
|
|
XDG_DATA_HOME: ${{ github.workspace }}/artifacts/xdg/data
|
|
XDG_CACHE_HOME: ${{ github.workspace }}/artifacts/xdg/cache
|
|
VULKAN_REPORT: ${{ github.workspace }}/artifacts/xdg/cache/acdream/diagnostics/graphical-capabilities-vulkan.json
|
|
|
|
steps:
|
|
- name: Checkout
|
|
uses: actions/checkout@v6
|
|
|
|
- name: Install .NET 10
|
|
uses: actions/setup-dotnet@v4
|
|
with:
|
|
dotnet-version: "10.0.x"
|
|
|
|
- name: Install lavapipe, the Vulkan loader and Xvfb
|
|
shell: bash
|
|
run: |
|
|
sudo apt-get update
|
|
sudo apt-get install -y --no-install-recommends \
|
|
jq \
|
|
libvulkan1 \
|
|
mesa-vulkan-drivers \
|
|
vulkan-tools \
|
|
xauth \
|
|
xvfb
|
|
|
|
- name: Record the software Vulkan device
|
|
shell: bash
|
|
run: |
|
|
set -euo pipefail
|
|
mkdir -p artifacts
|
|
# Evidence, not configuration: no ICD is forced, because on a runner
|
|
# with no GPU lavapipe is the only one the loader can find. If that
|
|
# ever stops being true the DeviceType assertion below turns red
|
|
# rather than silently measuring different hardware, which is the
|
|
# outcome this row wants; its whole point is that the gate passes on
|
|
# the weakest conformant device in existence.
|
|
ls -l /usr/share/vulkan/icd.d/ || true
|
|
vulkaninfo --summary 2>&1 | tee artifacts/vulkaninfo-summary.txt
|
|
|
|
- name: Build and publish the Linux graphical client
|
|
shell: pwsh
|
|
run: |
|
|
dotnet publish src/AcDream.App/AcDream.App.csproj `
|
|
-c Release `
|
|
-r linux-x64 `
|
|
--self-contained false `
|
|
-o artifacts/acdream-linux-x64
|
|
if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }
|
|
|
|
- name: Test the Vulkan backend's platform-independent decisions
|
|
shell: pwsh
|
|
run: |
|
|
dotnet test `
|
|
tests/AcDream.App.Tests/AcDream.App.Tests.csproj `
|
|
-c Release `
|
|
--filter "FullyQualifiedName~AcDream.App.Tests.Rendering.Gpu.Vk"
|
|
if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }
|
|
|
|
# (a) + (b): one run, two gates. The harness opens a real window, runs the
|
|
# capability gate against a real device, and presents the V6c/V6d
|
|
# verification scenes through the real RHI. ACDREAM_VULKAN_PROBE_FRAMES is
|
|
# what makes it terminate: nothing in CI ever closes a window.
|
|
- name: Probe the Vulkan capability gate on lavapipe
|
|
shell: bash
|
|
run: |
|
|
set -euo pipefail
|
|
root=artifacts/acdream-linux-x64
|
|
out="$PWD/artifacts/vulkan-probe"
|
|
mkdir -p "$out"
|
|
|
|
# 24-bit depth explicitly: xvfb-run's default screen is 8-bit, which
|
|
# leaves the X11 WSI without a usable visual.
|
|
ACDREAM_RENDER_BACKEND=vulkan \
|
|
ACDREAM_VULKAN_PROBE=1 \
|
|
ACDREAM_VULKAN_PROBE_FRAMES=30 \
|
|
ACDREAM_AUTOMATION_ARTIFACT_DIR="$out" \
|
|
ACDREAM_DISPLAY_PROTOCOL=x11 \
|
|
ACDREAM_NO_AUDIO=1 \
|
|
xvfb-run -a -s "-screen 0 1920x1080x24" \
|
|
"$root/AcDream.App" /tmp/not-needed 2>&1 | tee artifacts/vulkan-probe.log
|
|
|
|
test -f "$VULKAN_REPORT"
|
|
cp "$VULKAN_REPORT" artifacts/vulkan-capabilities-pass.json
|
|
|
|
jq -r '"device: \(.DeviceName) (\(.DeviceType)), \(.DeviceApiVersion), \(.DriverInfo)"' \
|
|
"$VULKAN_REPORT"
|
|
|
|
# The gate accepted the device outright.
|
|
jq -e '.SupportFailures | length == 0' "$VULKAN_REPORT"
|
|
test "$(jq -r '.ActiveDisplayProtocol' "$VULKAN_REPORT")" = X11
|
|
# A software device, which is the whole point of this row.
|
|
test "$(jq -r '.DeviceType' "$VULKAN_REPORT")" = Cpu
|
|
# Vulkan 1.3 floor, unpacked from VK_MAKE_API_VERSION: major is bits
|
|
# 22+, minor is bits 12-21. Evaluate() already rejects anything lower,
|
|
# so this asserts the report agrees with the verdict rather than
|
|
# re-deriving it.
|
|
jq -e '
|
|
((.DeviceApiVersionPacked / 4194304) | floor) as $major
|
|
| (((.DeviceApiVersionPacked % 4194304) / 4096) | floor) as $minor
|
|
| $major > 1 or ($major == 1 and $minor >= 3)' "$VULKAN_REPORT"
|
|
# Advertisement is not evidence: the active probe created the device,
|
|
# built the descriptor layouts and a pipeline from committed .spv, drew
|
|
# an offscreen triangle and read the pixels back.
|
|
jq -e '.FunctionProbe.Failures | length == 0' "$VULKAN_REPORT"
|
|
jq -e '.FunctionProbe.DeviceCreation and .FunctionProbe.OffscreenReadback' \
|
|
"$VULKAN_REPORT"
|
|
|
|
- name: Assert the offline render produced a real frame
|
|
shell: bash
|
|
run: |
|
|
set -euo pipefail
|
|
png=artifacts/vulkan-probe/vulkan-bringup.png
|
|
test -f "$png"
|
|
|
|
# PNG IHDR carries the dimensions at bytes 16..23, big-endian. Reading
|
|
# them proves the capture path returned a full backbuffer rather than a
|
|
# stub, without depending on a rasterizer's pixel values.
|
|
width=$(od -An -tu4 -j16 -N4 --endian=big "$png" | tr -d ' ')
|
|
height=$(od -An -tu4 -j20 -N4 --endian=big "$png" | tr -d ' ')
|
|
bytes=$(stat -c%s "$png")
|
|
echo "captured ${width}x${height}, ${bytes} bytes"
|
|
|
|
test "$width" -ge 640
|
|
test "$height" -ge 360
|
|
# A uniform-colour frame at this size encodes to a few kilobytes. This
|
|
# threshold is the "the scene actually drew" line, and is deliberately
|
|
# a byte count rather than a pixel baseline: lavapipe and any other
|
|
# rasterizer are free to disagree about shading, and nothing in CI has
|
|
# a reference frame to disagree with.
|
|
test "$bytes" -gt 8192
|
|
|
|
# (c) The gate's failure path, exercised on a device that actually
|
|
# supports everything, so the exit-code-4 contract is proven rather than
|
|
# assumed. Same knob slice V5 built for exactly this.
|
|
- name: Verify the forced-unsupported gate exits 4
|
|
shell: bash
|
|
run: |
|
|
set -euo pipefail
|
|
root=artifacts/acdream-linux-x64
|
|
out="$PWD/artifacts/vulkan-forced"
|
|
mkdir -p "$out"
|
|
|
|
set +e
|
|
ACDREAM_RENDER_BACKEND=vulkan \
|
|
ACDREAM_VULKAN_PROBE=1 \
|
|
ACDREAM_VULKAN_PROBE_FRAMES=30 \
|
|
ACDREAM_VULKAN_FORCE_UNSUPPORTED=timelineSemaphore \
|
|
ACDREAM_AUTOMATION_ARTIFACT_DIR="$out" \
|
|
ACDREAM_DISPLAY_PROTOCOL=x11 \
|
|
ACDREAM_NO_AUDIO=1 \
|
|
xvfb-run -a -s "-screen 0 1920x1080x24" \
|
|
"$root/AcDream.App" /tmp/not-needed \
|
|
> artifacts/vulkan-forced.log 2>&1
|
|
code=$?
|
|
set -e
|
|
|
|
cat artifacts/vulkan-forced.log
|
|
test -f "$VULKAN_REPORT"
|
|
cp "$VULKAN_REPORT" artifacts/vulkan-capabilities-forced.json
|
|
|
|
test "$code" -eq 4
|
|
test "$(jq -r '.ForcedUnsupportedFeature' "$VULKAN_REPORT")" = timelineSemaphore
|
|
test "$(jq -r '.Features.TimelineSemaphore' "$VULKAN_REPORT")" = false
|
|
jq -e '.SupportFailures | length > 0' "$VULKAN_REPORT"
|
|
jq -e '.SupportFailures | any(test("timelineSemaphore"))' "$VULKAN_REPORT"
|
|
# The refusal must be actionable: the operator is told where the full
|
|
# report is, not merely that something was unsupported.
|
|
grep -q 'graphical-capabilities-vulkan.json' artifacts/vulkan-forced.log
|
|
|
|
# (d) The committed .spv are the only shaders the Vulkan backend ever
|
|
# loads. An App test already re-hashes the GLSL sources against the
|
|
# manifest, which catches "edited a shader, forgot to recompile". Nothing
|
|
# until now tied the committed BINARIES to those sources, so a stale or
|
|
# hand-edited .spv would have shipped silently. Recompiling here closes
|
|
# that, and does it on a second operating system.
|
|
- name: Verify the committed SPIR-V is fresh
|
|
shell: bash
|
|
run: |
|
|
set -euo pipefail
|
|
committed=src/AcDream.App/Rendering/Shaders/spv
|
|
fresh="$PWD/artifacts/spv-fresh"
|
|
rm -rf "$fresh"
|
|
mkdir -p "$fresh"
|
|
|
|
pwsh tools/compile-shaders.ps1 -OutputDirectory "$fresh"
|
|
|
|
# The file SET first: a .spv present in one tree and not the other is
|
|
# drift the per-file compare would never visit.
|
|
diff <(cd "$committed" && ls -1 | sort) <(cd "$fresh" && ls -1 | sort)
|
|
|
|
drift=0
|
|
for path in "$committed"/*.spv; do
|
|
name=$(basename "$path")
|
|
if ! cmp -s "$path" "$fresh/$name"; then
|
|
echo "DRIFT: $name differs from a fresh compile"
|
|
echo " committed $(sha256sum "$path" | cut -d' ' -f1) $(stat -c%s "$path") bytes"
|
|
echo " fresh $(sha256sum "$fresh/$name" | cut -d' ' -f1) $(stat -c%s "$fresh/$name") bytes"
|
|
drift=$((drift + 1))
|
|
fi
|
|
done
|
|
|
|
# The manifest is compared as JSON rather than as bytes: it is written
|
|
# with Environment.NewLine, so a byte compare would report drift for
|
|
# the operating system rather than for the shaders.
|
|
diff <(jq -S . "$committed/shaders.manifest.json") \
|
|
<(jq -S . "$fresh/shaders.manifest.json")
|
|
|
|
if [ "$drift" -ne 0 ]; then
|
|
echo "$drift .spv artifact(s) are stale."
|
|
echo "Run tools/compile-shaders.ps1 and commit the result."
|
|
exit 1
|
|
fi
|
|
echo "all committed .spv match a fresh compile"
|
|
|
|
- name: Upload Vulkan evidence
|
|
if: always()
|
|
uses: actions/upload-artifact@v4
|
|
with:
|
|
name: linux-vulkan-evidence
|
|
if-no-files-found: warn
|
|
path: |
|
|
artifacts/vulkaninfo-summary.txt
|
|
artifacts/vulkan-probe.log
|
|
artifacts/vulkan-forced.log
|
|
artifacts/vulkan-capabilities-pass.json
|
|
artifacts/vulkan-capabilities-forced.json
|
|
artifacts/vulkan-probe/*.png
|