Custom HLSL and Render Pipelines¶
Custom shaders run in the same renderer as built-ins. You supply stage functions and, optionally, a bounded procedural recipe. You do not create a device, upload textures, bind arbitrary resources, or install a new backend.
Start with shader/effect ownership and safety. For executable source construction, see Python recipes and Rust recipes.
Source Contract¶
The library supplies resource declarations, Instance, VertexOutput,
default_vertex and default_pixel. Define only the stage functions you replace:
VertexOutput vertex(Instance instance, uint vertex_id : SV_VertexID) {
VertexOutput output = default_vertex(instance, vertex_id);
output.position.x += 12.0 * instance.timing.z * 2.0 / dimensions.x;
return output;
}
float4 pixel(VertexOutput input) : SV_Target {
float4 color = default_pixel(input);
color.rgb *= float3(0.5, 1.0, 0.8);
return color;
}
Vertex and pixel sources are compiled separately as Shader Model 5.0 (vs_5_0
and ps_5_0). A helper defined only in vertex source is not visible in pixel
source. Both stages always execute; omitting either source selects that
pipeline’s default. An empty string is not the same as an omitted stage.
Do not redeclare library structs/resources or use your own main entry point.
Typed wrapper declarations reject incompatible function signatures.
A string passed directly to compile is Sprite pixel-only shorthand. To use
Particles or Procedural, set the pipeline explicitly. When modifying a built-in,
retain its pipeline and execution recipe, not only one HLSL string.
Sprite¶
Sprite draws six vertices (two triangles) per artwork cell. It is suitable for tint, color separation, UV distortion, scaling, rotation and simple geometric deformation. Custom vertex code cannot increase the vertex count in this pipeline.
input.uv is normalized over the padded cell. sample_icon(uv, input) reads
premultiplied artwork and returns transparent outside that cell. default_pixel
samples without distortion. The pixel wrapper blends original/effect pixels
using strength; it does not undo custom vertex displacement. Increase
padding_px for local UV displacement, but keep atlas and allocation limits in
mind. Geometry/effect pixels can clip at the canvas edge.
Sprite accepts four finite parameters without a fixed schema. Its default tuple
(8, 3, 6, 30) serves the Glitch preset; custom Sprite source defines its own
meaning. Neither parameter(index) nor intermediate pass inputs exist here.
Particles¶
Particles generates a bounded grid of quads from vertex IDs and artwork samples. The default geometry can break an icon into colored pieces and gather them again; custom stages can choose different trajectories and fragment shading. The cap is 64x64 particles per padded cell, so large artwork can use coarser tiles.
The four parameters are cell size (1..32 pixels), radius (0..3 icon widths),
turns (-8..8), and dust size (0.1..2); defaults are (3, 1.5, 2, 0.65).
geometry.xy locates the icon center within its padded cell; .zw is icon size.
travel.xy is source minus current position and .zw is target minus current,
so a custom vertex can construct endpoint-relative transport. These inputs are
also present in the procedural pipeline.
The particle pixel input carries interpolated UV/color and pipeline-defined
shape data. Use its default_pixel helper as the starting point; do not copy
the Sprite VertexOutput declaration into particle source. Pause and reverse
must be functions of virtual time/seed, not accumulated simulation state.
Procedural Recipes¶
Procedural is the extension point for custom topology, named parameters, separate artwork layers and multi-pass compositing. The descriptor types map as follows; these are source descriptions until compilation/preparation.
Python |
Rust |
Responsibility |
|---|---|---|
|
|
Parameters, targets, ordered passes, artwork requests |
|
|
Name, kind, finite min/max/default |
|
|
Intermediate width/height |
|
|
Stage source, draw, input/output targets, blend |
|
|
Vertex-count formula and primitive topology |
Blend string |
|
Over, Add or Replace |
Compiled recipe |
|
Validated stages and resource plan |
Python descriptor arguments and defaults:
Constructor |
Arguments |
|---|---|
|
|
|
|
|
Keyword-only |
|
|
Parameter kinds are "float", "integer", "boolean"; topologies are
"triangles", "triangle-strip", "lines"; blends are "over", "add",
"replace". Python’s count_parameter is a declared parameter name;
Rust’s DrawSpec.parameter is its zero-based index. inputs and output
are zero-based target indices in both languages. Omitted inputs mean none;
omitted output means the scene. Pass-local stages override top-level stages,
which override pipeline defaults. Omitting the entire execution recipe creates
one default procedural pass, not the Silk Flow preset.
Parameters are float, integer or boolean (numeric 0/1). Names are unique,
nonempty and contain ASCII letters, digits or underscores. They occupy slots in
declaration order. HLSL parameter(index) reads one of up to 16 values from
b1; no uniform named after the parameter is generated. Python named overrides
use Effect(parameters=...); Rust uses ShaderProgram::with_parameters before
constructing the effect. Unknown names and invalid values are rejected.
vertices + multiplier * count_parameter determines a draw’s count. A count
parameter must have nonnegative integer bounds. Counts must be 1..65536; triangles
need multiples of three, lines multiples of two, triangle strips at least three.
Validation checks the permitted count range, not only the default value.
Request body_artwork / label_artwork only when needed. Complete artwork is
always available; optional instance.body and instance.label identify separate
atlas regions. Use sample_artwork(local_pixels, region) to read a region.
In vertex code, explicit-LOD sampling is required; the helper uses SampleLevel.
This enables independent body breakup and later label reveal without reuploading
artwork each frame. Badge content remains part of body artwork.
Pass Graph and Resource Bindings¶
Declare intermediate targets, then passes in execution order. Each target has
exactly one producer; inputs may reference only targets written by earlier
passes. A pass cannot read its own output. Every declared target must be
produced, and only the last pass may write to the scene (output=None).
Targets are cleared per icon/frame: there is no persistent feedback buffer,
compute stage, cross-frame simulation state or access to the desktop behind it.
Binding |
Ownership |
|---|---|
|
Prepared artwork atlas |
|
|
|
|
|
Canvas dimensions and offset |
|
Procedural parameter values |
For example, inputs=[2] binds target 2 at pass_inputs[0], not slot 2.
Intermediate passes render to their target dimensions; the final pass composites
to the scene. Over uses premultiplied alpha compositing; Add accumulates color;
Replace overwrites. Choose source coordinates appropriate to the current pass,
and use normalized UVs when sampling a full intermediate texture.
All resources/programs are prepared before playback. Limits are 16 parameters, four intermediate targets, eight passes, 64 MiB of target storage per recipe, and 256 MiB of prepared intermediate targets per session. Target axes are at most 8192. Combined top-level stage source and combined pass source each have a 1 MiB limit. Artwork packing also has 8192-pixel atlas-axis limits. These are validation bounds, not promises of a usable frame rate at maximum settings.
Validation Workflow¶
Compile trusted source before moving icons. Render an off-screen scene at zero, several intermediate times, the endpoint, then an earlier time again. Check clean endpoints, premultiplied alpha, padding/edge clipping and repeatable rewind. Test with labels/badges and more than one icon. Then test live playback separately: export throughput is not displayed FPS.