Shader
Description
Section titled “Description”The Shader object is the main building block in FragmentColor.
It takes a WGSL or GLSL shader source as input, parses it, validates it, and exposes the uniforms as keys.
Shader::new accepts a source string, a registry slug like "sdf2d/circle", an https:// URL, a local file path, or an array mixing any of those. Array parts are deduplicated by hash and concatenated in order, so you can pull pure helper functions from the public registry at https://fragmentcolor.org/shaders/ into your own shader without copy-pasting. Override the registry base with Shader::set_registry.
To draw your shader, you must use your Shader instance as input to a Renderer.
You can compose Shader instances into a Pass object to create more complex rendering pipelines.
You can also create renderings with multiple Render Passes by passing an array of Pass instances to Renderer::render.
Uniforms
Section titled “Uniforms”Classic uniforms are declared with var<uniform> and can be nested structs/arrays.
FragmentColor exposes every root and nested field as addressable keys using dot and index notation:
- Set a field:
shader.set("u.color", [r, g, b, a]) - Index arrays:
shader.set("u.arr[1]", value)
WGSL example
Section titled “WGSL example”struct MyUniform { color: vec4<f32>, arr: array<vec4<f32>, 2>};
@group(0) @binding(0) var<uniform> u: MyUniform;FragmentColor handles std140-style 16-byte alignment for you, and large uniform blobs are pooled internally â there is nothing to configure.
Textures and Samplers
Section titled “Textures and Samplers”Sampled textures and samplers are supported via texture_* and sampler declarations.
You can bind a Texture object created by the Renderer directly to a texture uniform (e.g., shader.set("tex", &texture));
samplers are provided automatically:
- If a texture is bound in the same group, the sampler defaults to that texture’s sampler.
- Otherwise, a reasonable default sampler is used.
WGSL example
Section titled “WGSL example”@group(0) @binding(0) var tex: texture_2d<f32>;@group(0) @binding(1) var samp: sampler;2D, 3D, cube, and array variants are all supported; the correct view dimension is inferred from the WGSL declaration. Integer textures map to Sint / Uint sample types; float textures use filterable float when the device allows it.
Storage Textures
Section titled “Storage Textures”Writeable/readable image surfaces are supported via storage textures (texture_storage_*).
Access flags are preserved from WGSL and mapped to the device:
read-> read-only storage accesswrite-> write-only storage accessread_write-> read+write (when supported)
WGSL example
Section titled “WGSL example”@group(0) @binding(0) var img: texture_storage_2d<rgba8unorm, write>;The declared storage format flows through to the binding layout untouched. You’re responsible for picking a format and access mode the adapter supports.
Storage Buffers
Section titled “Storage Buffers”Structured buffers are supported via var<storage, read> or var<storage, read_write>
and can contain nested structs/arrays. FragmentColor preserves and applies the WGSL access flags
when creating binding layouts and setting visibility.
WGSL example
Section titled “WGSL example”struct Buf { a: vec4<f32> };@group(0) @binding(0) var<storage, read> ssbo: Buf;Read-only buffers bind with read-only storage access; read_write allows writes where the device supports it. CPU-side updates use the same shader.set("path", value) API as uniforms, with array indexing (e.g. buf.items[2].v). Buffer byte spans are computed from the WGSL shape â arrays and structs honor stride and alignment automatically â and large buffers are pooled internally.
Push Constants
Section titled “Push Constants”Push constants are supported with var<push_constants> in all platforms.
They will fallback to regular uniform buffers when:
- push_constants are not natively supported (ex. on Web),
- multiple push-constant roots are declared, or
- the total push-constant size exceeds the device limit.
In fallback mode, push constants are rewritten as classic uniform buffers in a newly allocated bind group (one binding per push-constant root). The fallback group goes into the next free slot (max_existing_group + 1); FragmentColor does not check the device’s max_bind_groups limit, so a shader that already uses many bind groups and push constants can exceed it. Render pipelines fall back today; compute pipeline fallback may follow.
Example
Section titled “Example”1 collapsed line
async fn run() -> Result<(), Box<dyn std::error::Error>> {
use fragmentcolor::{Shader, Renderer};
let shader = Shader::new(r#" @vertex fn vs_main(@builtin(vertex_index) index: u32) -> @builtin(position) vec4<f32> { var pos = array<vec2<f32>, 3>( vec2<f32>(-1.0, -1.0), vec2<f32>( 3.0, -1.0), vec2<f32>(-1.0, 3.0) ); return vec4<f32>(pos[index], 0.0, 1.0); }
@group(0) @binding(0) var<uniform> resolution: vec2<f32>;
@fragment fn fs_main() -> @location(0) vec4<f32> { return vec4<f32>(1.0, 0.0, 0.0, 1.0); // Red }"#)?;
// Set the "resolution" uniformshader.set("resolution", [800.0, 600.0])?;let res: [f32; 2] = shader.get("resolution")?;
let renderer = Renderer::new();let target = renderer.create_texture_target([16, 16]).await?;renderer.render(&shader, &target)?;
5 collapsed lines
assert_eq!(res, [800.0, 600.0]);assert!(shader.list_uniforms().len() >= 1);Ok(())}fn main() -> Result<(), Box<dyn std::error::Error>> { pollster::block_on(run()) }import { Shader, Renderer } from "fragmentcolor";
const shader = new Shader(`
@vertex fn vs_main(@builtin(vertex_index) index: u32) -> @builtin(position) vec4<f32> { var pos = array<vec2<f32>, 3>( vec2<f32>(-1.0, -1.0), vec2<f32>( 3.0, -1.0), vec2<f32>(-1.0, 3.0) ); return vec4<f32>(pos[index], 0.0, 1.0); }
@group(0) @binding(0) var<uniform> resolution: vec2<f32>;
@fragment fn fs_main() -> @location(0) vec4<f32> { return vec4<f32>(1.0, 0.0, 0.0, 1.0); // Red }
`);
// Set the "resolution" uniformshader.set("resolution", [800.0, 600.0]);const res = shader.get("resolution");
const renderer = new Renderer();const target = await renderer.createTextureTarget([16, 16]);renderer.render(shader, target);from fragmentcolor import Shader, Renderer
shader = Shader(""" @vertex fn vs_main(@builtin(vertex_index) index: u32) -> @builtin(position) vec4<f32> { var pos = array<vec2<f32>, 3>( vec2<f32>(-1.0, -1.0), vec2<f32>( 3.0, -1.0), vec2<f32>(-1.0, 3.0) ); return vec4<f32>(pos[index], 0.0, 1.0); }
@group(0) @binding(0) var<uniform> resolution: vec2<f32>;
@fragment fn fs_main() -> @location(0) vec4<f32> { return vec4<f32>(1.0, 0.0, 0.0, 1.0); // Red }
""")
# Set the "resolution" uniformshader.set("resolution", [800.0, 600.0])res = shader.get("resolution")
renderer = Renderer()target = renderer.create_texture_target([16, 16])renderer.render(shader, target)import FragmentColor
let shader = try Shader(""" @vertex fn vs_main(@builtin(vertex_index) index: u32) -> @builtin(position) vec4<f32> { var pos = array<vec2<f32>, 3>( vec2<f32>(-1.0, -1.0), vec2<f32>( 3.0, -1.0), vec2<f32>(-1.0, 3.0) ) return vec4<f32>(pos[index], 0.0, 1.0) }
@group(0) @binding(0) var<uniform> resolution: vec2<f32>
@fragment fn fs_main() -> @location(0) vec4<f32> { return vec4<f32>(1.0, 0.0, 0.0, 1.0); // Red }
""")
// Set the "resolution" uniformtry shader.set("resolution", [800.0, 600.0])let res = try shader.get("resolution")
let renderer = Renderer()let target = try await renderer.createTextureTarget([16, 16])try renderer.render(shader, target)import org.fragmentcolor.*
val shader = Shader(""" @vertex fn vs_main(@builtin(vertex_index) index: u32) -> @builtin(position) vec4<f32> { var pos = array<vec2<f32>, 3>( vec2<f32>(-1.0, -1.0), vec2<f32>( 3.0, -1.0), vec2<f32>(-1.0, 3.0) ) return vec4<f32>(pos[index], 0.0, 1.0) }
@group(0) @binding(0) var<uniform> resolution: vec2<f32>
@fragment fn fs_main() -> @location(0) vec4<f32> { return vec4<f32>(1.0, 0.0, 0.0, 1.0); // Red }
""")
// Set the "resolution" uniformshader.set("resolution", floatArrayOf(800.0f, 600.0f))val res = shader.get("resolution")
val renderer = Renderer()val target = renderer.createTextureTarget(16u, 16u)renderer.render(shader, target)Methods
Section titled “Methods”Shader::new(input: string | string[])
Section titled “Shader::new(input: string | string[])”Creates a new Shader. The input is a single string, or an array of strings, of any of these shapes:
- a raw WGSL source string
- a registry slug like
"sdf2d/circle", which pulls a helper function from the public registry athttps://fragmentcolor.org/shaders/ - an
https://URL pointing at a.wgslfile - a local file path ending in
.wgsl,.glsl,.frag, or.vert
When you pass an array, parts are deduplicated by source hash and concatenated in order before validation, so you can compose pure helper functions from the registry with your own source.
If validation fails, the error message points at the line in the resulting WGSL. If validation passes, the shader is guaranteed to work on the GPU. All uniforms are initialized to zero.
GLSL is supported only as a single part (a .vert, .frag, or .glsl
file). Mixing GLSL with other parts is rejected.
Example
Section titled “Example”1 collapsed line
fn main() -> Result<(), Box<dyn std::error::Error>> {
use fragmentcolor::Shader;
let shader = Shader::new(r#" @vertex fn vs_main(@builtin(vertex_index) index: u32) -> @builtin(position) vec4<f32> { var pos = array<vec2<f32>, 3>( vec2<f32>(-1.0, -1.0), vec2<f32>( 3.0, -1.0), vec2<f32>(-1.0, 3.0) ); return vec4<f32>(pos[index], 0.0, 1.0); }
@group(0) @binding(0) var<uniform> resolution: vec2<f32>;
@fragment fn fs_main() -> @location(0) vec4<f32> { return vec4<f32>(1.0, 0.0, 0.0, 1.0); // Red }"#)?;
3 collapsed lines
assert!(shader.list_keys().len() >= 1);Ok(())}use fragmentcolor::Shader;
let main = r#" @vertex fn vs(@builtin(vertex_index) i: u32) -> @builtin(position) vec4<f32> { let p = array<vec2<f32>,3>(vec2f(-1.,-1.), vec2f(3.,-1.), vec2f(-1.,3.)); return vec4<f32>(p[i], 0.0, 1.0); }
@fragment fn fs(@builtin(position) pos: vec4<f32>) -> @location(0) vec4<f32> { let d = circle(pos.xy - vec2<f32>(400.0, 300.0), 100.0); let n = simplex2(pos.xy * 0.01); return vec4<f32>(vec3<f32>(step(0.0, d) + n * 0.1), 1.0); }"#;
let shader = Shader::new([ "sdf2d/circle", // pure function: fn circle(p: vec2<f32>, r: f32) -> f32 "noise/simplex2", // pure function: fn simplex2(v: vec2<f32>) -> f32 main,])?;1 collapsed line
Ok::<(), Box<dyn std::error::Error>>(())import { Shader } from "fragmentcolor";
const shader = new Shader(` @vertex fn vs_main(@builtin(vertex_index) index: u32) -> @builtin(position) vec4<f32> { var pos = array<vec2<f32>, 3>( vec2<f32>(-1.0, -1.0), vec2<f32>( 3.0, -1.0), vec2<f32>(-1.0, 3.0) ); return vec4<f32>(pos[index], 0.0, 1.0); }
@group(0) @binding(0) var<uniform> resolution: vec2<f32>;
@fragment fn fs_main() -> @location(0) vec4<f32> { return vec4<f32>(1.0, 0.0, 0.0, 1.0); // Red }`);from fragmentcolor import Shader
# Simple WGSL fragment shadershader = Shader("""@vertex fn vs_main(@builtin(vertex_index) i: u32) -> @builtin(position) vec4<f32> {let p = array<vec2<f32>,3>(vec2f(-1.,-1.), vec2f(3.,-1.), vec2f(-1.,3.));return vec4f(p[i], 0., 1.);}@fragment fn fs_main() -> @location(0) vec4<f32> { return vec4f(1., 0., 0., 1.); }""")import FragmentColor
let shader = try Shader(""" @vertex fn vs_main(@builtin(vertex_index) index: u32) -> @builtin(position) vec4<f32> { var pos = array<vec2<f32>, 3>( vec2<f32>(-1.0, -1.0), vec2<f32>( 3.0, -1.0), vec2<f32>(-1.0, 3.0) ) return vec4<f32>(pos[index], 0.0, 1.0) }
@group(0) @binding(0) var<uniform> resolution: vec2<f32>
@fragment fn fs_main() -> @location(0) vec4<f32> { return vec4<f32>(1.0, 0.0, 0.0, 1.0); // Red }
""")
let main = """ @vertex fn vs(@builtin(vertex_index) i: u32) -> @builtin(position) vec4<f32> { let p = array<vec2<f32>,3>(vec2f(-1.,-1.), vec2f(3.,-1.), vec2f(-1.,3.)) return vec4<f32>(p[i], 0.0, 1.0) }
@fragment fn fs(@builtin(position) pos: vec4<f32>) -> @location(0) vec4<f32> { let d = circle(pos.xy - vec2<f32>(400.0, 300.0), 100.0) let n = simplex2(pos.xy * 0.01) return vec4<f32>(vec3<f32>(step(0.0, d) + n * 0.1), 1.0) }
"""
let shader = try Shader.new([ "sdf2d/circle", // pure function: fn circle(p: vec2<f32>, r: f32) -> f32 "noise/simplex2", // pure function: fn simplex2(v: vec2<f32>) -> f32 main,])import org.fragmentcolor.*
val shader = Shader(""" @vertex fn vs_main(@builtin(vertex_index) index: u32) -> @builtin(position) vec4<f32> { var pos = array<vec2<f32>, 3>( vec2<f32>(-1.0, -1.0), vec2<f32>( 3.0, -1.0), vec2<f32>(-1.0, 3.0) ) return vec4<f32>(pos[index], 0.0, 1.0) }
@group(0) @binding(0) var<uniform> resolution: vec2<f32>
@fragment fn fs_main() -> @location(0) vec4<f32> { return vec4<f32>(1.0, 0.0, 0.0, 1.0); // Red }
""")
val main = """ @vertex fn vs(@builtin(vertex_index) i: u32) -> @builtin(position) vec4<f32> { let p = array<vec2<f32>,3>(vec2f(-1.,-1.), vec2f(3.,-1.), vec2f(-1.,3.)) return vec4<f32>(p[i], 0.0, 1.0) }
@fragment fn fs(@builtin(position) pos: vec4<f32>) -> @location(0) vec4<f32> { let d = circle(pos.xy - vec2<f32>(400.0, 300.0), 100.0) let n = simplex2(pos.xy * 0.01) return vec4<f32>(vec3<f32>(step(0.0, d) + n * 0.1), 1.0) }
"""
val shader2 = Shader.compose(listOf("sdf2d/circle", "noise/simplex2", main,))Shader::fetch(input)
Section titled “Shader::fetch(input)”Async constructor that returns a compiled Shader from one or more parts.
Each part can be:
- a raw WGSL source string
- a registry slug like
"sdf2d/circle" - an
https://URL pointing at a.wgslfile - a local file path ending in
.wgsl,.glsl,.frag, or.vert(native platforms only)
Parts are deduplicated by source hash and concatenated in order before
compilation. fetch is the async path used when any part needs network or
file I/O. Shader::new covers the same shapes for callers that prefer a
synchronous constructor.
Platforms
Section titled “Platforms”| Platform | Spelling | Async mechanism |
|---|---|---|
| Web (JS) | await Shader.fetch(input) | Promise via wasm_bindgen async |
| Python | Shader.fetch(input) | Blocks the calling thread via pollster::block_on |
| Swift | try await Shader.fetch(input) | async throws via uniffi async method |
| Kotlin | ShaderFetch(input) | suspend fun via uniffi async method |
Note (Swift / Kotlin): uniffi 0.31 does not support async constructors. The underlying uniffi binding is an
asyncinstance method; the SwiftShader.fetch(_:)and KotlinShaderFetch(...)wrappers handle the throw-away receiver internally so callers see a clean static factory.
Example
Section titled “Example”1 collapsed line
async fn run() -> Result<(), Box<dyn std::error::Error>> {
use fragmentcolor::Shader;
// Full registry URL.let shader = Shader::fetch("https://fragmentcolor.org/shaders/sdf2d/circle.wgsl").await?;
// Equivalent shorthand using the registry slug.let shader2 = Shader::fetch("sdf2d/circle").await?;
4 collapsed lines
let _ = (shader, shader2);Ok(())}fn main() { let _ = pollster::block_on(run()); }import { Shader } from "fragmentcolor";
// Full registry URL.const shader = await Shader.fetch("https://fragmentcolor.org/shaders/sdf2d/circle.wgsl");
// Equivalent shorthand using the registry slug.const shader2 = await Shader.fetch("sdf2d/circle");from fragmentcolor import Shader
# Full registry URL.shader = Shader.fetch("https://fragmentcolor.org/shaders/sdf2d/circle.wgsl")
# Equivalent shorthand using the registry slug.shader2 = Shader.fetch("sdf2d/circle")import FragmentColor
// Full registry URL.let shader = try await Shader.fetch("https://fragmentcolor.org/shaders/sdf2d/circle.wgsl")
// Equivalent shorthand using the registry slug.let shader2 = try await Shader.fetch("sdf2d/circle")import org.fragmentcolor.*
// Full registry URL.val shader = Shader.fetch("https://fragmentcolor.org/shaders/sdf2d/circle.wgsl")
// Equivalent shorthand using the registry slug.val shader2 = Shader.fetch("sdf2d/circle")Shader::set(key: string, value: any)
Section titled “Shader::set(key: string, value: any)”Sets the value of the uniform identified by the given key.
If the key does not exist or the value format is incorrect, the set method throws an exception. The shader remains valid, and if the exception is caught, the shader can still be used with the renderer.
Example
Section titled “Example”1 collapsed line
fn main() -> Result<(), Box<dyn std::error::Error>> {use fragmentcolor::{Renderer, Shader};let r = Renderer::new();let shader = Shader::new(r#"@group(0) @binding(0) var<uniform> resolution: vec2<f32>;
struct VOut { @builtin(position) pos: vec4<f32> };@vertex fn vs_main(@builtin(vertex_index) i: u32) -> VOut {var p = array<vec2<f32>, 3>(vec2<f32>(-1.,-1.), vec2<f32>(3.,-1.), vec2<f32>(-1.,3.));var out: VOut;out.pos = vec4<f32>(p[i], 0., 1.);return out;}@fragment fn main() -> @location(0) vec4<f32> { return vec4<f32>(1.,0.,0.,1.); }"#)?;
// Set scalars/vectors on declared uniformsshader.set("resolution", [800.0, 600.0])?;2 collapsed lines
Ok(())}import { Renderer, Shader } from "fragmentcolor";const r = new Renderer();const shader = new Shader(`
@group(0) @binding(0) var<uniform> resolution: vec2<f32>;
struct VOut { @builtin(position) pos: vec4<f32> };@vertex fn vs_main(@builtin(vertex_index) i: u32) -> VOut {var p = array<vec2<f32>, 3>(vec2<f32>(-1.,-1.), vec2<f32>(3.,-1.), vec2<f32>(-1.,3.));var out: VOut;out.pos = vec4<f32>(p[i], 0., 1.);return out;}@fragment fn main() -> @location(0) vec4<f32> { return vec4<f32>(1.,0.,0.,1.); }
`);
// Set scalars/vectors on declared uniformsshader.set("resolution", [800.0, 600.0]);from fragmentcolor import Renderer, Shaderr = Renderer()shader = Shader("""@group(0) @binding(0) var<uniform> resolution: vec2<f32>;
struct VOut { @builtin(position) pos: vec4<f32> };@vertex fn vs_main(@builtin(vertex_index) i: u32) -> VOut {var p = array<vec2<f32>, 3>(vec2<f32>(-1.,-1.), vec2<f32>(3.,-1.), vec2<f32>(-1.,3.));var out: VOut;out.pos = vec4<f32>(p[i], 0., 1.);return out;}@fragment fn main() -> @location(0) vec4<f32> { return vec4<f32>(1.,0.,0.,1.); }
""")
# Set scalars/vectors on declared uniformsshader.set("resolution", [800.0, 600.0])import FragmentColorlet r = Renderer()let shader = try Shader("""@group(0) @binding(0) var<uniform> resolution: vec2<f32>
struct VOut { @builtin(position) pos: vec4<f32> }@vertex fn vs_main(@builtin(vertex_index) i: u32) -> VOut {var p = array<vec2<f32>, 3>(vec2<f32>(-1.,-1.), vec2<f32>(3.,-1.), vec2<f32>(-1.,3.))var out: VOutout.pos = vec4<f32>(p[i], 0., 1.)return out}@fragment fn main() -> @location(0) vec4<f32> { return vec4<f32>(1.,0.,0.,1.); }
""")
// Set scalars/vectors on declared uniformstry shader.set("resolution", [800.0, 600.0])import org.fragmentcolor.*val r = Renderer()val shader = Shader("""@group(0) @binding(0) var<uniform> resolution: vec2<f32>
struct VOut { @builtin(position) pos: vec4<f32> }@vertex fn vs_main(@builtin(vertex_index) i: u32) -> VOut {var p = array<vec2<f32>, 3>(vec2<f32>(-1.,-1.), vec2<f32>(3.,-1.), vec2<f32>(-1.,3.))var out: VOutout.pos = vec4<f32>(p[i], 0., 1.)return out}@fragment fn main() -> @location(0) vec4<f32> { return vec4<f32>(1.,0.,0.,1.); }
""")
// Set scalars/vectors on declared uniformsshader.set("resolution", floatArrayOf(800.0f, 600.0f))Shader::get(key: string) -> any
Section titled “Shader::get(key: string) -> any”Returns the current value of the uniform identified by the given key.
Example
Section titled “Example”1 collapsed line
fn main() -> Result<(), Box<dyn std::error::Error>> {use fragmentcolor::Shader;
let shader = Shader::default();shader.set("resolution", [800.0, 600.0])?;let res: [f32; 2] = shader.get("resolution")?;
3 collapsed lines
assert_eq!(res, [800.0, 600.0]);Ok(())}import { Shader } from "fragmentcolor";
const shader = Shader.default();shader.set("resolution", [800.0, 600.0]);const res = shader.get("resolution");from fragmentcolor import Shader
shader = Shader.default()shader.set("resolution", [800.0, 600.0])res = shader.get("resolution")import FragmentColor
let shader = Shader.default()try shader.set("resolution", [800.0, 600.0])let res = try shader.get("resolution")import org.fragmentcolor.*
val shader = Shader.default()shader.set("resolution", floatArrayOf(800.0f, 600.0f))val res = shader.get("resolution")Shader::list_uniforms() -> [string]
Section titled “Shader::list_uniforms() -> [string]”Returns a list of all uniform names in the Shader (excluding struct fields).
Example
Section titled “Example”1 collapsed line
fn main() -> Result<(), Box<dyn std::error::Error>> {use fragmentcolor::Shader;
let shader = Shader::default();let list = shader.list_uniforms();
3 collapsed lines
assert!(list.contains(&"resolution".to_string()));Ok(())}import { Shader } from "fragmentcolor";
const shader = Shader.default();const list = shader.listUniforms();from fragmentcolor import Shader
shader = Shader.default()list = shader.list_uniforms()import FragmentColor
let shader = Shader.default()let list = shader.listUniforms()import org.fragmentcolor.*
val shader = Shader.default()val list = shader.listUniforms()Shader::list_keys() -> [string]
Section titled “Shader::list_keys() -> [string]”Returns a list of all keys in the Shader, including uniform names and struct fields using the dot notation.
Example
Section titled “Example”1 collapsed line
fn main() -> Result<(), Box<dyn std::error::Error>> {
use fragmentcolor::Shader;
let shader = Shader::default();let keys = shader.list_keys();
3 collapsed lines
assert!(keys.contains(&"resolution".to_string()));Ok(())}import { Shader } from "fragmentcolor";
const shader = Shader.default();const keys = shader.listKeys();from fragmentcolor import Shader
shader = Shader.default()keys = shader.list_keys()import FragmentColor
let shader = Shader.default()let keys = shader.listKeys()import org.fragmentcolor.*
val shader = Shader.default()val keys = shader.listKeys()Shader::from_vertex
Section titled “Shader::from_vertex”Build a basic WGSL shader source from a single Vertex layout.
This inspects the vertex position dimensionality (2D or 3D) and optional properties.
It generates a minimal vertex shader that consumes @location(0) position and a fragment shader that returns a flat color by default.
If a color: vec4<f32> property exists, it is passed through to the fragment stage and used as output.
This is intended as a fallback and for quick debugging. Canonical usage is the opposite: write your own shader and then build Meshes that match it.
Example
Section titled “Example”use fragmentcolor::{Shader, Vertex};
let vertex = Vertex::new([0.0, 0.0, 0.0]);let shader = Shader::from_vertex(&vertex);
1 collapsed line
let _ = shader;import { Shader, Vertex } from "fragmentcolor";
const vertex = new Vertex([0.0, 0.0, 0.0]);const shader = Shader.fromVertex(vertex);from fragmentcolor import Shader, Vertex
vertex = Vertex([0.0, 0.0, 0.0])shader = Shader.from_vertex(vertex)import FragmentColor
let vertex = try Vertex([0.0, 0.0, 0.0])let shader = Shader.fromVertex(vertex)import org.fragmentcolor.*
val vertex = Vertex(listOf(0.0f, 0.0f, 0.0f))val shader = Shader.fromVertex(vertex)Shader::from_mesh
Section titled “Shader::from_mesh”Build a basic WGSL shader source from the first vertex in a Mesh.
The resulting shader automatically adds the provided Mesh to its internal list of Meshes to render,
so the user doesn’t need to call Shader::add_mesh manually.
This function uses the first Vertex to infer position dimensionality and optional properties.
It generates a minimal vertex shader that consumes @location(0) position and a fragment shader that returns a flat color by default. If a color: vec4<f32> property exists, it is passed through to the fragment stage and used as output.
Empty Mesh Handling
Section titled “Empty Mesh Handling”If the Mesh has no vertices, a default shader is returned and a warning is logged. Because the default shader does not take any vertex inputs, it is compatible with any Mesh.
Example
Section titled “Example”use fragmentcolor::{Mesh, Shader};
let mut mesh = Mesh::new();mesh.add_vertex([0.0, 0.0, 0.0]);let shader = Shader::from_mesh(&mesh);
1 collapsed line
let _ = shader;import { Mesh, Shader } from "fragmentcolor";
const mesh = new Mesh();mesh.addVertex([0.0, 0.0, 0.0]);const shader = Shader.fromMesh(mesh);from fragmentcolor import Mesh, Shader
mesh = Mesh()mesh.add_vertex([0.0, 0.0, 0.0])shader = Shader.from_mesh(mesh)import FragmentColor
let mesh = Mesh()try mesh.addVertex([0.0, 0.0, 0.0])let shader = Shader.fromMesh(mesh)import org.fragmentcolor.*
val mesh = Mesh()mesh.addVertex(Vertex(listOf(0.0f, 0.0f, 0.0f)))val shader = Shader.fromMesh(mesh)Shader::add_mesh
Section titled “Shader::add_mesh”Attach a Mesh to this Shader. The Renderer will draw all meshes attached to it (one draw call per mesh, same pipeline).
This method now validates that the mesh’s vertex/instance layout is compatible with the shader’s @location inputs and returns ResultResult<(), ShaderError>.
- On success, the mesh is attached and will be drawn when this shader is rendered.
- On mismatch (missing attribute or type mismatch), returns an error and does not attach.
See also
Section titled “See also”Use Shader::validate_mesh for performing a compatibility check without attaching.
Example
Section titled “Example”1 collapsed line
fn main() -> Result<(), Box<dyn std::error::Error>> {use fragmentcolor::{Shader, Mesh};
let shader = Shader::new(r#"@vertex fn vs_main(@location(0) pos: vec3<f32>) -> @builtin(position) vec4<f32> { return vec4<f32>(pos, 1.0);}@fragment fn fs_main() -> @location(0) vec4<f32> { return vec4<f32>(1.,0.,0.,1.); }"#)?;
let mesh = Mesh::new();mesh.add_vertex([0.0, 0.0, 0.0]);
// Attach mesh to this shader (errors if incompatible)shader.add_mesh(&mesh)?;
// Renderer will draw the mesh when rendering this pass.// Each Shader represents a RenderPipeline or ComputePipeline// in the GPU. Adding multiple meshes to it will draw all meshes// and all its instances in the same Pipeline.
2 collapsed lines
Ok(())}import { Shader, Mesh } from "fragmentcolor";
const shader = new Shader(`
@vertex fn vs_main(@location(0) pos: vec3<f32>) -> @builtin(position) vec4<f32> { return vec4<f32>(pos, 1.0);}@fragment fn fs_main() -> @location(0) vec4<f32> { return vec4<f32>(1.,0.,0.,1.); }
`);
const mesh = new Mesh();mesh.addVertex([0.0, 0.0, 0.0]);
// Attach mesh to this shader (errors if incompatible)shader.addMesh(mesh);
// Renderer will draw the mesh when rendering this pass.// Each Shader represents a RenderPipeline or ComputePipeline// in the GPU. Adding multiple meshes to it will draw all meshes// and all its instances in the same Pipeline.from fragmentcolor import Shader, Mesh
shader = Shader("""@vertex fn vs_main(@location(0) pos: vec3<f32>) -> @builtin(position) vec4<f32> { return vec4<f32>(pos, 1.0);}@fragment fn fs_main() -> @location(0) vec4<f32> { return vec4<f32>(1.,0.,0.,1.); }
""")
mesh = Mesh()mesh.add_vertex([0.0, 0.0, 0.0])
# Attach mesh to this shader (errors if incompatible)shader.add_mesh(mesh)
# Renderer will draw the mesh when rendering this pass.# Each Shader represents a RenderPipeline or ComputePipeline# in the GPU. Adding multiple meshes to it will draw all meshes# and all its instances in the same Pipeline.import FragmentColor
let shader = try Shader("""@vertex fn vs_main(@location(0) pos: vec3<f32>) -> @builtin(position) vec4<f32> { return vec4<f32>(pos, 1.0)}@fragment fn fs_main() -> @location(0) vec4<f32> { return vec4<f32>(1.,0.,0.,1.); }
""")
let mesh = Mesh()try mesh.addVertex([0.0, 0.0, 0.0])
// Attach mesh to this shader (errors if incompatible)try shader.addMesh(mesh)
// Renderer will draw the mesh when rendering this pass.// Each Shader represents a RenderPipeline or ComputePipeline// in the GPU. Adding multiple meshes to it will draw all meshes// and all its instances in the same Pipeline.import org.fragmentcolor.*
val shader = Shader("""@vertex fn vs_main(@location(0) pos: vec3<f32>) -> @builtin(position) vec4<f32> { return vec4<f32>(pos, 1.0)}@fragment fn fs_main() -> @location(0) vec4<f32> { return vec4<f32>(1.,0.,0.,1.); }
""")
val mesh = Mesh()mesh.addVertex(Vertex(listOf(0.0f, 0.0f, 0.0f)))
// Attach mesh to this shader (errors if incompatible)shader.addMesh(mesh)
// Renderer will draw the mesh when rendering this pass.// Each Shader represents a RenderPipeline or ComputePipeline// in the GPU. Adding multiple meshes to it will draw all meshes// and all its instances in the same Pipeline.Shader::remove_mesh
Section titled “Shader::remove_mesh”Remove a single Mesh previously attached to this Shader. If the Mesh is attached multiple times, removes the first match.
Example
Section titled “Example”1 collapsed line
fn main() -> Result<(), Box<dyn std::error::Error>> {use fragmentcolor::{Shader, Mesh};
let shader = Shader::new(r#"struct VOut { @builtin(position) pos: vec4<f32> };@vertexfn vs_main(@location(0) pos: vec2<f32>) -> VOut { var out: VOut; out.pos = vec4<f32>(pos, 0.0, 1.0); return out;}@fragmentfn fs_main(_v: VOut) -> @location(0) vec4<f32> { return vec4<f32>(1.0,0.0,0.0,1.0); }"#)?;
let mesh = Mesh::new();mesh.add_vertex([0.0, 0.0]);shader.add_mesh(&mesh)?;
// Detach the meshshader.remove_mesh(&mesh);2 collapsed lines
Ok(())}import { Shader, Mesh } from "fragmentcolor";
const shader = new Shader(`
struct VOut { @builtin(position) pos: vec4<f32> };@vertexfn vs_main(@location(0) pos: vec2<f32>) -> VOut { var out: VOut; out.pos = vec4<f32>(pos, 0.0, 1.0); return out;}@fragmentfn fs_main(_v: VOut) -> @location(0) vec4<f32> { return vec4<f32>(1.0,0.0,0.0,1.0); }
`);
const mesh = new Mesh();mesh.addVertex([0.0, 0.0]);shader.addMesh(mesh);
// Detach the meshshader.removeMesh(mesh);from fragmentcolor import Shader, Mesh
shader = Shader("""struct VOut { @builtin(position) pos: vec4<f32> };@vertexfn vs_main(@location(0) pos: vec2<f32>) -> VOut { var out: VOut; out.pos = vec4<f32>(pos, 0.0, 1.0); return out;}@fragmentfn fs_main(_v: VOut) -> @location(0) vec4<f32> { return vec4<f32>(1.0,0.0,0.0,1.0); }
""")
mesh = Mesh()mesh.add_vertex([0.0, 0.0])shader.add_mesh(mesh)
# Detach the meshshader.remove_mesh(mesh)import FragmentColor
let shader = try Shader("""struct VOut { @builtin(position) pos: vec4<f32> }@vertexfn vs_main(@location(0) pos: vec2<f32>) -> VOut { var out: VOut out.pos = vec4<f32>(pos, 0.0, 1.0) return out}@fragmentfn fs_main(_v: VOut) -> @location(0) vec4<f32> { return vec4<f32>(1.0,0.0,0.0,1.0); }
""")
let mesh = Mesh()try mesh.addVertex([0.0, 0.0])try shader.addMesh(mesh)
// Detach the meshtry shader.removeMesh(mesh)import org.fragmentcolor.*
val shader = Shader("""struct VOut { @builtin(position) pos: vec4<f32> }@vertexfn vs_main(@location(0) pos: vec2<f32>) -> VOut { var out: VOut out.pos = vec4<f32>(pos, 0.0, 1.0) return out}@fragmentfn fs_main(_v: VOut) -> @location(0) vec4<f32> { return vec4<f32>(1.0,0.0,0.0,1.0); }
""")
val mesh = Mesh()mesh.addVertex(Vertex(listOf(0.0f, 0.0f)))shader.addMesh(mesh)
// Detach the meshshader.removeMesh(mesh)Shader::remove_meshes
Section titled “Shader::remove_meshes”Remove multiple meshes from this Shader.
Example
Section titled “Example”1 collapsed line
fn main() -> Result<(), Box<dyn std::error::Error>> {use fragmentcolor::{Shader, Mesh};
let shader = Shader::new(r#"struct VOut { @builtin(position) pos: vec4<f32> };@vertexfn vs_main(@location(0) pos: vec2<f32>) -> VOut { var out: VOut; out.pos = vec4<f32>(pos, 0.0, 1.0); return out;}@fragmentfn fs_main(_v: VOut) -> @location(0) vec4<f32> { return vec4<f32>(1.0,0.0,0.0,1.0); }"#)?;
let m1 = Mesh::new();m1.add_vertex([0.0, 0.0]);let m2 = Mesh::new();m2.add_vertex([0.5, 0.0]);
shader.add_mesh(&m1)?;shader.add_mesh(&m2)?;
shader.remove_meshes([&m1, &m2]);2 collapsed lines
Ok(())}import { Shader, Mesh } from "fragmentcolor";
const shader = new Shader(`
struct VOut { @builtin(position) pos: vec4<f32> };@vertexfn vs_main(@location(0) pos: vec2<f32>) -> VOut { var out: VOut; out.pos = vec4<f32>(pos, 0.0, 1.0); return out;}@fragmentfn fs_main(_v: VOut) -> @location(0) vec4<f32> { return vec4<f32>(1.0,0.0,0.0,1.0); }
`);
const m1 = new Mesh();m1.addVertex([0.0, 0.0]);const m2 = new Mesh();m2.addVertex([0.5, 0.0]);
shader.addMesh(m1);shader.addMesh(m2);
shader.removeMeshes([m1, m2]);from fragmentcolor import Shader, Mesh
shader = Shader("""struct VOut { @builtin(position) pos: vec4<f32> };@vertexfn vs_main(@location(0) pos: vec2<f32>) -> VOut { var out: VOut; out.pos = vec4<f32>(pos, 0.0, 1.0); return out;}@fragmentfn fs_main(_v: VOut) -> @location(0) vec4<f32> { return vec4<f32>(1.0,0.0,0.0,1.0); }
""")
m1 = Mesh()m1.add_vertex([0.0, 0.0])m2 = Mesh()m2.add_vertex([0.5, 0.0])
shader.add_mesh(m1)shader.add_mesh(m2)
shader.remove_meshes([m1, m2])import FragmentColor
let shader = try Shader("""struct VOut { @builtin(position) pos: vec4<f32> }@vertexfn vs_main(@location(0) pos: vec2<f32>) -> VOut { var out: VOut out.pos = vec4<f32>(pos, 0.0, 1.0) return out}@fragmentfn fs_main(_v: VOut) -> @location(0) vec4<f32> { return vec4<f32>(1.0,0.0,0.0,1.0); }
""")
let m1 = Mesh()try m1.addVertex([0.0, 0.0])let m2 = Mesh()try m2.addVertex([0.5, 0.0])
try shader.addMesh(m1)try shader.addMesh(m2)
shader.removeMeshes([m1, m2])import org.fragmentcolor.*
val shader = Shader("""struct VOut { @builtin(position) pos: vec4<f32> }@vertexfn vs_main(@location(0) pos: vec2<f32>) -> VOut { var out: VOut out.pos = vec4<f32>(pos, 0.0, 1.0) return out}@fragmentfn fs_main(_v: VOut) -> @location(0) vec4<f32> { return vec4<f32>(1.0,0.0,0.0,1.0); }
""")
val m1 = Mesh()m1.addVertex(Vertex(listOf(0.0f, 0.0f)))val m2 = Mesh()m2.addVertex(Vertex(listOf(0.5f, 0.0f)))
shader.addMesh(m1)shader.addMesh(m2)
shader.removeMeshes(listOf(m1, m2))Shader::clear_meshes
Section titled “Shader::clear_meshes”Remove all meshes attached to this Shader.
Example
Section titled “Example”1 collapsed line
fn main() -> Result<(), Box<dyn std::error::Error>> {use fragmentcolor::{Shader, Mesh};
let shader = Shader::new(r#"struct VOut { @builtin(position) pos: vec4<f32> };@vertexfn vs_main(@location(0) pos: vec2<f32>) -> VOut { var out: VOut; out.pos = vec4<f32>(pos, 0.0, 1.0); return out;}@fragmentfn fs_main(_v: VOut) -> @location(0) vec4<f32> { return vec4<f32>(1.0,0.0,0.0,1.0); }"#)?;
let mesh = Mesh::new();mesh.add_vertex([0.0, 0.0]);shader.add_mesh(&mesh)?;
// Clear allshader.clear_meshes();2 collapsed lines
Ok(())}import { Shader, Mesh } from "fragmentcolor";
const shader = new Shader(`
struct VOut { @builtin(position) pos: vec4<f32> };@vertexfn vs_main(@location(0) pos: vec2<f32>) -> VOut { var out: VOut; out.pos = vec4<f32>(pos, 0.0, 1.0); return out;}@fragmentfn fs_main(_v: VOut) -> @location(0) vec4<f32> { return vec4<f32>(1.0,0.0,0.0,1.0); }
`);
const mesh = new Mesh();mesh.addVertex([0.0, 0.0]);shader.addMesh(mesh);
// Clear allshader.clearMeshes();from fragmentcolor import Shader, Mesh
shader = Shader("""struct VOut { @builtin(position) pos: vec4<f32> };@vertexfn vs_main(@location(0) pos: vec2<f32>) -> VOut { var out: VOut; out.pos = vec4<f32>(pos, 0.0, 1.0); return out;}@fragmentfn fs_main(_v: VOut) -> @location(0) vec4<f32> { return vec4<f32>(1.0,0.0,0.0,1.0); }
""")
mesh = Mesh()mesh.add_vertex([0.0, 0.0])shader.add_mesh(mesh)
# Clear allshader.clear_meshes()import FragmentColor
let shader = try Shader("""struct VOut { @builtin(position) pos: vec4<f32> }@vertexfn vs_main(@location(0) pos: vec2<f32>) -> VOut { var out: VOut out.pos = vec4<f32>(pos, 0.0, 1.0) return out}@fragmentfn fs_main(_v: VOut) -> @location(0) vec4<f32> { return vec4<f32>(1.0,0.0,0.0,1.0); }
""")
let mesh = Mesh()try mesh.addVertex([0.0, 0.0])try shader.addMesh(mesh)
// Clear allshader.clearMeshes()import org.fragmentcolor.*
val shader = Shader("""struct VOut { @builtin(position) pos: vec4<f32> }@vertexfn vs_main(@location(0) pos: vec2<f32>) -> VOut { var out: VOut out.pos = vec4<f32>(pos, 0.0, 1.0) return out}@fragmentfn fs_main(_v: VOut) -> @location(0) vec4<f32> { return vec4<f32>(1.0,0.0,0.0,1.0); }
""")
val mesh = Mesh()mesh.addVertex(Vertex(listOf(0.0f, 0.0f)))shader.addMesh(mesh)
// Clear allshader.clearMeshes()Shader::validate_mesh
Section titled “Shader::validate_mesh”Validate that a Mesh is compatible with this Shader’s vertex inputs.
- Checks presence and type for all @location(…) inputs of the vertex entry point.
- Matches attributes in the following order:
- Instance attributes by explicit @location index (if the mesh has instances)
- Vertex attributes by explicit @location index (position is assumed at @location(0))
- Fallback by name (tries instance first, then vertex)
- Returns Ok(()) when all inputs are matched with a compatible wgpu::VertexFormat; returns an error otherwise.
- This method is called automatically when adding a Mesh to a Shader or Pass, so you usually don’t need to call it manually.
- If the Shader has no @location inputs (fullscreen/builtin-only), attaching a Mesh is rejected.
- This method does not allocate GPU buffers; it inspects CPU-side vertex/instance data only.
Example
Section titled “Example”1 collapsed line
fn main() -> Result<(), Box<dyn std::error::Error>> {use fragmentcolor::{Shader, Pass, Mesh};
let shader = Shader::new(r#"struct VOut { @builtin(position) pos: vec4<f32> };@vertex fn vs_main(@location(0) pos: vec3<f32>) -> VOut {var out: VOut;out.pos = vec4<f32>(pos, 1.0);return out;}@fragment fn fs_main(_v: VOut) -> @location(0) vec4<f32> { return vec4<f32>(1.,0.,0.,1.); }"#)?;let pass = Pass::from_shader("p", &shader);
let mesh = Mesh::new();mesh.add_vertices([[-0.5, -0.5, 0.0],[ 0.5, -0.5, 0.0],[ 0.0, 0.5, 0.0],]);
shader.validate_mesh(&mesh)?; // Okpass.add_mesh(&mesh)?;
2 collapsed lines
Ok(())}import { Shader, Pass, Mesh } from "fragmentcolor";
const shader = new Shader(`
struct VOut { @builtin(position) pos: vec4<f32> };@vertex fn vs_main(@location(0) pos: vec3<f32>) -> VOut {var out: VOut;out.pos = vec4<f32>(pos, 1.0);return out;}@fragment fn fs_main(_v: VOut) -> @location(0) vec4<f32> { return vec4<f32>(1.,0.,0.,1.); }
`);const pass = new Pass("p"); pass.addShader(shader);
const mesh = new Mesh();mesh.addVertices([ [-0.5, -0.5, 0.0], [ 0.5, -0.5, 0.0], [ 0.0, 0.5, 0.0], ]);
shader.validateMesh(mesh); // Ok;pass.addMesh(mesh);from fragmentcolor import Shader, Pass, Mesh
shader = Shader("""struct VOut { @builtin(position) pos: vec4<f32> };@vertex fn vs_main(@location(0) pos: vec3<f32>) -> VOut {var out: VOut;out.pos = vec4<f32>(pos, 1.0);return out;}@fragment fn fs_main(_v: VOut) -> @location(0) vec4<f32> { return vec4<f32>(1.,0.,0.,1.); }
""")rpass = Pass("p"); rpass.add_shader(shader)
mesh = Mesh()mesh.add_vertices([[-0.5, -0.5, 0.0],[ 0.5, -0.5, 0.0],[ 0.0, 0.5, 0.0],])
shader.validate_mesh(mesh); # Okrpass.add_mesh(mesh)import FragmentColor
let shader = try Shader("""struct VOut { @builtin(position) pos: vec4<f32> }@vertex fn vs_main(@location(0) pos: vec3<f32>) -> VOut {var out: VOutout.pos = vec4<f32>(pos, 1.0)return out}@fragment fn fs_main(_v: VOut) -> @location(0) vec4<f32> { return vec4<f32>(1.,0.,0.,1.); }
""")let pass = Pass("p"); pass.addShader(shader)
let mesh = Mesh()try mesh.addVertices([[-0.5, -0.5, 0.0],[ 0.5, -0.5, 0.0],[ 0.0, 0.5, 0.0],])
try shader.validateMesh(mesh); // Oktry pass.addMesh(mesh)import org.fragmentcolor.*
val shader = Shader("""struct VOut { @builtin(position) pos: vec4<f32> }@vertex fn vs_main(@location(0) pos: vec3<f32>) -> VOut {var out: VOutout.pos = vec4<f32>(pos, 1.0)return out}@fragment fn fs_main(_v: VOut) -> @location(0) vec4<f32> { return vec4<f32>(1.,0.,0.,1.); }
""")val pass = Pass("p"); pass.addShader(shader)
val mesh = Mesh()mesh.addVertices(listOf(Vertex(listOf(-0.5f, -0.5f, 0.0f)), Vertex(listOf(0.5f, -0.5f, 0.0f)), Vertex(listOf(0.0f, 0.5f, 0.0f))))
shader.validateMesh(mesh); // Okpass.addMesh(mesh)Shader::is_compute
Section titled “Shader::is_compute”Returns true if this Shader is a compute shader (has a compute entry point).
Example
Section titled “Example”1 collapsed line
fn main() -> Result<(), Box<dyn std::error::Error>> {use fragmentcolor::Shader;
let shader = Shader::new(r#"@compute @workgroup_size(1)fn cs_main() { }"#)?;
// Call the methodlet is_compute = shader.is_compute();
4 collapsed lines
let _ = is_compute;assert!(shader.is_compute());Ok(())}import { Shader } from "fragmentcolor";
const shader = new Shader(`
@compute @workgroup_size(1)fn cs_main() { }
`);
// Call the methodconst is_compute = shader.isCompute();from fragmentcolor import Shader
shader = Shader("""@compute @workgroup_size(1)fn cs_main() { }
""")
# Call the methodis_compute = shader.is_compute()import FragmentColor
let shader = try Shader("""@compute @workgroup_size(1)fn cs_main() { }
""")
// Call the methodlet is_compute = shader.isCompute()import org.fragmentcolor.*
val shader = Shader("""@compute @workgroup_size(1)fn cs_main() { }
""")
// Call the methodval is_compute = shader.isCompute()Shader::set_registry(base_url: string)
Section titled “Shader::set_registry(base_url: string)”Override the base URL used to resolve shader slugs (e.g. "sdf2d/circle").
When a slug is passed to Shader::new, it is expanded to:
<base_url>/<slug>.wgslThe default base URL is https://fragmentcolor.org/shaders/, which serves the public shader registry. Override it to point at your own collection (a CDN, a local dev server, or a mirror) without changing the rest of your code.
The base may end with or without a trailing slash; both are normalised at lookup time.
This setting is process-wide. It applies to every subsequent call to Shader::new(...) until overridden again.
Example
Section titled “Example”1 collapsed line
fn main() -> Result<(), Box<dyn std::error::Error>> {
use fragmentcolor::Shader;
// Point at your own mirror of the registryShader::set_registry("https://cdn.example.com/shaders/");
// Now the slug "sdf2d/circle" resolves to https://cdn.example.com/shaders/sdf2d/circle.wgsl// (Skipping the actual fetch in this doctest)3 collapsed lines
let _ = Shader::default();Ok(())}import { Shader } from "fragmentcolor";
// Point at your own mirror of the registryShader.setRegistry("https://cdn.example.com/shaders/");
// Now the slug "sdf2d/circle" resolves to https://cdn.example.com/shaders/sdf2d/circle.wgsl// (Skipping the actual fetch in this doctest)from fragmentcolor import Shader
# Point at your own mirror of the registryShader.set_registry("https://cdn.example.com/shaders/")
# Now the slug "sdf2d/circle" resolves to https://cdn.example.com/shaders/sdf2d/circle.wgsl# (Skipping the actual fetch in this doctest)import FragmentColor
// Point at your own mirror of the registryShader.setRegistry("https://cdn.example.com/shaders/")
// Now the slug "sdf2d/circle" resolves to https://cdn.example.com/shaders/sdf2d/circle.wgsl// (Skipping the actual fetch in this doctest)import org.fragmentcolor.*
// Point at your own mirror of the registryShader.setRegistry("https://cdn.example.com/shaders/")
// Now the slug "sdf2d/circle" resolves to https://cdn.example.com/shaders/sdf2d/circle.wgsl// (Skipping the actual fetch in this doctest)