Skip to content

Hello Triangle HLSL

This example shows how to use HLSL (High-Level Shading Language) shaders instead of GLSL in your KDGpu applications. While Vulkan natively uses SPIR-V bytecode, you can write shaders in either GLSL or HLSL and compile them to SPIR-V. This is particularly useful for developers familiar with DirectX or sharing shader code between D3D12 and Vulkan.

The example uses the KDGpuExample helper API for simplified setup. For comparison, see Hello Triangle which uses GLSL shaders.

Overview

What this example demonstrates:

  • Compiling HLSL shaders to SPIR-V using DXC (DirectX Shader Compiler)
  • Loading and using SPIR-V modules compiled from HLSL
  • HLSL syntax differences from GLSL (semantics, entry points, register bindings)

When to use HLSL:

  • You're porting shaders from DirectX to Vulkan
  • Your team is more familiar with HLSL syntax
  • You want to share shader code between D3D12 and Vulkan backends
  • You prefer HLSL's semantic annotations over GLSL's location qualifiers

Vulkan Requirements

  • Vulkan Version: 1.0+
  • Extensions: None (SPIR-V is Vulkan's native shader format)
  • Build Tools: DXC (DirectX Shader Compiler) for compiling HLSL to SPIR-V

Key Concepts

HLSL vs GLSL:

HLSL and GLSL have different syntax for shader inputs, outputs, and resource bindings:

  • Entry Points: HLSL uses C-style functions with arbitrary names; GLSL uses void [main()](bindgroup__indexing_2main_8cpp.md#function-main)
  • Semantics: HLSL uses semantics like SV_Position, COLOR0; GLSL uses layout(location = N)
  • Register Bindings: HLSL uses register(t0), register(b0); GLSL uses layout(binding = 0)
  • Shader Stages: HLSL uses file extensions .vs, .ps; GLSL uses .vert, .frag

DXC Compilation:

The DirectX Shader Compiler (DXC) is the modern HLSL compiler that can target SPIR-V. It replaces the older FXC compiler and supports Shader Model 6.0+. DXC can compile HLSL source code directly to SPIR-V bytecode that Vulkan understands.

For more on SPIR-V: https://www.khronos.org/spir/

Implementation

The rendering code is identical to Hello Triangle - the only difference is loading HLSL-compiled shaders instead of GLSL-compiled ones:

1
2
3
4
5
    auto vertexShaderPath = KDGpuExample::assetDir().file("shaders/examples/hello_triangle_hlsl/hello_triangle.vs.spv");
    auto vertexShader = m_device.createShaderModule(KDGpuExample::readShaderFile(vertexShaderPath));

    auto fragmentShaderPath = KDGpuExample::assetDir().file("shaders/examples/hello_triangle_hlsl/hello_triangle.ps.spv");
    auto fragmentShader = m_device.createShaderModule(KDGpuExample::readShaderFile(fragmentShaderPath));

Filename: hello_triangle/hello_triangle.cpp

Note that the loaded files are still .spv (SPIR-V bytecode) - the HLSL source has already been compiled to SPIR-V at build time.

Build-Time Shader Compilation:

The CMake build system automatically compiles HLSL shaders using DXC when you build the project. The CompileShader CMake module detects .hlsl files and invokes DXC:

DXC is invoked with these key arguments:

  • -spirv: Generate SPIR-V output instead of DXIL
  • -T <profile>: Target shader profile (e.g., vs_6_0 for vertex shader, ps_6_0 for pixel/fragment shader)
  • -E <entry>: Entry point function name
  • -fspv-target-env=vulkan1.0: Target Vulkan 1.0 SPIR-V environment

HLSL Shader Example:

Here's what a simple HLSL vertex shader looks like (compare to GLSL):

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
// HLSL vertex shader
struct VSInput {
    float3 position : POSITION;
    float3 color : COLOR0;
};

struct VSOutput {
    float4 position : SV_Position;
    float3 color : COLOR0;
};

cbuffer Transform : register(b0) {
    float4x4 transform;
};

VSOutput main(VSInput input) {
    VSOutput output;
    output.position = mul(transform, float4(input.position, 1.0));
    output.color = input.color;
    return output;
}

Equivalent GLSL:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
// GLSL vertex shader
#version 450

layout(location = 0) in vec3 position;
layout(location = 1) in vec3 color;

layout(location = 0) out vec3 fragColor;

layout(binding = 0) uniform Transform {
    mat4 transform;
};

void main() {
    gl_Position = transform * vec4(position, 1.0);
    fragColor = color;
}

Performance Notes

  • SPIR-V bytecode from HLSL and GLSL performs identically - they both compile to the same intermediate representation
  • DXC produces well-optimized SPIR-V output comparable to glslang
  • Choose HLSL vs GLSL based on team expertise and codebase requirements, not performance

See Also

Further Reading


Updated on 2026-03-31 at 00:02:07 +0000