A new shading language enters the ring
Unlike other 3D graphics apis, Vulkan did decouple human readable shaders from the api using SPIR-V as an intermediate representation. This makes it possible to use different shader language front-ends for writing your shaders, with the only requirement being to be able to compile that shading language to valid SPIR-V with Vulkan semantics.
This opens up Vulkan for shading languages other than glsl and made it possible to also add hlsl to most of my samples (back in 2020).
Though both languages have their issues. For glsl it’s mostly the language itself, lacking many language features and properties you’d expect from an up-to-date language. hlsl is much better on the language side of things, but it’s owned by Microsoft. And while they’ve been very open and even announced SPIR-V support for DX a few months ago, things are moving slow, and bugs often stay open for a long time. That’s also the reason why not all of my samples come with hlsl shaders. And one issue both share is the non-optimal tooling support. There are no official syntax highlighter and linters for glsl. For hlsl there are the ones from Microsoft, but they don’t support the Vulkan specific syntax additions. In general I always wondered why tooling support for current shading languages is so lackluster.
But I’m obviously not the only one seeing those issues. So some time ago researchers from NVIDIA, Carnegie Mellon University, Stanford, MIT, UCSD and the University of Washington teamed up to create the Slang shading language as a modern language with support for multiple graphics apis including Vulkan.
And at the end of 2024, Khronos launched the Slang initiative with the aim of getting Slang into stable open source hands. That’s an important step in making Slang available to (among others) Vulkan developers while also being able to steer things more directly (unlike with hlsl, where this is not possible).
Slang itself is a superset of hlsl, so the language looks very similar but comes with a lot of interesting additions. Both in terms of functionality and syntax. I already did play around with Slang in some demo projects and was impressed with the language itself and (esp. compared to HLSL) and it’s Vulkan implementation. Both glsl and hlsl can be a bit frustrating at times, and Slang somehow made me enjoy writing shaders again. More on this later.
So I decided to add Slang as a third shading language option to my Vulkan Samples. The repository contains almost a hundred samples, adding Slang shaders for all of them took me roughly 4 months in my spare time. This resulted in 170 shader files, many of which combine multiple shader stages (which is something not possible with e.g. glsl), resulting in over 300 SPIR-V files. If you’re interested in the changes, you can find look at the pull request.
With my samples now offering shaders in glsl, hlsl and Slang this repository could help people choose the right shading language and learn their respective syntax. Additional details can be found in this folder.
Example shader
To give you an impression of how a Slang shader looks, here is the stripped down shader for the basic ray tracing sample:
RaytracingAccelerationStructure accelStruct;
RWTexture2D<float4> image;
struct CameraProperties
{
float4x4 viewInverse;
float4x4 projInverse;
};
ConstantBuffer<CameraProperties> cam;
struct Attributes
{
float2 bary;
};
struct Payload
{
float3 hitValue;
};
[shader("raygeneration")]
void raygenerationMain()
{
uint3 LaunchID = DispatchRaysIndex();
uint3 LaunchSize = DispatchRaysDimensions();
const float2 pixelCenter = float2(LaunchID.xy) + float2(0.5, 0.5);
const float2 inUV = pixelCenter/float2(LaunchSize.xy);
float2 d = inUV * 2.0 - 1.0;
float4 target = mul(cam.projInverse, float4(d.x, d.y, 1, 1));
RayDesc rayDesc;
rayDesc.Origin = mul(cam.viewInverse, float4(0,0,0,1)).xyz;
rayDesc.Direction = mul(cam.viewInverse, float4(normalize(target.xyz), 0)).xyz;
rayDesc.TMin = 0.001;
rayDesc.TMax = 10000.0;
Payload payload;
TraceRay(accelStruct, RAY_FLAG_FORCE_OPAQUE, 0xff, 0, 0, 0, rayDesc, payload);
image[int2(LaunchID.xy)] = float4(payload.hitValue, 0.0);
}
[shader("closesthit")]
void closesthitMain(inout Payload p, in Attributes attribs)
{
const float3 barycentricCoords = float3(1.0f - attribs.bary.x - attribs.bary.y, attribs.bary.x, attribs.bary.y);
p.hitValue = barycentricCoords;
}
[shader("miss")]
void missMain(inout Payload p)
{
p.hitValue = float3(0.0, 0.0, 0.2);
}
Points of interest:
- All shader stages in a single file
- Bindings are deduced from the ordering, no need to explicitly state them in many cases
My impressions
I’ve been writing GPU shaders (or programs how they were called back than and should be again 😉) since the OpenGL ARB shaders were introduced in 2002. I even worked on maybe one of the first high-level shading languages using Pascal syntax. And while I’m not professionally writing shaders I do consider myself to have at least some experience, esp. when it comes down to the tooling side of things. As long as I can remember, it has always been suboptimal, esp. if you compare that to how good IDEs and debuggers on the CPU side have become.
glsl hasn’t evolved as a language at all for ages (and probably never will), hlsl, while nice, is owned by Microsoft and while it can be used for Vulkan it always felt kinda odd. Not only because it often lacks behind glsl when it comes to new features, but also because some Vulkan related things are implemented in very odd ways. Being able to use SPIR-V intrinsics is nice from a technical point-of-view, but not from the perspective of someone wanting to write understandable shaders. Where both also lack is IDE tooling and documentation. The glsl documentation improved a lot since we launched the new Vulkan docs site including the glsl spec, but still it’s missing things. And hlsl documentation is all over the place with even official resources on it sometimes being outdated.
And Slang (or respectively the Slang team) improves all of this, removing a lot of the frustration I had writing shaders with glsl and hlsl. The language being a superset of HLSL is nice, with lots of modern C++ features including modules. It comes with a standard library that offers things like a direct access to PI (yes, that’s a thing with other shading languages) and has good documentation. And the tooling was very good from the get-go. I use Visual Studio and Visual Studio code, and the official syntax highlighting and code completion addons provided are working very well. And there are a log of convenience features that make writing shaders much more enjoyable. Two of my favorites: Being able to put multiple shader stages in a single file, automatic deduction of bindings based on ordering, so no more need to explicitly state sets and bindings. I’m also impressed with how fast the Slang developers react to issues. Unlike DXC (the hlsl compiler), where I’ve been waiting for years to get things fixed, Issues I raised with Slang were fixed swiftly.
Running samples with Slang shaders
As noted above, all samples come with Slang shaders. If you’re using Visual Studio, you can easily access the Slang shaders from the solution file.
Shading language is selected via the “-s” command line argument. So for Slang, you need to run the sample with “-s slang”.
A note if you want to compile Slang shaders: The samples contain offline compiled SPIR-V files. If you want to play around with the Slang shaders and need to compile them, please use the latest Slang compiler release from here. The one in the latest LunarG Vulkan SDK is a bit outdated, and is missing some crucial fixes.