Skip to content
Voro
  1. Voro
  2. Authoring

Kernel FX

Kernel FX is the fast lane for pictures. You write a small Python function in the Numba-CUDA dialect, press Reload, and the engine compiles it to PTX and runs it natively on the GPU. Python is only the compiler. Once compiled, the frame loop never touches it.

The shipped effects in the Image view, such as Blur, Bitmosh and Ink Bleed, are Kernel FX sources frozen with their presets. Place Kernel FX itself when you want to write your own.

Place Kernel FX from the Image view of the Voro menu. The node arrives with a docked DAT named kernel_fx_source and a Scriptdat reference to it on the Kernel page. The default source copies the input to the output:

TOPOLOGY = "pixel"
def process(ctx):
ctx.outputs['out'].copy_(ctx.inputs['image'])

Wire an image into image. The result is on out.

The source is a Python file with the declarations first and the code after. The declarations are static rows, read at Reload.

  • PARAMS is a list of rows. Each row becomes a parameter on the node. A numeric row takes range, an optional soft_range, a mapping of linear, log or bipolar, units and precision. Read a value inside a kernel as p.Name.
  • ROLE is effect, generator or emitter. An effect takes its size from its first input. A generator owns its own output resolution. An emitter owns its resolution and treats image inputs as optional seeds.
  • INPUTS declares up to four ports and OUTPUTS up to four. STATE declares up to eight tensors that persist from frame to frame. STAGES orders up to eight launches, with an iteration count per stage.
  • TOPOLOGY is pixel, one worker per pixel, or element, one worker per element of the output.

A library of device functions is visible by name: clamp, mix, smoothstep, pixel_xy, uv, sample_bilinear, read_rgba, write_rgba, luma, hsv_to_rgb, hash21, noise2, fbm2 and others. Extra top-level defs in your source compile as device functions.

An effect that splits the red and blue channels apart:

PARAMS = [{"name": "Mix", "type": "float", "default": 1.0, "min": 0.0, "max": 1.0},
{"name": "Shift", "type": "float", "default": 4.0, "min": -50.0, "max": 50.0}]
INPUTS = [{"name": "image", "channels": 4}]
TOPOLOGY = "pixel"
def split(image, x, y, width, height, channels, c, amount):
return sample_bilinear(image, x + amount, y, width, height, channels, c)
def kernel(output, image, height, width, channels, params):
pixel = cuda.grid(1)
if pixel >= height * width:
return
x, y = pixel_xy(pixel, width)
base = pixel * channels
r = split(image, float(x), float(y), width, height, channels, 0, p.Shift)
b = split(image, float(x), float(y), width, height, channels, 2, -p.Shift)
write_rgba(output, base, mix(image[base], r, p.Mix), image[base + 1],
mix(image[base + 2], b, p.Mix), image[base + 3])

Press Reload on the Kernel page. The compile runs off the frame loop. A successful compile is adopted whole. A failed compile leaves the last good kernel running and reports the error on the node. Turning a parameter never recompiles. Changing a declaration does, so edit the text and Reload.

Preset on the Kernel page selects among the presets a source carries. A custom module keeps them in presets.json beside main.py.

Save as Custom Module on the node’s Voro page writes main.py and presets.json into a folder you name under voro_custom, and the module appears in the Custom column of the Image view. See Custom modules.

The reference page is Kernel FX.