-
-
Notifications
You must be signed in to change notification settings - Fork 291
Shaders in Axmol3
axmol PR #3233 — Migration of all engine and test shaders from GLSL to HLSL 5.1
This document describes the complete shader migration from GLSL (OpenGL ES 3.0) to HLSL 5.1 (D3D11/D3D12/Vulkan/GLES) for the axmol engine. The migration covers:
-
57 builtin engine shaders (
axmol/renderer/shaders/) -
44 cpp-tests shaders (
tests/cpp-tests/Source/shaders/) -
2 spine shaders (
extensions/spine/shaders/) -
19 Live2D shaders (
extensions/Live2D/.../shaders/)
| Old | New |
|---|---|
.vert |
_vs.hlsl |
.frag |
_ps.hlsl |
.fsh |
_ps.hlsl |
.vsh |
_vs.hlsl |
.vs |
_vs.hlsl |
.fs |
_ps.hlsl |
Stage detection: axslcc now detects shader stage only from file stem suffix (_vs, _ps, _cs). Legacy extension-based detection removed.
# Before: supported GLSL + HLSL extensions
axslcc_option(AXSLCC_SOURCE_FILE_EXTENSIONS ".hlsl;.vert;.vsh;.vs;.frag;.fsh;.fs")
# After: only .hlsl
axslcc_option(AXSLCC_SOURCE_FILE_EXTENSIONS ".hlsl")| File | Role |
|---|---|
axmol/renderer/shaders/base.hlsli |
Common macros, builtin sampler declarations |
axmol/cmake/Modules/AXSLCC.cmake |
Shader build rules |
axslcc/src/ |
Offline shader compiler |
// BEFORE (GLSL)
#version 310 es
layout(location = POSITION) in vec3 a_position;
layout(location = TEXCOORD0) in vec2 a_texCoord;
layout(location = TEXCOORD0) out vec2 v_texCoord;
layout(std140, set = 0, binding = 0) uniform vs_ub {
mat4 u_MVPMatrix;
};
void main() {
v_texCoord = a_texCoord;
gl_Position = u_MVPMatrix * vec4(a_position, 1.0);
}
// AFTER (HLSL)
#include "base.hlsli"
struct VS_IN {
float3 a_position : POSITION;
float2 a_texCoord : TEXCOORD0;
};
struct VS_OUT {
float2 v_texCoord : TEXCOORD0;
float4 position : SV_Position;
};
cbuffer vs_ub : register(b0, space0) {
float4x4 u_MVPMatrix;
};
VS_OUT main(VS_IN input) {
VS_OUT output;
output.position = mul(u_MVPMatrix, float4(input.a_position, 1.0));
output.v_texCoord = input.a_texCoord;
return output;
}// BEFORE (GLSL)
#version 310 es
precision highp float;
layout(location = TEXCOORD0) in vec2 v_texCoord;
layout(set = 1, binding = 0) uniform sampler2D u_tex0;
layout(std140, set = 0, binding = 1) uniform fs_ub {
vec4 u_color;
};
layout(location = SV_Target0) out vec4 FragColor;
void main() {
FragColor = texture(u_tex0, v_texCoord) * u_color;
}
// AFTER (HLSL)
#include "base.hlsli"
struct PS_IN {
float2 v_texCoord : TEXCOORD0;
};
cbuffer fs_ub : register(b1, space0) {
float4 u_color;
};
Texture2D u_tex0 : register(t0, space1);
float4 main(PS_IN input) : SV_Target0 {
return u_tex0.Sample(LinearClamp, input.v_texCoord) * u_color;
}#include "base.hlsli"
struct PS_IN {
float4 gl_FragCoord : SV_Position;
};
cbuffer fs_ub : register(b1, space0) {
float2 center;
float2 resolution;
float4 u_Time;
};
float4 main(PS_IN input) : SV_Target0 {
float2 fragCoord = input.gl_FragCoord.xy;
// ... shadertoy body ...
}| GLSL | HLSL |
|---|---|
vec2 |
float2 |
vec3 |
float3 |
vec4 |
float4 |
mat4 |
float4x4 |
mat3 |
float3x3 |
mix(a,b,t) |
lerp(a,b,t) |
fract(x) |
frac(x) |
mod(x,y) |
fmod(x,y) |
texture(sampler, uv) |
sampler.Sample(LinearClamp, uv) |
texture(samplerCube, dir) |
samplerCube.Sample(LinearClamp, dir) |
gl_Position |
output.position (SV_Position) |
gl_FragCoord.xy |
input.gl_FragCoord.xy (SV_Position) |
FragColor = val; |
return val; |
layout(location=SV_Target0) out vec4 FragColor; |
float4 main(...) : SV_Target0 |
| GLSL Location | HLSL Semantic |
|---|---|
POSITION (0) |
POSITION |
TEXCOORD0 |
TEXCOORD0 |
TEXCOORD1 |
TEXCOORD1 |
NORMAL |
NORMAL |
COLOR0 |
COLOR0 |
BINORMAL |
BINORMAL |
BLENDINDICES |
BLENDINDICES |
BLENDWEIGHT |
BLENDWEIGHT |
// Vertex shader UBO — set 0 → space 0, binding 0
cbuffer vs_ub : register(b0, space0) { ... }
// Fragment shader UBO — set 0 → space 0, binding 1
cbuffer fs_ub : register(b1, space0) { ... }// Texture at t0 — set 1 → space 1
Texture2D u_tex0 : register(t0, space1);
Texture2D u_tex1 : register(t1, space1);
TextureCube u_cubeTex : register(t0, space1);
// Builtin samplers are declared in base.hlsli
// Sampling: use LinearClamp, LinearWrap, PointClamp, etc.
u_tex0.Sample(LinearClamp, uv)// REMOVED — these were GLES2 legacy
#undef TANGENT
#undef BINORMAL
#undef BLENDINDICES
#undef BLENDWEIGHT
#define TANGENT TEXCOORD6
#define BINORMAL TEXCOORD7
#define BLENDINDICES COLOR1
#define BLENDWEIGHT COLOR2// BEFORE: SPOTLIGHT overlapped with POINTLIGHT array (TEXCOORD2–5)
#ifndef SPOTLIGHT
#define SPOTLIGHT TEXCOORD4
#endif
// AFTER: moved to TEXCOORD6 (after POINTLIGHT's 4-element range)
#ifndef SPOTLIGHT
#define SPOTLIGHT TEXCOORD6
#endifThe entire axmol/rhi/d3d12/Sampler12.h file was removed. Its BuiltinSamplers string contained sampler declarations that are now handled by base.hlsli.
// BEFORE: DXC rejected implicit float3x3 constructor
return float3x3(colorTransform[0].xyz, colorTransform[1].xyz, colorTransform[2].xyz) * YUV;
// AFTER: explicit matrix construction
float3x3 m = { colorTransform[0].xyz, colorTransform[1].xyz, colorTransform[2].xyz };
return mul(m, YUV);| Option | Description |
|---|---|
--dxil |
Windows only: compile SPIRV-Cross HLSL output to DXIL bytecode via DXC |
--dxc-reflect |
Windows only: use DXC reflection for REFL chunk (stub, validation only) |
axslcc performs per-target front-end compilation to enable source-level target macros:
for each target:
HLSL source + -DAXSLC_TARGET_<LANG>=1
→ glslang → SPIR-V
→ SPIRV-Cross + attribute remap → target output (HLSL/ESSL/GLSL/MSL)
→ SPIR-V raw (for Vulkan, direct copy)
→ (+ --dxil) DXC → DXIL bytecode (for HLSL targets, optional)
→ (+ --reflect) REFL chunk from SPIR-V reflection
→ packed into .sc file (axslcc --sc)
| Fix | File |
|---|---|
| Attribute remap for HLSL output | cross_compiler.cpp |
| Varying name normalize for GLES | cross_compiler.cpp |
| HLSL source semantic parse for REFL |
utils.cpp + reflection.cpp
|
separate_images for HLSL Texture2D |
reflection.cpp |
input. prefix strip from variable names |
utils.cpp |
SEH crash handler + g_inDxcCompile flag |
main.cpp + compiler.cpp
|
/EHa for cross-DLL C++ exception catch |
CMakeLists.txt |
- Engine RHI receives HLSL source code from SC files
- D3D12 driver compiles HLSL → DXIL at runtime using DXC
- Use
--dxiloption for offline DXIL pre-compilation
- SPIRV-Cross generates ESSL 300 from SPIR-V
- Varying names normalized to strip
input.prefix - GLES linker matches varyings by name
- SPIR-V binary directly embedded in SC file
- No cross-compilation needed
- Not tested yet
- Metal Y-flip handled by
AXSLC_TARGET_MSLmacro
Symptom: D3D12 CreateGraphicsPipelineState fails:
D3D12 ERROR: ID3D12Device::CreateGraphicsPipelineState: Vertex Shader - Pixel Shader
linkage error: Signatures between stages are incompatible.
Semantic 'TEXCOORD' of the input stage has a hardware register component mask that
is not a subset of the output of the previous stage.
[STATE_CREATION ERROR #662: CREATEGRAPHICSPIPELINESTATE_SHADER_LINKAGE_REGISTERMASK]
Root Cause: The shader compilation pipeline does not preserve HLSL semantic names for varyings through the intermediate SPIR-V representation:
-
glslangcompiles HLSL to SPIR-V, assigningLocationdecorations to struct members in declaration order (ignoring the semantic name/index) -
SPIRV-Crosscross-compiles SPIR-V to HLSL for D3D targets, generatingTEXCOORDnsemantics fromLocationvalues - If
VS_OUTandPS_INdeclare members in different orders, the cross-compiled semantics will mismatch
Example — incorrect:
// VS_OUT (from positionTextureColor_vs.hlsl):
struct VS_OUT {
float4 v_color : COLOR0; // location 0 → TEXCOORD0
float2 v_texCoord : TEXCOORD0; // location 1 → TEXCOORD1
float4 position : SV_Position;
};
// PS_IN (custom):
struct PS_IN {
float2 v_texCoord : TEXCOORD0; // location 0 → TEXCOORD0 (receives v_color!)
float4 v_color : COLOR0; // location 1 → TEXCOORD1 (receives v_texCoord!)
};
// → D3D12: PS expects float4 at TEXCOORD1 but VS writes float2 → PSO rejectedFix: Ensure PS_IN member declaration order matches VS_OUT:
struct PS_IN {
float4 v_color : COLOR0; // location 0 → TEXCOORD0 ✓
float2 v_texCoord : TEXCOORD0; // location 1 → TEXCOORD1 ✓
};Affected files: 9 custom PS shaders in tests/cpp-tests/Source/shaders/ paired with
positionTextureColor_vs had reversed v_texCoord/v_color order:
| File | Status |
|---|---|
example_Blur_ps.hlsl |
Fixed |
example_Outline_ps.hlsl |
Fixed |
example_Noisy_ps.hlsl |
Fixed |
example_GreyScale_ps.hlsl |
Fixed |
example_Sepia_ps.hlsl |
Fixed |
example_EdgeDetection_ps.hlsl |
Fixed |
example_Bloom_ps.hlsl |
Fixed |
example_CelShading_ps.hlsl |
Fixed |
example_Blur_winrt_ps.hlsl |
Fixed |
example_Normal_ps.hlsl |
Fixed |
example_HorizontalColor_ps.hlsl |
Fixed |
Normal_ps.hlsl |
Already correct |
This issue only affects PS shaders paired with a VS from a different file. When the VS and PS are written together (same prefix), the declaration order is naturally consistent.
After migration, the AXSLC_TARGET_* macros were removed from shader sources,
but they are still needed because the Y-axis origin differs between platforms
(GLSL/ESSL: bottom-left; HLSL/SPIRV/MSL: top-left).
axslcc now performs per-target front-end compilation, passing the appropriate
AXSLC_TARGET_<LANG>=1 as a preprocessor define via glslang. This allows
#include "base.hlsli" to resolve #ifdef conditionals correctly at source level.
base.hlsli provides two macros:
| Macro | Purpose |
|---|---|
AXSLC_UV_TOP |
Flag: 0 = bottom-left (GLSL/ESSL), 1 = top-left (HLSL/SPIRV/MSL) |
AX_Y_UP(v) |
Convert Y to bottom-left convention: (AXSLC_UV_TOP ? (1.0 - (v).y) : ((v).y))
|
vr_vs.hlsl uses AX_Y_UP for Y-up texcoord calculation:
output.v_texCoord = float2(input.a_texCoord.x, AX_Y_UP(input.a_texCoord));11 fullscreen PS shaders use #if AXSLC_UV_TOP for gl_FragCoord Y-flip:
#if AXSLC_UV_TOP
float2 fragCoord = float2(input.gl_FragCoord.x, u_screenSize.y - input.gl_FragCoord.y);
#else
float2 fragCoord = input.gl_FragCoord.xy;
#endif| File | Flip source |
|---|---|
example_Heart_ps.hlsl |
u_screenSize.y |
example_Julia_ps.hlsl |
u_screenSize.y |
example_Flower_ps.hlsl |
u_screenSize.y |
example_Mandelbrot_ps.hlsl |
u_screenSize.y |
example_Monjori_ps.hlsl |
u_screenSize.y |
example_Twist_ps.hlsl |
u_screenSize.y |
example_LensFlare_ps.hlsl |
resolution.y |
example_Plasma_ps.hlsl |
u_screenSize.y |
shadertoy_FireBall_ps.hlsl |
u_screenSize.y |
shadertoy_Glow_ps.hlsl |
u_screenSize.y |
shadertoy_LensFlare_ps.hlsl |
u_screenSize.y |
- Builtin engine shaders (57 files)
- cpp-tests shaders (44 GLSL → 50 HLSL)
- spine shaders (2 files)
- Live2D shaders (19 files)
- AXSLCC.cmake: only
.hlsl - axslcc: only
_vs/_ps/_csstage detection - D3D11/D3D12 basic rendering
- Vulkan basic rendering
- GLES (ANGLE) basic rendering
- Metal testing
-
--dxc-reflectDXC reflection extraction -
solid_circle/solid_capsuleSDF shaders (orphan, no test references)
- Cocos2d-x Migration Guide
- SpriteKit to Axmol Engine
- Update Guide to 2.3.0+ for Android
- Migration Guide for PR 3173: Refactor InputSystem