Thanks to visit codestin.com
Credit goes to github.com

Skip to content

ydah/zwgsl

Repository files navigation

zwgsl

A Ruby-inspired shading language that compiles to WGSL (WebGPU) and GLSL ES 3.0. Built with Zig for fast compilation, a small runtime surface, and tooling that can ship as a native library, an LSP server, or a browser wasm module.

Try it in the Playground.

Why zwgsl?

GPU shading languages are powerful, but the authoring experience is usually rigid: braces everywhere, repetitive type annotations, and weak structure for reusable shader logic. zwgsl keeps shader code close to Ruby-style flow and expression syntax while still targeting modern GPU backends.

  • end blocks instead of braces
  • layout-aware indentation and implicit statement separators
  • method chains, postfix conditionals, and implicit returns
  • let bindings, where clauses, type, match, trait, and impl
  • WGSL-first output with a retained GLSL ES 3.0 path

Features

  • Ruby-like syntax with def, do, end, symbols, method calls, and postfix if / unless
  • Indentation-sensitive layout resolver that inserts virtual indent / dedent / statement separators
  • let bindings and function-local where clauses
  • HM-style local type inference with let-generalization
  • Algebraic data types and constructor registration
  • Pattern matching with constructor, wildcard, binding, literal, and guarded arms
  • Dependent dimension matching for Vec(N), Mat(M, N), and tensor-style type applications
  • Generic structs, constructor inference, and phantom-type-safe annotations
  • trait / impl support with compile-time specialization and inline trait methods in WGSL output
  • Multi-stage WGSL pipeline with entry-point-aware HIR and SSA-style CFG MIR: AST -> HIR -> MIR -> WGSL
  • WGSL sampler lowering for uniforms, function parameters, and immutable local aliases
  • Source-aware LSP support: diagnostics, hover with lowering previews, completion, signature help, goto-definition, document symbols, semantic tokens
  • Browser playground with Monaco, wasm compilation, and compiler-backed worker tooling
  • C API surface for embedding the compiler in other tools

Documentation

Quick Example

zwgsl source:

version "300 es"
precision :fragment, :highp

uniform :model_matrix, Mat4
uniform :view_matrix, Mat4
uniform :projection_matrix, Mat4
uniform :light_pos, Vec3
uniform :base_color, Vec4

def phong_strength(normal: Vec3, light_dir: Vec3) -> Float
  max(dot(normal.normalize, light_dir.normalize), 0.0)
end

vertex do
  input :position, Vec3, location: 0
  input :normal, Vec3, location: 1
  varying :v_normal, Vec3
  varying :v_world_pos, Vec3

  def main
    world_pos = model_matrix * vec4(position, 1.0)
    self.v_normal = mat3(model_matrix) * normal
    self.v_world_pos = world_pos.xyz
    gl_Position = projection_matrix * view_matrix * world_pos
  end
end

fragment do
  varying :v_normal, Vec3
  varying :v_world_pos, Vec3
  output :frag_color, Vec4, location: 0

  def main
    light_dir = light_pos - v_world_pos
    light = phong_strength(v_normal, light_dir)
    frag_color = vec4(base_color.rgb * (0.2 + 0.8 * light), base_color.a)
  end
end

Compiled WGSL vertex output:

struct VertexInput {
    @location(0) position: vec3f,
    @location(1) normal: vec3f,
}

struct VertexOutput {
    @builtin(position) gl_Position: vec4f,
    @location(0) v_normal: vec3f,
    @location(1) v_world_pos: vec3f,
}

@group(0) @binding(0) var<uniform> model_matrix: mat4x4f;
@group(0) @binding(1) var<uniform> view_matrix: mat4x4f;
@group(0) @binding(2) var<uniform> projection_matrix: mat4x4f;
struct _zwgsl_uniform_light_pos {
    @align(16) value: vec3f,
}
@group(0) @binding(3) var<uniform> light_pos: _zwgsl_uniform_light_pos;
@group(0) @binding(4) var<uniform> base_color: vec4f;

var<private> gl_Position: vec4f;
var<private> position: vec3f;
var<private> normal: vec3f;
var<private> v_normal: vec3f;
var<private> v_world_pos: vec3f;

fn _zwgsl_vertex_main() {
    let world_pos: vec4f = model_matrix * vec4f(position, 1.0);
    v_normal = mat3x3f(model_matrix[0].xyz, model_matrix[1].xyz, model_matrix[2].xyz) * normal;
    v_world_pos = world_pos.xyz;
    gl_Position = projection_matrix * view_matrix * world_pos;
}

@vertex
fn main(input: VertexInput) -> VertexOutput {
    position = input.position;
    normal = input.normal;
    _zwgsl_vertex_main();
    var output: VertexOutput;
    output.gl_Position = gl_Position;
    output.v_normal = v_normal;
    output.v_world_pos = v_world_pos;
    return output;
}

Compiled WGSL fragment output:

