The first CI job in this project's history that renders a frame.
The whole row rests on a decision V6g already made and paid for. When
section 5.5.8 cut set 0 from ten dynamic storage descriptors to four, four
was not merely under the RX 9070 XT's eight - it is Vulkan's guaranteed
minimum, so no conformant device can fail the layout. That is what makes a
software-device row possible at all. Every other requirement was then
checked against Mesa's lvp_device.c rather than assumed, and all seventeen
features the gate demands are true on lavapipe - including
samplerAnisotropy, which V7 made load-bearing eight commits ago and which a
software rasterizer would have been entirely within its rights to decline.
Three things had to exist before the job could:
1. The harness could not stop. VulkanBringUpHost presents until its window
closes, which is right at a desk and impossible in CI, where nothing ever
closes a window. ACDREAM_VULKAN_PROBE_FRAMES gives it a budget; unset or
malformed is zero, which keeps the interactive behaviour, so no existing
invocation changes. The budget never cuts the capture short - the loop
stays open until the screenshot has been attempted - because a run whose
entire product is a PNG must not be able to exit green with an empty
artifact directory. The decision is a pure static method, tested without
a window or a driver.
2. tools/compile-shaders.ps1 was Windows-only and nobody had noticed,
because nothing had ever run it anywhere else. It built its paths from
embedded 'src\AcDream.App\...' literals; a backslash is a separator on
Windows and an ordinary filename character everywhere else, so on Linux
that is one long nonexistent file name.
3. The report's jq paths were invisible to the compiler. Renaming a record
property or swapping the enum converter would have left every test green
and turned CI red on someone else's branch days later, with a failure
that reads like a driver problem. VulkanCapabilityReportContractTests
pins the exact strings the job greps and pins its packed-version
arithmetic against VulkanApiVersion's own unpacking.
The job, eleven steps: install lavapipe and Xvfb; record vulkaninfo as
evidence; publish linux-x64; run the Gpu.Vk tests on a second operating
system; probe the gate under a 24-bit Xvfb screen (the default is 8-bit,
which leaves the X11 WSI without a usable visual) and assert an accepting
verdict on a Cpu device at API >= 1.3 with a clean active probe; assert the
captured PNG is a real frame by IHDR dimensions and byte count; re-run with
ACDREAM_VULKAN_FORCE_UNSUPPORTED=timelineSemaphore and assert exit 4 with an
actionable refusal; recompile the shaders and compare. Artifacts upload on
always(), so a red run ships its own diagnosis.
The .spv step is what ties the committed binaries to their sources. The
existing App test hashes GLSL against the manifest, which catches "edited a
shader, forgot to recompile"; nothing caught a stale or hand-edited .spv.
Verified on Windows before shipping: 19/19 artifacts byte-identical to a
fresh compile, zero drift.
No GL-versus-Vulkan pixel compare, for two independent reasons recorded in
section 5.5.20: linux-graphical asserts exit 4, so there is no left-hand
side, and the probe renders synthetic scenes rather than the DAT world CI
cannot have. The two jobs now say something sharper than a pixel diff would
have - on the same software Mesa stack, GL is refused and Vulkan is accepted
and draws. Physical Linux GPU and Wayland rows stay deferred on the Slice L
precedent; no hosted runner offers either.
Gates: Release build green, zero errors. App tests 4,152 / 3 skipped against
a 4,134 / 3 baseline at this branch's base (9b7f4343) - eighteen new, all
from this slice. Workflow validated by a real YAML parse plus an Actions
schema check and bash -n over all nine extracted run blocks; no actionlint
was available locally and none was downloaded. The job itself has not run:
its first execution is the CI run this commit triggers, and the V9 row stays
partial until that is green.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
461 lines
19 KiB
YAML
461 lines
19 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"
|
|
|
|
- name: Install Linux graphical smoke dependencies
|
|
shell: bash
|
|
run: |
|
|
sudo apt-get update
|
|
sudo apt-get install -y --no-install-recommends \
|
|
libgl1-mesa-dri \
|
|
mesa-utils \
|
|
xauth \
|
|
xvfb
|
|
|
|
- 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
|