Color Grade

Rendering & Graphics

A film-style color grade on a Super Nintendo, taking ten coefficients — three lifts, three gammas, a gain, a mix and a bias. That's more arguments than fit in registers, so the extras spill onto the soft stack: the register-overflow calling convention. The coefficients sweep over time, re-grading the scene through a range of looks. Runs automatically; no joypad needed. Written in C and compiled with the llvm-mos-based 65816 toolchain (+mos-a16, 16-bit accumulator mode).

loading core…

Self-running demo — the scene re-grades through looks on its own.

Click the screen to focus, then watch. Tab away and it pauses.

What it is

Passing arguments to a function is cheap when they fit in registers. But a colourist's grade needs many knobs at once, and the 65816 has few registers — so the compiler has to put the overflow somewhere. It spills them onto a software stack:

int16 color_grade(v, a,b,c, d,e,f, g, h, i) {  // 10 args
    acc  = v * g;          // gain
    acc += a + b + c;      // three lifts
    acc += d - e + f;      // three gammas
    acc += h * i;          // mix * bias
    return acc >> 3;
}

The callee then fetches those spilled arguments back off its frame. Here the grade is applied to a gradient with the coefficients animating, so the picture drifts through a sequence of looks. It stays bit-exact across every codegen mode.

Compiler stress-test

ItemWhat it exercises
calling conventionThe 65816 passes the first few function arguments in registers/zero-page. This grade takes ten — more than fit — so the extras are pushed onto the soft stack, and the callee reads them back off its frame. That register-overflow spill is the path under test.
ten coefficientsA film-style color grade: three lift values, three gammas, a gain, a mix and a bias, applied to a base value. All ten are genuinely used, so none can be optimised away — the spill is real.
sweepingThe coefficients animate over time, so the same gradient re-grades through a range of looks — the "live scene re-grade" a colourist would scrub through.
bit-exactint16 arguments, int32 accumulation, explicit widths throughout. The gate folds 100 graded values; host == default == +mos-a16 == +mos-xy16 == bsnes-jg, byte for byte.

Written in C with the llvm-mos 65816 toolchain and verified against bsnes-jg (and MAME where the SPC700 IPL is present). Hit Verify fidelity to reproduce the build gate's WRAM assert (gate hash 0x783F — a fold of 100 graded values) live in this tab. No far pointers — the same source passes every way: host == default == +mos-a16 == +mos-xy16 == bsnes-jg, -verify clean.