Skip to content

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.

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 fragment in 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.

One thing: draw with gfx.fill(shader or fragment).

function _draw()
gfx.fill(shader or fragment)
end

When 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.

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.

Won't compile: a table at the top level
local ball = { x = 5, y = 5 }
function fragment(x, y, u)
return 0, 0, 0.5
end
function _init()
ball = { x = 5, y = 5 } -- fine: inside a function that starts with _
end
function fragment(x, y, u)
return 0, 0, 0.5
end

The full rules are on the next page, What compiles.

The editor does not upload. The log shows the first error and its line:

Compile error on line 12: cannot combine vec2 and vec3

Fix it, or flip the switch to Interpreted to see the script run as written while you work on it.

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))
end

Reading 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.

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.

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.