04 July 2026
Four shader stages, one file
Ray generation, miss, closest hit and a compute pass all have to agree about a material — so in Rayxix they live next to each other.
A Vulkan ray tracing pipeline is not one program. It is a set of entry points the driver calls at different moments — you generate a ray, and depending on what it meets the hardware dispatches into a closest hit shader or a miss shader, neither of which you called.
The conventional layout gives each of those its own file. That is tidy right up until you add a field to the material struct, and then it is four edits which the compiler will only complain about if you are lucky.
One file, four exports
Slang lets all four be entry points in the same translation unit:
[shader("raygeneration")] void rayGen() { ... }
[shader("miss")] void miss(inout Payload p) { ... }
[shader("closesthit")] void closestHit(inout Payload p, ...) { ... }
[shader("compute")] void denoise() { ... }CMake compiles that one file into four SPIR-V modules and copies them beside the executable. Material, Payload and PushConstants are declared once, so a change to a material is a change in one place and every stage that reads it either compiles or does not.
What it costs
Two things, and both are real:
- The file is long. Fifteen hundred lines. It wants folding and it wants discipline about section comments, because there is no directory structure doing that job for you.
- Compile times are all-or-nothing. Touch the denoiser and the ray generation module rebuilds too. At this size it is under a second, so it has not been worth splitting; at ten times the size it would be.
The rule I would keep: things that must agree should be adjacent. Everything else can be anywhere.
The same argument turns up on the other side of this site, incidentally — one TSL node graph compiles to both WGSL and GLSL there for exactly the same reason.
© 2026 Tauras Giedraitis · Kaunas, Lithuania · All rights reserved