Shader-toolchain spike handoff¶
This page records the original offline compiler spike. Current optional native compiler/API clients, live reload and headless workflows are documented in native shader worker and live development builds. That opt-in capability supersedes this spike's blanket restriction on embedding the compiler; ordinary renderer/runtime targets remain compiler-independent.
Selection and boundary¶
Slang 2026.1.2 succeeds for this probe's vertex and fragment shaders on both
SPIR-V and WGSL. Use the CLI at build time, and package only generated assets.
Do not link Slang, DXC, or a validator into gameplay or download tools at runtime.
The separate lock is config/shader_toolchain.json; Clang 18 and
config/web_toolchain.json are unchanged.
The inspected checkout's installed RHI has lifecycle, surface/frame, and browser
clear operations, with no public shader/resource/pipeline API. The smoke app
still privately owns a WGSL triangle pipeline via internal/webgpu_probe.h.
This spike instead owns direct Vulkan and browser WebGPU resources under
tools/shader-probe/. Vulkan is headless/offscreen; the browser harness is plain
JS and also presents an animated canvas. Neither proves the RHI's swapchain or
Emscripten upload path. No probe headers, assets, compiler, or target are exported
or installed. An external game must wait for a separately scoped public
shader/resource/pipeline API to integrate its generated assets with Ludus.
Reproducible tools and host support¶
Run the explicit network acquisition step on Linux x86_64, Ubuntu 24.04:
./scripts/shader-probe bootstrap
npm ci --prefix tools/web-browser-tests
tools/web-browser-tests/node_modules/.bin/playwright install chromium
The Slang archive is the official slang-2026.1.2-linux-x86_64-glibc-2.27.tar.gz:
SHA256 bcffdccca2c16057d39f290a07f6f237b9a765238afc1974b68d09762e874b09.
The digest was matched against the official release API asset digest and the
acquired bytes. Bootstrap verifies before extraction, bounds extraction to its
own directory, and retains the archive/LICENSE. Slang reports 2026.1.2.
Its license is Apache-2.0 WITH LLVM-exception; the supplied license describes
embedded compilation-output permissions. Preserve licenses/notices for tool
redistribution and review upstream third-party notices as appropriate.
The selected Slang release also publishes Windows x86_64/aarch64, macOS x86_64/aarch64, Linux aarch64, and wasm packages. Only the Linux x86_64 package is pinned and exercised here. Its glibc floor is 2.27; this combined bootstrap has a 2.38 floor because the validator package needs glibc 2.38 and libstdc++ 13.1. Other hosts need separately pinned assets and verification before being supported.
SPIRV-Tools is the Ubuntu package 2025.1~rc1-1~ubuntu0.24.04.2; the executable
reports SPIRV-Tools v2025.1. Package SHA256:
529e396c337d15beb8ed0f03fcbeb2a066e5a818e032e197b6ef2d3328dc7ae9.
Bootstrap extracts it locally without a system install. Every compile verifies
spirv-val and spirv-dis executable digests against the lock. Licensing is
Apache-2.0; package notices are retained under usr/share/doc/spirv-tools.
WGSL is validated by Chromium 140.0.7339.186 / revision 1193, obtained through
integrity-locked Playwright 1.55.1 in tools/web-browser-tests/package-lock.json.
The runner rejects a different browser version. Chromium's Dawn/Tint compiler
provides getCompilationInfo() and actual createRenderPipelineAsync() validation.
Playwright is Apache-2.0; Chromium has BSD and bundled third-party notices.
These browser/test tools are not shipped. Recorded local Node was 22.23.1.
All compiler tools live under out/shader-tools/. Builds neither acquire tools
nor invoke Conan/network downloads. Native Vulkan headers/Volk come from the
existing Conan pins. A Vulkan loader and usable adapter are runtime prerequisites.
Exact build and validation commands¶
Prepare ordinary Ludus native dependencies with ./init.sh if missing, then:
out/host-tools/venv/bin/cmake --preset linux-clang-development \
-DLUDUS_BUILD_SHADER_PROBE=ON -DLUDUS_WARNINGS_AS_ERRORS=ON \
-DLUDUS_ENABLE_TIME_TRACE=OFF
./scripts/build linux-clang-development
out/build/linux-clang-development/tools/shader-probe/ludus_shader_probe
out/host-tools/venv/bin/ctest --test-dir out/build/linux-clang-development \
-R ludus_shader_probe --output-on-failure
./scripts/shader-probe check
CMake generates into out/build/linux-clang-development/tools/shader-probe/generated.
For just the assets use ./scripts/shader-probe compile (default out/shader-probe),
or compile --output PATH. It checks reflection, emitted layout/bindings, and
SPIR-V validity before emitting the C++ header. CMake reruns it when the source,
lock, script, or browser harness changes. Exact expanded CLI commands, tool pins,
source/artifact hashes, reflection, disassembly, and contract.json accompany
each build in the output directory. These are compiler evidence, not runtime results.
The exact compilation recipe, with S=tools/shader-probe/probe.slang,
C=out/shader-tools/slang/bin/slangc, and O=out/shader-probe:
S=tools/shader-probe/probe.slang
C=out/shader-tools/slang/bin/slangc
O=out/shader-probe
$C $S -target spirv -profile spirv_1_3 -entry vertexMain -stage vertex \
-o $O/probe.vertex.spv -reflection-json $O/vertex.reflection.json
$C $S -target spirv -profile spirv_1_3 -entry fragmentMain -stage fragment \
-o $O/probe.fragment.spv -reflection-json $O/fragment.reflection.json
$C $S -target wgsl -profile sm_6_0 -entry vertexMain -entry fragmentMain \
-o $O/probe.wgsl -reflection-json $O/wgsl.reflection.json
out/shader-tools/spirv-tools/usr/bin/spirv-val --target-env vulkan1.1 $O/probe.vertex.spv
out/shader-tools/spirv-tools/usr/bin/spirv-val --target-env vulkan1.1 $O/probe.fragment.spv
out/shader-tools/spirv-tools/usr/bin/spirv-dis $O/probe.fragment.spv
The wrapper supplies these paths automatically. WGSL's entries
have [shader] stage annotations in the source. sm_6_0 is the Slang frontend
profile, not a claim that WebGPU supports every Shader Model 6 feature.
Browser real software-GPU test:
LUDUS_SHADER_PROBE_SOFTWARE=1 node tools/shader-probe/browser-test.mjs
This consumes the native build's generated folder by default. It writes
out/shader-probe-browser/report.json and probe.png. A different folder can be
its first argument; output directory is its second. Omit the environment variable
for the default adapter/flag-free test; failure remains failure. For manual
hardware testing serve the generated directory over localhost/HTTPS and open it:
python3 -m http.server 8768 --bind 127.0.0.1 \
--directory out/build/linux-clang-development/tools/shader-probe/generated
Expect eight readback cases to pass, then a round blue circle over a red/right, green/down gradient with changing color. Save the visible report, console, and adapter/device/browser/OS details. Native hardware uses the same executable; it logs the selected adapter (currently the first enumerated graphics adapter). A software adapter's success does not close a physical-GPU gate.
Binding and independent layout contract¶
The source declares [[vk::binding(0, 0)]] ConstantBuffer<ProbeUniforms> probe.
Vulkan uses descriptor set 0, binding 0, type UNIFORM_BUFFER, fragment
visibility. Browser WebGPU uses group 0, binding 0, uniform buffer, fragment
visibility, minBindingSize=48, range 48, offset 0. No dynamic offsets or vertex
buffers. The vertex stage uses vertex index to generate a fullscreen triangle.
| Source entry | Emitted SPIR-V entry | Emitted WGSL entry |
|---|---|---|
| vertexMain | main (vertex module) | vertexMain |
| fragmentMain | main (fragment module) | fragmentMain |
Fragment output is location 0; fragment position uses SPIR-V OriginUpperLeft and WGSL builtin position. Vulkan uses a positive-height viewport and no culling. Both uploaded resolutions are framebuffer pixels, not CSS dimensions.
| Field | Source type | SPIR-V offset/size | WGSL offset/size |
|---|---|---|---|
| resolution | float2 | 0 / 8 | 0 / 8 |
| elapsedTime | float | 8 / 4 | 8 / 4 |
| direction | float3 | 16 / 12 | 16 / 12 |
| tint | float4 | 32 / 16 | 32 / 16 |
Both complete blocks are 48 bytes, 16-byte alignment for this source.
Unused bytes 12–15 and 28–31 are zeroed on upload. Vulkan's emitted std140 struct,
member decorations, vector types, and per-target reflection establish its
contract. WGSL is checked separately from emitted member types and @align
annotations: resolution 16, elapsedTime 8, direction 16, tint 16. Applying WGSL
AlignOf/SizeOf yields the table and the rounded struct size independently of
Slang reflection. See the WGSL layout specification.
uniform_layout.h declares separate VulkanUniforms and WebGpuUniforms, each
with explicit padding plus standard-layout/offsetof/sizeof/alignof assertions.
Clang 18 compiles both checks. Browser CPU uploads use DataView little-endian
float writes at the separately verified WGSL offsets; this spike does not test
an Emscripten C++ upload struct. Equality here is measured, not an assumption of
HLSL/SPIR-V/WGSL packing equivalence. Dynamic binding alignment is a separate
backend limit, not this struct's 16-byte alignment. The verifier deliberately
supports only this shape and fails closed if it changes.
Exercised subset and outcomes¶
The supported evidence is 32-bit floating scalar/vector arithmetic, one mixed uniform block, vertex index, builtin fragment position, fullscreen triangle, and one RGBA output. No matrices, arrays in uniforms, interpolated varyings, textures/samplers, storage buffers, push constants, f16, subgroup operations, compute, depth, blending, or culling are proven. No DXC evaluation was needed because the preferred Slang path passed the actual shaders and pipelines.
On October 1, 2026 (Toronto), Vulkan on llvmpipe LLVM 20.1.2 / Mesa
25.2.8-0ubuntu0.24.04.2 and Chromium's SwiftShader passed 8/8 cases each.
Each renders all 6,144 pixels at 96×64 and 64×96, at elapsed 0 and 2, through
RGBA8 UNORM and RGBA8 sRGB targets. Every RGBA channel is checked against an
independent numerical expectation with a tolerance of two levels on each 8-bit channel. Browser
maximum observed error was 0; native mismatches beyond tolerance were 0.
The elapsed update changes RGB by the uploaded direction times elapsed×0.05.
The circle radius is 0.2×framebuffer height in both dimensions, so the portrait
and landscape comparisons check aspect correction. The spatial red/green
oracle detects a flipped readback/origin. sRGB targets check standard linear to
sRGB encoding; UNORM checks linear quantization. Canvas presentation uses the
preferred bgra8unorm; browser/monitor color management is not established by
the offscreen color tests.
Chromium reported no compilation or validation errors and continued animating
23 frames in the first captured report. Its launch flags are recorded in the
report and match the existing software-GPU runner. No mock/provider is used.
The run without software-GPU flags failed with No WebGPU adapter; it
rendered zero frames and is recorded separately, without a fallback. Physical
/dev/dri was unavailable. Vulkan validation layers were not installed;
SPIRV-Tools validation, Vulkan return codes and pixel readback are the Vulkan
evidence. Remaining gates: real native Vulkan GPU, flag-free browser WebGPU on
physical GPUs, target browser/OS combinations and hosted HTTPS/iframe behavior,
plus future public RHI integration. A compact committed
evidence snapshot records the exact shader
hashes, commands, contracts, eight-case results, and default-browser failure.
Repository checks¶
Validation commands and final results are recorded below after the run. Local
raw logs are under out/shader-probe-*.log; generated artifacts and the browser
report remain available under out/ and are intentionally not committed.
| Command | Outcome |
|---|---|
./scripts/shader-probe bootstrap |
Passed: verified cached official archives and re-extracted tools; version and validator digests matched. |
./scripts/shader-probe compile |
Passed: both SPIR-V stages validated for Vulkan 1.1; WGSL emitted; per-target contract checks passed. |
./scripts/shader-probe check |
Passed with clang-format/clang-tidy 18, actual native compile flags, and private probe header coverage. |
python3 tools/shader-probe/verify_contract.py out/shader-probe |
3 tests passed, including rejection of reflection offset/size/binding drift and emitted WGSL alignment/binding/entry drift. |
./scripts/build linux-clang-debug |
Passed. |
./scripts/test linux-clang-debug |
29 registered tests, zero failures; 2 live Wayland tests skipped. |
./scripts/build linux-clang-development |
Passed with warnings as errors and the probe enabled. |
./scripts/test linux-clang-development |
30 registered tests, zero failures; 2 live Wayland tests skipped; both probe CTests passed. |
./scripts/build linux-clang-asan-ubsan |
Passed with warnings as errors and the probe enabled. |
./scripts/test linux-clang-asan-ubsan |
27 registered tests, zero failures; 2 live Wayland tests skipped; probe pixel and contract tests passed, with no ASan/UBSan/LSan findings. |
LUDUS_SHADER_PROBE_SOFTWARE=1 node tools/shader-probe/browser-test.mjs out/shader-probe |
Passed: pinned Chromium/SwiftShader, eight readbacks plus animated presentation, no compilation/validation errors. |
node tools/shader-probe/browser-test.mjs out/shader-probe out/shader-probe-browser-default |
Failed: no WebGPU adapter without flags; hardware/flag-free gate remains open. |
./scripts/install-sdk linux-clang-development |
Refused the existing assertion-policy-v1 prefix, preserving it. Clean-prefix install and external consumer passed as described below. |
./scripts/check linux-clang-development --all |
Passed: repository formatting, include boundary, and clang-tidy 18 after disabling cached time tracing. |
Final cmake --build ... --target ludus_shader_probe -j2 and ctest --test-dir ... -R ludus_shader_probe --output-on-failure in Development and ASan/UBSan |
Passed after final array-index type adjustments: 2/2 tests in each tree. |
git diff --check |
Passed. |
Initial sandboxed test runs failed because socket/process inspection was denied;
LeakSanitizer could not inspect threads. The successful native suites and SDK
consumer were rerun outside those sandbox restrictions, without disabling leak
checks. Browser execution also needed localhost sockets and a browser process
outside the sandbox. Initial repository tidy rejected the pre-existing cached
-ftime-trace option; reconfigure with -DLUDUS_ENABLE_TIME_TRACE=OFF for analysis.
The tools-specific check removes only Ninja's module response-file argument and
the compiler profiling flag while preserving all warning/exception/include flags.
To preserve the older SDK, validation used this separate prefix and consumer:
out/host-tools/venv/bin/cmake --install out/build/linux-clang-development \
--prefix "$PWD/out/install/shader-probe-development"
out/host-tools/venv/bin/cmake -S tests/sdk_consumer \
-B out/build/shader-probe-sdk-consumer -G Ninja \
-DCMAKE_MAKE_PROGRAM="$PWD/out/host-tools/venv/bin/ninja" \
-DCMAKE_CXX_COMPILER="$PWD/out/host-tools/bin/clang++" \
-DCMAKE_BUILD_TYPE=RelWithDebInfo \
-DCMAKE_PREFIX_PATH="$PWD/out/install/shader-probe-development;$PWD/out/conan/linux-clang-development"
out/host-tools/venv/bin/cmake --build out/build/shader-probe-sdk-consumer -j2
out/build/shader-probe-sdk-consumer/ludus_sdk_consumer
The existing engine.verify_sdk_install check confirmed installed files,
manifest/policy, header privacy, and absence of source/build paths in exports.
Additional inspection found no shader-probe files/targets or Slang dependencies
in the installed SDK. The consumer reported Development policy version 2 and
passed its installed Input/RHI checks. The probe executable's runtime dependency
list contains no shader compiler libraries.