Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Introduction

GLML (OpenGL Meta Language) is a functional DSL that compiles to GLSL fragment shaders. It brings ML-style programming to GPU shaders with HM-style type inference, first-class functions, algebraic data types, and pattern matching.

Try GLML Playground: www.glml-lang.com/playground

Playground

What GLML Gives You

  • Type inference: No need to annotate every variable.
  • First-class functions: Pass functions as values, store them in records and variants, build higher-order helpers that compose.
  • Algebraic data types: Define type shape = | Circle of float | Rect of float * float and match on it.
  • Purity: Every GLML program is a pure function from vec2 (screen coordinate) to vec3 (RGB color). No side effects, no mutation.
  • Recursion: Write recursive shaders; the compiler compiles them to bounded while loops (capped at 1000 iterations, since GLSL doesn't support recursion).

Example: Mandelbrot Shader in GLML

#extern vec2 u_resolution
#extern float u_time

// Normalizes coordinates to [-1, 1] and handles aspect ratio
let get_uv coord =
  let top = 2.0 * coord - u_resolution in
  let bot = #min(u_resolution.0, u_resolution.1) in
  top / bot

// Recursive Mandelbrot function
// zx, zy: Current state
// cx, cy: Constant location
let rec mandel zx zy cx cy i =
  if #length([zx, zy]) > 2. || i > 150. then
    i
  else
    let next_zx = zx * zx - zy * zy + cx in
    let next_zy = 2.0 * zx * zy + cy in
    mandel next_zx next_zy cx cy (i + 1.)

let main (coord : vec2) =
  let uv = get_uv coord in

  // Coordinates for the Seahorse Valley, zooming in and out
  let zoom = #exp(#sin(u_time * 0.4) * 4.5 + 3.5) in
  let cx = -0.7453 + uv.0 / zoom in
  let cy = 0.1127 + uv.1 / zoom in

  // Mandelbrot starting at <0., 0.>
  let iter = mandel 0.0 0.0 cx cy 0.0 in

  if iter > 149.0 then
    [ 0.0, 0.0, 0.0 ]
  else
    let n = iter / 150.0 in
    #sin(n * [10.0, 20.0, 30.0] + u_time) * 0.5 + 0.5

Program Structure

Every GLML program must define a main function with the signature main : vec2 -> vec3, a pure function from pixel coordinates on the fragment to the RGB color for that pixel.

External uniforms from the host application are declared with #extern:

#extern vec2 u_resolution
#extern float u_time

Getting Started

Web Playground

The fastest way to try GLML is the online playground at glml-lang.com/playground. Paste any GLML program or select one of the examples and see the compiled GLSL output immediately. Programs can be run with ctrl-enter.

Building from Source

git clone https://github.com/glml-lang/glml
cd glml
nix develop         # enters a shell with all dependencies

With opam

Requires OCaml 5.3.0+:

git clone https://github.com/glml-lang/glml
cd glml
opam switch create . 5.3.0
opam install . --deps-only
eval $(opam env)

Compiling a Shader

Compile a .glml file to GLSL and print to stdout:

dune exec GLML -- build examples/rainbow.glml

Write the output to a file:

dune exec GLML -- build examples/rainbow.glml -o shader.glsl

Inspecting Compiler Passes

GLML has a multi-pass pipeline. You can dump the AST at any pass for debugging:

# Dump all passes to stdout
dune exec GLML -- build examples/rainbow.glml -p all

# Dump a specific pass
dune exec GLML -- build examples/rainbow.glml -p typecheck

# Dump all passes to files in a directory
dune exec GLML -- build examples/rainbow.glml -p all -d /tmp/passes

# List available pass names
dune exec GLML -- list-passes

Running Tests

make test       # run all expect tests
dune promote    # accept new output after intentional changes

Information Dump

This page contains a dump of current TODO items and unorganized thoughts and tasks. This will likely stay a mess.

Tasks / Bugs

  • Allow binary operations and pipes to be used as curried functions
  • Nested destructing, removing mat pattern case
  • User defined constraints for broadcasting/others, eg: let f (x : a) (y : b) : c where (a b +> c) = x + y
  • Type annotations for arbitrary terms
  • Type aliases with parameters
  • Curried builtin functions for partial application
  • Auto lift f : float -> float to work over vecs?
  • Add builtin GLSL function callers or GLSL extern libraries
  • uintBitsToFloat instead of fat structs to represent variants, instead storing as uvec4 in raw bits
  • Reuse fields with same type for structs / defunctionalization
  • Tuples and static arrays
  • when clause for match statements
  • Add types to new passes like specialize_params
  • Potentially some kind of recursive types like type list['a] = Nil | Cons of 'a * list['a]
  • Passing constraints to mono is a bit weird, they should concretize that in typecheck step
  • Merging specialize and mono passes since they interplay like they're supposed to be the same pass
  • Mutual recursion
  • Add a guide or overview to playground
  • Refactor Makefile if needed to shared playground build files, since we rebuild playground on serve
  • Add common GLSL builtins: #radians / #degrees, #refract, #faceforward, #dFdx, #dFdy, #fwidth,#matrixCompMult, #transpose, #inverse, #determinant

Example Ideas

  • Raymarched volumetric clouds
  • Buffer passing uses (e.g. Game of Life)
  • Lava lamp-like example
  • Example storing functions and hotswapping functions during executions to justify DFns in variants
  • Pathtracing
  • Make logo with GLML (finish beaver)

Long Term Tasks

  • Update GLML screenshot
  • Implicit error field added to every function to propagate error color back
  • Add support for LSP hover or something, at least a simple [inspect] for the playground
  • Size dependent types
  • Swizzle syntax or some kind of rank polymorphism
  • Function inlining / specialize (but likely everything is specialized)
  • Dead code elimination
  • Constant folding/propagation (Sparse conditional constant propagation)
  • Doc strings or emission of helpful comments
  • Better benchmarking tests
  • Add sliders in playground to change values
  • Update blog posts
  • Set up Neovim/Emacs/Treesitter plugins
  • Set up Github organization
  • Add language reference and examples to mdbook

Potentially Interesting Thoughts

  • wasm_of_ocaml build? Core seems to cause Error: Base_am_testing not implemented
  • Write Nix derivation for Javascript and OCaml bindings
  • Emit on compilation what data needs to be passed from host
  • Indexing vectors by arbitrary terms?
  • Differentiate int and float division explicitly
  • Have int <: float be a true subtype (currently can't assign let (x : option[float]) = Some 5)
  • WebGPU backend for computer shaders and SSBOs?
  • Export to shadertoy?

Resources