Rendering Pipeline
12.1Draw packets: the deferred command buffer
Game logic, running on fibers across the update threads, records what to draw.
The recording surface is ALDrawPacketBuffer::AddWriteWork (@ 0x39488c);
the consumer is ALDrawPacketGLES::ExecPacketMain (@ 0x393c74), a large
switch over 16-bit opcodes 0x00–0x41. Even material parameter
updates are opcodes: ALShaderImpGLES::SetMaterialParam (@ 0x39018c)
never calls glUniform*; it writes work items (0xf alpha, 0x10 UV offset, 0x11/0x12
light colors, 0x13 camera-relative light direction, 0x19 diffuse, 0x1c light arrays…).
| Opcode | GL effect | Opcode | GL effect |
|---|---|---|---|
| 0x00–0x0e | state setup (viewport, clears, binds) | 0x2a–0x2d | VBO bind / vertex attrib setup (real VBOs, not client arrays) |
| 0x16 | compressed texture upload (glCompressedTexImage2D) | 0x31 | glVertexAttribPointer (dropped if location −1) |
| 0x1b/0x1c | uEyePos / light arrays upload | 0x32–0x34 | constant-value attribute fallbacks |
| 0x37/0x38 | bulk matrix uploads (e.g. uBoneMatrix[]) | 0x3f | alpha/stencil test state (depth test, stencil invert, color mask) |
| 0x3b | end-of-frame marker | 0x3c/0x3d | frame-order tags written by the render thread itself |
Submission is two-lane: game logic enqueues; and 3D model draws + 2D sprite draws run on
ALJobPool worker threads via JobFuncScreenDraw, each
batch tagged with 0x3d (LastPacketOrder) so the render thread can
preserve submit order across workers.
The render thread
One thread owns the GL context (the deferred-bind workaround that the port shim reproduces):
DrawThreadFunc @ 0x3942f0:
/* DrawThreadFunc @ 0x3942f0 — the render thread's whole loop */
DrawThreadState = 0;
DrawThreadSema->Post();
while (!ThreadTerminate) {
DrawThreadState = 1;
ALScreenList::DrawAll(GetScreenList()); // replay screens' packets
SetReady();
/* atomic: CurrentDrawOrder++ */
if (order != 0) { // tag frame order (0x3d)
packet.write(0x3d, order); AddWriteWork(8);
}
SwapBuffer(); // flip packet buffers
packet.write(0x3c); AddWriteWork(4); // 0x3c end tag
SetReady();
DrawThreadState = 2;
DrawWaitSema->Wait(); // sleep until next frame's work
DrawThreadSema->Post();
}
12.2The uber-shader system
Shaders ship embedded in the binary as ALLZ-compressed streams (decompressed
at init). The default program is an uber-shader with per-feature variant selection:
72 × 48 variant grid, addressed by 576-entry tables; the shader use-table that pairs
with it is never read by the active path (authored metadata, safe to skip in a
reimplementation). Uniform set: model/view/projection matrices, uFog/uFogColor
(fog opcodes 0x11/0x1c/0x1b; the legacy FA/FB fog computation is dead code),
uEyePos, light arrays, bone palette (uBoneMatrix[], 0x37/0x38 uploads).
The recovered GLSL for skinning: one-bone and two-bone paths selected per vertex attribute.
12.3The 3D world
- Camera —
ALCameraImp(@0x329210), far clip 1000.0 at +0x230, frustum culling viaCheckVisibility. ViewType 0–8 selects cab / outer / shashou (look-ahead) / replay / debug cameras (setOuterView@0x1dd89cwith keyframedVIEW_CTR_CMDmacro data). - Route scenery — map data streams as area packs (Chapter 9); per-frame
MainGameMapCheckswaps double-banked areas. Four display limits bound draw distance; the recursivedATtag format (magic case-fixup "DAT"↔"dAT") organizes sub-tables; rails/subrails carry cant and radius for the curve resistance (Chapter 7). - Models — PS2-GS-style literal packets (
PDB/DB2) for static geometry;AMSskeletal meshes for trains with strip topology, per-block bone-ID remap, and tangent frames. A 3500-slotModelInfoBuffercaps model instances (silent drop beyond). - Textures — GPU-compressed ETC1 (FourCC
"ETC1", verified via opcode 0x16 paths), KTX containers, plus a GPU palette-lookup technique for palettized sources. Day/night variants stream as*TexD/*TexNarchive pairs. - Other trains — three distance gates (400²/100²/450² m) select LOD/timetable behavior for taikou (opposing) trains; 3 fixed rendering slots.
12.4Sprites, HUD and the kanji engine
2D is the same packet system: ALSpriteDrawGLES::Draw → ALVertexList::AddRect →
opcodes. The sprite sort record (120 B), register record (44 B) and command (68 B) layouts are
fully mapped. The HUD (speedometer, gauges, timetable) and all menus are sprite sheets; text
goes through the kanji glyph-cache engine: glyphs rasterized into an atlas
texture on demand, with the menu font drawn from pre-baked sheets for the fixed UI strings.
12.5The dead PS2 layer
A complete PS2-era immediate-mode layer rides along unused: WinAdd* and
ScreenEffect* tables, ALTileMap/ALFontCodeConv
hierarchies, all unreferenced by the live path. Its presence explains the engine's
PS2/PSP-era packet design (the opcode stream is a portable command buffer, originally for a
GPU without programmable shaders).