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:
- Stage Attributes (if required): Metadata applied before the function declaration, mandatory for stages like compute or geometry shaders.
- Return Type and Semantic: The data type returned by
the function, tagged with a system-value semantic (prefixed with
SV_) or packaged in astruct. - Function Identifier: The designated entry point
name provided to the compiler via the
/Eflag or compilation API. - 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
voidand rely on memory writes through buffers or output streams.