Shaders are regular HLSL 2021 files that live alongside C++ source code in the solution.
They are compiled to platform bytecode by EsotericaReflector.exe (invoked via the Esoterica.Scripts.Reflect project or RunReflection.bat) — DXIL for D3D12, SPIR-V for Vulkan, and so on.
The engine creates root signatures and pipelines from the bytecode at initialization.
We start with the simplest possible surface shader — Downsample.esf — to show the complete example before diving into each piece.
#include "Base/Render/RHI.esh"
#include "Engine/Render/Shaders/CommonSamplers.esh"
#include "Engine/Render/Shaders/FullscreenTriangle.esh"
#ifndef __cplusplus
ESF_RESOURCE_TABLE struct DownsampleResourceTable
{
Texture2D m_inputTexture;
SamplerState m_inputSampler;
};
#endif
#include "Engine/_Module/_AutoGenerated/ShaderReflection/Downsample.esh"
#ifndef __cplusplus
ESF_PARAMETER string ShaderType = "Surface";
#define DownsampleRootSignature EE_BEGIN_ROOT_SIGNATURE \
DownsampleResourceTableRootConstant( "b0", "space=0" )
EE_DECLARE_ROOT_CONSTANTS( DownsampleResourceTableData, b0, space0 );
[RootSignature( DownsampleRootSignature )]
float4 PS_main( FullscreenTriangleVertexOutput input ) : SV_Target
{
DownsampleResourceTable resourceTable = CreateDownsampleResourceTable( RootConstants );
return resourceTable.m_inputTexture.SampleLevel( resourceTable.m_inputSampler, input.m_uv, 0 );
}
#endif
Every .esf follows this structure:
RHI.esh (shared CPU/GPU types), helper headers, and the auto-generated reflection header.ESF_RESOURCE_TABLE, defines the inputs the shader consumes. Wrapped in #ifndef __cplusplus because it's HLSL-only.ESF_PARAMETER — the only mandatory one is ShaderType. Tells the reflector which pipeline to build.PS_main here; MaterialShaderMain, VS_main, or CS_main for other shader types. Calls Create<Name>ResourceTable once at the top to decode the packed data.This is the pattern for every shader in the engine. The rest of this document explains each piece in detail.
Shader compilation is separate from C++ compilation. After changing a .esf or.esh:
RunReflection.bat (or build Esoterica.Scripts.Reflect in Visual Studio). This generates reflection headers and compiles bytecode.CompileShaders.bat. This recompiles changed shaders and hot-reloads them into the live engine without restarting.When EsotericaReflector.exe runs, it performs these steps for each .esf (as part of its broader C++ reflection pass):
ESF_PARAMETER, ESF_RESOURCE_TABLE, and ESF_CUSTOM_PARAMETERS declarations..esh file under Code/Engine/_Module/_AutoGenerated/ShaderReflection/<Name>.esh containing the packed data struct, Create<Name>ResourceTable, and Load<Name>ResourceTable functions.