Polygon Scanline Fill
Rendering & Graphics
A filled star tumbles and morphs its point count while a
scanline rasteriser fills it —
running on a Super Nintendo. The catch: the per-scanline edge-crossing table is a
C99 variable-length array (int16_t xs[nv]), a runtime-sized
stack frame (alloca/VLA). Every other demo here has a fixed-size frame; this one
makes the soft-stack target adjust its stack pointer at run time. Runs automatically; no
joypad needed. Written in C and compiled with the
llvm-mos-based 65816 toolchain
(+mos-a16, 16-bit accumulator mode).
Self-running demo — the star tumbles and morphs on its own.
Click the screen to focus, then watch. Tab away and it pauses.
What it is
To fill a polygon, you walk down the screen one scanline at a time; at each line you find where the polygon's edges cross it, sort those x-positions, and paint the spans between alternating pairs (the "even-odd" rule). The number of crossings depends on how many sides the polygon has — so this demo sizes the crossing table with a variable-length array, allocated on the stack at the run-time width:
// nv is only known at run time (it comes from the star's point count):
uint16_t nv = 2 * npoints; // 6 .. 16, changes as the star morphs
int16_t xs[nv]; // ← C99 VLA: a RUNTIME-sized stack frame
for (y = ymin; y <= ymax; y++) {
nx = scan_crossings(px, py, nv, y, xs); // edge x-crossings into xs[]
for (k = 0; k+1 < nx; k += 2) // even-odd: fill between pairs
fill_span(xs[k], xs[k+1], y);
}
Because nv isn't known until run time (the star morphs from 3 to 8 points), the
compiler can't bake the frame size in — it must emit a runtime stack-pointer adjustment. On a
target that synthesizes its stack in software, that's the interesting part. It works, and the
filled area stays bit-exact across every codegen mode.
Compiler stress-test
| Item | What it exercises |
|---|---|
| int16_t xs[nv] | A C99 variable-length array whose size is a run-time value. Every other demo in the battery has a fixed-size stack frame; this one forces the soft-stack target to do a runtime stack-pointer adjustment (the alloca/VLA path). |
| soft-stack target | llvm-mos synthesizes a software stack in zero page — a runtime frame adjustment is not obviously supported. Idea #36 anticipated "a gap is a finding." It is not a gap: the VLA lowers correctly. |
| even-odd scanline fill | For each scanline, the x-crossings of the polygon edges are collected into the VLA, sorted, and filled in pairs — the classic polygon rasteriser, with a runtime-sized crossing table. |
| __divsi3 / __mulsi3 | Each edge crossing is a signed 32-bit divide; each vertex is placed with a 32-bit multiply off a sine LUT. Divide-and-multiply heavy, on top of the VLA frame. |
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 CRC 0x8ED9 — a rotate-XOR fold of the filled area over 16
morphing frames) live in this tab. No far pointers — the same source passes every way: host ==
default == +mos-a16 == +mos-xy16 == bsnes-jg, -verify
clean.