ShaderTranslator provides a layered compilation pipeline that separates shader language parsing from target code generation. This allows optional, modular support for different input languages.
┌─────────────────────────────────────────────────────────────┐
│ Input Languages │
├─────────────────┬──────────────────┬────────────────────────┤
│ GLSL │ HLSL │ SPIR-V │
│ (glslang) │ (DXC/Optional) │ (pre-compiled) │
└────────┬────────┴────────┬─────────┴───────────┬────────────┘
│ │ │
└─────────────────┴─────────────────────┘
▼
┌──────────────┐
│ SPIR-V │ ← Intermediate bytecode
│ (cached) │
└──────┬───────┘
│
┌─────────────────┴─────────────────┐
▼ ▼
┌─────────────┐ ┌─────────────┐
│ SPIRV-Cross │ │ SPIRV-Cross │
│ MSL │ │ HLSL │
└─────────────┘ └─────────────┘
│ │
▼ ▼
Metal API D3D12 API
GLSL Parser (Required - glslang)
- Library: glslang from ZVulkan
- Linking: Static, always available
- Usage: Automatic for all GLSL shaders
- Entry Point:
ShaderCompiler::CompileGLSL()
// Always available - no conditional compilation needed
auto result = compiler.CompileGLSL(glslSource, ShaderStage::Vertex, TargetLanguage::MSL);HLSL Parser (Optional - DXC)
- Library: DirectX Shader Compiler (dxcompiler.dylib/dll)
- Linking: Dynamic, loaded only when enabled
- CMake Flag:
SHADER_TRANSLATOR_ENABLE_HLSL_INPUT=ON - Compile Define:
SHADER_TRANSLATOR_HLSL_INPUT_ENABLED - Entry Point:
ShaderCompiler::CompileHLSL()
#ifdef SHADER_TRANSLATOR_HLSL_INPUT_ENABLED
auto result = compiler.CompileHLSL(hlslSource, ShaderStage::Vertex, TargetLanguage::MSL);
#endifAll language parsers produce SPIR-V bytecode as intermediate representation:
- Platform-agnostic
- Cacheable to disk
- Optimizable
// Get SPIR-V for caching
compiler.CompileGLSL(source, stage, TargetLanguage::SPIRV);
std::vector<uint32_t> spirv = compiler.GetLastSPIRV();
// Save to disk...
// Later: translate cached SPIR-V
SPIRVTranslator translator;
auto mslResult = translator.TranslateToMSL(cachedSPIRV, 22);Metal Shading Language (MSL) - Always enabled
- Library: spirv-cross-msl
- Target: Metal 2.0-2.4 (macOS 10.13+)
HLSL Output (Optional)
- Library: spirv-cross-hlsl
- CMake Flag:
SHADER_TRANSLATOR_ENABLE_HLSL=ON(default ON) - Target: Shader Model 6.0+ (D3D12)
// Metal renderer shader compilation
ShaderCompiler compiler;
auto result = compiler.CompileGLSL(
glslShaderSource,
ShaderStage::Vertex,
TargetLanguage::MSL,
PlatformVersion{ .metalVersion = 22 }
);
if (result.success) {
MTL::Library* library = device->newLibrary(
NS::String::string(result.source.c_str(), NS::UTF8StringEncoding),
nullptr, &error
);
}// Pre-compile shaders at build time and cache SPIR-V
ShaderCompiler compiler;
// Compile GLSL to SPIR-V
compiler.CompileGLSL(glslSource, stage, TargetLanguage::SPIRV);
std::vector<uint32_t> spirv = compiler.GetLastSPIRV();
// Save SPIR-V to .spv file
SaveToFile("shader.vert.spv", spirv);
// At runtime: Load and translate
std::vector<uint32_t> cachedSPIRV = LoadFromFile("shader.vert.spv");
SPIRVTranslator translator;
auto mslResult = translator.TranslateToMSL(cachedSPIRV, 22);// Convert HLSL to MSL (requires DXC)
#ifdef SHADER_TRANSLATOR_HLSL_INPUT_ENABLED
ShaderCompiler compiler;
// HLSL → SPIR-V → MSL
auto result = compiler.CompileHLSL(
hlslSource,
ShaderStage::Fragment,
TargetLanguage::MSL
);
// Or: HLSL → SPIR-V → HLSL (for D3D12 compatibility layer)
auto d3d12Result = compiler.CompileHLSL(
hlslSource,
ShaderStage::Fragment,
TargetLanguage::HLSL // Re-emit as D3D12-compatible HLSL
);
#endifEnable if:
- Building cross-platform shader tools
- Converting D3D12 shaders to Metal
- Porting Windows games to macOS
- Research/experimentation with shader languages
Disable if:
- Only using GLSL shaders (GZDoom default)
- Minimizing dependencies
- DXC not available on platform
macOS (VulkanSDK):
cmake .. -DSHADER_TRANSLATOR_ENABLE_HLSL_INPUT=ON \
-DSHADER_TRANSLATOR_USE_SYSTEM_DXC=ON
# DXC from: /path/to/VulkanSDK/x.x.x/macOS/lib/libdxcompiler.dylibWindows (VulkanSDK):
cmake .. -DSHADER_TRANSLATOR_ENABLE_HLSL_INPUT=ON
# DXC from: C:\VulkanSDK\x.x.x\Lib\dxcompiler.libBundled DXC (future):
cmake .. -DSHADER_TRANSLATOR_ENABLE_HLSL_INPUT=ON \
-DSHADER_TRANSLATOR_USE_SYSTEM_DXC=OFF
# Uses libraries/ShaderTranslator/thirdparty/dxc/ (not yet implemented)Linkage: Static Reason: Core dependency from ZVulkan, always available Binary Impact: ~500KB (already in GZDoom for Vulkan renderer)
Linkage: Dynamic (runtime loaded) Reason: Optional feature, large library (~53MB) Binary Impact:
- Disabled: 0 bytes (code removed by #ifdef)
- Enabled: ~5KB (DXC API wrapper code only)
Runtime Loading:
#ifdef SHADER_TRANSLATOR_HLSL_INPUT_ENABLED
// DXC loaded via DxcCreateInstance() - only called when CompileHLSL() is used
// If DXC library not found, runtime error is thrown
#endifLinkage: Static Reason: Core translation engine Binary Impact:
- MSL support: ~200KB (required for Metal)
- HLSL support: +50KB (optional, for D3D12 output)
| Configuration | glslang | DXC | spirv-cross-msl | spirv-cross-hlsl | Use Case |
|---|---|---|---|---|---|
| Minimal (GZDoom default) | ✅ Static | ❌ | ✅ Static | ❌ | GLSL → MSL only |
| Full Output | ✅ Static | ❌ | ✅ Static | ✅ Static | GLSL → MSL/HLSL |
| Full Input | ✅ Static | ✅ Dynamic | ✅ Static | ✅ Static | GLSL/HLSL → MSL/HLSL |
#include <shadertranslator/shader_translator.h>
ShaderTranslator::ShaderCompiler compiler;
std::string glsl = R"(
#version 450
layout(location = 0) in vec3 position;
void main() {
gl_Position = vec4(position, 1.0);
}
)";
auto result = compiler.CompileGLSL(
glsl,
ShaderTranslator::ShaderStage::Vertex,
ShaderTranslator::TargetLanguage::MSL,
ShaderTranslator::PlatformVersion{ .metalVersion = 20 }
);
if (result.success) {
// result.source contains MSL code ready for MTLDevice
CreateMetalShader(result.source);
}#ifdef SHADER_TRANSLATOR_HLSL_INPUT_ENABLED
std::string hlsl = R"(
struct VS_INPUT {
float3 position : POSITION;
};
float4 main(VS_INPUT input) : SV_POSITION {
return float4(input.position, 1.0);
}
)";
auto result = compiler.CompileHLSL(
hlsl,
ShaderTranslator::ShaderStage::Vertex,
ShaderTranslator::TargetLanguage::MSL
);
if (result.success) {
// HLSL translated to Metal - use on macOS
}
#endif// Build time: Compile once
ShaderCompiler compiler;
compiler.CompileGLSL(glslSource, stage, TargetLanguage::SPIRV);
std::vector<uint32_t> spirv = compiler.GetLastSPIRV();
// Save to asset file
SaveAssetFile("shader.vert.spv", spirv);
// Runtime: Fast translation from cached SPIR-V
std::vector<uint32_t> cachedSPIRV = LoadAssetFile("shader.vert.spv");
SPIRVTranslator translator;
// Platform-specific translation
#ifdef __APPLE__
auto result = translator.TranslateToMSL(cachedSPIRV, 22);
#elif defined(_WIN32)
auto result = translator.TranslateToHLSL(cachedSPIRV, 60);
#endiflibshader-translator.a: 53 KB
Dependencies:
- glslang (from ZVulkan): ~500 KB (shared with Vulkan)
- spirv-cross-msl: ~200 KB
Total overhead: ~753 KB
libshader-translator.a: 58 KB (+5 KB for DXC wrapper)
Runtime dependencies:
- libdxcompiler.dylib: 53 MB (not bundled, VulkanSDK)
- spirv-cross-hlsl: +50 KB
Total static overhead: ~808 KB
| Operation | Time (macOS M1) | Cacheable |
|---|---|---|
| GLSL → SPIR-V (glslang) | ~5-20ms | ✅ Yes |
| HLSL → SPIR-V (DXC) | ~10-30ms | ✅ Yes |
| SPIR-V → MSL (SPIRV-Cross) | ~2-5ms | ❌ No (fast enough) |
| SPIR-V → HLSL (SPIRV-Cross) | ~2-5ms | ❌ No (fast enough) |
Recommendation: Cache SPIR-V bytecode for production, compile on-demand for development.
| Engine | GLSL Support | HLSL Support | SPIR-V Caching | Metal Backend |
|---|---|---|---|---|
| GZDoom (ShaderTranslator) | ✅ glslang | ✅ DXC (opt) | ✅ Yes | ✅ MSL |
| Unreal Engine | ❌ HLSL only | ✅ Native | ✅ Yes | ✅ SPIRV-Cross |
| Unity | ✅ Limited | ✅ Native | ✅ Yes | ✅ SPIRV-Cross |
| id Tech 7 | ❌ | ✅ HLSL only | ✅ Yes | ✅ Custom |
| Source 2 | ✅ Via VPC | ❌ | ❌ No | ✅ Custom |
ShaderTranslator provides best-of-both-worlds: native GLSL support (for existing shaders) + optional HLSL (for cross-platform tools).