SRSL — SpaRcle Shader Language¶
SRSL — собственный шейдерный язык SREngine. Синтаксис основан на GLSL (типы, встроенные функции, операторы те же), но один .srsl файл описывает сразу все стадии шейдера, настройки пайплайна и свойства материала. Движок транслирует SRSL в GLSL (Vulkan) и WGSL (WebGPU).
Где что лежит¶
| Что | Путь |
|---|---|
| Шейдеры движка | Resources/Engine/Shaders/ |
| Общие include-файлы | Resources/Engine/Shaders/Common/ |
| Шейдеры редактора | Resources/Editor/Shaders/ |
| Реализация компилятора | Engine/libs/Graphics/src/Graphics/SRSL/, Engine/libs/Graphics/inc/Graphics/SRSL/ |
| Точка входа компиляции | SRSLShader::Load() — Shader.cpp |
| Встроенные переменные (uniform'ы, сэмплеры, push-константы, выходы) | ShaderVariables.cpp |
Enum'ы ShaderType | ShaderProperties.h |
| Enum'ы пайплайна, имена макросов движка | ShaderUtils.h |
| Имена вершинных атрибутов | VertexAttributeToName() — Vertices.cpp |
| Генераторы кода | GLSLCodeGenerator.cpp, WGSLCodeGenerator.cpp |
| Сгенерированный код (кэш) | <Cache>/Shaders/<путь шейдера>/<хэш макросов>/shader.{vert,frag,comp} |
Конвейер компиляции¶
.srsl → Lexer → PreProcessor (#include, #define, #if...) → LexicalAnalyzer (AST)
→ RefAnalyzer (что реально используется) → SRSLShader::Prepare()
(настройки, uniform-блоки, сэмплеры, биндинги, стадии)
→ GLSLCodeGenerator / WGSLCodeGenerator → кэш на диске
Важные следствия:
- Неиспользуемое вырезается.
RefAnalyzerстроит граф использования от точек входа. Uniform'ы, сэмплеры, SSBO и[[shared]]переменные, недостижимые изvertex()/fragment()/compute(), не попадают ни в блоки, ни в биндинги, ни в свойства материала. Поэтому в шейдерах встречаются строки видаfloat unused_var = SUN_INTENSITY;— это принудительное «использование». - Шейдер компилируется отдельно для каждого набора макросов (
ShaderParams). Один.srslдаёт много вариантов (со скелетом/без, проход теней, color-pass и т.д.). - Биндинги назначаются автоматически: сначала uniform-блоки, затем SSBO-блоки, затем сэмплеры (по алфавиту имён, т.к. это
Map). Вручную биндинги не указываются.
Структура файла¶
// 1. Настройки пайплайна (глобальные "объявления" вида <Ключ> <Значение>;)
ShaderType Spatial;
PolygonMode Fill;
CullMode Back;
DepthCompare LessOrEqual;
PrimitiveTopology TriangleList;
BlendEnabled false;
DepthWrite true;
DepthTest true;
// 2. Ресурсы: uniform'ы, сэмплеры, SSBO, константы, структуры
[[uniform], [public]] vec4 color = vec4(1.0);
[[uniform], [public]] sampler2D diffuse;
// 3. Переменные, передаваемые между стадиями
[[shared]] vec2 uv;
// 4. Обычные функции (как в GLSL)
float luminance(vec3 c) { return dot(c, vec3(0.2126, 0.7152, 0.0722)); }
// 5. Точки входа
void vertex() {
uv = UV;
OUT_POSITION = PROJECTION_MATRIX * VIEW_MATRIX * MODEL_MATRIX * vec4(VERTEX, 1.0);
}
void fragment() {
COLOR = texture(diffuse, uv) * color;
}
Порядок объявлений на верхнем уровне не важен: точки входа, функции и ресурсы могут стоять в любом месте файла.
Точки входа¶
| Функция | Стадия | Расширение в кэше |
|---|---|---|
void vertex() | Vertex | .vert |
void fragment() | Fragment | .frag |
[[THREADS(x, y, z)]] void compute() | Compute | .comp |
Стадия существует, только если объявлена соответствующая функция. compute() обязан иметь декоратор [[THREADS(x, y, z)]] с тремя ненулевыми аргументами — это local_size рабочей группы.
Настройки пайплайна¶
Записываются на верхнем уровне как Ключ Значение;. Парсятся в SRSLShader::PrepareSettings(). Их можно оборачивать в #ifdef.
| Ключ | Значения | Обязателен |
|---|---|---|
ShaderType | Spatial, SpatialCustom, Skinned, PostProcessing, Skybox, Simple, Canvas, Particles, Compute, Line, Custom, RayTrace | да (иначе ошибка) |
PolygonMode | Fill, Line, Point | нет |
CullMode | None, Front, Back, FrontAndBack | нет |
DepthCompare | Never, Less, Equal, LessOrEqual, Greater, NotEqual, GreaterOrEqual, Always | нет |
PrimitiveTopology | PointList, LineList, LineStrip, TriangleList, TriangleStrip, TriangleFan, ...WithAdjacency, PathList | нет |
BlendEnabled | true / false | нет |
AlphaCoverage | true / false | нет |
DepthWrite | true / false | нет |
DepthTest | true / false | нет |
Назначение основных ShaderType:
Spatial— статические меши;Skinned— меши со скелетом;SpatialCustom— пространственный, только вершины.PostProcessing— полноэкранный проход (геометрии нет, вершины генерируются изVERTEX_INDEX).Skybox,Canvas(2D UI),Particles,Line(линия с началом и концом),Compute.Custom— всё настраивается вручную.
Типы¶
- Скаляры:
bool,int,uint,float - Векторы:
vec2..4,ivec2..4,uvec2..4,bvec2..4 - Матрицы:
mat2,mat3,mat4 - Сэмплеры:
sampler1D,sampler2D,sampler2DArray,sampler2DMS,sampler3D,samplerCube,sampler1DShadow,sampler2DShadow - Массивы: тип с размером записывается у типа:
mat4[4] m;,float[256] buf;. Массив без размераT[]— только для SSBO. Внутри функций допустим и GLSL-стиль:float a[4] = { ... }; - Структуры:
struct Name { vec3 a; float b; };— можно использовать в SSBO и в коде.
bool в uniform-блоке хранится как int.
Декораторы¶
Синтаксис: [[dec1], [dec2(arg)]] перед объявлением. Одиночный: [[dec]].
Переменные верхнего уровня¶
| Декоратор | Смысл |
|---|---|
[[uniform]] | Поле uniform-блока BLOCK (per-object данные). Для сэмплера — текстура. |
[[uniform(NAME)]] | Поле uniform-блока с именем NAME. SHARED — общий для всех объектов блок (данные кадра/камеры). |
[[public]] | Свойство видно в материале (редактор, .mat-файлы). Без него переменная задаётся только из кода движка. |
[[const], [uniform]] | Push-константа (быстрые per-draw данные, общий лимит размера ~128 байт). |
[[const]] | Константа времени компиляции (const в GLSL). Значение обязательно. |
[[shared]] | Переменная, передаваемая vertex → fragment (varying). Пишется в vertex(), читается в fragment(). |
[[shared(workgroup)]] | Разделяемая память рабочей группы в compute (shared в GLSL). |
[[ssbo(NAME)]] | Поле storage-буфера NAME. Несколько переменных с одним NAME образуют один буфер; массив без размера должен быть последним. |
[[readonly]], [[writeonly]], [[coherent]], [[volatile]], [[restrict]] | Модификаторы SSBO-блока. |
[[attachment(N)]] | Сэмплер — аттачмент кадрового буфера (выставляется проходами рендера). |
Функции¶
| Декоратор | Смысл |
|---|---|
[[THREADS(x, y, z)]] | Размер рабочей группы для compute(). Обязателен. |
Значения по умолчанию¶
Uniform'ы с инициализатором получают значение по умолчанию для материала:
[[uniform], [public]] float intensity = 1.0;
[[uniform], [public]] vec3 tint = vec3(1.0, 0.5, 0.2); // vec2/vec3/ivec3/vec4 или скаляр
[[uniform], [public]] vec4 c = vec4(1.0); // один аргумент — заполняет все компоненты
[[uniform], [public]] sampler2D tex = "Engine/Textures/white.png"; // путь к текстуре по умолчанию
Поддерживаются только литералы (float, int, vec2, vec3, ivec3, vec4, строка для сэмплера) — выражения не вычисляются.
Встроенные переменные¶
Их не нужно объявлять — достаточно использовать. Движок сам добавит их в нужный блок и будет заполнять. Актуальный список — ShaderVariables.cpp.
Uniform-блок SHARED (данные кадра)¶
| Имя | Тип |
|---|---|
VIEW_MATRIX, INVERSE_VIEW_MATRIX, VIEW_NO_TRANSLATE_MATRIX | mat4 |
PROJECTION_MATRIX, INVERSE_PROJECTION_MATRIX, PROJECTION_NO_FOV_MATRIX | mat4 |
ORTHOGONAL_MATRIX, PIXEL_ORTHOGONAL_MATRIX, LIGHT_SPACE_MATRIX | mat4 |
TIME, CAMERA_NEAR, CAMERA_FAR | float |
SUN_INTENSITY, SHADOW_STRENGTH, AMBIENT_INTENSITY | float |
RENDER_PASS_TYPE | int |
RESOLUTION, ASPECT | vec2 |
CASCADE_LIGHT_SPACE_MATRICES | mat4[4] |
CASCADE_RADII, CASCADE_SPLITS | vec4 |
CASCADE_CENTERS | vec3[4] |
DIRECTIONAL_LIGHT_DIRECTION, VIEW_POSITION, VIEW_DIRECTION | vec3 |
SUN_COLOR, SKY_COLOR, GROUND_COLOR | vec3 |
Uniform-блок BLOCK (данные объекта)¶
| Имя | Тип |
|---|---|
MODEL_MATRIX, MODEL_NO_SCALE_MATRIX | mat4 |
SKELETON_MATRICES_{128,256,384}, SKELETON_MATRIX_OFFSETS_{128,256,384} | mat4[N] |
HALF_SIZE_NEAR_PLANE, TEXT_ATLAS_SIZE, UI_SCALE, UI_PIVOT | vec2 |
LINE_START_POINT, LINE_END_POINT | vec3 |
LINE_COLOR, RGBA_VALUE, SLICED_TEXTURE_BORDER, SLICED_WINDOW_BORDER, NDC_RECT | vec4 |
SSAO_SAMPLES | vec4[64] |
TEXT_RECT_X, TEXT_RECT_Y, TEXT_RECT_WIDTH, TEXT_RECT_HEIGHT, SPRITE_FILL_AMOUNT | float |
FILL_CENTER, SPRITE_MODE, SPRITE_FILL_ORIGIN, SPRITE_FILL_METHOD, SPRITE_FILL_CLOCKWISE | int |
Push-константы¶
| Имя | Тип |
|---|---|
PC_SHADOW_CASCADE_INDEX, PC_COLOR_BUFFER_MODE, COMPUTE_STAGE | int |
PC_COLOR_BUFFER_VALUE | vec3 |
Сэмплеры¶
| Имя | Тип |
|---|---|
SKYBOX_DIFFUSE | samplerCube |
TEXT_ATLAS_TEXTURE, SSAO_NOISE | sampler2D |
Вершинные атрибуты (вход vertex, по умолчанию доступны и во fragment)¶
Набор атрибутов задаёт не шейдер, а меш (ShaderParams::VertexLayoutDescriptions). Имена:
VERTEX (позиция), NORMAL, TANGENT (vec4, w — знак битангенса), UV, UV1..UV7, COLOR0..COLOR7, BLEND_INDICES, BLEND_WEIGHTS, BLEND_INDICES2, BLEND_WEIGHTS2, MATERIAL_INDICES, MATERIAL_WEIGHTS, MATERIAL_ID0..7, BLEND_FACTOR, POSITION0, POSITION1, CUSTOM0..CUSTOM7.
Атрибуты автоматически передаются из vertex во fragment. Если во vertex() присвоить им новое значение (например, VERTEX = (MODEL_MATRIX * vec4(VERTEX, 1.0)).xyz;), во fragment() придёт уже изменённое значение — так VERTEX во фрагментном шейдере становится мировой позицией.
Специальные переменные стадий¶
| Стадия | Имя | Тип | Описание |
|---|---|---|---|
| vertex | OUT_POSITION | vec4 | Выходная позиция в clip-space (gl_Position). Если не задана, используется vec4(VERTEX, 1.0). |
| vertex | VERTEX_INDEX | int | Индекс вершины (gl_VertexIndex). |
| fragment | COLOR | vec4 | Алиас основного выхода COLOR_INDEX_0. |
| fragment | COLOR_INDEX_0..8 | vec4 | Выходы в аттачменты (MRT). |
| fragment | FRAG_COORD | vec4 | gl_FragCoord. |
| compute | GLOBAL_INVOCATION_ID, WORK_GROUP_ID, NUM_WORK_GROUPS, LOCAL_INVOCATION_ID | uvec3 | |
| compute | LOCAL_INVOCATION_INDEX | uint |
GLSL-встроенные (gl_FragCoord, gl_InstanceIndex, gl_GlobalInvocationID, gl_Layer, barrier() ...) тоже работают, но для переносимости на WebGPU предпочтительнее SRSL-имена.
Выход COLOR_INDEX_N становится настоящим layout(location = N) out, только если определён макрос USE_COLOR_INDEX_N (его выставляет проход рендера по числу аттачментов фреймбуфера). Иначе запись в него эмулируется локальной переменной и просто отбрасывается — поэтому шейдер может писать в COLOR_INDEX_1..3 безопасно для любого фреймбуфера.
Хуки специальных проходов¶
| Функция | Когда вызывается |
|---|---|
void fragment_color_buffer_discard() | В конце fragment() в color-buffer проходе (SR_DEFINE_COLOR_PASS, выбор объектов мышью). Обычно содержит discard;, чтобы объект не выбирался. |
void fragment_depth_buffer_discard() | В конце fragment() в проходе каскадных теней (SR_DEFINE_CASCADED_SHADOW_MAP_PASS). Например, для alpha-test теней. |
Препроцессор¶
| Директива | Описание |
|---|---|
#include <Path/To/file.srsl> | Путь только в угловых скобках и относительно Resources/: #include <Engine/Shaders/Common/utils.srsl>. Текст вставляется на место директивы, поэтому include можно делать и внутри функции (см. *.inl.srsl). |
#define NAME | Определить макрос. Значений у макросов нет — только «определён / не определён». |
#undef NAME | Удалить макрос. |
#ifdef NAME / #ifndef NAME | |
#if <выражение> | Поддерживаются defined(NAME), &&, \|\|, true, false, числа и + - * /. |
#else, #endif | #elif нет — используйте вложенные #if. |
Других директив нет (нет #pragma, #version, макросов с подстановкой значений) — неизвестная директива в активной ветке является ошибкой.
Макросы, которые выставляет движок¶
| Макрос | Источник / смысл |
|---|---|
HAS_SKELETON | Меш со скелетом |
HAS_NORMAL, HAS_ROUGHNESS, HAS_ORM, HAS_SSS, HAS_EMISSION, HAS_DETAIL_WEIGHT, HAS_ALPHA_MASK | Материал: задана соответствующая текстура |
HAS_ALPHA | Материал прозрачный / с альфа-тестом |
DISABLE_BLENDING | Материал |
SR_DEFINE_COLOR_PASS | Проход color-buffer (picking) |
SR_DEFINE_CASCADED_SHADOW_MAP_PASS | Проход рендера каскадных теней |
SR_DEFINE_USE_CASCADED_SHADOW_MAP | Основной проход с тенями |
SR_DEFINE_DEBUG_CASCADED_SHADOW_MAP_PASS, SR_DEFINE_DEBUG_NORMALS, SR_DEFINE_WIREFRAME | Отладка |
CASCADES_INSTANCING, CASCADES_FRUSTUM_CULLING | Варианты прохода теней |
USE_COLOR_INDEX_0..8 | Реальное количество выходов фреймбуфера |
Имена макросов движка — константы SHADER_MACRO_* в ShaderUtils.h.
Связь с материалами¶
[[uniform], [public]] переменные и сэмплеры становятся свойствами материала (MaterialData, MaterialData.h). Материалы (.mat) хранятся в формате SRA (SRASerializer, SRASerialization.h) и сериализуются через рефлексию, вручную их обычно не пишут — материал создаётся и редактируется в редакторе.
Что важно знать со стороны шейдера:
defaultShader.shaderPath— путь к.srsl(отResources/).defaultShader.uniforms/defaultShader.samplers— значения свойств:id(= имя переменной в шейдере),type(ShaderVarType:Float,Int,Vec2,Vec3,Vec4,Sampler2D, ...) иvalue(для сэмплера — путь к текстуре).- Значение, совпадающее со значением по умолчанию из шейдера, не сохраняется.
shaderDefines— макросы, которые материал передаёт в шейдер (HAS_NORMAL,HAS_ORM,HAS_ALPHA, ...). В редакторе они выставляются флагами группыParams(hasNormals,hasORM,hasAlpha, ...). Изменение макросов пересобирает вариант шейдера.
Фрагмент .mat (сокращён):
sra format
0-r:Root
1-o:asset
2-v:type
3-s:FileMaterialResource
2-o:ptr
3-o:data
4-v:type
5-s:MaterialData
4-o:ptr
5-a:shaderDefines
6-k:item
7-v:first
8-s:HAS_NORMAL
7-v:second
8-s:
5-o:defaultShader
6-v:shaderPath
7-s:Engine/Shaders/standard.srsl
6-a:uniforms
7-k:i
8-o:d
9-v:id
10-s:color
9-v:type
10-s:Vec4
9-o:value
10-v:x
11-f:1
...
6-a:samplers
7-k:i
8-o:d
9-v:id
10-s:diffuse
9-v:type
10-s:Sampler2D
9-v:value
10-s:Engine/Textures/default_improved.png
Свойство, не используемое ни одной точкой входа в данном варианте шейдера, в материале не появится.
Примеры¶
Стандартный меш с переопределяемыми функциями¶
standard.srsl подключает Common/standard-header.srsl (настройки пайплайна, SSBO костей) и Common/standard-vertex.srsl (vertex() + [[shared]] mat3 TBN). Расширение через макросы-хуки:
SR_STANDARD_VERTEX_POSITION_FUNC—vertex()вызовет вашуvec3 CalculateVertexPosition()вместоVERTEX;STANDARD_SHADER_CUSTOM_FRAGMENT— нужно самому определитьfragment_uv(),fragment_diffuse(vec2),fragment_normal(vec2).
Пример (сокращённый grass.srsl):
[[uniform], [public]] sampler2D windMask;
void fragment_depth_buffer_discard() {
if ((texture(diffuse, UV) * color).a <= alphaThreshold) {
discard;
}
}
#define SR_STANDARD_VERTEX_POSITION_FUNC
vec3 CalculateVertexPosition() {
return VERTEX + NORMAL * sin(TIME * 0.0005) * 0.1;
}
#include <Engine/Shaders/standard.srsl>
CullMode None; // настройка после include переопределяет значение из standard-header
Функции и переменные можно использовать до их объявления в файле (diffuse, color объявлены в standard.srsl ниже по тексту). Если настройка пайплайна встречается несколько раз, побеждает последняя.
Полноэкранный пост-эффект¶
ShaderType PostProcessing;
PolygonMode Fill;
CullMode Back;
DepthCompare LessOrEqual;
PrimitiveTopology TriangleList;
BlendEnabled false;
DepthWrite false;
DepthTest false;
[[uniform]] sampler2D colorMap;
[[public], [uniform(SHARED)]] float vignetteIntensity = 0.5;
[[shared]] vec2 uv;
void vertex() {
uv = vec2(float((VERTEX_INDEX << 1) & 2), float(VERTEX_INDEX & 2));
OUT_POSITION = vec4(uv * 2.0 - 1.0, 0.0, 1.0);
}
void fragment() {
COLOR = texture(colorMap, uv);
}
Compute с SSBO и shared-памятью¶
ShaderType Compute;
[[uniform]] sampler2D hdrTexture;
[[uniform]] vec2 resolution;
[[ssbo(reductionOut)]] float[] reductionOut;
[[shared(workgroup)]] float[256] shSum;
[[THREADS(16, 16, 1)]]
void compute() {
uint tid = gl_LocalInvocationIndex;
...
barrier();
...
}
SSBO со структурами и инстансингом¶
struct SynapseRenderData {
vec3 from;
vec3 to;
vec4 startColor;
vec4 endColor;
};
[[ssbo(synapses)]] uint synapsesCount;
[[ssbo(synapses)]] SynapseRenderData[] synapses;
[[shared]] vec4 startColor;
void vertex() {
startColor = synapses[gl_InstanceIndex].startColor;
...
}
Тесты¶
Модульные тесты транслятора — Resources/ModuleTests/SRSL/*.srsl. Тест SRSLTest (SRSLTest.h) компилирует каждый файл в GLSL, пишет результат в ModuleTests/SRSL/Result/ и сравнивает с ModuleTests/SRSL/Expected/. Эти файлы — хорошие минимальные примеры отдельных возможностей языка (массивы, SSBO, циклы, workgroup-память и т.д.).
Подводные камни¶
ShaderTypeобязателен — без него шейдер не загрузится.- Переменная объявлена, но не используется из точки входа → её нет в сгенерированном коде и в материале. Если свойство «пропало» из материала — проверьте, достижимо ли оно из
vertex()/fragment()при текущих макросах. - Размер массива пишется у типа (
vec4[64] x), а не у имени, для объявлений верхнего уровня. - Только
#include <...>, кавычки"..."не поддерживаются. Путь — отResources/. - Нет
#elifи макросов со значениями. - Uniform-блоки выравниваются по 16 байт, поля внутри сортируются по размеру — не полагайтесь на порядок объявления.
[[readonly]]и[[writeonly]]одновременно на SSBO — предупреждение и игнорирование.- Шейдер должен компилироваться и в GLSL, и в WGSL. Избегайте GLSL-специфики, которой нет в WGSL (например, неявных преобразований
int ↔ float— пишитеfloat(x)). - Не редактируйте файлы в кэше
Shaders/— они перегенерируются из.srsl.
In progress...