Vulkan High Level Shader Language Comparison
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 and HLSL. 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
For more details on using HLSL with Vulkan, visit this chapter. |
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 GLSL and HLSL. E.g. there are no semantic in GLSL while some newer GLSL functionality may not (yet) be available in HLSL. |
Extensions
In GLSL extensions need to be explicitly enabled using the #extension
directive. This is not necessary in HLSL. The compiler will implicitly select suitable SPIR-V extensions based on the shader. If required one can use -fspv-extension
arguments to explicitly select extensions.
Data types
Types work similar in GLSL and HLSL. But where GLSL e.g. has explicit vector or matrix types, HLSL uses basic types. On the other hand HLSL offers advanced type features like C++ templates. This paragraph contains a basic summary with some examples to show type differences between the two languages. |
GLSL | HLSL | Example |
---|---|---|
vecn |
floatn |
vec4 → float4 |
ivecn |
intn |
ivec3 -→ int3 |
matnxm or shorthand matn |
floatnxm |
mat4 → float4x4 |
The syntax for casting types also differs:
GLSL:
mat4x3 mat = mat4x3(ubo.view);
HLSL:
float4x3 mat = (float4x3)(ubo.view);
An important difference: Matrices in GLSL are column-major, while matrices in HLSL are row-major. This affects things like matrix construction. |
Implicit vk Namespace
For Vulkan concepts that are not available in DirectX, an implicit namespace has been added that marks Vulkan specific features.
SPIR-V macro
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);
SPIR-V intrinsics
DXC supports SPIR-V intrinsics with the GL_EXT_spirv_intrinsics
extension. This adds support for embedding arbitrary SPIR-V in the middle of 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];
Built-ins vs. Semantics
While GLSL makes heavy use of input and output variables built into the languages called "built-ins", there is no such concept in HLSL. HLSL instead uses semantics, strings that are attached to inputs or inputs that contain information about the intended use of that variable. They are prefixed with |
Examples
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;
}
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;
}
Shader interface
Shader interfaces greatly differ between GLSL and HLSL. |
Descriptor bindings
GLSL
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.
HLSL way
<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.
Vulkan namespace
[[vk::binding(binding-index, set-index)]]
<type> <name>
With this option, descriptor set and binding indices are explicitly set using vk::binding
.
It’s possible to use both the |
Uniforms
GLSL
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;
HLSL
<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 |
Shader inputs
GLSL
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;
HLSL
[[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)
Shader outputs
Passing data between stages
E.g. for vertex and tessellations shaders.
GLSL
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;
}
HLSL
[[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;
}
Writing attachments
For fragment shaders.
Push constants
Push constants must be handled through a root signature in D3D. |
Specialization constants
Specialization constants are only available in Vulkan, D3D doesn’t offer anything similar. |
Sub passes
Texture reads
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 |
Built-ins and functions mapping
Buffer device address
Currently, HLSL only supports a subset of |
Raytracing
Shader stage selection
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
Built-Ins
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 |
AY_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:
|
Requires |
Compute
Local workgroup size
Built-Ins
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. |
Barriers
Barriers heavily differ between GLSL and HLSL. With one exception there is no direct mapping. To match HLSL in GLSL you often need to call multiple different barrier types in glsl. |
Example:
GLSL:
groupMemoryBarrier();
barrier();
for (int j = 0; j < 256; j++) {
doSomething;
}
groupMemoryBarrier();
barrier();
HLSL:
GroupMemoryBarrierWithGroupSync();
for (int j = 0; j < 256; j++) {
doSomething;
}
GroupMemoryBarrierWithGroupSync();
GLSL |
HLSL |
groupMemoryBarrier |
GroupMemoryBarrier |
groupMemoryBarrier + barrier |
GroupMemoryBarrierWithGroupSync |
memoryBarrier + memoryBarrierImage + memoryBarrierImage |
DeviceMemoryBarrier |
memoryBarrier + memoryBarrierImage + memoryBarrierImage + barrier |
DeviceMemoryBarrierWithGroupSync |
All above barriers + barrier |
AllMemoryBarrierWithGroupSync |
All above barriers |
AllMemoryBarrier |
memoryBarrierShared (only) |
n.a. |
Mesh, task (amplification) and geometry shaders
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]}) |
Tessellation shaders
GLSL | HLSL |
---|---|
gl_InvocationID |
SV_OutputControlPointID |
gl_TessLevelInner |
SV_InsideTessFactor |
gl_TessLevelOuter |
SV_TessFactor |
gl_TessCoord |
SV_DomainLocation |
Subgroups
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 |
Misc
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 |
Functions
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 |