Interpreted and compiled
The panel can run your fragment function in two ways. Both draw the same picture.
| Interpreted | Compiled | |
|---|---|---|
| What the panel runs | Your Lua function, called once per pixel | A compact program built from your function, run 64 pixels at a time |
| Plasma on a 64 × 64 panel | 28.7 µs per pixel, about 8 frames per second | 6.0 µs per pixel, about 30 frames per second |
| What you can write | Any Lua | A subset: numbers, vectors, if, fixed loops, helpers, built-ins |
| Vector math | Slow; more than a few operations per pixel goes over the frame’s work limit | Free: vectors are taken apart by the compiler |
| When errors show up | While it runs, on the panel | Before upload, in the editor, with the line |
print inside the shader |
Works | Ignored |
Use interpreted to poke at a shader with print, or when it needs Lua the compiler does not accept. Use compiled for anything you want to watch: it is the normal way to run a shader.
The switch
Section titled “The switch”In CURIO Code, next to the Upload button, there is a switch: Interpreted or Compiled.
- On Interpreted, Upload sends your script as it is.
- On Compiled, the editor first compiles
fragmentin your browser, then sends your script with the compiled program attached.
Flipping the switch while connected uploads again, so you can watch the difference. The log shows what the compiler made of your shader:
Compiled fragment(): 25 instructions, 4 registers, cost 57 of 1000. About 6.3 µs per pixel, 26 ms for a 64 × 64 frame.What your script has to do
Section titled “What your script has to do”One thing: draw with gfx.fill(shader or fragment).
function _draw() gfx.fill(shader or fragment)endWhen the editor compiles your script it adds a line at the end that sets a global called shader to the compiled program. shader or fragment therefore picks the compiled program when it exists and your Lua function when it does not. The same script is valid on both settings of the switch.
If your script calls gfx.fill(fragment), it still runs, but always interpreted. The editor’s log points this out.
What gets compiled
Section titled “What gets compiled”Only the fragment function and what it uses: the helper functions it calls, the constants it reads and the uniforms it declares. Everything else in the script (_init, _update, _draw, your game logic) stays ordinary Lua and runs as before.
There is one rule to know about. Outside of functions whose names start with an underscore, the script has to stick to what a shader can use, even for things the shader never touches. In practice: keep tables and strings inside _init, _update and _draw.
local ball = { x = 5, y = 5 }
function fragment(x, y, u) return 0, 0, 0.5endfunction _init() ball = { x = 5, y = 5 } -- fine: inside a function that starts with _end
function fragment(x, y, u) return 0, 0, 0.5endThe full rules are on the next page, What compiles.
When compiling fails
Section titled “When compiling fails”The editor does not upload. The log shows the first error and its line:
Compile error on line 12: cannot combine vec2 and vec3Fix it, or flip the switch to Interpreted to see the script run as written while you work on it.
Declaring uniforms
Section titled “Declaring uniforms”A compiled shader needs to know the type of every uniform it reads. Declare each one in a comment at the top of the script:
-- @uniform time float-- @uniform resolution vec2-- @uniform tint vec3
function fragment(x, y, u) local p = vec2(x, y) / u.resolution return u.tint * (0.5 + 0.5 * sin(p.x * 6 + u.time))endReading u.something without a declaration is a compile error that tells you what to add. The values still come from the uniforms table, exactly as in an interpreted shader, including values sent from outside.
How much faster
Section titled “How much faster”It depends on where the shader spends its time.
| Shader | Interpreted | Compiled |
|---|---|---|
Plasma: arithmetic and six sin/cos |
28.7 µs per pixel | 6.0 µs |
Clouds: one four-octave fbm |
24 µs | 15.5 µs |
Compiling removes the cost of calling Lua for every pixel and of creating vectors. It does not make a heavy built-in cheaper: fbm is the same machine code either way, so a shader that is mostly fbm gains less.
A 64 × 64 frame has 4,096 pixels and about 25 ms to shade them at 30 frames per second, which is roughly 6 µs per pixel. The plasma just fits.
The same picture, exactly
Section titled “The same picture, exactly”A shader gives the same pixels interpreted and compiled. Both use the same built-in functions and the same final step that turns a colour into what the LEDs show. The exception is a constant written only with whole numbers that overflows, such as 70000 * 70000: Lua computes that with 32-bit integers, the compiler with floats.
Next: What compiles.