How it works

This TV runs on texture nodes

I built a working computer out of Substance Designer graphs and gave it a game. Every brick you see break was decided by a Pixel Processor executing the game's own machine code. Nobody animated that screen.

37,190 function nodes in the filmed computer29 KB compiled, as filmed5,701 / 5,701 frames bit-exactSound on
01
A computer from 1977

The machine

CHIP-8 is a tiny virtual computer from the late 70s, first run on the COSMAC VIP. It has 4 KB of memory, sixteen 8-bit registers, a stack, two timers, a hex keypad and a 64×32 screen with one bit per pixel. Programs are raw machine code: 35 kinds of two-byte instruction.

The game is Br8kout, a Breakout clone SharpenedSpoon wrote in 2014 and released CC0. I didn't port it or redraw it. The graph reads the ROM's bytes out of a bitmap and executes them, seven instructions every 60th of a second, while a recorded keypad tape plays the paddle.

Below is the same program, same tape and same rules, running in your browser on an interpreter written to the graph's spec. The strip under the screen is the part I still find hard to believe. That's the whole machine: registers, stack, timers and all 2,048 screen pixels, packed into 48 pixels of a texture. In the film's build, it's exactly what one Pixel Processor hands to the next.

Figure 1 · Br8kout+, running
0:00
Machine state · 64×1 texture · 144 words, 3 per pixel · V registers, timers, PC, RNG, stack, screen
VTPRStackFramebuffer, 128 wordsUnused
Frame
0
Replayed from power-on
0
PC
200
I
000
Delay timer
0
Clock
60 Hz · 7 per frame
    4 KB of RAM is this image

    One byte per pixel. Font at the top, the game from byte 512. Amber is the program counter, green is I. Hover to read a byte.

    Hover the map to read a byte
    Br8kout by SharpenedSpoon (CC0) with my 66-byte level-clear patch. Drag the slider back and the machine reboots from power-on and replays up to that frame, which is exactly what Designer does for every frame it renders.
    02
    The problem

    Four things a Pixel Processor doesn't have

    A Pixel Processor exists to shade textures. It runs one small function for every output pixel and forgets everything afterwards. You get arithmetic, comparisons, if/else, a while loop, variables and image samplers. That's the whole toolbox, and a computer needs four things it doesn't include.

    01Memory

    There's no writable memory. So the 4 KB of RAM is a 64×64 grayscale image, one byte per pixel, read with a sampler. You saw it in figure 1. Anything the program changes lives in graph variables instead. Getting RAM writes around that came later, and chapter 6 is how.

    02Arrays

    There are no arrays. When an instruction says "add to register X" and X is only known while the program runs, the graph has to pick the right variable out of sixteen. Every write is a tree of if/else nodes with a Set at each leaf.

    It only works because Designer runs nothing but the branch an if/else takes. Writing one of 16 registers costs four comparisons and a single Set. Writing one of the 144 words that make up the machine costs about seven comparisons. The framebuffer depends on that one engine behavior.

    Figure 2 · Writing to register VXif/else tree, 16 leaves
    X =VB
    4comparisons run
    1Set runs
    15Sets skipped
    Each diamond is an if/else node comparing X against a midpoint. Only the lit path executes. The 144-word machine state uses the same shape, one level deeper per doubling.

    03Bits

    CHIP-8 is built on AND, OR, XOR and shifts. Designer has no XOR and no bit shift, and its And and Or work on true and false, not on bytes. So every byte gets split into its eight bits with floor and modulo, combined bit by bit, and put back together.

    That broke once in a way I didn't expect. On the CPU engine, pow(2, n) comes back a hair under the whole number. One framebuffer word came out as 49,151 instead of 49,152, and a single pixel went wrong. Now every power of two in the graph gets rounded.

    Figure 3 · XOR, from floor and modulobit b = floor(v / 2b) mod 2

    04Time

    Nothing survives between frames. Each frame is cooked from scratch, so each one boots the computer from power-on and replays the game up to that moment, keypad tape included. Frame 60 is 420 instructions. Frame 5,700 is 39,900.

    Figure 4 · What one frame costsframe 5,700 · 39,900 instructions
    Top bar: one Pixel Processor running the whole replay. Bottom: the same work cut into five chained segments of 8,000 instructions each. Every segment is its own GPU job.
    03
    The wall

    The crash

    My first version ran the whole CPU inside one Pixel Processor. Up to 36,000 instructions it was perfect. At 65,000 the GPU sat on a single job too long, macOS revoked Designer's access to it, and every cook after that came back empty until I restarted. Once the hang outlived the process and took the whole session down with it.

    So the CPU is cut into five Pixel Processors. Each one is its own GPU job, runs at most 8,000 instructions, and writes the entire machine into a 64×1 float texture. In the film's build that's 144 numbers, three to a pixel. The next node reads that texture and picks up exactly where the last one stopped.

    A Pixel Processor runs once per output pixel, so every segment actually runs the full computer 64 times side by side, and each copy keeps only its own three numbers. Across the 95 second run that's about 114 million CHIP-8 instructions, times 64.

    The full SUBSTANCE-8 graph in Designer: ROM and tape inputs, five segment nodes chained left to right, a display node and outputs
    The real graph in Designer. ROM and keypad tape come in as bitmaps on the left, five segments hand the machine forward, the display node unpacks the framebuffer.
    One segment's function graph zoomed out: a tall column of nodes
    Inside one segment, zoomed all the way out.
    Close view of if/else and Set nodes in a segment
    Closer: the if/else trees and Set nodes from figure 2.
    04
    Bit for bit

    Proof

    An emulator that's almost right is wrong. One bad collision bit and the ball goes somewhere else a few seconds later. So a separate interpreter, written to the same spec and checked against the Timendus CHIP-8 test suite, plays the same ROM with the same tape, and every frame Designer rendered was diffed against it. All 5,701 frames of the 95 second run match.

    The Br8kout+ frames in this figure came out of the 29 KB .sbsar on a different machine, a Windows PC, through sbsrender. The Dino Run frames are from the new build. Your browser recomputes the reference and diffs them live. Frame 5,700 took 1.3 seconds, including startup.

    Figure 5 · Designer against the referencepick a frame
    Designer, sbsrender
    Reference, computed now
    Difference · black means identical
    …
    05
    66 bytes

    The ending Br8kout never had

    I gave the computer a player that can't miss and found out Br8kout has no ending. I disassembled all 199 bytes. There's no brick counter anywhere, so when the last brick goes the ball just bounces around an empty field forever.

    That felt like a letdown, so I wrote the ending in the game's own machine language: 66 bytes appended to the ROM and four of the original instructions rewired. It counts bricks in a register the game never touches. At 49 it freezes the ball for a second, clears the screen, puts up the next level number from the computer's built-in font, and starts a fresh wall one step faster. It only uses instructions the Designer CPU already had.

    Figure 6 · Br8kout+, the patch
    Green lines are the four original instructions I rewired. Everything else is new code at 0x2C7, right after the original ROM ends.
    06
    37,190 nodes, then 124,062

    A second game

    A machine that only runs Br8kout is just a very elaborate Br8kout. So I pointed the memory bitmap at a different program: Dino Run, a CC0 endless runner by Andrezinrc. On the untouched 37,190-node graph, at seven instructions per frame, 2,992 frames came out, 49.9 seconds of game, every one bit-exact.

    The first run: Dino Run on the unchanged computer graph, through the CRT graph. No 3D, no grade, no edits.

    A real computer also has to write to its memory, and the RAM here is an image, which a Pixel Processor can't write to. So the bitmap stays read-only and writes go somewhere else: an overlay carried in the machine state, handed from segment to segment with everything else. Reads look in the overlay first and fall through to the bitmap. That's how FX33, FX55 and FX65 got in, the instructions that store bytes to RAM and load them back.

    The overlay has a limit. The state texture has room for 47 slots, and a test ROM proved all of them for 172 frames, covering BCD, sprites drawn from RAM and self-modifying code. But the full overlay about halves the GPU budget even when it's empty, so the slot count is a per-ROM build setting and the shipped graphs use 4. A ROM that writes to more than four different addresses would lose those writes. Verification would catch it, but the game would be wrong. Dino Run writes three addresses. Br8kout+ writes none.

    Dino Run also got its author's clock back, 15 instructions per frame instead of 7. That's 900 instructions a second, frame 5,700 replays 85,500 of them, and the graph grew to 14 segments of 6,500 instructions each: 124,062 function nodes in a 35.4 KB .sbsar. On the new build, Dino Run and Br8kout+ each pass 5,701 of 5,701 frames bit-exact. The Br8kout film at the top of this page still comes from the old graph.

    Then it went back on the TV. This is one take with no cuts, 13.4 seconds, the camera held by hand in the same den. On the screen are game frames 2,879 to 3,683, between the level-ups at scores 20 and 30 that flip the screen.

    Rendered at 512 samples with every light in its own pass, like the film. I pulled the screen's spill down to about a third and took the yellow cast off the cabinet. Room tone only.

    Watch the score

    About a second before that clip ends, the score reaches 29 and the 9 comes out wrong. That isn't a render fault. Dino Run runs F055 with I pointing at the font, right at the glyph for 9 at address 0x2D, and from frame 1,803 on that overwrites the top row of the digit. Of Dino's three write addresses, this is the only one where a byte actually changes. The other two write 255 over 255.

    The graph reproduces it exactly, because the graph is running the machine code, whatever that code does. My read is that the ROM's author didn't mean it, but that's an inference, and I haven't run it on another emulator. I kept it. It's a corrupted digit on a TV, produced by a texture graph writing a byte into its own font. Set figure 1 to Dino Run and drag to frame 3,650 to see it, with the whole 5,701-frame run on the same slider.

    07
    2,852 nodes

    The tube

    The CRT is a second graph. It takes the current frame and the four before it and keeps the brightest, each faded by 0.62 per frame. That's why the ball leaves a short trail like real phosphor. Then it draws soft round beam spots, a glow, two scanlines per row, an RGB mask and the curve of the glass.

    The game is 2:1 and the TV glass is 4:3, so every game pixel lands 1.51 times taller than it is wide. The graph squashes its dots ahead of time so they come out round.

    Figure 7 rebuilds that recipe in your browser so you can pull it apart. The video under it is the real graph's output, straight from sbsrender.

    Figure 7 · Phosphor, rebuilt livedriven by figure 1's machine
    A browser approximation of the graph's recipe for pulling it apart, not its output.
    The real CRT graph at native resolution: triad mask, scanlines, beam spots, the ball's phosphor trail.
    The CRT graph in Designer
    The CRT graph in Designer: five frame inputs, an energy node, the tube node.
    The level clear from figure 6, through the CRT graph. No 3D, no grade, no edits.
    08
    Staging

    The room

    Then the picture needed somewhere to live. The room is Blender with CC0 assets from Poly Haven: a 70s TV with the maker's logo painted out and a SUBSTANCE-8 badge in its place, a wooden table, a lounge chair, a plant and paneled walls. The CRT frames map onto the curved glass as emission, and a hidden green light over the glass lets the game light the room.

    Here's one frame, end to end, through every program that touched it.

    Figure 8 · Program to picturefilm frame 1,300 · game frame 5,407
    Pipeline stage

    Every light in the room rendered into its own pass. Once that's done, light is addition: residual plus each light times its gain, plus the lens effects. Those passes are real, from the final render. Move the sliders and you're relighting frame 1,300 yourself.

    Figure 9 · Relight itscene-linear passes · AgX view
    Passes are log-encoded for the web and shown through an AgX approximation. The film used the same passes at full float precision.

    The finish happened in After Effects: an AgX view, a light grade, fine grain and a vignette, plus a power-on and power-off built from the screen's own light pass, so the room really does go dark when the TV does.

    Figure 10 · Graded against raw
    Final graded frameRaw render frame
    Raw renderFinal
    Drag across the frame.

    The last 25 seconds of the cut were one slow shot of a nearly empty board, so I re-timed it instead of cutting away: three times speed through the dead rally, real time for the last two seconds, half speed on the final brick. The dots below are game events I tracked out of the screen frames. The same events trigger every blip in the soundtrack, which is synthesized from scratch.

    Figure 11 · The edit is a curveplay the clip
    09
    The receipt

    Numbers

    37,190function nodes in the computer, as filmed
    2,852function nodes in the tube
    29 KBcompiled .sbsar as filmed, stock nodes, no plugins
    5 × 8,000instructions per chained Pixel Processor
    39,900instructions replayed for frame 5,700
    5,701frames of Dino Run and of Br8kout+, all bit-exact against the reference
    124,062function nodes in the current Dino Run build, 35.4 KB
    61,300function nodes in the current Br8kout+ build, 35.6 KB
    47RAM overlay slots proven, 4 shipped
    144words of machine state in a 64×1 texture, as filmed
    66bytes of new machine code for the ending
    31of 35 CHIP-8 instructions in the film's build, before RAM writes

    The film's build ran 31 of the 35 CHIP-8 instructions, everything Br8kout needs. The current build runs 35 of 35 if you count 0NNN as ignored, 34 if you don't. Programs can write to RAM, up to four addresses in the shipped graphs.

    Br8kout by SharpenedSpoon (CC0, via John Earnest's chip8Archive). Level clear by me.
    Dino Run by Andrezinrc (CC0, via chip8Archive).
    TV, furniture and HDRI from Poly Haven (CC0).
    Built in Substance 3D Designer. Staged in Blender. Finished in After Effects.