HLSL Entry Point Function Structure for DirectX

This article provides an overview of how to structure an HLSL (High-Level Shading Language) entry point function when targeting the DirectX graphics pipeline. It breaks down the essential components of the entry point, including function signatures, input and output parameter semantics, attributes, and data structures. You will also see concrete structural patterns for common shader stages such as Vertex, Pixel, and Compute shaders.


Understanding the Entry Point Function

An HLSL entry point function is the main execution hook called by the GPU for a specific programmable stage of the DirectX pipeline. When compiling HLSL code—whether using the legacy FXC (Effect-Compiler) or the modern DXC (DirectX Shader Compiler)—you must explicitly specify the name of this function (often defaulted to main, VSMain, PSMain, or CSMain) alongside the target shader profile (e.g., vs_6_0, ps_6_0).

Unlike standard C/C++ functions, an HLSL entry point requires hardware-level routing information known as semantics. Semantics tell the DirectX input assembler, rasterizer, and output merger how to map hardware registers to your function arguments and return values.


Core Components of an HLSL Entry Point

A standard entry point function contains four primary elements:

  1. Stage Attributes (if required): Metadata applied before the function declaration, mandatory for stages like compute or geometry shaders.
  2. Return Type and Semantic: The data type returned by the function, tagged with a system-value semantic (prefixed with SV_) or packaged in a struct.
  3. Function Identifier: The designated entry point name provided to the compiler via the /E flag or compilation API.
  4. Parameter List: The incoming data, either passed as individual variables with semantics or bundled into an input struct.

Structure 1: Vertex Shader Entry Point

A Vertex Shader processes individual vertices from the Input Assembler and must output at least a homogeneous clip-space position marked with the SV_Position system semantic.

Using structures for input and output is the industry standard for maintainability:

// Input layout matching the C++ Input Layout / Vertex Buffer
struct VS_INPUT
{
    float3 Position : POSITION;
    float3 Normal   : NORMAL;
    float2 TexCoord : TEXCOORD0;
};

// Output layout passed to the Rasterizer or Pixel Shader
struct VS_OUTPUT
{
    float4 Position : SV_Position; // System Value required by rasterizer
    float3 Normal   : NORMAL;
    float2 TexCoord : TEXCOORD0;
};

// Entry point function
VS_OUTPUT VSMain(VS_INPUT input)
{
    VS_OUTPUT output;
    
    // Transform position to clip space
    output.Position = mul(float4(input.Position, 1.0f), WorldViewProjection);
    output.Normal = mul(input.Normal, (float3x3)World);
    output.TexCoord = input.TexCoord;
    
    return output;
}

Structure 2: Pixel Shader Entry Point

A Pixel Shader consumes interpolated data from the rasterizer and outputs color values to one or more Render Targets. The output must bind to the SV_Target system semantic.

struct PS_INPUT
{
    float4 Position : SV_Position;
    float3 Normal   : NORMAL;
    float2 TexCoord : TEXCOORD0;
};

// Entry point returning a single render target color
float4 PSMain(PS_INPUT input) : SV_Target
{
    float4 textureColor = DiffuseTexture.Sample(LinearSampler, input.TexCoord);
    return textureColor;
}

For Multiple Render Targets (MRT), wrap the return types in an output structure:

struct PS_OUTPUT
{
    float4 Color        : SV_Target0;
    float4 WorldNormal  : SV_Target1;
};

PS_OUTPUT PSMainMRT(PS_INPUT input)
{
    PS_OUTPUT output;
    output.Color = DiffuseTexture.Sample(LinearSampler, input.TexCoord);
    output.WorldNormal = float4(normalize(input.Normal), 1.0f);
    return output;
}

Structure 3: Compute Shader Entry Point

Compute Shaders operate outside the traditional graphics rasterization pipeline and do not return data directly to a render target. Instead, they write to Unordered Access Views (UAVs).

Compute entry points require the [numthreads(X, Y, Z)] attribute directly above the function declaration to define thread group dimensions, and they accept system-generated thread IDs:

RWTexture2D<float4> OutputUAV : register(u0);

[numthreads(8, 8, 1)]
void CSMain(
    uint3 groupID          : SV_GroupID,
    uint3 dispatchThreadID : SV_DispatchThreadID,
    uint3 groupThreadID    : SV_GroupThreadID,
    uint  groupIndex       : SV_GroupIndex)
{
    // Write directly to a resource based on 2D thread coordinates
    OutputUAV[dispatchThreadID.xy] = float4(1.0f, 0.0f, 0.0f, 1.0f);
}

Key Rules for DirectX HLSL Entry Points

  • Semantic Matching: Every argument and return member in an entry point must have a bound semantic unless it is passed via constant buffers (cbuffer).
  • System-Value Semantics: Variables driving hardware functionality must use SV_ prefixes (e.g., SV_Position, SV_Target, SV_DispatchThreadID).
  • Void Returns: Entry points that do not directly feed the next fixed-function stage (such as Compute Shaders or Stream-Out Geometry Shaders) return void and rely on memory writes through buffers or output streams.