Shaders and screen effects
Shader bundles and materials on nodes, compiling GLSL, and ScreenEffect nodes.
Files: shaders.odin, shader_compile.odin (#+build !js), screen_effects.odin.
File overviews
Section titled “File overviews”shaders.odin
Section titled “shaders.odin”Shaders: small GPU programs that change how a node’s pictures are drawn (a hit flash, an outline, dissolving away, swapped colors), and screen effects over the whole scene (CRT, bloom, color grading).
- On a node (
Node_SetShader, a scene file’sshaderattribute and<shaderParam>children): the shader colors every pixel the node draws from an image or text (sprites, animated sprites, tilemaps, labels, textured particles), and its children’s, unless a child has a shader of its own (a shader on a character’s body colors the sprite under it). Shapes drawn with lines and fills aren’t affected. - On the screen (a ScreenEffect node): the scene is drawn into a picture first, then drawn through the shader. Several ScreenEffects run one after the other.
- Built-in effects (
builtin:hit_flash,builtin:outline,builtin:dissolve,builtin:palette_swap,builtin:crt,builtin:bloom,builtin:color_grade) ship with the engine; your own are GLSL fragment shaders in the project (assets of kind “Shader”,.frag), compiled by the editor into a.frag.shaderfile beside them (shader_compile.odin). - Parameters are the members of the shader’s
Paramsuniform block (std140): floats, vec2, vec3, vec4 (a color when its comment sayscolor). Their comments give a default and a range for editors:float amount; // default=0.5 min=0 max=1. Namestime,texelSizeandtextureSizeare filled by the engine: seconds the game has run, 1 / the picture’s size in pixels, the picture’s size.Node_SetShaderParamsets the others, and so can an Animator (shader.<name>tracks).
Where shaders run: SDL’s GPU renderer only (the project’s Renderer set to GPU; Auto picks it
when the project has shaders: Game_LoadProject). Shaders are SPIR-V (Vulkan: Linux, Windows) and
MSL (Metal: macOS); on Windows the GPU renderer is asked for Vulkan. On other renderers, and on
the web, nodes draw as if they had no shader (logged once), except builtin:hit_flash, which
draws the picture a second time added in the flash color: it brightens (an approximation).
Params must be used by main for the engine to fill it: the compiler drops a block nothing
reads.
A shader written for it (the inputs and outputs SDL’s renderer gives every fragment shader):
#version 450layout(location = 0) in vec4 v_color; // the vertex color: tint and opacitylayout(location = 1) in vec2 v_uv; // where in the picture, 0 to 1layout(location = 0) out vec4 o_color;layout(set = 2, binding = 0) uniform sampler2D u_texture;layout(set = 3, binding = 0) uniform Params { float amount; // default=1 min=0 max=1 vec4 tint; // color default=#ff0000 float time; // filled by the engine};void main() { vec4 c = texture(u_texture, v_uv) * v_color; o_color = vec4(mix(c.rgb, tint.rgb, amount * (0.5 + 0.5 * sin(time * 6.0))), c.a);}Example
// Given: hero: ^e.Spritee.Node_SetShader(hero, "builtin:hit_flash")e.Node_SetShaderParam(hero, "amount", 1)shader_compile.odin
Section titled “shader_compile.odin”Compiling GLSL fragment shaders into the .frag.shader files games load (shaders.odin): glslang
makes SPIR-V (Vulkan), SDL_shadercross MSL (Metal) and the resource counts, and the Params
block gives the parameters. Editors call it when a shader is saved, before Play and before
exports; tools/compile_shaders compiles the engine’s built-in effects with it.
The tools are built by tools/build_shader_tools.sh into shader_tools/<os>_<arch>/
(ShaderTools_Find); releases ship them beside the editor.
screen_effects.odin
Section titled “screen_effects.odin”Screen effects: a ScreenEffect node runs its shader (shaders.odin) over the whole scene as drawn: the scene goes into a picture the screen’s size first, then that picture is drawn through each ScreenEffect in turn (in the order they were added), the last one onto the screen.
includeScreenLayers(on by default): the effect covers the scene’s screen layers too (a HUD on an old TV); off, it covers the world only and screen layers draw over it untouched.- A ScreenEffect that’s hidden, or whose shader can’t run (not the GPU renderer), is skipped.
- Its parameters are its shader’s (
Node_SetShaderParamon the effect node).
Example
tv := e.CreateScreenEffect(scene, "builtin:crt")e.Node_SetShaderParam(tv, "scanlines", 0.8)ShaderParamType
Section titled “ShaderParamType”ShaderParamType :: enum { Float, Vec2, Vec3, Vec4, // A vec4 shown as a color (its comment says `color`) Color,}A shader parameter’s type
enum. Source: shaders.odin:67
ShaderParam
Section titled “ShaderParam”ShaderParam :: struct { // Its name in the shader name: string, // Its type type: ShaderParamType, // Its byte offset in the uniform block (std140) offset: int, // Its default (unused components 0), and the range editors give it value: [4]f32, min, max: f32, // What editors call it ("" its name) label: string, // The engine fills it (`time`, `texelSize`, `textureSize`): editors don't show it builtin: bool,}A shader parameter: a member of its Params block
struct. Source: shaders.odin:77
ShaderStage
Section titled “ShaderStage”ShaderStage :: enum { // Each pixel's color (nodes' and screen effects' shaders) Fragment, // Each vertex's position (the engine's own: GPU particles) Vertex,}A shader’s stage: what it computes
enum. Source: shaders.odin:94
ShaderBundle
Section titled “ShaderBundle”ShaderBundle :: struct { // The format of the file version: int, // Its stage stage: ShaderStage, // How many storage buffers it reads (vertex shaders: the engine's GPU particles) storageBuffers: int, // Its parameters, and the size of its uniform block in bytes (0: none) params: []ShaderParam, uniformSize: int, // How many samplers (textures) and uniform blocks it uses samplers, uniformBuffers: int, // The code: SPIR-V and MSL (base64 in the file) spirv, msl: []byte,}A compiled shader: what a .frag.shader (or .vert.shader) file holds
struct. Source: shaders.odin:102
ShaderProgram
Section titled “ShaderProgram”ShaderProgram :: struct { // Its path ("builtin:outline", or a project path) path: string, bundle: ShaderBundle, // It couldn't be made on a renderer (logged once) failed: bool, // The GPU shader made per GPU device gpu: [dynamic]ShaderOnDevice,}A loaded shader (Game_LoadShader), shared by every node using it
struct. Source: shaders.odin:119
ShaderMaterial
Section titled “ShaderMaterial”ShaderMaterial :: struct { program: ^ShaderProgram, // One per parameter, in the program's order values: [dynamic][4]f32, // The render state made per renderer (SDL keeps uniform values per state) states: [dynamic]ShaderState,}A node’s shader and its parameter values (Node.shader)
struct. Source: shaders.odin:137
BuiltinShader
Section titled “BuiltinShader”BuiltinShader :: enum { hit_flash, outline, dissolve, palette_swap, crt, bloom, color_grade,}The built-in effects (shaders/builtin/<name>.frag, compiled by tools/compile_shaders)
enum. Source: shaders.odin:155
ShaderTools
Section titled “ShaderTools”ShaderTools :: struct { glslang: string, shadercross: string, // The font tool beside them (font_compile.odin; "" when that build has none) msdfAtlasGen: string,}Where the two shader compilers are
struct, only in builds with #+build !js. Source: shader_compile.odin:19
ScreenEffect
Section titled “ScreenEffect”ScreenEffect :: struct { // The node part: transform, hierarchy, hooks using node: Node, // The effect covers the scene's screen layers too includeScreenLayers: bool,}A shader over the whole scene (its shader is the effect)
struct. Source: screen_effects.odin:21
Procedures
Section titled “Procedures”Shader_BuiltinOf
Section titled “Shader_BuiltinOf”Shader_BuiltinOf :: proc(path: string) -> (BuiltinShader, bool)A built-in effect’s path from its name, and the name from a path.
Inputs
- path: “builtin:outline”
Returns
- The effect, and whether the path is one
procedure. Source: shaders.odin:199
Shader_BuiltinBundle
Section titled “Shader_BuiltinBundle”Shader_BuiltinBundle :: proc(b: BuiltinShader) -> (ShaderBundle, bool)A built-in effect’s compiled bundle (its parameters: what editors show).
Returns
- The bundle (temp allocated), and false if it can’t be read
procedure. Source: shaders.odin:212
Game_ReleaseShaders
Section titled “Game_ReleaseShaders”Game_ReleaseShaders :: proc(game: ^Game)Frees the game’s shaders’ GPU code: call before destroying the renderer when a game isn’t destroyed with Game_Destroy (editors’ previews). Game_Destroy does it.
procedure. Source: shaders.odin:220
Shader_ParseBundle
Section titled “Shader_ParseBundle”Shader_ParseBundle :: proc(text: []byte, allocator := context.allocator) -> (bundle: ShaderBundle, ok: bool)Reads a compiled shader (a .frag.shader file’s text) into a bundle allocated with allocator.
Returns
- The bundle, and false when the text isn’t one (or another version)
procedure. Source: shaders.odin:230
Shader_FormatBundle
Section titled “Shader_FormatBundle”Shader_FormatBundle :: proc(bundle: ShaderBundle) -> stringWrites a bundle as a .frag.shader file’s text (temp allocated).
procedure. Source: shaders.odin:261
Shader_ParseParams
Section titled “Shader_ParseParams”Shader_ParseParams :: proc(source: string) -> (params: []ShaderParam, size: int, problem: string)The parameters of a GLSL fragment shader: the members of its uniform block at set 3, binding 0
(Params), with std140 offsets and what their comments say (default=, min=, max=,
label=, color). Names the engine fills (time, texelSize, textureSize) are marked
builtin.
Inputs
- source: The shader’s text
Returns
- The parameters (temp allocated) and the block’s size in bytes;
problemnames what couldn’t be read (“” when fine)
procedure. Source: shaders.odin:287
Game_LoadShader
Section titled “Game_LoadShader”Game_LoadShader :: proc(game: ^Game, path: string) -> ^ShaderProgramLoads a shader for the game (once: later calls return the same one).
Inputs
- game: The game
- path: “builtin:<name>”, or a project shader’s path (its compiled
<path>.shaderis read)
Returns
- The shader, or nil (logged) when it isn’t there or can’t be read
procedure. Source: shaders.odin:353
Node_SetShader
Section titled “Node_SetShader”Node_SetShader :: proc(node: ^Node, path: string) -> boolGives a node a shader (replacing its own), with the shader’s default parameters.
Inputs
- node: The node
- path: “builtin:<name>”, a project shader’s path, or “” for none
Returns
- Whether it has the shader now
procedure. Source: shaders.odin:392
Node_SetShaderParam
Section titled “Node_SetShaderParam”Node_SetShaderParam :: proc{shaderParamFloat, shaderParamVec2, shaderParamVec4, shaderParamColor}Sets one of a node’s shader parameters.
Inputs
- node: The node (with a shader)
- name: The parameter
- value: A number, a 2, 3 or 4 component vector, or a color
Returns
- Whether the node’s shader has that parameter
Example
// Given: hero: ^e.Sprite with builtin:hit_flashe.Node_SetShaderParam(hero, "amount", 1)e.Node_SetShaderParam(hero, "flashColor", sdl.Color{255, 80, 80, 255})procedure group. Source: shaders.odin:428
Node_SetShaderParamText
Section titled “Node_SetShaderParamText”Node_SetShaderParamText :: proc(node: ^Node, name, text: string) -> boolSets one of a node’s shader parameters from text (ShaderParam_Parse): what scene files and
editors give.
Returns
- Whether the node’s shader has that parameter and the text is a value of it
procedure. Source: shaders.odin:437
Node_ResetShaderParams
Section titled “Node_ResetShaderParams”Node_ResetShaderParams :: proc(node: ^Node)Puts every one of a node’s shader parameters back to the shader’s defaults
procedure. Source: shaders.odin:448
Node_GetShaderParam
Section titled “Node_GetShaderParam”Node_GetShaderParam :: proc(node: ^Node, name: string) -> ([4]f32, bool)One of a node’s shader parameters (all four components; a float’s is the first).
Returns
- The value, and whether the node’s shader has that parameter
procedure. Source: shaders.odin:459
ShaderParam_Parse
Section titled “ShaderParam_Parse”ShaderParam_Parse :: proc(type: ShaderParamType, text: string) -> ([4]f32, bool)A shader parameter’s value from text: “0.5”, “1, 2”, “1, 0, 0, 1”, or a color (“#ff8000”, “#ff800080”).
Returns
- The four components (missing ones 0; a color’s alpha 1 unless given), and whether it read
procedure. Source: shaders.odin:472
ShaderParam_Format
Section titled “ShaderParam_Format”ShaderParam_Format :: proc(type: ShaderParamType, value: [4]f32) -> stringA shader parameter’s value as text (temp allocated): a number, comma-separated components, or a
color (#rrggbbaa) for colors.
procedure. Source: shaders.odin:497
Renderer_SupportsShaders
Section titled “Renderer_SupportsShaders”Renderer_SupportsShaders :: proc(renderer: ^sdl.Renderer) -> boolWhether a renderer runs shaders: SDL’s GPU renderer, on a device taking SPIR-V or MSL.
procedure. Source: shaders.odin:513
ShaderTools_Find
Section titled “ShaderTools_Find”ShaderTools_Find :: proc(dir: string) -> (ShaderTools, bool)Finds the shader tools in a folder of builds (<dir>/<os>_<arch>/glslang, shadercross, and
the font tool msdf-atlas-gen when it’s there).
Inputs
- dir: The folder holding the
<os>_<arch>folders (the engine’sshader_tools, or a release’s)
Returns
- The tools (temp allocated paths), and whether both are there
procedure, only in builds with #+build !js. Source: shader_compile.odin:36
Shader_Compile
Section titled “Shader_Compile”Shader_Compile :: proc(tools: ShaderTools, source, name, scratch: string, stage := ShaderStage.Fragment) -> (bundle: ShaderBundle, messages: string, ok: bool)Compiles a GLSL fragment shader.
Inputs
- tools: The compilers (ShaderTools_Find)
- source: The shader’s text
- name: The file’s name in messages (“assets/fx/water.frag”)
- scratch: A folder for the compilers’ files (made when missing)
Returns
- The bundle (its code temp allocated), and the compilers’ messages with the file’s name and line (“assets/fx/water.frag:12: ‘colr’ : undeclared identifier”); ok false when it failed
procedure, only in builds with #+build !js. Source: shader_compile.odin:63
Shader_CompileFile
Section titled “Shader_CompileFile”Shader_CompileFile :: proc(tools: ShaderTools, path, name, scratch: string, force := false, stage := ShaderStage.Fragment) -> (messages: string, ok: bool, compiled: bool)Compiles a shader file into its .shader file beside it (water.frag -> water.frag.shader),
when the source is newer than it (or force).
Inputs
- tools: The compilers
- path: The
.fragfile - name: Its name in messages
- scratch: A folder for the compilers’ files
- force: Compile even when it’s up to date
Returns
- The messages (“” none), whether it compiled (or was up to date), and whether it compiled now
procedure, only in builds with #+build !js. Source: shader_compile.odin:131
CreateScreenEffect
Section titled “CreateScreenEffect”CreateScreenEffect :: proc(scene: ^Scene, shader: string) -> ^ScreenEffectCreates a ScreenEffect and adds it to the scene (at the start of the next update).
Inputs
- scene: The scene
- shader: The effect (“builtin:crt”, a project shader’s path)
Returns
- The effect node (its shader set; a shader that can’t load leaves it without one, logged)
procedure. Source: screen_effects.odin:38
Constants and variables
Section titled “Constants and variables”SHADER_BUNDLE_VERSION
Section titled “SHADER_BUNDLE_VERSION”SHADER_BUNDLE_VERSION :: 1The file format version Shader_Compile writes
constant. Source: shaders.odin:152
BUILTIN_SHADER_INFO
Section titled “BUILTIN_SHADER_INFO”BUILTIN_SHADER_INFO := [BuiltinShader]struct { description: string, screen: bool }{ .hit_flash = {"Flashes the picture toward a color: hits, pickups", false}, .outline = {"Draws a line around the picture's opaque pixels: selection, highlights", false}, .dissolve = {"Eats the picture away in a noisy pattern with a glowing edge: deaths, teleports", false}, .palette_swap = {"Replaces up to four colors with others: team colors, skins", false}, .crt = {"An old TV: scanlines, curved glass, a dark border", true}, .bloom = {"Bright parts glow into their surroundings", true}, .color_grade = {"Brightness, contrast, saturation, tint and a vignette over the scene", true},}What each built-in effect does, and whether it’s for the screen (a ScreenEffect) or a node
variable. Source: shaders.odin:166
SHADER_BUILTIN_PREFIX
Section titled “SHADER_BUILTIN_PREFIX”SHADER_BUILTIN_PREFIX :: "builtin:"The prefix of a built-in effect’s path (“builtin:outline”)
constant. Source: shaders.odin:188