struct FragmentInput {
    @location(0) v_normal: vec3f,
    @location(1) v_world_pos: vec3f,
}

struct FragmentOutput {
    @location(0) frag_color: vec4f,
}

@group(0) @binding(0) var<uniform> model_matrix: mat4x4f;
@group(0) @binding(1) var<uniform> view_matrix: mat4x4f;
@group(0) @binding(2) var<uniform> projection_matrix: mat4x4f;
struct _zwgsl_uniform_light_pos {
    @align(16) value: vec3f,
}
@group(0) @binding(3) var<uniform> light_pos: _zwgsl_uniform_light_pos;
@group(0) @binding(4) var<uniform> base_color: vec4f;

var<private> v_normal: vec3f;
var<private> v_world_pos: vec3f;
var<private> frag_color: vec4f;

fn phong_strength(normal: vec3f, light_dir: vec3f) -> f32 {
    return max(dot(normalize(normal), normalize(light_dir)), 0.0);
}

fn _zwgsl_fragment_main() {
    let light_dir: vec3f = light_pos.value - v_world_pos;
    let light: f32 = phong_strength(v_normal, light_dir);
    frag_color = vec4f(base_color.rgb * (0.2 + 0.8 * light), base_color.a);
}

@fragment
fn main(input: FragmentInput) -> FragmentOutput {
    v_normal = input.v_normal;
    v_world_pos = input.v_world_pos;
    _zwgsl_fragment_main();
    var output: FragmentOutput;
    output.frag_color = frag_color;
    return output;
}

Advanced Examples

Pattern matching over ADTs (Open in Playground):

type Shape
  Circle(radius: Float)
  Rect(width: Float, height: Float)
  Point
end

def area(shape: Shape) -> Float
  match shape
  when Circle(radius)
    3.14159 * radius * radius
  when Rect(width, height)
    width * height
  when Point
    0.0
  end
end

compute do
  def main
    value: Float = area(Circle(2.0))
  end
end

Dependent dimensions that lower to fixed-size WGSL types (Open in Playground):

compute do
  def main
    transform: Mat(4, 4) = mat4(1.0)
    value: Vec(4) = vec4(1.0)
    energy: Float = dot(value, value)
  end
end

Trait-constrained specialization:

trait Numeric
  def add(other: Self) -> Self end
  def mul(other: Self) -> Self end
end

impl Numeric for Float
  def add(other: Self) -> Self
    self + other
  end

  def mul(other: Self) -> Self
    self * other
  end
end

def lerp(a: T, b: T, t: Float) -> T where T: Numeric
  a.mul(1.0 - t).add(b.mul(t))
end

compute do
  def main
    value: Float = lerp(1.0, 2.0, 0.5)
  end
end

Installation

From Source

Requires Zig 0.15.x.

git clone https://github.com/ydah/zwgsl
cd zwgsl
zig build -Doptimize=ReleaseFast

Test Suite

zig build test
bash script/validate_generated_wgsl.sh
zig build benchmark

script/validate_generated_wgsl.sh validates generated WGSL fixtures when naga, tint, or WGSL_VALIDATOR is available. Use WGSL_VALIDATOR_REQUIRED=1 bash script/validate_generated_wgsl.sh or bash script/validate_generated_wgsl.sh --require-validator when a missing external validator should fail the run. zig build benchmark prints CSV compile-time measurements for representative examples.

CLI

zig build
zig-out/bin/zwgsl compile --target wgsl examples/hello_triangle.zw
zig-out/bin/zwgsl check examples/hello_triangle.zw
zig-out/bin/zwgsl fmt --check examples/hello_triangle.zw
zig-out/bin/zwgsl source-map --stage vertex examples/hello_triangle.zw
zig-out/bin/zwgsl lsp

Use --target glsl-es-300 for GLSL ES 3.0 output, --stage to select a single generated stage, and -o / --output to write compile output to a file. Use zwgsl fmt --write <input.zw> to rewrite a source file in place. Use zwgsl source-map to emit a JSON mapping from generated WGSL lines back to source lines when debugging output.

Browser Wasm Build

zig build wasm

That installs zig-out/bin/zwgsl.wasm, which the playground syncs into playground/src/generated/zwgsl.wasm so Vite can fingerprint the asset.

Artifacts

zig build installs:

  • zig-out/lib/libzwgsl.a
  • zig-out/lib/libzwgsl.dylib or the platform equivalent shared library
  • zig-out/include/zwgsl.h
  • zig-out/bin/zwgsl
  • zig-out/bin/zwgsl-lsp

The repository also includes a Dockerfile, a Homebrew formula template under packaging/homebrew/, and an npm compiler package scaffold under packages/compiler/.

Tagged releases attach a Linux x86_64 CLI/LSP/library archive, a standalone zwgsl.wasm asset, and an @zwgsl/compiler npm package tarball with checksum files for each asset.

C API

See C API for ABI/versioning, options, ownership, and C++ integration notes.

