Make the repo approachable for outside contributors and an IDE integrator:
- New contributor docs:
- docs/EXECUTION_MODEL.md — semi-technical tour of the static / native-overlay
/ interpreter execution model and how coverage gaps get plugged.
- docs/ARCHITECTURE.md — component-level view (recompiler, runtime, LLE+HLE
BIOS, overlay backend static->gcc->tcc, renderers, oracle model).
- docs/BUILDING.md — accurate dependency table, per-platform prereqs, build
steps, and the submodule + local-junction framework-linking setup.
- CONTRIBUTING.md — distilled core rules, verification expectations, PR flow.
- Declutter root: move ~14 internal dev-logs into docs/internal/ (history
preserved via rename) with an index README; repoint CLAUDE.md references.
- README: correct build recipe, LLE+HLE + overlay-backend summary, links to the
new docs.
- code_provider.c: update the stale backend comment to the real static->gcc->tcc
resolution (dead SLJIT.md reference removed).
- config_schema.md: drop the obsolete "once Phase A2/A3 ships --config" note.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
8.3 KiB
OpenGL Renderer — Handoff
Branch: feat/gl-renderer (both psxrecomp and TombaRecomp).
Last commits this session: 0631538..45cc5c3 (psxrecomp).
Status: v2 architecture, opt-in, rendering cleanly on every screen
tested. Software renderer is still the committed default; OpenGL is
selected per-game via [video] renderer = "opengl". TombaRecomp
game.toml has LOCAL (uncommitted) overrides (renderer = "opengl",
texture_filtering = "bilinear", supersampling = 2) for testing — do
NOT commit those; committed defaults are software / nearest.
Goal: a hardware (OpenGL) renderer as a selectable option alongside software, enabling higher internal-resolution scaling without the software renderer's per-pixel CPU cost. Make it default-with-software-fallback only after broad visual validation.
Architecture (v2 — "GPU-authoritative VRAM")
- Facade + vtable.
gpu.ccallsgr_*(gpu_render.h/.c); aGpuRenderBackendvtable dispatches to software (sw_*) or OpenGL (glb_*).gr_set_backend()picks at startup from the config. - GL backend in
runtime/src/gpu_gl_renderer.c(+runtime/include/gpu_gl_renderer.h). The single authoritative VRAM is the hr FBO (s_hr_tex, RGBA8, 1024·S × 512·S, + depth24/stencil8); internal scale S comes from[video] supersampling(GL-side; the SW hi-res mirror is forced off under GL). - Mask bit exact: FBO alpha carries bit15; the stencil buffer mirrors it
(bit0).
check_mask= stencil test EQUAL 0; writing 1 under check uses GL_INVERT. Textured prims/copies/uploads draw in two passes split by the texel STP bit so each pass writes one known stencil value. - Coherency model (all state in gpu_gl_renderer.c):
s_up_pending: CPU→VRAM writes land in the CPU array immediately and accumulate a pending rect;flush_cpu_upload()quad-blits the rect into the hr FBO (and subimages the raw mirror directly) before the next GPU op, present, or readback.s_pack_dirty: hr-FBO content not yet re-encoded into the native R16UI raw mirror (s_raw_tex) — the texture-sampling source and readback surface.pack_flush()runs a scissored PACK pass over the union before any textured draw that samples a dirty page/CLUT.s_gpu_dirty: CPU array stale;ensure_cpu()= flush + pack + full readback. Used by GPUREAD, A0 mask-check reads, vram peek, screenshots, 24-bit present.
- Texture sampling (Beetle-PSX model, structure studied from
beetle-psx-libretro
rsx/):- Per-primitive uv sampling bounds (
u_limits), clamped in the fragment shader when no texture window is active. Filtered neighbours and S>1 interpolation overshoot can never read outside the prim's own texture rect. Rect prims pass exact bounds ([u0, u1-1] forward); triangles compute min/max with the exclusive max edge backed off for axis-aligned (2D) uv mappings; uv ranges crossing a 256 wrap boundary widen to the full page. - Bilinear (
texture_filtering = "bilinear"): nearest texel is the base (cutout + STP authority), neighbours toward the sub-texel offset, per-texel opacity weights with renormalised colour, discard iff opacity < 0.5. The SW renderer's bilinear uses the same formulation (GL/SW parity); SW nearest is untouched. - Sample-grid alignment: all draw shaders shift positions by
u_shift = 0.5/S − 1/64so GL's center-sample grid matches the PS1 integer DDA (fixes striped/squished fonts). Beetle instead uses a+0.001uv epsilon and truncation — coarser; we keep u_shift.
- Per-primitive uv sampling bounds (
- GPU ops: fills (scissored clear, wrap-split 4 ways), copy_rect (hr→scratch blit + masked quad back), all triangle/rect/line prims, batched CPU→VRAM uploads. Blending via glBlendFuncSeparate — alpha (mask bit) is never blended.
- Present: one deterministic path per depth. 15-bit always blits the display rect from the hr FBO (4:3 letterboxed); 24-bit FMV syncs and presents the CPU conversion (letterboxed). The old per-frame FBO-vs-CPU alternation (menu jitter) is structurally gone.
- Init is all-or-nothing: any shader/FBO failure → clean fallback to pure software.
Verified (2026-06-12 session)
- BIOS → intro FMV (24-bit) → title → memcard slot screen → save-block list
all render clean under GL at S=1 and S=2 with bilinear on:
- Title vertical line at VRAM x=640 — GONE.
- Memory-card screens' cross seams (tile edges at 704/864/256/368) — GONE.
- Menu text fully readable under bilinear.
- Root cause of all the black lines: the v1 bilinear base texel was
floor(uv − 0.5)— one texel OUTSIDE the prim on top/left edges. With u0=v0=0 tiles (card wallpaper, title bg page seam) the neighbour wrapped to empty VRAM → texel 0 → whole edge column/row discarded. They were never coherency holes; both the FBO and (post-sync) CPU VRAM agreed. - gl_vram_diff at a quiesced (static-screen) moment: 0 mismatches.
- Audio fade (below) built and boots clean; user feel-test still pending.
Diagnostics (debug TCP 4470, dev builds)
gl_fbo_peek x= y= w= h=— read GPU-side VRAM rect (via pack) without writing CPU VRAM; returns pending/pack rects + gpu_dirty.gl_vram_diff— full-VRAM FBO-vs-CPU mismatch count/bbox/samples. Forces a full pack first so it reads FBO truth even if the raw-mirror invariant broke. Run it BEFORE any screenshot — screenshots ensure_cpu (CPU := FBO) and make the diff trivially clean.gl_coh_ring n= [frame_min=]— always-on coherency event ring: flushes, fills, copy src/dst, draw bboxes, packs, readbacks, presents, probe perturbations, each with rect + frame. The event AFTER a "flush" names the op that triggered it. Keep n ≤ ~2000 per query (TCP send budget).- Caveats: peeks/diffs themselves flush pending uploads mid-frame (they're recorded in the ring as peek/diff events). Animated screens legitimately diverge mid-frame (gpu_dirty=1); only quiesced screens give clean diffs.
Open / deferred
- User visual validation of the whole pass (incl. fullscreen letterbox
- audio fade feel) — everything above was screenshot-verified only.
- Gameplay smoke under GL this build (last session verified gameplay renders; this session's shader changes were validated on title/menu screens which exercise the same textured paths).
- Mirrored-texture mappings: bilinear/limits keep the full inclusive range
for mirrored rects (no exclusive-edge backoff); ±1 texel at
exact-integer uv remains the documented 1/64-bias tradeoff. Beetle
handles mirrors with a +1 uv offset (
off_u/off_vin gpu_polygon_sub.cpp) — adopt if a mirrored sprite ever shows it. - Dirty-sub-rect readbacks (ensure_cpu is a full 1 MB read), dither (SW lacks it too), GL_LINES vs Bresenham edge cases.
- S=4 unvalidated (S=2 verified this session).
Audio fade (shipped with this branch, main.cpp)
turbo_loads mutes sequenced load BGM (tempo is frame-bound; can't
decouple). sdl_audio_update() ramps gain over 40 ms at mute/unmute edges
with an 8-frame unmute hangover to debounce burst gaps. The fade tail is
sized to the ramp and clamped to sdl_audio_buf (the first version
overflowed the 2048-frame buffer and corrupted adjacent statics — wedged
the boot before display enable).
Useful commands / paths
- Build:
PATH=/c/msys64/mingw64/bin:$PATH cmake --build build-stable --target psx-runtime(fromTombaRecomp/; GL source is inpsxrecomp/runtime/). - Run:
./build-stable/psx-runtime.exe --game game.toml(BIOS path baked). - Debug:
python ../psxrecomp/tools/debug_client.py --port 4470 <cmd>;screenshot_file path=...png(CPU-side render after FBO sync — native res, shows FBO truth; the window shows the S× blit). - Input injection:
press buttons=N frames=Mis consumed faster than some screens poll — preferset_input buttons=0xNNNN, sleep ~1–2 s, thenclear_input. Circle (0x2000) = confirm in Tomba menus; Start (0x0008) skips the intro FMV. - A/B vs software:
sed -i 's/renderer = "opengl"/renderer = "software"/' game.toml. - Beetle PSX GL reference (GPL — structure only, never copy code):
psxrecomp/beetle-psx/rsx/rsx_lib_gl.cpp,rsx/shaders_gl/*.glsl.h,mednafen/psx/gpu_polygon_sub.cpp(uv limits, may_be_2d, mirror offsets). - GP0 ring decode:
python TombaRecomp/_shots/decode_frame.py <frame>. - Attract demo auto-plays on cold boot (AP 97400 = canned demo).