This is a fork. It forks sydarn/yabause
(a fork of Yabause / Yaba Sanshiro by devmiyax),
pinned at a40dace1
(devmiyax B2_1_11, standalone 1.11.0 lineage). Branch h700 is the only maintained
branch here; upstream development on this line has stopped. The Sega Titan Video (ST-V)
support was developed on a stv branch and merged into h700 in aa20951b (2026-10-02); that
branch was then deleted from the remote and is kept locally only.
It targets low-end ARM handhelds — verified on the Anbernic RGSP
(Allwinner H700, 4× Cortex-A53, Mali-G31 / libmali, glibc 2.35) with the NextUI/minarch-gl
frontend — and is loaded as yabasanshiro_libretro.so. It plays Sega Saturn discs and
Sega Titan Video (ST-V) arcade ROMs (the latter ported from the kronos branch of
libretro/yabause, see Sega Titan Video (ST-V) below).
Make the abandoned port work, and make it fast (a40dace1..h700):
0be6fb9e— make the libretro port in this pin build again: libchdr wiring,vdp1/vdp2compiled as C++, a GLES compatibility header,YabNanosleep, …258a192a— run the async VDP thread through a core-owned shared EGL context:eglCreateContext(share = frontend context), and the engine draws through a mirror FBO whose colour attachment is the frontend's texture. VDP rendering moves to a worker thread whilevideo_cb()stays on the frontend's thread. Without it the port is synchronous and pinned to one CPU core; with it the emulation thread drops from ~1.0 to ~0.7 cores and the frame rate gains 3–9 % depending on the game (see Measured).b2bb246e— hand the frontend's GL context back after borrowing the shared one (SDL tracks "which context is current" per thread in TLS; bypassing it left the frontend without a context).
GL / correctness on this driver (textures are shared between the two contexts, FBOs are not):
074c4034— give the core-owned mirror FBO the depth and stencil the engine's VDP2 compositor needs: it clears depth to 0 and draws every layer withGL_GEQUAL+ per-layer z, and implements VDP2 windows through the stencil buffer. Without them a windowed background layer covers the whole screen (Demon Castle Dracula X: walls and stairs vanish).9e9593f6— re-assert the mirror's attachments on every hand-back, and re-read the frontend's colour attachment every frame (a frontend may recreate its FBO under the same name).caf6c0f2— size that attachment to the render area, not to the frontend's power-of-two texture. A framebuffer is as large as the intersection of its attachments and the engine's per-frame clear is not scissored, so this removes a 4 MB depth+stencil clear per frame: the depth/stencil work is now free (back to the pre-fix frame rate).26364f71— resolve the ES 3.1 entry points (glBindImageTexture/glDispatchCompute/glMemoryBarrier) that the RBG compute path calls; without them Virtua Fighter 2's hi-res title screen crashed the core about 9 s in (PC=0).
libretro feature parity with the reference core (the yabasanshiro branch of
libretro/yabause, tip 09ed8e5b):
8d8cab15,247ee0f4,ea028ee4— savestates, plusSAVE_RAM/SYSTEM_RAMand the Saturn memory map.ea028ee4also fixes silent audio when loading a state on a cold start: the SCSP chunk never carried the sound CPU's registers (the Musashi hooks were empty) and restoring SP before SR left the CPU in the wrong mode. Savestate SCSP chunk version 4 → 5; a core older than this cannot read new states (re-save if you switch back).5e408a8b—resolution_mode = originalmaps toRES_NATIVE(draw straight into the framebuffer) andResize(..., ORIGINAL), like the reference core, whose comment notes the upscale path is broken under a libretro HW context.c42b9781— core options v2 (categories, submenus, visibility, translations),libretro.hup to the v2 option commands,yabasanshiro_libretro.info.50e91159— the disk control interface (v1 + extended) and.m3uplaylists, so multi-disc games swap discs from the frontend menu.610bb618— the reference core's low-risk robustness fixes (error.callocation, the thread CPU-index mutex,BackupManagerflush, HLE BIOS default Slave-CPU proc,debug.clog mutex).6cf146a3(restored by843509a2) — align the two rendering differences with the reference core: the VDP1 shadow-discard threshold and the RGB shader bit ops. An earlier revert (cfcba53f) had blamed the threshold for SotN's missing walls; that premise was disproven — the walls were lost to the missing depth/stencil on the async mirror FBO (074c4034).
SCSP threading (68821ac4, 8fd15b47, b000abc0) — the async sound CPU thread used
ctime() as a struct timespec *, so its timed wait returned EINVAL immediately and the thread
span at ~1.09 cores; fixing the clock source restored the on-demand synchronisation path
(Golden Axe: 2.57 → 1.87 cores, +2 % frames). The pending main-CPU interrupt word is atomic now.
An optional realtime priority switch exists (YAB_SCSP_RT_PRIO/YAB_SCSP_RT_NICE, off by default:
it measured neutral on this device).
Two build switches, both off by default, both required for the async build:
| Define | Meaning |
|---|---|
YAB_ASYNC_RENDERING |
upstream's VDP render thread (its #define in vdp1.h is commented out); on by default in this branch |
YAB_CORE_SHARED_CONTEXT |
the core-owned shared context added here (no-op when undefined); on by default in this branch |
Both are added by yabause/src/libretro/Makefile.common now, so a plain make platform=arm64 gives
the shipping asynchronous core. Build the old synchronous one with make platform=arm64 YAB_SYNC=1
(kept for A/B comparisons -- it is how the ~+20 % below was measured).
Ported from the kronos branch of libretro/yabause
(tip 3791ffb, "Merge pull request #328 from WizzardSK/aarch64-kronos"; that branch is in turn the
libretro port of FCare/Kronos). Both are GPL-2.0, like this
tree. What came over:
stv.c/stv.h— the ST-V BIOS table (BiosList: file name + CRC32 + region) and the game table (GameList, 103 titles), zip ROM/BIOS loading, the 48 MiB ROM-board image, the NV-RAM template.eeprom.c/eeprom.hand the SMPC bit-bang that drives it (PDR10x3fwrite, DO bit read back through PDR2) — without it the ST-V BIOS spins forever waiting for the EEPROM.decrypt.c/decrypt.h, the cs1 decryption channel incs0.c, andjunzip.c/junzip.hfor reading game ROMs andstvbios.zipstraight out of the zip.cs0.c'sCART_ROMSTVcartridge type (48 MiB) and thememory.cDMA read path the decryption needs.- The IOGA / JAMMA cabinet input:
peripheral.ckeepsIOPORT/PerCabAddand the games read it at0x04000000(0x01P1,0x03P2,0x05system = coin/test/service/START);libretro.cbinds the frontend's joypad to thePERJAMMA_*keys. - The
memory.c/smpc.c/scu.c/yabause.calignments the port needs (yabsys.isSTVgating of the Saturn-only init paths, the OREG[31] handshake, the PDR2 sound-CPU wire) and the libretro hooks:stv_modedetected from the zip entry names + CRC32,stv_favorite_region, the ST-V key descriptors.
Two deliberate differences from Kronos, both measured:
- The backup-RAM card layout stays the classic yabause one (
header[32]=FF 'B' FF 'a' …, card byte N at offset2N+1). Kronos' dense layout made every<rom>.savwritten by an earlier build unreadable, and a real BIOS then answers "The System Memory is not ready for use. Please clear all files using Sega Saturn's Memory Manager." (Guardian Heroes, Castlevania SotN).6407c37dreverts just that group; theextend_backupvalue and the real word/long handlers from the same commit are kept. SmpcSetTiming()gives the INTBACK case a timing for everyIREG[0](56753e92). The old code handled onlyIREG[0] == 0x01/0x00; cotton2 asks with0xFF/0x80, soSmpcExec()never ran the command,SRstayed 0 and the game never read the pad. That is why the picture-good line never saw coin or START (the only build that did was the full Kronos SMPC port, which came with a black picture and a deadlock at the resolution switch). Coin, START and the in-game 1P start were verified on this tree afterwards.
To run an ST-V game, put stvbios.zip in the frontend's system directory (next to
saturn_bios.bin) and the game zips in the ROM directory. The board is recognised from the zip
entry names + CRC32, so a re-zipped or renamed stvbios.zip silently falls back to Saturn mode.
yabasanshiro_libretro.info declares that file the way Kronos' .info does (firmware1, optional,
because Saturn titles do not need it), so RetroArch's Information → Firmware list names it too.
The core advertises block_extract = true, so the frontend has to hand the romset zip over
unextracted — the core opens it itself (RetroArch would otherwise extract a member to
/tmp/cache/… and the board detection would never run). A side effect is that Saturn CD images
must be uncompressed (.cue/.bin/.iso/.chd); a zipped disc is not something this core can
mount.
The ST-V SRAM is per game: <save>/stv/<game>.ram, next to the <game>.nv EEPROM, matching
Kronos' naming (the layout is still our classic 64 KB card image, not Kronos' dense 32 KB one). A
Saturn keeps one shared internal memory card, as on hardware. Making that file actually appear
re-exposed a latent bug: bupfilename was assigned only on the extend_backup path, so with the
extend_backup = 0 the ST-V credit fix needs, YabauseDeInit() called T123Save(..., NULL) and
the core never persisted its card at all — only the frontend's per-ROM .sav carried it.
The probes and recorders that the porting effort needed (SMPC/IOGA loggers, black-box recorders,
per-frame stat dumps, the autopress.txt hook) were removed in 09b9f8b8: the shipping core runs
no test instrumentation.
cd yabause/src/libretro
# asynchronous build (the default now: Makefile.common adds both defines)
make platform=arm64
# synchronous build (A/B reference)
make platform=arm64 YAB_SYNC=1(Before this branch made them the default you had to pass them yourself, and only in the environment
-- a CFLAGS= on the command line overrides the makefile's own CFLAGS += $(FLAGS) and would drop
the platform flags (-DAARCH64, -D_OGLES3_, -I…, -D__LIBRETRO__, -std=gnu99, …).)
platform=arm64 selects -DAARCH64 -march=armv8-a+crc+fp+simd -mcpu=cortex-a53, FORCE_GLES=1,
-D_OGLES3_ -DHAVE_OPENGLES -DHAVE_OPENGLES3 and HAVE_GLSYM_PRIVATE.
Output: yabause/src/libretro/yabasanshiro_libretro.so.
Cross-compiling for the device, two requirements are easy to miss:
- Link
libmaliexplicitly (-l:libmali.so.0inLDFLAGS):libGLESv2.so.2on the device is a stub that exports no GL entry points; the real symbols live inlibmali.so.0. - Add
-ldlwhen the sysroot is glibc < 2.34 (for example the gcc 8.3 / glibc 2.28 toolchain used to build the NextUI cores): the shared-context path usesdlopen/dlsymandlibdlis still a separate library there. Newer sysroots merge it into libc, so the same source can build locally without it.
The built file must keep an _ in its name when you drop it into a frontend: minarch derives the
per-core configuration directory from the part before the first _ (and segfaults without one).
- The core renders into a core-owned mirror FBO over the colour texture the frontend hands it (FBOs are not shared between the two EGL contexts on this driver, textures are). It attaches its own depth+stencil, sized to the render area.
- It asks for
depthandstencilinretro_hw_render_callback; a frontend that builds the per-slot FBOs itself should attachGL_DEPTH24_STENCIL8(RetroArch'sgl3and NextUI's minarch-gl do). - If the frontend rotates hw-render FBOs between slots (RetroArch's
gfx/video_thread_hw.cring, minarch-gl's three-slot ring), the core follows it: it re-readshw_render.get_current_framebuffer()on everyretro_runand re-points its mirror at the new texture. A frontend must not rotate slots for a core that caches the framebuffer once atGLSM_CTL_STATE_SETUP— glsm-based cores (flycast, mupen64plus_next) do exactly that and would render into a slot nobody presents. video_cb(RETRO_HW_FRAME_BUFFER_VALID)is issued on the frontend's thread; the render worker onlyglFinish()es and marks the frame, so the frontend keeps its own context for its shader chain and swap.
Anbernic RGSP (Allwinner H700, Mali-G31), minarch-gl hardware render, CPU governor pinned to
performance (1512 MHz on all four cores), 4000-frame windows, frameskip disabled, debug HUD
off. Values are fps at frame 3600 / average over the window; the control is the same tree built
synchronously (no YAB_ASYNC_RENDERING/YAB_CORE_SHARED_CONTEXT):
| Build | Golden Axe | Daytona USA |
|---|---|---|
| synchronous | 50.3 / 54.3 | 33.6 / 39.1 |
| async + shared context | 60.2 / 60.4 | 40.4 / 43.6 |
⇒ +19.7 % (Golden Axe) and +20.2 % (Daytona USA) at frame 3600, or +11.2 % / +11.5 % on the
window average. Whole-process CPU (all threads) measures 1.41 → 1.71 cores on Golden Axe and
~1.9 on Daytona, and the async build runs one thread more (9 vs 8): the VDP worker now does the
rendering the emulation thread used to do inline, so the emulation thread is no longer pinned to a
single core. (Re-measured 2026-10-02 on the merged h700; these numbers supersede the earlier
58.4 / 40.3, which came from a different sampling point.) Both windows are no-input runs, i.e. the
titles' attract/title screens — treat them as a rendering/threading benchmark, not a gameplay one.
Known costs: the worker performs one glFinish() per frame (a whole-pipeline drain on the render
thread, so the frame is complete in the shared texture before the frontend samples it from its own
context — this is not the frontend's ring fence, which is a glFenceSync/glClientWaitSync pair
guarding a slot against being overwritten while the GPU still reads it, and which measured as never
blocking), and the presentation path adds a normalise pass in the frontend.
- Upstream: sydarn/yabause → devmiyax's Yaba Sanshiro → Yabause. The upstream website (uoyabause.org) is offline.
- ST-V support is ported from the
kronosbranch oflibretro/yabause(tip3791ffb), which is the libretro port of FCare/Kronos. Thestv.c/eeprom.c/decrypt.c/junzip.cfiles, the cs0/cs1 cartridge and decryption channels, the IOGA/JAMMA input model and the SMPC/peripheral alignments come from there (GPL-2.0, same as this tree). - The upstream tree's own documentation is kept as-is:
yabause/README,README.LIN,README.QT,README.WIN,README.DC,README.MACandyabause/doc/. - Upstream community, not maintained by this fork: Discord · Liberapay. The badges that used to be at the top of this file were removed: they reported the upstream author's Travis / AppVeyor / Snapcraft accounts, and none of those report anything for this branch.
- License: GPL-2.0 (
LICENSE, unchanged). Everything added on this branch is GPL-2.0 as well.