---
title: "HLSL vs GLSL"
url: https://recursiveai.org/oneshader/articles/hlsl-vs-glsl/
description: "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."
date: 2026-10-26
author: OneShader team
---

# 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.

October 26, 2026 · OneShader team

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](https://docs.vulkan.org/guide/latest/hlsl.html)). 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

|  | GLSL | HLSL |
|---|---|---|
| Owner | Khronos | Microsoft |
| Native APIs | OpenGL, Vulkan | Direct3D 11 and 12; Vulkan through SPIR-V |
| Reference compiler to SPIR-V | [glslang](https://github.com/KhronosGroup/glslang) | [DXC](https://github.com/microsoft/DirectXShaderCompiler) (“the reference HLSL to SPIR-V compiler”, [Vulkan Guide](https://docs.vulkan.org/guide/latest/hlsl.html)) |
| Direct3D 12 | No direct path | DXIL today; SPIR-V once Shader Model 7 ships ([DirectX blog](https://devblogs.microsoft.com/directx/directx-adopting-spir-v/), September 2024) |
| Generic code | No templates | Templates and operator overloading in [HLSL 2021](https://github.com/microsoft/DirectXShaderCompiler/wiki/HLSL-2021) |
| New GPU features | Extensions (`#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](https://learn.microsoft.com/en-us/windows/uwp/gaming/glsl-to-hlsl-reference) (updated 20 October 2022) and the Vulkan Guide’s [shader language comparison](https://docs.vulkan.org/guide/latest/high_level_shader_language_comparison.html) list the mappings in full. The ones you meet in every shader:

| Concept | GLSL | HLSL |
|---|---|---|
| Vector and matrix types | `vec4`, `mat4` | `float4`, `float4x4` |
| Matrix times vector | `m * v` | `mul(m, v)` |
| Textures and samplers | `sampler2D`, or `texture2D` plus `sampler` | `Texture2D` plus `SamplerState`, `tex.Sample(s, uv)` |
| Resource binding on Vulkan | `layout(set = 0, binding = 1)` | `[[vk::binding(1, 0)]]` or `register(t1, space0)` |
| Stage inputs and outputs | `in`/`out` with `layout(location = n)`, `gl_Position` | Parameters and return values with semantics, `SV_Position`, `SV_Target` |
| Entry point | Always `main` | Any name, chosen with `-E` |
| Interpolation and fractions | `mix`, `fract` | `lerp`, `frac` |
| Screen-space derivatives | `dFdx`, `dFdy` | `ddx`, `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.frag · GLSL 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.hlsl · HLSL*

```
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:

*Terminal · glslang 16.6.0*

```
glslangValidator -V --target-env vulkan1.2 fade.frag -o fade.glsl.spv
spirv-val --target-env vulkan1.2 fade.glsl.spv
```

*Terminal · DXC 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
```

*Output · spirv-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](https://recursiveai.org/oneshader/articles/hlsl-to-spirv-compiler/)). 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](https://github.com/KhronosGroup/SPIRV-Cross)) and naga for WGSL. naga also reads GLSL directly, “GLSL 440+ and Vulkan semantics only” ([naga README](https://github.com/gfx-rs/wgpu/tree/trunk/naga)).
- **From HLSL:** DXC to SPIR-V, then the same two tools. Or [OneShader](https://recursiveai.org/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:

*Terminal · SPIRV-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](https://recursiveai.org/oneshader/articles/hlsl-to-wgsl-converter/) 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](https://docs.vulkan.org/guide/latest/slang.html)).

## 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](https://github.com/KhronosGroup/glslang/issues/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](https://recursiveai.org/oneshader/docs/getting-started/), or [compile HLSL to SPIR-V](https://recursiveai.org/oneshader/cookbook/hlsl-to-spirv/) in the cookbook.
