skills/power2d-codegen/SKILL.md
Shader code generation framework that transforms WGSL and GLSL shader source files into TypeScript companion modules with type-safe APIs for Babylon.js. Use when writing or modifying shader codegen, material generation, or working with @avtools/power2d-codegen.
npx skillsauth add avneeshsarwate/avtools power2d-codegenInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
@avtools/power2d-codegen is a shader code generation framework that transforms WGSL and GLSL shader source files into TypeScript companion modules with fully type-safe APIs for Babylon.js integration. It parses shader ASTs (using wgsl_reflect for WGSL, and a custom regex-based parser for GLSL), extracts uniform structs, texture bindings, and instance attributes, then emits .generated.ts files containing TypeScript interfaces, setter functions, material factories, and metadata arrays. The generated code eliminates manual boilerplate when working with Babylon.js ShaderMaterial and ComputeShader APIs.
Package location: packages/power2d-codegen/
Entry point: packages/power2d-codegen/mod.ts
Runtime: Deno (uses deno.json with npm:wgsl_reflect@^1.2.3)
The codegen handles four distinct shader categories, each identified by a file suffix convention:
| Category | WGSL Suffix | GLSL Suffix | Purpose |
|---|---|---|---|
| Material | .material.wgsl | .material.glsl | Fill shaders for 2D shapes (vertex + fragment) |
| Stroke Material | .strokeMaterial.wgsl | .strokeMaterial.glsl | Stroke/line shaders with arc-length data |
| Fragment Effect | .fragFunc.wgsl | .fragFunc.glsl | Post-processing / multi-pass fragment effects |
| Compute | .compute.wgsl | (N/A) | GPU compute shaders with storage buffers |
| Category | Output Suffix |
|---|---|
| Material (WGSL/GLSL) | .generated.ts / .material.gl.generated.ts |
| Stroke Material (WGSL/GLSL) | .generated.ts / .strokeMaterial.gl.generated.ts |
| Fragment Effect (WGSL/GLSL) | .frag.generated.ts / .frag.gl.generated.ts |
| Compute (WGSL only) | .generated.ts |
WGSL types are mapped to TypeScript types:
| WGSL Type | TypeScript Type | Babylon Setter |
|---|---|---|
| f32 | number | setFloat |
| i32 | number | setInt |
| u32 | number | setUInt |
| bool | boolean | setFloat (converted to 0/1) |
| vec2f | BABYLON.Vector2 \| readonly [number, number] | setVector2 |
| vec3f | BABYLON.Vector3 \| readonly [number, number, number] | setVector3 |
| vec4f | BABYLON.Vector4 \| readonly [number, number, number, number] | setVector4 |
| mat4x4f | BABYLON.Matrix \| Float32Array \| readonly number[] | setMatrix |
GLSL equivalents use float, int, vec2, vec3, vec4, mat4.
For compute shaders, additional integer vector types are supported: vec2i, vec3i, vec4i, vec2u, vec3u, vec4u.
For each shader file, the generator produces:
BasicUniforms)BABYLON.ShaderMaterial (e.g., setBasicUniforms(material, { speed: 2.0 }))BasicUniformDefaults){ name, kind, bindingName } objects used for UI generation (e.g., BasicUniformMeta)setUniforms(), setTexture(), setCanvasSize(), dispose() (e.g., BasicMaterialInstance)ShaderMaterial and returns a handle object (e.g., createBasicMaterial(scene))BasicMaterial)Fragment effects additionally generate:
Effect class extending CustomShaderEffect with typed setUniforms() and setSrcs() methodsCompute shaders additionally generate:
pack functions for storage buffer data// Material shader codegen
import {
WGSL_MATERIAL_SUFFIX, // ".material.wgsl"
WGSL_MATERIAL_OUTPUT_SUFFIX, // ".generated.ts"
generateMaterialTypesSource,
} from "@avtools/power2d-codegen";
const result = generateMaterialTypesSource(shaderCode: string, shaderBaseName: string);
// Returns: { typesSource: string }
// Stroke material shader codegen
import {
WGSL_STROKE_SUFFIX, // ".strokeMaterial.wgsl"
WGSL_STROKE_OUTPUT_SUFFIX, // ".generated.ts"
generateStrokeMaterialTypesSource,
} from "@avtools/power2d-codegen";
const result = generateStrokeMaterialTypesSource(shaderCode: string, shaderBaseName: string);
// Returns: { typesSource: string }
// Fragment effect shader codegen
import {
WGSL_FRAG_SUFFIX, // ".fragFunc.wgsl"
WGSL_FRAG_TYPES_SUFFIX, // ".frag.generated.ts"
generateFragmentShaderArtifactsSource,
buildFragmentShaderErrorArtifactSource,
getFragmentShaderNaming,
} from "@avtools/power2d-codegen";
const result = generateFragmentShaderArtifactsSource({
shaderCode: string,
shaderBaseName: string,
shaderFxImportPath: string, // e.g., "@avtools/shader-fx-babylon"
});
// Returns: { typesSource, shaderPrefix, effectClassName, uniformInterfaceName }
const naming = getFragmentShaderNaming(shaderBaseName: string);
// Returns: { shaderPrefix, effectClassName, defaultUniformInterfaceName }
const errorSource = buildFragmentShaderErrorArtifactSource({
effectClassName, uniformInterfaceName, shaderPrefix,
relativeSourcePath, errorMessage,
});
// Returns: string (TypeScript source that throws on construction)
// Compute shader codegen
import {
WGSL_COMPUTE_SUFFIX, // ".compute.wgsl"
WGSL_COMPUTE_DEFAULT_SUFFIX, // ".generated.ts"
generateComputeShaderTypesSource,
} from "@avtools/power2d-codegen";
const result = generateComputeShaderTypesSource({
shaderCode: string,
shaderFileName: string, // e.g., "animation.compute.wgsl"
shaderStem: string, // e.g., "animation.compute"
});
// Returns: { typesSource: string, shaderPrefix: string }
import {
GLSL_MATERIAL_SUFFIX, // ".material.glsl"
GLSL_MATERIAL_OUTPUT_SUFFIX, // ".material.gl.generated.ts"
generateMaterialTypesSource_GL,
} from "@avtools/power2d-codegen";
import {
GLSL_STROKE_SUFFIX, // ".strokeMaterial.glsl"
GLSL_STROKE_OUTPUT_SUFFIX, // ".strokeMaterial.gl.generated.ts"
generateStrokeMaterialTypesSource_GL,
} from "@avtools/power2d-codegen";
import {
GLSL_FRAG_SUFFIX, // ".fragFunc.glsl"
GLSL_FRAG_TYPES_SUFFIX, // ".frag.gl.generated.ts"
generateFragmentShaderArtifactsSource_GL,
} from "@avtools/power2d-codegen";
GLSL generators have the same signatures as their WGSL counterparts.
import { readTextFile, writeFileIfChanged } from "@avtools/power2d-codegen";
const content = await readTextFile("/path/to/shader.wgsl");
// Only writes if content differs from existing file. Creates directories as needed.
const didWrite = await writeFileIfChanged("/path/to/output.ts", generatedCode);
// Returns: boolean (true if file was written, false if unchanged)
.material.wgsl)Must define exactly two functions:
struct MyUniforms {
speed: f32,
color: vec4f,
}
// Required: vertShader with 3 args (no instancing) or 4 args (with instancing)
fn vertShader(position: vec2f, uv: vec2f, uniforms: MyUniforms) -> vec2f {
return position;
}
// Required: fragShader with 2+ args (uv, uniforms, then optional texture/sampler pairs)
fn fragShader(uv: vec2f, uniforms: MyUniforms) -> vec4f {
return uniforms.color;
}
With instancing:
struct MyInstance {
offset: vec2f,
scale: f32,
}
fn vertShader(position: vec2f, uv: vec2f, uniforms: MyUniforms, instance: MyInstance) -> vec2f {
return position * instance.scale + instance.offset;
}
fn fragShader(uv: vec2f, uniforms: MyUniforms, instance: MyInstance) -> vec4f {
return uniforms.color;
}
With textures (texture/sampler pairs appended after uniforms in fragShader):
fn fragShader(
uv: vec2f,
uniforms: MyUniforms,
myTex: texture_2d<f32>,
myTexSampler: sampler
) -> vec4f {
return textureSample(myTex, myTexSampler, uv);
}
.strokeMaterial.wgsl)Must define strokeVertShader (8 args) and strokeFragShader (4+ args):
fn strokeVertShader(
centerPos: vec2f,
normal: vec2f,
side: f32,
arcLength: f32,
normalizedArc: f32,
miterFactor: f32,
thickness: f32,
uniforms: MyUniforms
) -> vec2f {
return centerPos + normal * side * thickness * miterFactor;
}
fn strokeFragShader(
uv: vec2f,
arcLength: f32,
normalizedArc: f32,
uniforms: MyUniforms
// optional texture/sampler pairs follow
) -> vec4f {
return vec4f(1.0, 0.0, 0.0, 1.0);
}
.fragFunc.wgsl)Must define one or more pass functions named pass0, pass1, etc. in contiguous order starting from 0:
struct BlurUniforms {
radius: f32, // 5.0 min=0 max=50 step=1
strength: f32, // 1.0 min=0 max=2
}
// Single-pass effect: just pass0
fn pass0(uv: vec2f, u: BlurUniforms, inputTex: texture_2d<f32>, inputTexSampler: sampler) -> vec4f {
return textureSample(inputTex, inputTexSampler, uv);
}
Multi-pass effects can reference earlier passes:
fn pass0(uv: vec2f, u: BlurUniforms, inputTex: texture_2d<f32>, inputTexSampler: sampler) -> vec4f {
// horizontal blur
return textureSample(inputTex, inputTexSampler, uv);
}
fn pass1(uv: vec2f, u: BlurUniforms, inputTex: texture_2d<f32>, inputTexSampler: sampler, pass0Texture: texture_2d<f32>, pass0Sampler: sampler) -> vec4f {
// vertical blur, reading from pass0's output
return textureSample(pass0Texture, pass0Sampler, uv);
}
Uniform comment annotations for fragment effects support inline defaults and UI hints:
struct MyUniforms {
radius: f32, // 5.0 min=0 max=50 step=1
color: vec3f, // [1.0, 0.5, 0.0]
enabled: bool, // true
}
The comment after each field is parsed for:
min=N, max=N, step=N for UI slider generation.compute.wgsl)Standard WGSL compute shaders with @group/@binding annotations. The generator reflects all uniforms, storage buffers, textures, samplers, and storage textures:
struct Particle {
position: vec2f,
velocity: vec2f,
}
@group(0) @binding(0) var<uniform> params: ParamsUniforms;
@group(0) @binding(1) var<storage, read_write> particles: array<Particle>;
@compute @workgroup_size(64)
fn main(@builtin(global_invocation_id) id: vec3u) {
// ...
}
Generated output includes:
ParamsUniforms interface + createUniformBuffer_params() + updateUniformBuffer_params()Particle interface + packParticle() for storage buffer serializationcreateStorageBuffer_particles() + writeStorageValue_particles() + updateStorageBuffer_particles()createShader() + updateBindings() for the compute pipeline.material.glsl)Same structure as WGSL but with GLSL types. The generator uses parseStructs() and parseFunctions() from the custom GLSL parser:
struct MyUniforms {
float speed;
vec4 color;
};
vec2 vertShader(vec2 position, vec2 uv, MyUniforms uniforms) {
return position;
}
vec4 fragShader(vec2 uv, MyUniforms uniforms) {
return uniforms.color;
}
Textures in GLSL use sampler2D (single argument, not texture/sampler pairs):
vec4 fragShader(vec2 uv, MyUniforms uniforms, sampler2D myTex) {
return texture(myTex, uv);
}
The vite-shader-plugin tool watches for shader file changes during development and invokes the appropriate generator:
import { generateMaterialTypesSource, readTextFile, writeFileIfChanged } from "@avtools/power2d-codegen";
// Read the shader source
const shaderCode = await readTextFile("src/shaders/basic.material.wgsl");
// Generate TypeScript companion
const { typesSource } = generateMaterialTypesSource(shaderCode, "basic");
// Write only if changed (avoids unnecessary HMR triggers)
await writeFileIfChanged("src/shaders/basic.material.wgsl.generated.ts", typesSource);
// In application code, import the generated module
import { createBasicMaterial } from "./basic.material.wgsl.generated.ts";
const mat = createBasicMaterial(scene);
mat.setUniforms({ speed: 2.0, color: [1, 0, 0, 1] });
mat.setCanvasSize(canvas.width, canvas.height);
mesh.material = mat.material;
// Later:
mat.dispose();
import { BlurEffect } from "./blur.fragFunc.wgsl.frag.generated.ts";
const blur = new BlurEffect(engine, { inputTex: someShaderSource }, 1280, 720);
blur.setUniforms({ radius: 10, strength: 1.5 });
// Chain effects
blur.setSrcs({ inputTex: previousEffect });
import {
createUniformBuffer_params,
updateUniformBuffer_params,
createStorageBuffer_particles,
writeStorageValue_particles,
updateStorageBuffer_particles,
createShader,
} from "./animation.compute.wgsl.generated.ts";
const params = createUniformBuffer_params(engine, { deltaTime: 0.016 });
const particles = createStorageBuffer_particles(engine, 1024, {
initial: initialParticleData,
});
const compute = createShader(engine, {
params,
particles: particles.buffer,
});
// Per frame:
updateUniformBuffer_params(params, { deltaTime: dt });
await compute.shader.dispatchWhenReady(16, 1, 1);
import { watchShaders } from "@avtools/power2d-codegen";
// or via the shader-watch tool:
// deno run --allow-read --allow-write tools/shader-watch/watch.ts src generated
When a shader has a compilation error, the vite plugin generates a stub module that throws at runtime with a descriptive message:
const errorSource = buildFragmentShaderErrorArtifactSource({
effectClassName: "BlurEffect",
uniformInterfaceName: "BlurUniforms",
shaderPrefix: "Blur",
relativeSourcePath: "src/shaders/blur.fragFunc.wgsl",
errorMessage: "Missing pass0 function",
});
// Writes a .ts file where constructing BlurEffect throws the error message
No nested structs or arrays in material/stroke uniforms. Uniform struct fields must be scalar or vector types only. Fragment effect uniforms do support fixed-size arrays of scalars and vectors.
Texture parameters must come in pairs (WGSL). In WGSL shaders, every texture_2d<f32> argument must be immediately followed by a sampler argument. In GLSL, textures use single sampler2D arguments.
Fragment effect pass naming is strict. Pass functions must be named pass0, pass1, ... with no gaps. Each pass must return vec4f. Later passes can reference earlier passes via pass0Texture/pass0Sampler naming convention.
Uniform binding names are prefixed. The generator creates Babylon uniform binding names as {argName}_{fieldName} (e.g., uniforms_speed). This is an internal detail but matters if you inspect the raw ShaderMaterial.
Internal power2d uniforms are injected. Materials automatically get power2d_shapeTranslate, power2d_shapeRotation, power2d_shapeScale, power2d_canvasWidth, power2d_canvasHeight uniforms. Stroke materials additionally get power2d_strokeThickness.
Name conversion uses PascalCase. The shaderBaseName (derived from the filename without the suffix) is converted to PascalCase for all generated identifier names. Hyphens and underscores are treated as word separators.
Generated files should not be hand-edited. All output files begin with // Auto-generated by ... DO NOT EDIT. and are overwritten on each generation pass.
GLSL output uses ShadersStore (WebGL), WGSL uses ShadersStoreWGSL (WebGPU). The generated material factories register shaders in the appropriate Babylon shader store depending on the language.
Compute shader codegen is WGSL-only. There is no GLSL compute shader generator.
wgsl_reflect dependency. WGSL parsing depends on wgsl_reflect@^1.2.3 (via npm). The GLSL parser is a lightweight custom implementation using regex (glsl/parseGlsl.ts).
Fragment effect uniform annotations are optional. If no comment annotation is present on a struct field, defaults are zero-initialized and no UI metadata is emitted for that field.
Boolean uniforms are encoded as floats. WGSL bool uniforms are passed to Babylon as setFloat with ? 1 : 0 conversion since Babylon's ShaderMaterial does not have a native boolean setter.
tools/vite-shader-plugin/ -- The primary consumer. A Vite plugin that watches shader files during development, calls the appropriate generator on file change, and writes .generated.ts files alongside the source shaders. Supports all seven shader categories, configurable source/output directories, and custom import path overrides for fragment effects.
tools/shader-watch/ -- A standalone Deno file watcher that monitors a directory for .material.wgsl and .strokeMaterial.wgsl changes and regenerates TypeScript output. Used for code generation outside the Vite dev server context.
packages/power2d/ -- The 2D rendering library that consumes the generated material and stroke material TypeScript files. The generated factory functions produce ShaderMaterial instances configured for power2d's shape rendering pipeline (pixel-space coordinates, shape transforms, alpha blending).
packages/shader-fx-babylon/ -- Consumes generated fragment effect files. Provides the CustomShaderEffect base class that the generated *Effect classes extend. The generated effect classes wire up multi-pass rendering, texture input management, and typed uniform setters.
*.material.wgsl -----> vite-shader-plugin / shader-watch
*.strokeMaterial.wgsl -----> calls power2d-codegen generators
*.fragFunc.wgsl -----> writes .generated.ts files
*.compute.wgsl -----> consumed by app code / power2d / shader-fx-babylon
*.material.glsl ----->
In the monorepo deno.json, the package is mapped as:
{
"name": "@avtools/power2d-codegen",
"exports": "./mod.ts",
"imports": {
"wgsl_reflect": "npm:wgsl_reflect@^1.2.3"
}
}
The tools/vite-shader-plugin imports directly from the source files via relative paths rather than the package alias, since it runs in a Node/Vite context.
packages/power2d-codegen/
mod.ts # Public API re-exports
deno.json # Package manifest
codegen/
codegenIO.ts # readTextFile, writeFileIfChanged
wgsl/
generateMaterialTypesCore.ts # .material.wgsl -> .generated.ts
generateStrokeMaterialTypesCore.ts # .strokeMaterial.wgsl -> .generated.ts
generateFragmentShaderCore.ts # .fragFunc.wgsl -> .frag.generated.ts
generateComputeShaderTypesCore.ts # .compute.wgsl -> .generated.ts
glsl/
parseGlsl.ts # Regex-based GLSL struct/function parser
generateMaterialTypesCore_GL.ts # .material.glsl -> .material.gl.generated.ts
generateStrokeMaterialTypesCore_GL.ts # .strokeMaterial.glsl -> .strokeMaterial.gl.generated.ts
generateFragmentShaderCore_GL.ts # .fragFunc.glsl -> .frag.gl.generated.ts
tools
Deno Jupyter-to-iframe communication infrastructure for embedding interactive web components in notebooks. Provides HTTP/WebSocket bridge, session management, and adapter pattern. Use when writing code that uses @avtools/ui-bridge for notebook UI, WebSocket clients, or component adapters.
tools
GPU-accelerated shader effects framework built on Babylon.js. Provides composable graph-based post-processing, multi-pass rendering, feedback loops, and fluid simulation. Use when writing code that uses @avtools/shader-fx for shader effects, effect chains, or GPU computations.
tools
GPU-accelerated 2D graphics rendering library built on Babylon.js. Provides styled shapes with custom shaders, stroke outlines, and thin instancing for 10,000+ shapes. Use when writing code that uses @avtools/power2d for 2D rendering, shape creation, materials, instancing, or canvas textures.
tools
Music theory and MIDI note manipulation library. Provides type definitions, data structures, and utilities for Ableton Live clips, musical scales, Bezier curve envelopes, and MPE data. Use when writing code that uses @avtools/music-types for clips, notes, scales, curves, or MIDI operations.