101 lines
7.2 KiB
CMake
101 lines
7.2 KiB
CMake
# Compiles src/shaders/scene.hlsl (which includes src/sceneShared.h) into a C header of SPIR-V, DXIL, MSL and GLSL ES
|
|
# with SDL_shadercross and SPIRV-Cross. Run by the build as
|
|
# cmake -DSHADERCROSS=<tool> -DSPIRVCROSS=<tool> -DSOURCE=<scene.hlsl> -DOUTPUT=<sceneShaders.h> -P shaderHeader.cmake
|
|
# so the header is generated into the build tree like the icon and the other embedded files.
|
|
#
|
|
# SDL_shadercross has no GLSL destination (DXBC, DXIL, MSL, SPIRV, HLSL, JSON), so the ES form is a second step over the
|
|
# SPIR-V it already produced: one pipeline, four outputs. SPIRVCROSS may be empty, and then the ES strings are null and
|
|
# the GLES backend refuses to start -- which is what a build without the host tool should do rather than fail.
|
|
|
|
# Invoked in script mode: -DSHADERCROSS -DSOURCE=<file.hlsl> -DOUTPUT=<header> -DENTRIES=<name:stage;...>
|
|
# -DPREFIX=<lowerCamel prefix> -DTYPE=<struct name>. ENTRIES, PREFIX and TYPE default to the scene's.
|
|
if(NOT ENTRIES)
|
|
set(ENTRIES vertexStatic:vertex vertexSkinned:vertex fragmentMain:fragment depthMain:fragment depthCutoutMain:fragment particleVertex:vertex particleFragment:fragment lineVertex:vertex lineFragment:fragment postVertex:vertex postFragment:fragment skyFragment:fragment bloomDown:fragment bloomUp:fragment)
|
|
endif()
|
|
if(NOT PREFIX)
|
|
set(PREFIX sceneShader)
|
|
endif()
|
|
if(NOT TYPE)
|
|
set(TYPE SceneShaderT)
|
|
endif()
|
|
get_filename_component(sourceName ${SOURCE} NAME)
|
|
string(TOUPPER "${PREFIX}" guard)
|
|
string(REPLACE "SHADER" "_SHADERS_H" guard "${guard}")
|
|
set(entries ${ENTRIES})
|
|
set(formats SPIRV DXIL MSL)
|
|
|
|
# GLSL ES 3.10 for the GLES backend. Three switches matter and each is load bearing:
|
|
# --es --version 310 ES 3.1, the floor the scene needs; its storage buffers are ES 3.1 only.
|
|
# --combined-samplers-inherit-bindings
|
|
# HLSL's separate Texture2D and SamplerState have no ES equivalent, so SPIRV-Cross folds
|
|
# each pair into one sampler and keeps the HLSL t-register as the binding, which is the
|
|
# slot the engine already binds by.
|
|
# --remove-unused-variables Without it every entry point declares every cbuffer and both storage buffers in the file:
|
|
# fragmentMain came out with twelve blocks instead of two, and a fragment shader that
|
|
# merely declares an unused SSBO can fail to link where MAX_FRAGMENT_SHADER_STORAGE_BLOCKS
|
|
# is zero, which ES 3.1 permits.
|
|
# --flip-vert-y SDL_GPU puts the framebuffer origin at the top left, as Vulkan, D3D12 and Metal do, and
|
|
# GL puts it at the bottom left. Without this every render to a texture arrives upside
|
|
# down -- the GUI came out mirrored the first time this ran. Inverting gl_Position.y is
|
|
# the same fix as a negative viewport height, and it also reverses triangle winding, which
|
|
# renderGles.c undoes by flipping the front face it sets.
|
|
# --fixup-clipspace HLSL puts clip-space Z in [0, w] and GL in [-w, w], and GL then stores (z/w + 1) / 2.
|
|
# Without the rewrite a depth written as d lands in the buffer as (d + 1) / 2, while the
|
|
# shader's own shadow comparison still computes d -- so everything in the near half of a
|
|
# light's range read as lit. Sponza showed it as a 2.6x too bright mid-shadow band with
|
|
# correct highlights and correct deep shadow, which is what a half-range comparison does.
|
|
set(esslFlags --es --version 310 --combined-samplers-inherit-bindings --remove-unused-variables --flip-vert-y --fixup-clipspace)
|
|
get_filename_component(sourceDir ${SOURCE} DIRECTORY)
|
|
get_filename_component(includeDir ${sourceDir} DIRECTORY)
|
|
get_filename_component(outputDir ${OUTPUT} DIRECTORY)
|
|
set(work ${outputDir}/work)
|
|
file(MAKE_DIRECTORY ${work})
|
|
|
|
set(header "// Generated from ${sourceName} by cmake/shaderHeader.cmake with SDL_shadercross; do not edit.\n")
|
|
string(APPEND header "// SPIR-V for Vulkan, DXIL for Direct3D 12, MSL for Metal, GLSL ES for OpenGL ES, one set per entry point.\n\n")
|
|
string(APPEND header "#ifndef ${guard}\n#define ${guard}\n\n#include <stddef.h>\n#include <stdlib.h>\n\n")
|
|
string(REGEX REPLACE "T$" "S" structName "${TYPE}")
|
|
string(APPEND header "typedef struct ${structName} {\n\tconst char *entryPoint;\n\tconst unsigned char *spirv;\n\tsize_t spirvSize;\n\tconst unsigned char *dxil;\n\tsize_t dxilSize;\n\tconst unsigned char *msl;\n\tsize_t mslSize;\n\tconst char *essl; // GLSL ES 3.10 source, or NULL when the host tool was absent\n} ${TYPE};\n\n")
|
|
foreach(entry IN LISTS entries)
|
|
string(REPLACE ":" ";" parts ${entry})
|
|
list(GET parts 0 name)
|
|
list(GET parts 1 stage)
|
|
foreach(format IN LISTS formats)
|
|
string(TOLOWER ${format} extension)
|
|
set(blob ${work}/${name}.${extension})
|
|
execute_process(COMMAND ${SHADERCROSS} ${SOURCE} -s HLSL -d ${format} -t ${stage} -e ${name} -I ${includeDir} -o ${blob} RESULT_VARIABLE code OUTPUT_VARIABLE out ERROR_VARIABLE err)
|
|
if(NOT code EQUAL 0)
|
|
message(FATAL_ERROR "Shader ${name} (${format}) failed to compile:\n${out}\n${err}")
|
|
endif()
|
|
file(READ ${blob} hex HEX)
|
|
string(REGEX REPLACE "([0-9a-f][0-9a-f])" "0x\\1," bytes "${hex}")
|
|
string(APPEND header "static const unsigned char _${name}${format}[] = {\n\t${bytes}\n};\n\n")
|
|
endforeach()
|
|
# The ES form, cross compiled from the SPIR-V above. Emitted as a C string rather than bytes because that is what
|
|
# glShaderSource takes; the escaping is only quotes and backslashes, neither of which GLSL ES has a use for.
|
|
set(esslSymbol "NULL")
|
|
if(SPIRVCROSS)
|
|
set(essl ${work}/${name}.essl)
|
|
execute_process(COMMAND ${SPIRVCROSS} ${esslFlags} ${work}/${name}.spirv --output ${essl} RESULT_VARIABLE code OUTPUT_VARIABLE out ERROR_VARIABLE err)
|
|
if(NOT code EQUAL 0)
|
|
message(FATAL_ERROR "Shader ${name} (GLSL ES) failed to cross compile:\n${out}\n${err}")
|
|
endif()
|
|
file(READ ${essl} esslText)
|
|
# SPIRV-Cross defaults an ES fragment shader to "precision mediump float", and a struct
|
|
# declared inside a uniform block carries no qualifier of its own, so Light's members inherit
|
|
# mediump -- fp16 on a real tiler. The scene's own values do not fit that: a camera or a
|
|
# light tens of units from the origin loses whole units of position. Ask for highp instead.
|
|
string(REPLACE "precision mediump float;" "precision highp float;" esslText "${esslText}")
|
|
string(REPLACE "\\" "\\\\" esslText "${esslText}")
|
|
string(REPLACE "\"" "\\\"" esslText "${esslText}")
|
|
string(REPLACE "\n" "\\n\"\n\t\"" esslText "${esslText}")
|
|
string(APPEND header "static const char _${name}ESSL[] =\n\t\"${esslText}\";\n\n")
|
|
set(esslSymbol "_${name}ESSL")
|
|
endif()
|
|
string(SUBSTRING ${name} 0 1 initial)
|
|
string(SUBSTRING ${name} 1 -1 rest)
|
|
string(TOUPPER ${initial} initial)
|
|
string(APPEND header "static const ${TYPE} ${PREFIX}${initial}${rest} = { \"${name}\", _${name}SPIRV, sizeof(_${name}SPIRV), _${name}DXIL, sizeof(_${name}DXIL), _${name}MSL, sizeof(_${name}MSL), ${esslSymbol} };\n\n")
|
|
endforeach()
|
|
string(APPEND header "#endif\n")
|
|
file(WRITE ${OUTPUT} "${header}")
|