Lua-inspired language that emits cross-platform shader code.
- Odin 89.2%
- Python 3.3%
- HLSL 2.1%
- Shell 2%
- WGSL 1.9%
- Other 1.5%
HLSL requires mul(matrix, vector) instead of the * operator for matrix-vector, vector-matrix, and matrix-matrix multiplication. The SPIR-V backend already handled this with dedicated opcodes. Update golden tests accordingly. |
||
|---|---|---|
| docs | ||
| examples | ||
| luma | ||
| tests | ||
| tools | ||
| .gitignore | ||
| issues.md | ||
| main.odin | ||
| plan.md | ||
| README.md | ||
Luma
A Lua-inspired shader language that cross-compiles to GLSL, HLSL, MSL, WGSL, and SPIR-V. Written in pure Odin. Ships as both a standalone CLI and an embeddable library.
Quick Start
# Build
odin build . -out:luma
# Compile a shader
./luma compile shader.luma --target=glsl
./luma compile shader.luma --target=spirv -o shader.spv
./luma compile shader.luma --target=spirv --debug -o shader.spv # with debug info
# Type-check only
./luma check shader.luma
# Inspect
./luma dump-ast shader.luma
./luma dump-ir shader.luma
./luma reflect shader.luma
./luma reflect --spirv shader.luma # Vulkan descriptor set layout
Example
struct Uniforms
model: mat4
view_proj: mat4
end
@group(0) @binding(0)
uniform uniforms: Uniforms
struct VertexInput
@location(0) position: vec3
@location(1) uv: vec2
end
struct VertexOutput
@builtin(position) position: vec4
@location(0) uv: vec2
end
@entry(vertex)
function main(input: VertexInput) -> VertexOutput
let clip_pos = uniforms.view_proj * uniforms.model * vec4(input.position, 1.0)
return VertexOutput {
position = clip_pos,
uv = input.uv,
}
end
Language Overview
Types
| Category | Types |
|---|---|
| Scalar | bool, int, uint, float, half |
| Vector | vec2-vec4, ivec2-ivec4, uvec2-uvec4, bvec2-bvec4 |
| Matrix | mat2-mat4, mat2x3, mat3x4, etc. |
| Sampler | sampler2D |
| Array | [N]T (e.g. [16]vec4) |
| Struct | User-defined with struct ... end |
Entry Points
@entry(vertex) -- vertex shader
@entry(fragment) -- fragment shader
@entry(compute) @workgroup_size(8, 8, 1) -- compute shader
Bindings
@group(0) @binding(0) uniform uniforms: MyStruct -- uniform buffer
@group(0) @binding(1) buffer data: MyBuffer -- storage buffer
@group(1) @binding(0) uniform tex: sampler2D -- auto-splits to texture + sampler
Groups default to 0. Bindings auto-increment within each group.
Control Flow
if condition then
-- ...
elseif other then
-- ...
else
-- ...
end
for i = 0, 10 do ... end
for i = 0, 100, 2 do ... end -- with step
while condition do ... end
Builtins
Math: abs, sign, floor, ceil, round, fract, sqrt, sin, cos, tan, pow, exp, log, min, max, clamp, mix, smoothstep, step
Vector: normalize, length, dot, cross, distance, reflect, refract
Matrix: transpose, inverse, determinant
Texture: sample(tex, uv), sample_level(tex, uv, lod)
Compute: barrier()
Attributes
| Attribute | Usage |
|---|---|
@entry(stage) |
Mark function as shader entry point |
@location(N) |
I/O location (auto-assigned if omitted) |
@builtin(name) |
Built-in variable (position, vertex_index, etc.) |
@group(N) |
Descriptor set / bind group |
@binding(N) |
Binding number within group |
@workgroup_size(X,Y,Z) |
Compute workgroup dimensions |
@varying |
Struct shared between vertex output and fragment input |
@spec(id) |
Specialization constant |
Inline Entry Points
-- Tuple return syntax
@entry(fragment)
function main(@location(0) uv: vec2) -> (color: vec4)
return vec4(uv, 0.0, 1.0)
end
-- Single output shorthand
@entry(fragment)
function main(@location(0) uv: vec2) -> vec4
return vec4(uv, 0.0, 1.0)
end
Backend Targets
| Target | Flag | Version |
|---|---|---|
| GLSL | --target=glsl |
4.50 |
| HLSL | --target=hlsl |
Shader Model 6.0 |
| MSL | --target=msl |
Metal 2.4 |
| WGSL | --target=wgsl |
WebGPU |
| SPIR-V | --target=spirv |
1.5 |
CLI Reference
luma compile [options] <file> Compile shader to target
--target=<target> glsl, hlsl, msl, wgsl, spirv (default: glsl)
--include-dir=<path> Add include search path
--debug Emit debug info (SPIR-V: OpLine/OpSource)
-o <file> Output file (default: stdout)
luma check <file> Parse and typecheck only
luma dump-ast <file> Print abstract syntax tree
luma dump-ir <file> Print intermediate representation
luma reflect [options] <file> Emit binding reflection as JSON
--spirv SPIR-V-specific reflection (descriptor sets, layout)
Library Usage
import "luma"
// Full compilation
result := luma.compile(source, luma.Compile_Options{
target = .GLSL_450,
})
// Individual pipeline stages
ast, diags := luma.compile_parse(source, "shader.luma")
ast, sema, diags := luma.compile_check(source, "shader.luma")
ir_module, diags := luma.compile_lower(source, "shader.luma")
info, diags := luma.compile_reflect(source, "shader.luma")
spirv_info, diags := luma.compile_reflect_spirv(source, "shader.luma")
Building
Requires Odin compiler.
odin build . -out:luma
For SPIR-V validation, install spirv-tools:
spirv-val output.spv
Testing
bash tests/run_golden.sh # run all 40 golden tests
bash tests/run_golden.sh --update # regenerate golden files