Quick Start
Write and compile your first BWSL shader in 5 minutes.
Turn the guide into code
Take the key idea from this page into the playground and validate it in a real shader instead of leaving it as theory.
Open PlaygroundLet's build a simple hologram effect and compile it end to end. This page assumes bwslc is already on your PATH; if you built from source and have not installed it, use ./build/bwslc from the compiler checkout.
Create Your First Shader
Create a file called hologram.bwsl:
pipeline Hologram {
enum HologramPalette {
Cyan
Amber
}
attributes {
position: float3
normal: float3
texcoord: float2
}
resources {
modelViewMatrix: mat4
projectionMatrix: mat4
normalMatrix: mat3
time: float
}
variants {
pulseBoost: bool = false;
palette: HologramPalette = HologramPalette::Cyan;
}
pass "MainPass" {
use attributes { position, normal, texcoord }
use resources { modelViewMatrix, projectionMatrix, normalMatrix, time }
vertex {
float4 worldPos = resources.modelViewMatrix * float4(attributes.position, 1.0);
output.position = resources.projectionMatrix * worldPos;
output.worldPos = worldPos.xyz;
output.normal = resources.normalMatrix * attributes.normal;
}
fragment {
float3 n = normalize(input.normal);
float3 viewDir = normalize(-input.worldPos);
float fresnel = pow(1.0 - saturate(dot(n, viewDir)), 2.5);
float scanline = sin(input.worldPos.y * 60.0 + resources.time * 8.0);
scanline = smoothstep(0.3, 0.7, scanline * 0.5 + 0.5);
float3 color = variants.palette == HologramPalette::Amber
? float3(1.0, 0.65, 0.1)
: float3(0.0, 0.85, 1.0);
float intensity = (fresnel + 0.15) * scanline;
if (variants.pulseBoost) {
intensity = intensity * 1.35;
}
output.color = float4(color * intensity, fresnel * 0.8 + 0.2);
}
}
}
Declare Resources in Source
resources {} is part of the pipeline. That block declares the shader-visible values the host must provide.
For this shader:
modelViewMatrix,projectionMatrix, andnormalMatrixare engine transformstimeis a host-driven animation value
Host Metadata Stays Outside BWSL
Render-graph details such as targets, pass ordering, or preview-only bindings are host concerns. In the docs playground those live in preview JSON, not in the language itself.
Add Compile-Time Variants
The variants {} block declares shader choices the compiler can specialize away. In this shader:
pulseBoostis a boolean variant that changes intensitypaletteis backed by theHologramPaletteenum, so tools can show named choices instead of free-form strings
Variants are accessed through the variants namespace inside stage code. When you compile a concrete selection, inactive branches become constants or disappear from the generated output.
Compile the Shader
Run the compiler directly on the shader source:
bwslc hologram.bwsl -o output/ -gles
CLI Options
SPIR-V is generated internally and written by default when no format is requested. Flags such as -metal, -hlsl, -glsl, and -gles add translated source outputs; add -spv when you also want SPIR-V files beside those outputs. See the CLI Reference for the full command-line surface.
This generates:
output/hologram.vert— GLSL ES vertex shaderoutput/hologram.frag— GLSL ES fragment shaderoutput/hologram.vert.jsonandoutput/hologram.frag.json— WebGL stage metadata
Add -spv when you also want the generated SPIR-V sidecars:
bwslc hologram.bwsl -o output/ -gles -spv
That also writes:
output/hologram.vert.spv— SPIR-V vertex shaderoutput/hologram.frag.spv— SPIR-V fragment shader
Add -bindings when you also want a pass-level resource binding summary:
bwslc hologram.bwsl -o output/ -gles -bindings
That writes output/hologram.bindings.json.
Compile a concrete variant with repeatable -variant flags:
bwslc hologram.bwsl -o output/ -gles -variant pulseBoost=true -variant palette=Amber
Use -dump-variant-space when tooling needs to inspect the available variant values without compiling shader outputs:
bwslc hologram.bwsl -dump-variant-space
Understanding the Structure
Pipeline Architecture
Every BWSL shader starts with a pipeline declaration. Pipelines contain one or more pass blocks, each representing a render pass.
Key Concepts
| Concept | Description |
|---|---|
| Attributes | Vertex input data such as position, normals, and UVs |
| Resources | Shader-visible values declared in source and provided by the host |
| Variants | Compile-time choices that specialize one pipeline into concrete shader permutations |
| Enums | Named option sets for variants, pattern logic, and strongly typed shader state |
| Passes | Individual render passes in your pipeline |
| Reflection | Compiler output describing resolved bindings and WebGL/backend metadata |
Use in Your Engine
Here's a minimal WebGL 2 sketch. Every uniform resource is emitted as its own std140 uniform block named ub_<resource>, and textures are t_<texture>. Read those names from bindings.json (glName) instead of hard-coding them:
const [vertSource, fragSource, bindings] = await Promise.all([
fetch('output/hologram.vert').then((r) => r.text()),
fetch('output/hologram.frag').then((r) => r.text()),
fetch('output/hologram.bindings.json').then((r) => r.json()),
]);
const program = createShaderProgram(gl, vertSource, fragSource);
// One uniform buffer per uniform resource, bound by its block name.
const uniformBuffers = {};
let nextBinding = 0;
for (const res of bindings.resources) {
if (res.type !== 'uniform') continue;
const blockIndex = gl.getUniformBlockIndex(program, res.glName); // "ub_time", ...
const binding = nextBinding++;
gl.uniformBlockBinding(program, blockIndex, binding);
const buffer = gl.createBuffer();
gl.bindBuffer(gl.UNIFORM_BUFFER, buffer);
gl.bufferData(gl.UNIFORM_BUFFER, 64, gl.DYNAMIC_DRAW); // a mat4 is the largest member here
uniformBuffers[res.name] = { buffer, binding };
}
// Each frame: write std140 data and bind the buffers.
function setUniform(name, data) {
const { buffer, binding } = uniformBuffers[name];
gl.bindBuffer(gl.UNIFORM_BUFFER, buffer);
gl.bufferSubData(gl.UNIFORM_BUFFER, 0, data);
gl.bindBufferBase(gl.UNIFORM_BUFFER, binding, buffer);
}
setUniform('modelViewMatrix', modelViewMatrix); // Float32Array(16)
setUniform('projectionMatrix', projectionMatrix);
setUniform('normalMatrix', normalMatrix3x4); // std140 pads each mat3 column to 4 floats: Float32Array(12)
setUniform('time', new Float32Array([performance.now() / 1000]));
See Bindings and Reflection for the full naming scheme and the bindings.json format.
Next Steps
- Learn Pipelines in more depth
- Explore Shader Variants
- Read about Enums and Eval
- Read about Resources
- Browse the Intrinsics
- Try the Playground