Skip to main content

Shader System

All shaders in Glitter are written in Slang and compiled to SPIR-V at load time. Every shader is described by a JSON config file under resources/testGame/shaders/. Shader and JSON changes do not require a CMake reconfigure or C++ rebuild.


How it works

The entry point for application code is always ShaderSystem — never call loadShaderFromJson directly.


JSON config schema

Minimal single-pass graphics shader

shaders/tonemap.json
{
"description": "Tonemapping pass",
"source": "slang/tonemap.slang",
"type": "graphics"
}

Single-pass with explicit entry points

shaders/tonemap.json
{
"source": "slang/tonemap.slang",
"type": "graphics",
"vertex_entry": "vertexMain",
"fragment_entry": "fragmentMain"
}

Entry point keys are optional — Slang defaults: vertexMain, fragmentMain, computeMain.

Compute shader

shaders/ibl_spmap.json
{
"source": "slang/ibl_spmap.slang",
"type": "compute",
"always_build": true
}

always_build: true bypasses the binary cache and forces a Slang recompile every run. Used for IBL precompute shaders where the output is written to disk.

Family shader (multiple render passes, one .slang file)

shaders/pbr.json
{
"source": "slang/pbr_geometry.slang",
"vertex_entry": "vertexMain",
"passes": {
"geometry": { "fragment_entry": "gbufferFragment" },
"forward": { "fragment_entry": "forwardPbrFragment" }
}
}

A family shader has no top-level fragment_entry. Each pass compiles an independent VkShaderModule pair sharing the same vertex stage.


Key JSON fields

FieldTypeRequiredDefaultNotes
sourcestringyesPath to .slang or .spv, relative to the JSON file
type"graphics" / "compute"no"graphics"
vertex_entrystringno"vertexMain"
fragment_entrystringno"fragmentMain"Omit for family shaders
compute_entrystringno"computeMain"
passesobjectnoPresent = family shader. Keys are pass names.
always_buildboolnofalsetrue disables binary cache

C++ API

Usage example
// Single-pass shader
auto handle = shaders.loadShader("tonemap.json");

// Family shader — must specify a pass name
auto gbuffer = shaders.loadShaderPass("pbr.json", "geometry");
auto forward = shaders.loadShaderPass("pbr.json", "forward");

Both methods return tl::expected<ShaderHandle, std::string> — always check the result.

Cache key format

ShaderSystem caches handles by canonical_uri + "#" + passName:

CallCache key
loadShader("tonemap.json")shaders://tonemap.json#
loadShaderPass("pbr.json", "geometry")shaders://pbr.json#geometry

Family shader contract

Calling loadShader() (no pass name) on a family shader returns a tl::make_unexpected at debug log level — this is intentional. The scene loader calls loadShader for every object, and the renderer resolves the correct pass lazily via loadShaderPass.


Adding a new shader

  1. Write the .slang file in resources/testGame/shaders/slang/.
  2. Create the JSON config in resources/testGame/shaders/. Use an existing file as template.
  3. Load it via shaders.loadShader("my_shader.json") or shaders.loadShaderPass(...).
  4. Shader and JSON changes do not require a CMake reconfigure or C++ rebuild.