#include "zwgsl.h"

ZwgslOptions options = zwgsl_options_default();
options.target = ZWGSL_TARGET_WGSL;

ZwgslResult result = zwgsl_compile(source, source_len, options);

if (result.error_count == 0) {
    if (result.vertex_source != NULL) {
        puts(result.vertex_source);
    }
    if (result.fragment_source != NULL) {
        puts(result.fragment_source);
    }
    if (result.compute_source != NULL) {
        puts(result.compute_source);
    }
}

zwgsl_free(&result);

LSP

The server entry point is zwgsl-lsp, implemented under src/lsp/. See Editor Setup for Neovim, Helix, VS Code, and Zed configuration notes, including the local VS Code extension in editors/vscode.

Current editor-facing features:

  • incremental didChange with full-document change compatibility
  • diagnostics from compiler errors and warnings
  • hover with source-aware type / declaration info and method lowering previews
  • completion for locals, declarations, stage builtins, constructors, fields, and methods
  • signature help for functions, constructors, and supported builtins
  • code actions for common stage declaration, unused uniform, and type/constructor casing fixes
  • goto-definition for values, functions, and type declarations
  • document symbols for stages, declarations, functions, types, traits, and impls
  • document formatting through the same formatter used by zwgsl fmt
  • rename for resolved document-local symbols
  • semantic tokens for keywords, functions, variables, uniforms, varyings, builtins, constructors, traits, types, numbers, strings, comments, operators, and properties

Playground

The playground lives under playground/ and uses Monaco plus the real wasm compiler build.

cd playground
npm install
npm run dev

The repository also includes a GitHub Pages workflow that publishes the production build to https://ydah.github.io/zwgsl/ when Pages is enabled with GitHub Actions as the publishing source.

Current capabilities:

  • Monaco language registration for zwgsl
  • sample selector backed by repository examples and focused feature fixtures, with ?sample=<id> direct links and ?source=<base64url> share links
  • live WGSL compilation through zwgsl.wasm
  • output tabs for combined WGSL, stage-specific WGSL, diagnostics, and generated resource layout
  • compiler-backed diagnostics, hover, completion, signature help, and goto-definition from the wasm build
  • WebGPU preview surface with animated iTime / iResolution uniforms, persisted generated controls with color pickers, sampler placeholders, and 2D texture upload
  • npm run sync-wasm to refresh the generated wasm asset from zig-out/bin/zwgsl.wasm

For a GitHub Pages build, set the base path before running Vite:

cd playground
PLAYGROUND_BASE_PATH=/zwgsl/ npm run build

Architecture

See Architecture for the responsibilities of the frontend, HIR/MIR WGSL path, retained GLSL path, diagnostics, and tooling entry points.

flowchart TD
    Source["Source (.zw)"] --> Lexer
    Lexer --> LayoutResolver["Layout Resolver"]
    LayoutResolver --> Parser
    Parser --> AST

    AST --> HM["HM Inference + Sema"]
    HM --> TypedAST["Typed AST"]

    TypedAST --> HIR
    TypedAST --> IR

    HIR --> MIR
    MIR --> WGSLEmitter["WGSL Emitter"]
    WGSLEmitter --> WGSL["WGSL / WebGPU"]

    IR --> GLSLEmitter["GLSL Emitter"]
    GLSLEmitter --> GLSL["GLSL ES 3.0"]
Loading

Project Status

Implemented behavior is tracked here and in the Feature Matrix. Forward-looking work is tracked separately in the Roadmap.

Area Status
Package version 0.1.0
Verified Zig 0.15.2 in CI; requires Zig 0.15.x
Verified Node.js 24 in CI for the playground
Lexer + layout resolver Implemented
Parser + source positions Implemented
let / where Implemented
HM-style local inference Implemented for local bindings and let-generalization
ADTs + pattern matching Implemented
Dependent dimensions Implemented for Vec(N) / Mat(M, N) matching and WGSL type lowering
Generic structs + phantom tags Implemented
trait / impl specialization Implemented as compile-time static dispatch with inline trait methods in WGSL output
WGSL pipeline Implemented as AST -> HIR -> MIR -> WGSL, with entry-point HIR and SSA-style CFG-based MIR lowering
GLSL ES 3.0 backend Implemented
LSP Implemented
Playground Implemented

Repository Layout

src/
  ast.zig
  compiler.zig
  hir.zig
  hir_builder.zig
  ir.zig
  ir_builder.zig
  layout.zig
  lsp/
  mir.zig
  mir_builder.zig
  parser.zig
  sema.zig
  typeclass.zig
  wgsl_emitter.zig
tests/
  fixtures/
examples/
editors/
  vscode/
playground/
include/

License

Licensed under the MIT License.

About

Ruby-inspired shading language that compiles to WGSL and GLSL ES 3.0, with HM-style inference, pattern matching, an LSP server, and a WebGPU playground.

Topics

Resources

License

Contributing

Security policy

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors