An interactive 2D visualizer for CNN and RNN architectures, written in Odin on top of raylib. Native, lightweight, real-time. Designed as a faster, more focused alternative to web-only tools like TensorFlow Playground.
- Layer types:
Input,Conv2D,MaxPool,AveragePool,Flatten,Dense,SimpleRNN,LSTM,GRU,Output. - Live animated forward pass: colored pulses propagate along connections; speed adjustable at runtime.
- Recurrent self-loops:
SimpleRNN/LSTM/GRUlayers render with at-1self-loop arc. - Time-unrolled RNN view: press
Uto expand recurrent layers into N copies labelledt-1,t,t+1,t+2and connected by explicit time-step arrows. Downstream layers shift right automatically; render-only, no data-model change. - Multi-channel feature maps: layers with channels > 1 render with offset "stacked card" shadows scaled by channel count.
- Parameter count and FLOPs: live totals in the status bar, plus per-layer numbers in the property panel. Standard formulas (Conv:
(k²·in_ch+1)·filters; LSTM:4·(in+units+1)·units; MAC counted as 2 FLOPs). Bump afiltersvalue and watch the totals jump. - Full graph editor:
- Right-click any layer to open a property panel with
-/+and</>controls. N(or the + Add Layer toolbar button) opens a layer-type picker.- Drag from a layer's right-edge handle to another layer to create a connection.
- Click a connection to select it;
Deleteremoves it. - Move any layer with left-drag; pan with middle-drag; zoom around the cursor with the mouse wheel.
- Right-click any layer to open a property panel with
- Live shape propagation: editing any param (filters, kernel, units, etc.) re-derives
output_shapeand propagatesinput_shapethrough the rest of the graph via a topological forward pass. - Undo / Redo with
Ctrl+Z/Ctrl+Y(depth 64), plus toolbar buttons that show stack depth. - Copy / Paste any layer with
Ctrl+C/Ctrl+V. Pasted copies land near the camera target (consecutive pastes step diagonally so they don't fully overlap) and inherit the source's parameters; connections are not copied. - JSON persistence:
Ssaves toarchitecture.json,Lloads. Pretty-printed and hand-editable. - Screenshot export:
Pwritesvisualizer_NNN.png. - Four built-in demos: CNN, SimpleRNN, LSTM, GRU.
Tabcycles between them.
Requires the Odin compiler with the vendored raylib bindings (default in current Odin builds).
odin build . -out:nn-odinray
./nn-odinrayTested on Odin dev-2026-05 with raylib 5.5. The code uses only core: and vendor:raylib, so no external dependencies beyond the toolchain.
| Flag | Effect |
|---|---|
--demo cnn|rnn|lstm|gru |
Pick the startup demo (default: cnn). |
--unrolled |
Start with the time-unrolled RNN view enabled. |
--shot <path.png> |
Render ~30 frames then save the screen to a PNG and exit. Useful for headless verification. |
--save-test <path.json> |
Save the chosen demo, immediately re-load it, and print a round-trip summary. |
--shape-test |
Run shape-propagation tests (mutate, delete, insert) and print derived shapes. |
| Input | Action |
|---|---|
| Left-drag on layer | Move layer |
| Left-drag from output handle (right-edge dot) | Create new connection (drop on target layer) |
| Left-click on connection | Select connection (then Delete to remove) |
| Left-drag on empty canvas | Pan |
| Middle-drag | Pan |
| Right-click on layer | Open property panel |
| Wheel | Zoom (anchored at cursor) |
| Key | Action |
|---|---|
N |
Open layer-type picker |
Delete / Backspace |
Remove selected connection (or hovered/selected layer) |
Ctrl+Z / Ctrl+Y |
Undo / Redo |
Ctrl+C / Ctrl+V |
Copy hovered/selected layer / paste near the camera target |
Tab |
Cycle between demo architectures |
U |
Toggle time-unrolled RNN view |
S / L |
Save / Load architecture.json |
R |
Re-run auto-layout and re-fit camera |
+ / - |
Adjust animation speed |
P |
Save screenshot PNG |
H |
Toggle the help panel |
Esc |
Close picker / cancel connection drag / close panel |
The JSON format is intentionally flat and human-editable:
{
"version": 1,
"anim_speed": 0.6,
"layers": [
{
"id": 0,
"type": "input",
"pos": [80.0, 308.0],
"input_shape": [3, 32, 32],
"output_shape": [3, 32, 32],
"params": { "channels": 3, "height": 32, "width": 32 }
},
{
"id": 1,
"type": "conv2d",
"pos": [260.0, 270.0],
"input_shape": [3, 32, 32],
"output_shape": [16, 32, 32],
"params": { "filters": 16, "kernel": 3, "stride": 1, "padding": 1 }
}
],
"connections": [
{ "from": 0, "to": 1, "weight": 1.0 }
]
}Layer type values: input, conv2d, maxpool, avgpool, flatten, dense, rnn, lstm, gru, output.
Param fields not relevant to a given layer type are written as zero/empty but ignored on load.
main.odin window/loop, input handling, CLI flags, undo/redo wiring
model.odin layer/connection types, params, shape propagation, param/FLOPs, mutations, hit testing
demos.odin built-in CNN/RNN/LSTM/GRU architectures
visualizer.odin layout, drawing (cards, connections, self-loops, unrolled view), pulse animation
io.odin JSON persistence + undo/redo history snapshots
ui.odin toolbar, status bar, help panel, property panel, layer picker
~2600 lines of Odin across six files. No external libraries beyond Odin's core: and vendor:raylib.
This project is visualization only. It deliberately does not:
- Train models or run backpropagation.
- Render activation values per-pixel (deferred — would require a sampling backend).
- Render in 3D (raylib's 2D pipeline is enough for this scope).
- Implement a full deep-learning framework.
MIT © phiat




