While Vulkan itself consumes shaders in a binary format called SPIR-V, shaders are usually written in a high level language. This section provides a mapping between shader functionality for the most common ones used with Vulkan: GLSL, HLSL, and Slang. This is mostly aimed at people wanting to migrate from one high level shader language to another. It’s meant as a starting point and not as a complete porting guide to one language from another.
|
Tip
|
For more details on using HLSL with Vulkan, visit this chapter. For more details on using Slang with Vulkan, visit this chapter. |
|
Note
|
The following listings are by no means complete, and mappings for newer extensions may be missing. Also note that concepts do not always map 1:1 between languages. E.g. there are no semantics in GLSL, while some newer GLSL functionality may not (yet) be available in HLSL or Slang. Slang is largely compatible with HLSL 2018 but adds several advanced features like generics, interfaces, and modules. In most cases where HLSL syntax is shown, Slang supports the same syntax, but may offer additional options. |
In GLSL extensions need to be explicitly enabled using the #extension directive. This is not necessary in HLSL or Slang. Both compilers will implicitly select suitable SPIR-V extensions based on the shader.
For HLSL, if required one can use -fspv-extension arguments to explicitly select extensions.
For Slang, extensions can be explicitly enabled using command-line options like -D or through the Slang API. Slang also supports SPIR-V intrinsics similar to HLSL.
|
Note
|
Types work similarly across GLSL, HLSL, and Slang. GLSL has explicit vector or matrix types, while HLSL and Slang use basic types with numeric suffixes. HLSL offers some advanced type features like C++ templates, and Slang extends this further with full generic support. This section contains a basic summary with examples to show type differences between the languages. |
| GLSL | HLSL | Example | Slang-specific |
|---|---|---|---|
vecn |
floatn |
vec4 → float4 |
|
ivecn |
intn |
ivec3 → int3 |
|
matnxm or shorthand matn |
floatnxm |
mat4 → float4x4 |
|
n/a |
n/a |
interface types |
|
n/a |
limited templates |
full generics support |
|
Note
|
Slang uses the same basic data types as HLSL (floatn, intn, floatnxm, etc.) but adds several advanced features like interfaces and full generic support. |
The syntax for casting types differs:
GLSL:
mat4x3 mat = mat4x3(ubo.view);HLSL:
float4x3 mat = (float4x3)(ubo.view);Slang:
float4x3 mat = (float4x3)(ubo.view);
// Or using generics
let mat = cast<float4x3>(ubo.view);It is important to be mindful that matrices in GLSL are column-major, while matrices in HLSL and Slang are row-major by default. This affects things like matrix construction.
Slang extends HLSL’s type system with several advanced features:
Slang supports interfaces similar to C# or Java:
interface IMaterial {
float3 evaluateBRDF(float3 viewDir, float3 lightDir, float3 normal);
}
struct LambertianMaterial : IMaterial {
float3 albedo;
float3 evaluateBRDF(float3 viewDir, float3 lightDir, float3 normal) {
return albedo * max(0, dot(normal, lightDir)) / 3.14159;
}
}For Vulkan concepts that are not available in DirectX, an implicit namespace has been added that marks Vulkan specific features.
Slang supports the same vk namespace as HLSL for Vulkan-specific functionality. This allows Slang code to use the same Vulkan-specific attributes and functions as HLSL:
// Binding a resource in Slang using vk namespace
[[vk::binding(0, 0)]]
Texture2D albedoMap;
// Push constants in Slang
struct PushConstants {
float4x4 transform;
};
[[vk::push_constant]]
PushConstants pushConstants;In addition to supporting the HLSL vk namespace, Slang also provides its own parameter block system that can be used alongside the vk namespace for more organized resource binding (see the Parameter Blocks section below).
When using DXC to compile HLSL to SPIR-V you can use the __spirv__ macro for Vulkan specific code. This is useful if HLSL shaders need to work with both Vulkan and D3D:
#ifdef __spirv__
[[vk::binding(0, 1)]]
#endif
ConstantBuffer<Node> node : register(b0, space1);Slang provides similar conditional compilation capabilities, but with more options for cross-API development. Slang does not automatically define target-specific preprocessor macros (preprocessing runs once, regardless of how many targets you compile for), so any target macro must be passed on the command line with -D:
// TARGET_VULKAN is defined on the command line,
// e.g. `slangc -target spirv -DTARGET_VULKAN=1 ...`
#ifdef TARGET_VULKAN
[[vk::binding(0, 1)]]
#endif
ConstantBuffer<Node> node : register(b0, space1);
// Using Slang's target-agnostic parameter blocks instead avoids
// the need for conditional compilation altogether
ParameterBlock<Resources> resources;Slang’s multi-target compilation system allows you to write shaders that can be compiled for multiple graphics APIs from a single source file, with conditional compilation to handle API-specific differences where parameter blocks aren’t a good fit.
DXC supports SPIR-V intrinsics with the GL_EXT_spirv_intrinsics extension. This adds support for embedding arbitrary SPIR-V in the middle of GLSL for features not available in DirectX. For this new keywords are added to the vk namespace that map SPIR-V opcodes, incl. vk::ext_extension, vk::ext_capability, vk::ext_builtin_input, vk::ext_execution_mode and vk::ext_instruction.
Example for using the stencil export SPIR-V extension in HLSL:
[[vk::ext_capability(/* StencilExportEXT */ 5013)]]
[[vk::ext_extension("SPV_EXT_shader_stencil_export")]]
vk::ext_execution_mode(/* StencilRefReplacingEXT */ 5027);Example for setting up the built-in to access vertex positions in ray tracing:
[[vk::ext_extension("SPV_KHR_ray_tracing_position_fetch")]]
[[vk::ext_capability(RayTracingPositionFetchKHR)]]
[[vk::ext_builtin_input(HitTriangleVertexPositionsKHR)]]
const static float3 gl_HitTriangleVertexPositions[3];Slang supports the same SPIR-V intrinsics capabilities as HLSL, allowing you to access Vulkan-specific features that don’t have direct mappings in the language. The syntax is identical to HLSL:
[[vk::ext_capability(/* StencilExportEXT */ 5013)]]
[[vk::ext_extension("SPV_EXT_shader_stencil_export")]]
vk::ext_execution_mode(/* StencilRefReplacingEXT */ 5027);In addition, Slang provides a more structured approach to extension capabilities through its module system, allowing you to encapsulate extension-specific code in dedicated modules:
// In a dedicated module for stencil export functionality
module Extensions.StencilExport;
[[vk::ext_capability(/* StencilExportEXT */ 5013)]]
[[vk::ext_extension("SPV_EXT_shader_stencil_export")]]
vk::ext_execution_mode(/* StencilRefReplacingEXT */ 5027);
public void writeStencil(uint value) {
// Implementation using stencil export
}
// In main shader code
import Extensions.StencilExport;
void main() {
// Use the extension functionality
writeStencil(42);
}|
Note
|
While GLSL makes heavy use of input and output variables built into the languages called "built-ins", there is no such concept in HLSL or Slang. HLSL and Slang instead use semantics, strings that are attached to inputs or outputs that contain information about the intended use of that variable. They are prefixed with Slang follows HLSL’s semantic-based approach but provides additional features through its interface system that can abstract away some of the semantic details. |
Slang allows you to define interfaces that can abstract away some of the semantic details:
// Define a vertex shader interface
interface IVertexShader
{
// Input structure with semantics
struct Input
{
float3 position;
float3 normal;
float2 texCoord;
};
// Output structure with semantics
struct Output
{
float4 position : SV_POSITION;
float3 worldPos : POSITION0;
float3 normal : NORMAL0;
float2 texCoord : TEXCOORD0;
};
// Vertex shader function
Output computeVertex(Input input);
}
// Implement the interface
struct StandardVertexShader : IVertexShader
{
// Implementation of the vertex shader function
IVertexShader.Output computeVertex(IVertexShader.Input input)
{
IVertexShader.Output output;
// Implementation...
return output;
}
}
// Use the interface in a shader entry point
[shader("vertex")]
IVertexShader.Output vertexMain(IVertexShader.Input input)
{
StandardVertexShader vertexShader;
return vertexShader.computeVertex(input);
}This approach allows for more modular and reusable shader code while still leveraging the semantic system.
Writing positions from the vertex shader:
GLSL:
layout (location = 0) in vec4 inPos;
void main() {
// The vertex output position is written to the gl_Position built-in
gl_Position = ubo.projectionMatrix * ubo.viewMatrix * ubo.modelMatrix * inPos.xyz;
}HLSL:
struct VSOutput
{
// The SV_POSITION semantic declares the Pos member as the vertex output position
float4 Pos : SV_POSITION;
};
VSOutput main(VSInput input)
{
VSOutput output = (VSOutput)0;
output.Pos = mul(ubo.projectionMatrix, mul(ubo.viewMatrix, mul(ubo.modelMatrix, input.Pos)));
return output;
}Slang:
struct VSOutput
{
// Same as HLSL, using SV_POSITION semantic
float4 Pos : SV_POSITION;
};
// Standard approach (identical to HLSL)
VSOutput main(VSInput input)
{
VSOutput output = (VSOutput)0;
output.Pos = mul(ubo.projectionMatrix, mul(ubo.viewMatrix, mul(ubo.modelMatrix, input.Pos)));
return output;
}
// Alternative using interfaces
interface IVertexTransform
{
float4x4 getTransform();
}
struct StandardTransform : IVertexTransform
{
float4x4 projectionMatrix;
float4x4 viewMatrix;
float4x4 modelMatrix;
float4x4 getTransform()
{
return mul(projectionMatrix, mul(viewMatrix, modelMatrix));
}
}
VSOutput transformedMain(VSInput input, IVertexTransform transform)
{
VSOutput output = (VSOutput)0;
output.Pos = mul(transform.getTransform(), float4(input.Pos, 1.0));
return output;
}Reading the vertex index:
GLSL:
void main()
{
// The vertex index is stored in the gl_VertexIndex built-in
outUV = vec2((gl_VertexIndex << 1) & 2, gl_VertexIndex & 2);
}HLSL:
struct VSInput
{
// The SV_VertexID semantic declares the VertexIndex member as the vertex index input
uint VertexIndex : SV_VertexID
};
VSOutput main(VSInput input)
{
VSOutput output = (VSOutput)0;
output.UV = float2((input.VertexIndex << 1) & 2, input.VertexIndex & 2);
return output;
}Slang:
struct VSInput
{
// Same as HLSL, using SV_VertexID semantic
uint VertexIndex : SV_VertexID
};
VSOutput main(VSInput input)
{
VSOutput output = (VSOutput)0;
output.UV = float2((input.VertexIndex << 1) & 2, input.VertexIndex & 2);
return output;
}
// Alternative using a utility function
float2 calculateUVFromVertexID(uint vertexID)
{
return float2((vertexID << 1) & 2, vertexID & 2);
}
VSOutput alternativeMain(VSInput input)
{
VSOutput output = (VSOutput)0;
output.UV = calculateUVFromVertexID(input.VertexIndex);
return output;
}|
Note
|
Shader interfaces greatly differ between GLSL, HLSL, and Slang. GLSL uses a more procedural approach with global variables, while HLSL uses a more object-oriented approach with explicit structures and semantics. Slang extends HLSL’s approach with additional features like parameter blocks, interfaces, and modules. |
Slang introduces a powerful concept called parameter blocks that provides a more structured way to organize shader resources:
// Define a parameter block type
struct MaterialResources
{
Texture2D albedoMap;
SamplerState samplerState;
struct Constants {
float4 baseColor;
float roughness;
float metallic;
} constants;
};
// Declare a parameter block - Slang assigns its descriptor set and
// binding automatically, no explicit binding is required
ParameterBlock<MaterialResources> material;
// Usage in shader code
float4 sampleAlbedo(float2 uv)
{
// Access resources through the parameter block
return material.albedoMap.Sample(material.samplerState, uv);
}
float getRoughness()
{
// Access constants through the parameter block
return material.constants.roughness;
}Parameter blocks offer several advantages: * Logical grouping of related resources * Cleaner shader code with hierarchical access * Better compatibility across different graphics APIs * Support for nested resources and constants * Improved reflection capabilities
layout (set = <set-index>, binding = <binding-index>) uniform <type> <name>There are two options for defining descriptor set and binding indices in HLSL when using Vulkan.
<type> <name> : register(<register-type><binding-index>, space<set-index>)Using this syntax, descriptor set and binding indices will be implicitly assigned from the set and binding index.
[[vk::binding(binding-index, set-index)]]
<type> <name>With this option, descriptor set and binding indices are explicitly set using vk::binding.
|
Note
|
It’s possible to use both the |
layout (set = <set-index>, binding = <binding-index>) uniform <type> <name>Examples:
// Uniform buffer
layout (set = 0, binding = 0) uniform UBO
{
mat4 projection;
} ubo;
// Combined image sampler
layout (set = 0, binding = 1) uniform sampler2D samplerColor;<type> <name> : register(<register-type><binding-index>, space<set-index>)or
[[vk::binding(binding-index, set-index)]]
<type> <name>Examples:
// Uniform buffer
struct UBO
{
float4x4 projection;
};
ConstantBuffer<UBO> ubo : register(b0, space0);
// Combined image sampler
Texture2D textureColor : register(t1);
SamplerState samplerColor : register(s1);If using the HLSL descriptor binding syntax <register type> can be:
| Type | Register Description | Vulkan resource |
|---|---|---|
b |
Constant buffer |
Uniform buffer |
t |
Texture and texture buffer |
Uniform texel buffer and read-only shader storage buffer |
c |
Buffer offset |
|
s |
Sampler |
same |
u |
Unordered Access View |
Shader storage buffer, storage image and storage texel buffer |
layout (location = <location-index>) in <type> <name>;Example:
layout (location = 0) in vec3 inPos;
layout (location = 1) in vec3 inNormal;
layout (location = 2) in vec2 inUV0;
layout (location = 3) in vec2 inUV1;[[vk::location(<location-index>)]] <type> <name> : <semantic-type>;Example:
struct VSInput
{
[[vk::location(0)]] float3 Pos : POSITION;
[[vk::location(1)]] float3 Normal : NORMAL;
[[vk::location(2)]] float2 UV0 : TEXCOORD0;
[[vk::location(3)]] float2 UV1 : TEXCOORD1;
};
VSOutput main(VSInput input) {
}<semantic type> can be
| Semantic | Description | Type |
|---|---|---|
BINORMAL[n] |
Binormal |
float4 |
BLENDINDICES[n] |
Blend indices |
uint |
BLENDWEIGHT[n] |
Blend weights |
float |
COLOR[n] |
Diffuse and specular color |
float4 |
NORMAL[n] |
Normal vector |
float4 |
POSITION[n] |
Vertex position in object space. |
float4 |
POSITIONT |
Transformed vertex position |
float4 |
PSIZE[n] |
Point size |
float |
TANGENT[n] |
Tangent |
float4 |
TEXCOORD[n] |
Texture coordinates |
float4 |
n is an optional integer between 0 and the number of resources supported. (source)
E.g. for vertex and tessellations shaders.
layout (location = <location-index>) out/in <type> <name>;Example:
layout (location = 0) out vec3 outNormal;
layout (location = 1) out vec3 outColor;
layout (location = 2) out vec2 outUV;
layout (location = 3) out vec3 outViewVec;
void main() {
gl_Position = vec4(inPos, 1.0);
outNormal = inNormal;
}[[vk::location(<location-index>)]] <type> <name> : <semantic-type>;Example:
struct VSOutput
{
float4 Pos : SV_POSITION;
[[vk::location(0)]] float3 Normal : NORMAL;
[[vk::location(1)]] float3 Color : COLOR;
[[vk::location(2)]] float2 UV : TEXCOORD0;
[[vk::location(3)]] float3 ViewVec : TEXCOORD1;
}
VSOutput main(VSInput input) {
VSOutput output = (VSOutput)0;
output.Pos = float4(input.Pos.xyz, 1.0);
output.Normal = input.Normal;
return output;
}For fragment shaders.
layout (location = <attachment-index>) out/in <type> <name>;Example:
layout (location = 0) out vec4 outPosition;
layout (location = 1) out vec4 outNormal;
layout (location = 2) out vec4 outAlbedo;
void main() {
outPosition = ...
outNormal = ...
outAlbedo = ...
}|
Note
|
Push constants must be handled through a root signature in D3D. |
layout (push_constant) uniform <structure-type> { <members> } <name>Example:
layout (push_constant) uniform PushConsts {
mat4 matrix;
} pushConsts;|
Note
|
Specialization constants are only available in Vulkan, D3D doesn’t offer anything similar. |
layout (constant_id = <specialization-constant-index>) const int <name> = <default-value>;Example:
layout (constant_id = 0) const int SPEC_CONST = 0;layout (input_attachment_index = <input-attachment-index>, binding = <binding-index>) uniform subpassInput <name>;Example:
layout (input_attachment_index = 0, binding = 0) uniform subpassInput input0;|
Note
|
Where GLSL uses global functions to access images, HLSL uses member functions of the texture object. |
Example:
GLSL:
layout (binding = 0, set = 0) uniform sampler2D sampler0;
void main() {
vec4 color = texture(sampler0, inUV);
}HLSL:
Texture2D texture0 : register(t0, space0);
SamplerState sampler0 : register(s0, space0);
float4 main(VSOutput input) : SV_TARGET {
float4 color = texture0.Sample(sampler0, input.UV);
}| GLSL | HLSL |
|---|---|
texture |
Sample |
textureGrad |
SampleGrad |
textureLod |
SampleLevel |
textureSize |
GetDimensions |
textureProj |
n.a., requires manual perspective divide |
texelFetch |
Load |
sparseTexelsResidentARB |
CheckAccessFullyMapped |
|
Note
|
Slang supports the same texture operations as HLSL, with identical function names and behavior. |
Slang also supports more advanced texture operations through its parameter block system:
// Define a parameter block with textures
struct TextureResources
{
Texture2D albedoMap;
Texture2D normalMap;
Texture2D roughnessMap;
SamplerState samplerState;
}
// Declare a parameter block
[[vk::binding(0, 0)]]
ParameterBlock<TextureResources> textures;
// Sample textures through the parameter block
float4 sampleAlbedo(float2 uv)
{
return textures.albedoMap.Sample(textures.samplerState, uv);
}layout (set = <set-index>, binding = <image-binding-index>, <image-format>) uniform <memory-qualifier> <image-type> <name>;Example:
layout (set = 0, binding = 0, rgba8) uniform writeonly image2D outputImage;|
Note
|
Currently, HLSL only supports a subset of |
Example:
layout(push_constant) uniform PushConstants {
uint64_t bufferAddress;
} pushConstants;
layout(buffer_reference, scalar) buffer Data {vec4 f[]; };
void main() {
Data data = Data(pushConstants.bufferAddress);
}While GLSL implicitly detects the shader stage (for raytracing) via file extension (or explicitly via compiler arguments), for HLSL raytracing shaders need to be marked by the [shader("stage")] semantic:
Example:
[shader("closesthit")]
void main(inout RayPayload rayPayload, in float2 attribs) {
}Stage names match GLSL: raygeneration, intersection, anyhit, closesthit, miss, callable
| GLSL | HLSL | Note |
|---|---|---|
accelerationStructureEXT |
RaytracingAccelerationStructure |
|
executeCallableEXT |
CallShader |
|
ignoreIntersectionEXT |
IgnoreHit |
|
reportIntersectionEXT |
ReportHit |
|
terminateRayEXT |
AcceptHitAndEndSearch |
|
traceRayEXT |
TraceRay |
|
rayPayloadEXT (storage qualifier) |
Last argument of TraceRay |
|
rayPayloadInEXT (storage qualifier) |
First argument for main entry of any hit, closest hit and miss stage |
|
hitAttributeEXT (storage qualifier) |
Last argument of ReportHit |
|
callableDataEXT (storage qualifier) |
Last argument of CallShader |
|
callableDataInEXT (storage qualifier) |
First argument for main entry of callabe stage |
|
gl_LaunchIDEXT |
DispatchRaysIndex |
|
gl_LaunchSizeEXT |
DispatchRaysDimensions |
|
gl_PrimitiveID |
PrimitiveIndex |
|
gl_InstanceID |
InstanceIndex |
|
gl_InstanceCustomIndexEXT |
InstanceID |
|
gl_GeometryIndexEXT |
GeometryIndex |
|
gl_VertexIndex |
SV_VertexID |
|
gl_WorldRayOriginEXT |
WorldRayOrigin |
|
gl_WorldRayDirectionEXT |
WorldRayDirection |
|
gl_ObjectRayOriginEXT |
ObjectRayOrigin |
|
gl_ObjectRayDirectionEXT |
ObjectRayDirection |
|
gl_RayTminEXT |
RayTMin |
|
gl_RayTmaxEXT |
RayTCurrent |
|
gl_IncomingRayFlagsEXT |
RayFlags |
|
gl_HitTEXT |
RayTCurrent |
|
gl_HitKindEXT |
HitKind |
|
gl_ObjectToWorldEXT |
ObjectToWorld4x3 |
|
gl_WorldToObjectEXT |
WorldToObject4x3 |
|
gl_WorldToObject3x4EXT |
WorldToObject3x4 |
|
gl_ObjectToWorld3x4EXT |
ObjectToWorld3x4 |
|
gl_RayFlagsNoneEXT |
RAY_FLAG_NONE |
|
gl_RayFlagsOpaqueEXT |
RAY_FLAG_FORCE_OPAQUE |
|
gl_RayFlagsNoOpaqueEXT |
RAY_FLAG_FORCE_NON_OPAQUE |
|
gl_RayFlagsTerminateOnFirstHitEXT |
RAY_FLAG_ACCEPT_FIRST_HIT_AND_END_SEARCH |
|
gl_RayFlagsSkipClosestHitShaderEXT |
RAY_FLAG_SKIP_CLOSEST_HIT_SHADER |
|
gl_RayFlagsCullBackFacingTrianglesEXT |
RAY_FLAG_CULL_BACK_FACING_TRIANGLES |
|
gl_RayFlagsCullFrontFacingTrianglesEXT |
RAY_FLAG_CULL_FRONT_FACING_TRIANGLES |
|
gl_RayFlagsCullOpaqueEXT |
RAY_FLAG_CULL_OPAQUE |
|
gl_RayFlagsCullNoOpaqueEXT |
RAY_FLAG_CULL_NON_OPAQUE |
requires |
gl_RayFlagsSkipTrianglesEXT |
RAY_FLAG_SKIP_TRIANGLES |
requires |
gl_RayFlagsSkipAABBEXT |
RAY_FLAG_SKIP_PROCEDURAL_PRIMITIVES |
|
gl_HitKindFrontFacingTriangleEXT |
HIT_KIND_TRIANGLE_FRONT_FACE |
|
gl_HitKindBackFacingTriangleEXT |
HIT_KIND_TRIANGLE_BACK_FACE |
|
gl_HitTriangleVertexPositionsEXT |
Requires SPIR-V intrinsics: [[vk::ext_extension("SPV_KHR_ray_tracing_position_fetch")]]
[[vk::ext_capability(RayTracingPositionFetchKHR)]]
[[vk::ext_builtin_input(HitTriangleVertexPositionsKHR)]] |
Requires |
shadercallcoherent |
n.a. |
|
Note
|
Slang supports the same raytracing built-ins as HLSL, with identical function names and behavior. Additionally, Slang’s interface and module system can be used to create more modular and reusable raytracing code. |
// Define a ray tracing interface
interface IRayTracer
{
struct RayPayload
{
float3 color;
float distance;
int materialID;
};
void traceScene(inout RayPayload payload, float3 origin, float3 direction);
}
// Implement the interface
struct StandardRayTracer : IRayTracer
{
RaytracingAccelerationStructure accelerationStructure;
void traceScene(inout IRayTracer.RayPayload payload, float3 origin, float3 direction)
{
// Configure ray
uint rayFlags = RAY_FLAG_NONE;
uint instanceMask = 0xFF;
uint rayContributionToHitGroupIndex = 0;
uint multiplierForGeometryContributionToHitGroupIndex = 1;
uint missShaderIndex = 0;
// Trace ray
TraceRay(
accelerationStructure,
rayFlags,
instanceMask,
rayContributionToHitGroupIndex,
multiplierForGeometryContributionToHitGroupIndex,
missShaderIndex,
origin,
0.001f, // Min t
direction,
10000.0f, // Max t
payload);
}
}layout (local_size_x = <local-size-x>, local_size_y = <local-size-y>, local_size_z = <local-size-z>) in;Example:
layout (local_size_x = 1, local_size_y = 1, local_size_z = 1) in;| GLSL | HLSL |
|---|---|
gl_GlobalInvocationID |
SV_DispatchThreadID |
gl_LocalInvocationID |
SV_GroupThreadID |
gl_WorkGroupID |
SV_GroupID |
gl_LocalInvocationIndex |
SV_GroupIndex |
gl_NumWorkGroups |
n.a. |
gl_WorkGroupSize |
n.a. |
|
Note
|
Slang supports the same compute shader semantics as HLSL, with identical names and behavior. Additionally, Slang’s interface and module system can be used to create more modular and reusable compute shader code. |
// Define a compute kernel interface
interface IComputeKernel
{
void execute(uint3 globalID, uint3 groupID, uint3 localID);
}
// Implement a specific compute kernel
struct ImageProcessingKernel : IComputeKernel
{
RWTexture2D<float4> outputImage;
Texture2D<float4> inputImage;
SamplerState samplerState;
void execute(uint3 globalID, uint3 groupID, uint3 localID)
{
// Process image at the current pixel
uint2 pixelCoord = globalID.xy;
float2 uv = float2(pixelCoord) / float2(1920, 1080); // Example resolution
// Sample input and write to output
float4 color = inputImage.Sample(samplerState, uv);
outputImage[pixelCoord] = color;
}
}
// Main compute shader entry point
[numthreads(16, 16, 1)]
void main(
uint3 dispatchThreadID : SV_DispatchThreadID,
uint3 groupID : SV_GroupID,
uint3 groupThreadID : SV_GroupThreadID)
{
// Create and use the kernel
ImageProcessingKernel kernel;
kernel.execute(dispatchThreadID, groupID, groupThreadID);
}Example:
GLSL:
barrier();
for (int j = 0; j < 256; j++) {
doSomething;
}
barrier();HLSL:
GroupMemoryBarrierWithGroupSync();
for (int j = 0; j < 256; j++) {
doSomething;
}
GroupMemoryBarrierWithGroupSync();|
Note
|
Barriers heavily differ between GLSL and HLSL. Some HLSL barriers don’t have direct mapping to GLSL (such functions are in italics, and GLSL barriers have been used for them as accurately as possible). |
GLSL |
HLSL |
memoryBarrierShared |
GroupMemoryBarrier |
barrier |
GroupMemoryBarrierWithGroupSync |
memoryBarrierImage + memoryBarrierBuffer |
DeviceMemoryBarrier |
memoryBarrierImage + memoryBarrierBuffer + barrier |
DeviceMemoryBarrierWithGroupSync |
memoryBarrier + barrier |
AllMemoryBarrierWithGroupSync |
memoryBarrier |
AllMemoryBarrier |
|
Note
|
|
These shader stages share several functions and built-ins
| GLSL | HLSL |
|---|---|
EmitMeshTasksEXT |
DispatchMesh |
SetMeshOutputsEXT |
SetMeshOutputCounts |
EmitVertex |
StreamType<Name>.Append (e.g. {TriangleStream<MSOutput>}) |
EndPrimitive |
StreamType<Name>.RestartStrip |
gl_PrimitiveShadingRateEXT |
SV_ShadingRate |
gl_CullPrimitiveEXT |
SV_CullPrimitive |
gl_in |
Array argument for main entry (e.g. {triangle VSInput input[3]}) |
| GLSL | HLSL |
|---|---|
gl_InvocationID |
SV_OutputControlPointID |
gl_TessLevelInner |
SV_InsideTessFactor |
gl_TessLevelOuter |
SV_TessFactor |
gl_TessCoord |
SV_DomainLocation |
| GLSL | HLSL |
|---|---|
gl_HelperInvocation |
WaveIsHelperLane |
n.a. |
WaveOnce |
readFirstInvocationARB |
WaveReadFirstLane |
readInvocationARB |
WaveReadLaneAt |
anyInvocationARB |
WaveAnyTrue |
allInvocationsARB |
WaveAllTrue |
allInvocationsEqualARB |
WaveAllEqual |
ballotARB |
WaveBallot |
gl_NumSubgroups |
NumSubgroups decorated OpVariable |
gl_SubgroupID |
SubgroupId decorated OpVariable |
gl_SubgroupSize |
WaveGetLaneCount |
gl_SubgroupInvocationID |
WaveGetLaneIndex |
gl_SubgroupEqMask |
n.a. |
gl_SubgroupGeMask |
n.a. |
gl_SubgroupGtMask |
n.a. |
gl_SubgroupLeMask |
n.a. |
gl_SubgroupLtMask |
SubgroupLtMask decorated OpVariable |
subgroupElect |
WaveIsFirstLane |
subgroupAny |
WaveActiveAnyTrue |
subgroupAll |
WaveActiveAllTrue |
subgroupBallot |
WaveActiveBallot |
subgroupAllEqual |
WaveActiveAllEqual |
subgroupBallotBitCount |
WaveActiveCountBits |
subgroupAnd |
WaveActiveBitAdd |
subgroupOr |
WaveActiveBitOr |
subgroupXor |
WaveActiveBitXor |
subgroupAdd |
WaveActiveSum |
subgroupMul |
WaveActiveProduct |
subgroupMin |
WaveActiveMin |
subgroupMax |
WaveActiveMax |
subgroupExclusiveAdd |
WavePrefixSum |
subgroupExclusiveMul |
WavePrefixProduct |
subgroupBallotExclusiveBitCount |
WavePrefixCountBits |
subgroupBroadcast |
WaveReadLaneAt |
subgroupBroadcastFirst |
WaveReadLaneFirst |
subgroupQuadSwapHorizontal |
QuadReadAcrossX |
subgroupQuadSwapVertical |
QuadReadAcrossY |
subgroupQuadSwapDiagonal |
QuadReadAcrossDiagonal |
subgroupQuadBroadcast |
QuadReadLaneAt |
| GLSL | HLSL | Note |
|---|---|---|
gl_PointSize |
[[vk::builtin("PointSize")]] |
Vulkan only, no direct HLSL equivalent |
gl_BaseVertexARB |
[[vk::builtin("BaseVertex")]] |
Vulkan only, no direct HLSL equivalent |
gl_BaseInstanceARB |
[[vk::builtin("BaseInstance")]] |
Vulkan only, no direct HLSL equivalent |
gl_DrawID |
[[vk::builtin("DrawIndex")]] |
Vulkan only, no direct HLSL equivalent |
gl_DeviceIndex |
[[vk::builtin("DeviceIndex")]] |
Vulkan only, no direct HLSL equivalent |
gl_ViewportMask |
[[vk::builtin("ViewportMaskNV")]] |
Vulkan only, no direct HLSL equivalent |
gl_FragCoord |
SV_Position |
|
gl_FragDepth |
SV_Depth |
|
gl_FrontFacing |
SV_IsFrontFace |
|
gl_InstanceIndex |
SV_InstanceID |
|
gl_ViewIndex |
SV_ViewID |
|
gl_ClipDistance |
SV_ClipDistance |
|
gl_CullDistance |
SV_CullDistance |
|
gl_PointCoord |
SV_Position |
|
gl_Position |
SV_Position |
|
gl_PrimitiveID |
SV_PrimitiveID |
|
gl_ViewportIndex |
SV_ViewportArrayIndex |
|
gl_Layer |
SV_RenderTargetArrayIndex |
|
gl_SampleID |
SV_SampleIndex |
|
gl_SamplePosition |
EvaluateAttributeAtSample |
|
subpassLoad |
<SubPassInput>.SubpassLoad |
|
imageLoad |
RWTexture1D/2D/3D<T>[] |
|
imageStore |
RWTexture1D/2D/3D<T>[] |
|
atomicAdd |
InterlockedAdd |
|
atomicCompSwap |
InterlockedCompareExchange |
|
imageAtomicExchange |
InterlockedExchange |
|
nonuniformEXT |
NonUniformResourceIndex |
|
gl_BaryCoordEXT |
SV_Barycentrics |
|
gl_BaryCoordNoPerspEXT |
SV_Barycentrics with noperspective |
One of Slang’s most distinctive features is its module system, which allows for better code organization and reuse. This feature is not available in either GLSL or standard HLSL.
In Slang, you can organize code into modules:
// File: Lighting.slang
module Lighting;
// Public functions must be marked with 'public'
public float3 calculateDirectLighting(float3 normal, float3 lightDir, float3 color)
{
float NdotL = max(0, dot(normal, lightDir));
return color * NdotL;
}
// Private function (not exported)
float calculateAttenuation(float distance)
{
return 1.0 / (distance * distance);
}You can import modules to use their exported functionality:
// File: Fragment.slang
module Fragment;
// Import another module
import Lighting;
[shader("fragment")]
float4 fragmentMain(float3 normal : NORMAL, float3 worldPos : POSITION) : SV_TARGET
{
float3 lightDir = normalize(float3(1, 1, 1));
float3 lightColor = float3(1, 1, 1);
// Use function from imported module - modules don't introduce namespaces
float3 directLighting = calculateDirectLighting(normal, lightDir, lightColor);
return float4(directLighting, 1.0);
}Slang supports hierarchical module organization:
// File: Rendering/Materials/PBR.slang
module Rendering.Materials.PBR;
public interface IMaterial { ... }
public struct PBRMaterial : IMaterial { ... }
// File: Main.slang
module Main;
// Import specific module
import Rendering.Materials.PBR;
// Use imported types
PBR::PBRMaterial material;|
Note
|
Most GLSL functions are also available in HLSL and vice-versa. This chapter lists functions with divergent names. Functions that have a 1:1 counterpart (e.g. |
| GLSL | HLSL |
|---|---|
dFdx |
ddx |
dFdxCoarse |
ddx_coarse |
dFdxFine |
ddx_fine |
dFdy |
ddy |
dFdyCoarse |
ddy_coarse |
dFdyFine |
ddy_fine |
fma |
mad |
fract |
frac |
mix |
lerp |
|
Note
|
Slang supports all the same intrinsic functions as HLSL, with identical names. Additionally, Slang allows you to define your own generic functions that can work with multiple types and supports operator overloading and custom function definitions within interfaces. |
Here’s an example of Slang’s generic functions:
// Generic interpolation function
T interpolate<T>(T a, T b, float t)
{
return lerp(a, b, t);
}
// Usage with different types
float result1 = interpolate(1.0f, 2.0f, 0.5f); // Returns 1.5
float3 result2 = interpolate(float3(1,0,0), float3(0,1,0), 0.5f); // Returns (0.5, 0.5, 0)Slang also supports operator overloading and custom function definitions within interfaces, allowing for more expressive and reusable code:
// Define an interface with operations
interface IVector<T>
{
T dot(T other);
T normalize();
T scale(float factor);
}
// Implement for float3
struct Float3Vector : IVector<float3>
{
float3 value;
float3 dot(float3 other)
{
return dot(value, other);
}
float3 normalize()
{
return normalize(value);
}
float3 scale(float factor)
{
return value * factor;
}
}