Recursive AI

HLSL vs GLSL

HLSL vs GLSL compared on syntax, compilers and targets: both compile to SPIR-V for Vulkan, so the choice comes down to your APIs, tooling and language features.

HLSL and GLSL are both C-like shading languages, and on Vulkan the GPU can’t tell them apart: both compile to SPIR-V. GLSL is the Khronos language of OpenGL and Vulkan. HLSL is Microsoft’s language for Direct3D, and since Vulkan 1.2 the Vulkan Guide calls it “a first class shading language for Vulkan that can be used just as easily as GLSL” (HLSL in Vulkan). Neither runs as-is on Metal or WebGPU, which take MSL and WGSL. So the choice comes down to which APIs you ship on, which compiler you want to depend on, and which language features you’ll use.

HLSL vs GLSL at a glance

GLSLHLSL
OwnerKhronosMicrosoft
Native APIsOpenGL, VulkanDirect3D 11 and 12; Vulkan through SPIR-V
Reference compiler to SPIR-VglslangDXC (“the reference HLSL to SPIR-V compiler”, Vulkan Guide)
Direct3D 12No direct pathDXIL today; SPIR-V once Shader Model 7 ships (DirectX blog, September 2024)
Generic codeNo templatesTemplates and operator overloading in HLSL 2021
New GPU featuresExtensions (#extension GL_KHR_shader_subgroup_arithmetic)Shader models (-T cs_6_0), plus vk:: attributes for Vulkan-only features

Syntax differences

Microsoft’s GLSL-to-HLSL reference (updated 20 October 2022) and the Vulkan Guide’s shader language comparison list the mappings in full. The ones you meet in every shader:

ConceptGLSLHLSL
Vector and matrix typesvec4, mat4float4, float4x4
Matrix times vectorm * vmul(m, v)
Textures and samplerssampler2D, or texture2D plus samplerTexture2D plus SamplerState, tex.Sample(s, uv)
Resource binding on Vulkanlayout(set = 0, binding = 1)[[vk::binding(1, 0)]] or register(t1, space0)
Stage inputs and outputsin/out with layout(location = n), gl_PositionParameters and return values with semantics, SV_Position, SV_Target
Entry pointAlways mainAny name, chosen with -E
Interpolation and fractionsmix, fractlerp, frac
Screen-space derivativesdFdx, dFdyddx, ddy

The one that causes real bugs is matrix order. The Vulkan Guide puts it this way: “matrices in GLSL are column-major, while matrices in HLSL and Slang are row-major by default”, which “affects things like matrix construction”. In HLSL, m[i] is row i. In GLSL it is column i. A port that keeps the same data and the same multiplication order without thinking about this transposes every transform.

The same shader in both languages

A fragment shader that samples a texture, blends toward a tint and applies a stripe pattern, written once in each language with the same bindings:

fade.fragGLSL 450
#version 450
 
layout(set = 0, binding = 0) uniform Params { vec4 Tint; float Fade; };
layout(set = 0, binding = 1) uniform texture2D Albedo;
layout(set = 0, binding = 2) uniform sampler LinearWrap;
 
layout(location = 0) in vec2 uv;
layout(location = 0) out vec4 color;
 
void main()
{
    vec4 texel = texture(sampler2D(Albedo, LinearWrap), uv);
    color = mix(texel, Tint, Fade) * vec4(vec3(fract(uv.x * 4.0)), 1.0);
}
Fade.hlslHLSL
cbuffer Params : register(b0) { float4 Tint; float Fade; };
[[vk::binding(1)]] Texture2D<float4> Albedo;
[[vk::binding(2)]] SamplerState LinearWrap;
 
float4 main(float2 uv : TEXCOORD0) : SV_Target
{
    float4 texel = Albedo.Sample(LinearWrap, uv);
    return lerp(texel, Tint, Fade) * float4((float3)frac(uv.x * 4.0), 1.0);
}

Both compile to valid SPIR-V for Vulkan 1.2, with Params at binding 0, the texture at 1 and the sampler at 2:

Terminalglslang 16.6.0
glslangValidator -V --target-env vulkan1.2 fade.frag -o fade.glsl.spv
spirv-val --target-env vulkan1.2 fade.glsl.spv
TerminalDXC 1.9.0.5512 · OneShader 1.2.0
dxc -spirv -T ps_6_0 -E main Fade.hlsl -Fo fade.dxc.spv
oneshader -T ps_6_0 -E main --target spirv --target msl --target wgsl Fade.hlsl -Fo fade
spirv-val --target-env vulkan1.2 fade.dxc.spv
spirv-val --target-env vulkan1.2 fade.spv
naga fade.wgsl
Outputspirv-val prints nothing on success
Validation successful

The HLSL version has to say a little more about bindings, since register numbers don’t become Vulkan bindings the way you might expect (why). In return, the HLSL file also compiles for Direct3D with DXC, unchanged.

Getting to Metal and WebGPU

Neither language reaches Metal or WebGPU without a translator, and the translators work from SPIR-V or have their own front end:

  • From GLSL: glslang to SPIR-V, then SPIRV-Cross for MSL (“Convert SPIR-V to readable, usable and efficient Metal Shading Language”, README) and naga for WGSL. naga also reads GLSL directly, “GLSL 440+ and Vulkan semantics only” (naga README).
  • From HLSL: DXC to SPIR-V, then the same two tools. Or OneShader, which writes SPIR-V, MSL and WGSL itself, from one front end, with the same bindings and buffer byte layout on all three.

For the GLSL file above, the SPIR-V route works on this shader:

TerminalSPIRV-Cross · naga 30.0.1
spirv-cross fade.glsl.spv --msl --output fade.glsl.metal
naga fade.glsl.spv fade.glsl.wgsl
naga fade.glsl.wgsl
Output
Validation successful

The HLSL run above produced fade.spv, fade.metal and fade.wgsl in one command, and the WGSL passes naga. Translating through SPIR-V has costs that show up on less simple shaders. The HLSL to WGSL comparison has a constant buffer where it produces WGSL that naga itself rejects.

OneShader reads HLSL only. It has no GLSL input or output, and it doesn’t compile Slang, a third language the Vulkan Guide also covers (Slang in Vulkan).

Which to choose

  • GLSL if you target OpenGL or OpenGL ES, or you have a large GLSL codebase on Vulkan and no Direct3D plans. Its tooling is Khronos’s own.
  • HLSL if Direct3D is in your future or your present, if your team or engine already writes it, or if you want templates and the shader-model feature set. On Vulkan it is as supported as GLSL. Since glslang’s HLSL front end was deprecated in April 2026 (issue #4210), compile it with DXC or OneShader.
  • Either, plus a translator, if you target Metal or WebGPU too. Write HLSL and OneShader covers vertex, pixel and compute shaders for all three non-Microsoft APIs in one step. For geometry, tessellation, mesh or ray-tracing stages, use DXC and a SPIR-V translator.

Common questions

Is HLSL faster than GLSL?

No language is faster in itself: on Vulkan both become SPIR-V, and the driver compiles that. Differences come from what each compiler emits for the same code, so compare the SPIR-V, not the language.

How do I convert GLSL to HLSL?

By hand, using the mapping tables above, or by compiling the GLSL to SPIR-V with glslang and converting that with SPIRV-Cross, which writes HLSL, or naga, whose HLSL back end targets “Shader Model 5.0+ (DirectX 11+)”. The result is generated code, best as a starting point for a port. OneShader doesn’t convert GLSL.

Can I use HLSL with Vulkan?

Yes. Compile it with dxc -spirv or oneshader and load the SPIR-V as you would from GLSL. From the application’s side, the Vulkan Guide says, “using HLSL is exactly the same as using GLSL”.

To try HLSL on Vulkan, Metal and WebGPU from one source, install dotnet tool install -g RecursiveAI.OneShader.Tool and see getting started, or compile HLSL to SPIR-V in the cookbook.