shader
stableWGSL and GLSL shader source utilities: brace validation, entry-point and uniform extraction, comment stripping, header injection, and code templates.
use plugin shader Plugin snippets can be edited in the Playground, but native capabilities are executed locally with the Zolo CLI.
Functions (11)
- validate_braces Check that braces are balanced
- extract_entry_points List shader entry-point function names
- extract_uniforms List uniform variable declarations
- strip_comments Remove line and block comments
- add_header Prepend #define directives to source
- wgsl_vertex_template Generate a WGSL vertex shader template
- wgsl_fragment_template Generate a WGSL fragment shader template
- wgsl_compute_template Generate a WGSL compute shader template
- detect_shader_language Detect whether source is WGSL or GLSL
- count_lines Count total, code, blank, and comment lines
- validate_source Run combined validation checks
From import to result
- 01Import only what you use
- 02Copy the smallest example
- 03Run it with the Zolo CLI
Overview¶
shader is a dependency-free toolkit for inspecting and generating shader source
code as plain strings — there are no GPU handles, no compilation, and no state to
manage. Every function takes a source string (or a name) and returns a string,
bool, or plain table, so it slots cleanly into a build step, a hot-reload watcher,
or a code generator. It understands both WGSL (the modern @vertex / var<uniform>
dialect) and GLSL (void main, uniform Type name;), and most functions handle
either language transparently.
Reach for it when you need to sanity-check hand-written or generated shaders
(balanced braces and parentheses, a present entry point), introspect a shader's
entry points and uniforms, normalize source by stripping comments or injecting
#define headers, or scaffold WGSL boilerplate for vertex, fragment, and compute
stages.
Common patterns¶
Validate a shader before handing it to the GPU, and bail out early on errors:
use plugin shader::{validate_source}
let src = "@vertex fn vs() -> @builtin(position) vec4<f32> \{ return vec4(0.0); }"
let report = validate_source(src)
if report["valid"] {
print("shader ok")
} else {
print("invalid shader: {report["errors"][1]}")
}Generate a vertex/fragment pair, confirm the dialect, and tally its size:
use plugin shader::{wgsl_vertex_template, detect_shader_language, count_lines}
let vs = wgsl_vertex_template("vs_main")
print("language: {detect_shader_language(vs)}")
let stats = count_lines(vs)
print("{stats["code"]} code lines, {stats["blank"]} blank")Introspect a shader's interface — its entry points and the uniforms it expects:
use plugin shader::{extract_entry_points, extract_uniforms}
let src = r#"
@group(0) @binding(0) var<uniform> mvp: mat4x4<f32>;
@vertex
fn vs_main(in: VertexInput) -> VertexOutput { }
"#
print("entry: {extract_entry_points(src)[0]}")
let u = extract_uniforms(src)
print("uniform {u[0]["name"]}: {u[0]["type"]}")Check that braces are balanced
Returns true if all curly braces in the shader source are balanced. Correctly skips braces inside // line comments and /* */ block comments.
use plugin shader::{validate_braces}
let src = "@vertex fn vs() \{ return; }"
print(validate_braces(src)) // true
let broken = "@vertex fn vs() \{ return;"
print(validate_braces(broken)) // falseList shader entry-point function names
Scans WGSL source for @vertex, @fragment, and @compute attributes and returns a table of the associated function names. Also recognizes void main() in GLSL source.
use plugin shader::{extract_entry_points}
let src = r#"
@vertex
fn vs_main(in: VertexInput) -> VertexOutput { }
@fragment
fn fs_main() -> @location(0) vec4<f32> { }
"#
let eps = extract_entry_points(src)
print(eps[0]) // vs_main
print(eps[1]) // fs_mainList uniform variable declarations
Extracts uniform variable declarations from WGSL (var<uniform>) and GLSL (uniform Type name;). Each entry is a table with name and type fields.
use plugin shader::{extract_uniforms}
let src = "
@group(0) @binding(0) var<uniform> mvp: mat4x4<f32>;
@group(0) @binding(1) var<uniform> time: f32;
"
let uniforms = extract_uniforms(src)
print(uniforms[0]["name"]) // mvp
print(uniforms[0]["type"]) // mat4x4<f32>
print(uniforms[1]["name"]) // timeIt works on GLSL declarations too (uniform Type name;):
use plugin shader::{extract_uniforms}
let glsl = "uniform mat4 u_mvp;\nuniform float u_time;"
let u = extract_uniforms(glsl)
print("{u[0]["type"]} {u[0]["name"]}") // mat4 u_mvpRemove line and block comments
Removes all // line comments and /* */ block comments from shader source, preserving newlines so line numbers remain meaningful.
use plugin shader::{strip_comments}
let src = "// vertex shader\nfn vs() \{ /* init */ return; }"
let clean = strip_comments(src)
print(clean)
// "\nfn vs() { return; }"Prepend #define directives to source
Prepends #define KEY VALUE lines from the defines table to the shader source. Useful for injecting compile-time constants into GLSL shaders.
use plugin shader::{add_header}
let src = "void main() \{ float x = MAX_LIGHTS; }"
let result = add_header(src, #{"MAX_LIGHTS": "8", "SHADOW_PASS": "1"})
print(result)
// #define MAX_LIGHTS 8
// #define SHADOW_PASS 1
// void main() { float x = MAX_LIGHTS; }Numeric values are accepted as well as strings, so a config map of feature flags can be turned into a header in one call:
use plugin shader::{add_header, detect_shader_language}
let base = "void main() \{ gl_Position = vec4(0); }"
let withDefs = add_header(base, #{"QUALITY": 2, "USE_FOG": "1"})
print(detect_shader_language(withDefs)) // glslGenerate a WGSL vertex shader template
Generates a complete WGSL vertex shader boilerplate with standard VertexInput/VertexOutput structs, an MVP uniform, and a vertex function named name.
use plugin shader::{wgsl_vertex_template}
let src = wgsl_vertex_template("vs_main")
print(src)
// struct VertexInput { @location(0) position: vec3<f32>, ... }
// @vertex fn vs_main(in: VertexInput) -> VertexOutput { ... }Generate a WGSL fragment shader template
Generates a WGSL fragment shader boilerplate with diffuse texture sampling, a sampler, and basic Lambertian lighting in a function named name.
use plugin shader::{wgsl_fragment_template}
let src = wgsl_fragment_template("fs_main")
print(detect_shader_language(src)) // wgslGenerate a WGSL compute shader template
Generates a WGSL compute shader boilerplate with a storage buffer binding and a workgroup size annotation. workgroup_x/y/z default to 64, 1, 1 if not provided.
use plugin shader::{wgsl_compute_template}
let src = wgsl_compute_template("cs_main", 256, 1, 1)
print(src)
// @compute @workgroup_size(256, 1, 1)
// fn cs_main(@builtin(global_invocation_id) id: vec3<u32>) { ... }Detect whether source is WGSL or GLSL
Heuristically detects the shader language by scoring WGSL markers (@vertex, var<, fn , etc.) against GLSL markers (#version, void main, gl_Position, etc.). Returns "wgsl", "glsl", or "unknown".
use plugin shader::{wgsl_vertex_template, detect_shader_language}
let src = wgsl_vertex_template("vs")
print(detect_shader_language(src)) // wgsl
let glsl = "#version 330 core\nvoid main() \{ gl_Position = vec4(0); }"
print(detect_shader_language(glsl)) // glslSource with no recognizable markers falls through to "unknown", which is handy
as a guard before picking a compiler path:
use plugin shader::{detect_shader_language}
let lang = detect_shader_language("just a plain text blob")
if lang == "unknown" {
print("cannot tell which shader dialect this is")
}Count total, code, blank, and comment lines
Counts lines in shader source and categorises them. Returns a table with total, code, blank, and comment counts.
use plugin shader::{wgsl_vertex_template, count_lines}
let src = wgsl_vertex_template("vs_main")
let stats = count_lines(src)
print(stats["total"]) // number of total lines
print(stats["code"]) // non-blank, non-comment lines
print(stats["blank"]) // empty lines
print(stats["comment"]) // lines starting with // or /*Run combined validation checks
Runs combined checks on a shader source: balanced braces, balanced parentheses, and the presence of at least one entry point (@vertex, @fragment, @compute, or void main). Returns a table with valid (bool) and errors (table of error strings).
use plugin shader::{validate_source}
let good = "@vertex fn vs() -> @builtin(position) vec4<f32> \{ return vec4(0.0); }"
let result = validate_source(good)
print(result["valid"]) // true
let bad = "fn broken( \{ }"
let r2 = validate_source(bad)
print(r2["valid"]) // false
print(r2["errors"][1]) // unbalanced parentheses (or similar)Because errors is itself a table, you can iterate every problem found in a
single pass instead of inspecting one index at a time:
use plugin shader::{validate_source}
let r = validate_source("fn broken( \{")
for err in r["errors"] {
print("- {err}")
}