Simulate the Model 3 network board in the Libretro core and carry its
frames over RetroArch Netplay's packet interface
(RETRO_ENVIRONMENT_SET_NETPACKET_INTERFACE), so linked cabinets can play
together without the standalone socket board.
Supported families: Daytona USA 2, Harley-Davidson, Scud Race, Sega
Rally 2, Ski Champ, Spikeout and Spikeout Final Edition (Type 1); Le
Mans 24, Virtual On 2 and Dirt Devils (Type 2). Two to sixteen cabinets,
with a handshake, a common roster, role checks and a per-frame exchange
with a timeout. New options: Network Board (connected by default) and
Linked Cabinets (2-16).
Squashed from these commits, whose intermediate steps (Daytona 2 only,
then Scud Race, then all Type 1, then Type 2) have no value on their own:
e7ea7d48b0 Add experimental Daytona 2 cabinet networking
437256ddbb Add experimental Scud Race cabinet networking
c4f012958a Recognize linked-cabinet network hardware
d43eda03ec Enable Sega Rally 2 network transport
55b59bbcb3 Support multi-cabinet linked play
bc3788aea7 Enable netpacket transport for all Type 1 games
f7029c25f6 Add Type 2 linked-cabinet transport
03ee7631c0 Connect the network board by default
ad99627b37 Document all supported linked-cabinet families
74da0c469b Document linked-cabinet role requirements (option text)
Adapted on dev:
- The game-aware NVRAM settings options are not on dev, so each
cabinet's link role (Master/Slave/Satellite) and car or machine number
are set in the game's Service Menu, as on real cabinets; the option
descriptions say so. Option visibility per game is left out.
- The NetBoard config defaults were only defined for Windows; they are
now defined on every platform, and NetworkCabinets uses unsigned
bounds so it compiles.
- Network, NetworkCabinets and SimulateNet are applied after the
Supermodel.ini merge, like the other core options.
- jni/Android.mk gets -DNET_BOARD and the new source file.
- The README part of 74da0c4 waits for the dev README.
Tested: Linux, two RetroArch 1.22.2 instances over localhost Netplay:
Daytona 2 (Type 1) and Le Mans 24 (Type 2) link as Master and Slave and
exchange frames; Daytona 2 races show the other cabinet's car at full
speed. Single player with the board connected and no Netplay: Daytona 2,
Scud Race, Sega Rally 2 and Le Mans 24 boot to attract mode as before,
same speed and log. Windows, macOS, RPi5 and Android arm64 (ndk-build)
builds pass.
Keep the active sound-board state tied to the content configuration,
document the restart requirement, and notify the user when the option
changes. This avoids partially applying a live change across the SCSP
and DSB paths.
Ported from PR #13 commit e225710 (applied manually — libretro.cpp
context conflicted with commit-15 variables not present on this branch).
Keep CPU model, bus multiplier, and PCI bridge tied to Model 3 stepping. Manual frequency choices now affect only the emulated per-frame cycle budget, so 25 and 70 MHz no longer silently initialize 66 MHz internal timing.
Port upstream standalone commit fef2b38 so Step 1.x exposes only MPC105 and Step 2.x only MPC106. Keep bridge selection tied to hardware stepping even when the Libretro CPU-frequency override is active, and remove obsolete per-game PCI overrides.
VBO double-buffering (New3D):
- Allocate 2x RAM region [ROM_VERTS][RAM_slot0][RAM_slot1]; toggle
m_ramSlot each frame so CPU writes slot N while GPU reads slot N-1.
- GL_MAP_UNSYNCHRONIZED_BIT added to UpdateDynamic: safe with double-
buffer, eliminates driver GPU sync stall on GLES3/TBDR.
- vboOffset for RAM models adjusted to include slot offset.
- ramVerts guard: clamp upload to MAX_RAM_VERTS to prevent overflow
at high PPC frequencies (66MHz+ generates more geometry per frame).
Shader (R3DShaderTriangles.h):
- Fragment 'in' varyings annotated with explicit 'highp' to match vertex
shader output precision. Required for GLES3 precision consistency;
also prepares for future mediump fragment optimisation.
- GetR3DFragment() helper added to GLSLVersion for when mediump fragment
path is properly implemented (not yet wired in).
In-game timing overlay (libretroGui):
- Libretro_InitOverlay / Libretro_ShutdownOverlay / Libretro_DrawTimingOverlay
added; renders ImGui window with PPC/Render/GPU/Sync/Sound/Total (ms).
- GPU timer via GL_EXT_disjoint_timer_query if available (guarded against
NULL function pointer); shows 0.0ms when extension absent.
- Overlay initialised in context_reset, destroyed in context_destroy.
Model3.cpp: remove leftover DumpTimings() debug call from RunMainBoardFrame.
Add emit_ram_load32() which bypasses jit_read32() C call for word-aligned
RAM reads (addr%4==0, addr<0x800000) via a direct LDR from ram_ptr.
Guards: two TBNZ alignment checks prevent the mismatch that occurs at
addr%4==2, where the interpreter uses XOR-addressed Read16 pairs that
read different host bytes than a plain LDR.
Stores stay on the slow path to preserve smc_write() cache-flush detection.
ram_ptr field added to PPC_REGS and set via ppc_set_ram_ptr() at init.
LDRH/STRH added to Arm64Emitter for future 16-bit fast-path work.
- 53C810.cpp: revert entirely — all changes were cosmetic (whitespace,
removed already-commented lines, one DebugLog that is a release no-op)
- Real3D.cpp: revert entirely — only change was commenting out a DebugLog
that is already a release no-op via the LibretroWrapper fix
- Model3.cpp: restore two DebugLog("write32") calls removed in debug-probe
cleanup pass — release no-ops, no reason to diverge from dev
- ppc.cpp: remove spurious blank line added inside ppc_init
- LibretroWrapper: add #ifndef DEBUG early-return in DebugLog() — was
unconditionally calling __android_log_vprint + vsnprintf on every call,
including hot paths (every Write8, every SCSI register access)
- 53C810: remove all SCSI_LOG/__android_log_print probe blocks from Run(),
WriteRegister(), ReadRegister()
- Real3D: comment out DebugLog in ReadRegister (polled every ping-pong line)
- Model3: remove MODEL3_LOG blocks from WriteSystemRegister/Read8/Read32;
remove write32 DebugLog calls; route DumpTimings through InfoLog and
fire every 5 frames for active profiling
- Remove unrealistic PPC frequency options (8, 16, 25 MHz); keep range
33-200 MHz with descriptive labels tied to hardware stepping defaults
- Add missing 33 MHz case to the PPC bus multiplier switch in Model3.cpp
- Fix frameskip counter using modulo arithmetic for a consistent render
pattern (skip N, render 1) instead of the previous jittery increment
logic
- Add AdjustAudioForCPUFrequency() to libretroAudio.cpp: scales
samples-per-frame proportionally when the PPC is underclocked, so
audio stays in sync and avoids crackling at reduced frequencies
- Change resolution_multiplier from int to float in CoreOptionsTypes.h
to support the new 0.5x (Half, 248x192) option alongside existing
1x/2x/3x/4x modes; update all dependent code in libretro.cpp
- Add 'Half (248x192)' internal resolution option targeted at low-end
mobile devices (RPi5, Mali-G52) where GPU load reduction matters
- Frame skip (0-3): skips TileGen.DrawLine + SyncGPUs on non-rendered frames,
uses video_cb(NULL) to dupe last frame; MT path fixed via m_skipRender member
so the PPC thread respects the skip hint
- Sound enable/disable: early return in RunSoundBoardFrame() gated on g_options
- SoundVolume and MusicVolume: wired at runtime via s_runtime_config so changes
take effect immediately without restart
- PPC frequency: added 8/16/25/33 MHz options for more aggressive underclocking
GetCPUClockFrequencyInHz() was reading from config["PowerPCFrequency"]
(the INI/config system) to determine cycles per frame, while the core
option only wrote to g_options.ppc_frequency — which was applied to
ppc_config hardware init but never to the cycle count calculation.
Result: changing the core option changed the hardware multiplier but
not the actual Hz used per frame, so performance was unaffected.
Fix: check g_options.ppc_frequency first in GetCPUClockFrequencyInHz(),
short-circuiting the config/stepping lookup when a frequency override
is active.
Related-to:#4
Implement configurable PowerPC CPU frequency to address performance
bottlenecks on low-end hardware (SBCs, Android devices). Users can now
trade cycle accuracy for playable framerates via RetroArch core options.
Features:
- Added supermodel_ppc_frequency core option with Auto/50/66/100/133/166/200 MHz
- Exposed in RetroArch UI under 'CPU' category
- Auto mode uses game stepping defaults (original behavior)
- Manual frequency selection applies to all games
- Proper PPC multiplier calculation for each frequency tier
- PCI bridge model selection based on frequency range
Changes:
- Src/Model3/Model3.cpp: Implement frequency override in LoadGame()
- Src/OSD/libretro/CoreOptionsTypes.h: Add ppc_frequency field
- Src/OSD/libretro/libretro_core_options.h: Define core option and parser
Related-To: #4