shader
stableWGSL and GLSL shader source utilities: brace validation, entry-point and uniform extraction, comment stripping, header injection, and code templates.
use plugin shader::{validate_braces, extract_entry_points, extract_uniforms, …} 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
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 = "
@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)) // false
List 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 = "
@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_main
List 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"]) // time
It 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_mvp
Remove 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)) // glsl
Generate 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)) // wgsl
Generate 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)) // glsl
Source 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}")
}