193 Commits
Author SHA1 Message Date
LibretroAdmin c652c082c2 pvr/spg: a frame ends where the game renders, or reads its pads, after its vertical blank in interrupt
A retro_run - where the frontend reads its pad and shows the picture -
ended at line 0. Most games start the render of the frame they have
made, and read their pads, in their vertical blank in interrupt, which
need not be there:

- Sega Rally 2 (Windows CE) has its interrupt at line 0 and starts its
  render a line after it: that render was the first thing in the next
  run, and its picture was shown at that run's end, a frame after the
  game started it.
- Virtua Fighter 3tb and Jet Grind Radio have theirs at line 260: they
  render, then read the pads. Read in the middle of a run, the pads were
  read a run before the render that answered them could end one.

Now a run may end in the lines after the vertical blank in interrupt
(32, and never on or past the vertical blank out line, where the
hardware's maple transfer reads the pads): at a render to the screen the
game starts there - its picture is shown at once - or, before one, at a
maple transfer the game starts there, which is then made at the start of
the next run, with that run's pad. No time goes by between two runs, so
the transfer is made where the machine stands, as the game started it;
only the processor's last few hundred cycles of its time slice run before
it. A run that ended in a window ends in the next one, at either, or at
the window's end in a frame with neither (a game at 30 frames a second, a
loading screen), unless it rendered to the screen elsewhere in that frame:
then it goes on to line 0, where the runs of a game that does nothing in
the window (renders mid-frame, has the hardware read its pads) end as
before - but not past the next window: where line 0 came while the window
was open (Sega Rally 2's interrupt is at line 0), it ends at the window's
end. Each run has one vertical blank in interrupt in it. A window closed
early has the line event aimed again: one aimed at its end and taken for
a later line lost lines - a frame 13 lines (Sega Rally 2) or 233 lines
(Jet Grind Radio) short.

Measured with tools/lag: machine frames from the press to the first
picture that differs, RetroArch's default ("late") polling, the same
with threaded rendering on; this core before / after, and upstream
flycast with "Delay Frame Swapping" on (its default) / off, from states
each core made by booting the game the same way:

  Sega Rally 2, title, START          4 / 3      upstream 4 / 3
  Virtua Fighter 3tb, attract, START  3 / 2      upstream 3 / 2
  Jet Grind Radio, title, START       3-4 / 2-3  upstream 5 / 3
  Dead or Alive 2 Millennium, START   1 / 1      upstream 4 / 3
  Jet Grind Radio, street (s10), jump 3-4 / 2-3
  Jet Grind Radio, graffiti (s9), jump 3-4 / 2-3
  Virtua Fighter 4, START; DOA2, COIN 1-2 / 1-2  (they render before their
                                                 interrupt and read their
                                                 pads after line 0)

(Jet Grind Radio runs at 30 frames a second: the lag depends on which of
its two frames the press comes in, and is one frame less in the one of
them that was the longer.)

Where a run ends is decided by the machine alone, the same however the
frame is shown or not (a frame run ahead, fast-forward's frameskip): a
run starting in a window is one that ended in a window, a transfer
waiting for the next run is one started with nothing scheduled to end it
(it is made at the start of the next run, whether or not a state was
loaded in between), no run ends with the window open (dc_run()'s
backstop closes it too), and whether the game has started a render since
the last vertical blank - which the vertical blank goes by to show a
framebuffer the game wrote itself, and which was never so at line 0 - is
now in the state: bit 1 of pvr_serialize_v22()'s first byte, 0 in every
state before, which a state saved at line 0 has too. The state's size is
the same, and old states load as before. Without that bit, a frame run
ahead on the live test disc, which writes its framebuffer itself, was not
the frame run straight through (tools/threads/headless.sh).

tools/determinism (plain, again, reload, run-ahead, rollback, and frames
not shown, every one or RetroArch's run-ahead way) passes with threaded
rendering on and off on Jet Grind Radio, Sega Rally 2, Virtua Fighter
3tb, Virtua Fighter 4, Dead or Alive 2 and the live test disc;
tools/threads/headless.sh passes; tools/lag/samestep.sh has every
renderer in step on Jet Grind Radio, Sega Rally 2, Virtua Fighter 4 and
Dead or Alive 2. The sound each retro_run makes stays a frame's on
average, with no drift: over 1800 frames 735/736 samples a run in the
Dreamcast games and 737/738 in the NAOMI ones, as before; Sega Rally 2
alternates 713-718 and 754-759 where it renders every other frame (the
run ends at its render one frame and at the window's end the next), and
the first run after loading a state saved at line 0 is shorter, once.

A frame is a duplicate (video_cb with no picture) when no picture was
made in its run: a render to the screen or of the framebuffer the game
wrote itself. That was asked of the vblank, which no longer always ends
the run; a render to a texture alone no longer counts as a new picture.
2026-10-24 06:01:45 +02:00
LibretroAdmin a4d4ef8c2c macOS on Apple silicon: the ARM64 recompilers (SH4, ARM7, DSP)
The arm64 macOS build (platform=osx arch=arm) used the interpreters.
It now has the same recompilers as arm64 Linux, made to follow Apple's
rules for code written at run time:

- The code goes in memory mapped with MAP_JIT (vmem_platform_alloc_jit()),
  once for the SH4, once for the ARM7 and once for the DSP, at start-up,
  and kept for every game after that. MAP_JIT cannot be given an address
  (no MAP_FIXED), so the static arrays in the library's text are not
  used there (and are not built in: no 17 MB of zeroes in the dylib).
  The memory is asked for right after the core's own __TEXT, then below
  it, so that the core's functions are within one branch's reach
  (128 MB); where the system puts it elsewhere, it is taken anyway.
- Such memory is writable or executable per thread
  (pthread_jit_write_protect_np()). Everything that writes code -
  compiling a block, relinking one, the main loop, the fast-memory
  rewrite in the fault handler, the ARM7 and DSP compilers, clearing the
  cache - is wrapped in vmem_jit_write_begin()/end(), a per-thread count,
  so nested writers are fine and other threads go on running code. No
  lock. On every other platform the two are empty.
- Calls and branches from the code to the core: when the target is out
  of reach, through a register (x16/x17, vixl's scratch registers). The
  three block-linking routines find their caller by the address after
  the call, which has to be inside the block as linked: for those a
  block calls a trampoline beside the main loop instead (one bl, as
  today). Where the code is in reach - always, on every other platform -
  exactly the same instructions are emitted as before.
- ngen_arm64.S is ELF assembly: for macOS its routines are in
  rec_arm64.cpp in Apple's syntax (underscored names, .private_extern,
  .alt_entry where one routine runs into the next), and the ARM7's
  dispatcher in arm64.cpp is written for both assemblers.
- No JIT memory (a hardened frontend without the
  com.apple.security.cs.allow-jit entitlement): the SH4 and ARM7
  interpreters are used, and the DSP is left out (no effects), instead
  of a crash.
- PAGE_SIZE is 16 KB on Apple's ARM systems: with 4 KB, mprotect() of the
  page protections (textures, the SH4's code pages) was refused there, a
  die() - that was already so for the interpreter build, and for iOS and
  tvOS, which keep the interpreters.

Linux and Android arm64 are unchanged in what they emit; iOS and tvOS
stay on the interpreters (HOST_APPLE_JIT is for a macOS target only),
and macOS x86-64 keeps its x64 recompiler as before. No save-state
change.

Verified here (Linux, no Mac):
- full cross-build and link of parallel_dc_libretro.dylib for
  arm64-apple-macos (clang 18, MacOSX 13.3 SDK, ld64.lld): links with
  every symbol resolved against libSystem, libc++ and OpenGL; the
  assembly routines come out in order and the fall-through symbols are
  marked alt_entry
- full cross-build and link for x86_64-apple-macos (x64 recompiler)
- the changed files compile for arm64 iOS and tvOS targets, with
  HOST_APPLE_JIT off there
- Linux aarch64 (platform=arm64) with the same recompilers:
  tools/threads/headless.sh passes under qemu-aarch64
- the same, with every call and branch to the core forced through a
  register and the link trampolines on (the code macOS runs when its JIT
  memory lands out of reach): headless.sh passes too

Not verified: anything on a real Mac - MAP_JIT, the per-thread write
protection, the placement near the core's code, the fault handler and
the fallback without the entitlement. Unverified until GitLab CI builds
it and someone runs it on Apple silicon.
2026-10-23 10:32:12 +02:00
LibretroAdmin 53b4d950b3 gdrom, reios, rom_luts, cheats: lead-out, WinCE boot, BIOS choice, tables
- A GD-ROM's single-density lead-out is track 2's end, not 13085 (falls
  back to it when the image gives none); a GDI track ends where its file
  ends; a GD CHD track's pad frames come off its end (Shenmue disc 3,
  Re-Volt's CD player). Every GDI and GD CHD game gets these TOC values.
- Windows CE under the HLE BIOS: the boot file's first 2 KB goes to
  8ce01000 (it was skipped), the rest to 8c010000 as before.
- With no disc, a hard reset leaves the drive busy for 1 s, then "no
  disc" with unit attention, as upstream (it said "no disc" at once).
- The real BIOS for the games that need it when its file is there:
  MIL-CDs, Jimmy White's 2, Railroad Tycoon II (EU), Miss Moonlight,
  KenJu, Force Five, Fushigi no Dungeon, Slave Zero (PAL), WSB 2K2;
  without it they boot as today. The HLE BIOS even with the real one for
  Stupid Invaders, Suika and Cool Boarders Burrrn.
- Depth scales: NHL 2K2 1e8; Re-Volt JP, Sturmwind, V-Rally, South Park
  Rally, Capcom vs SNK Pro (DC and NAOMI) added; Samurai Shodown VI's
  replaced by upstream's game patch. The no-RGB list applies only with
  the RGB cable chosen. TrickStyle (US) in English, the flash's language
  put back at the end.
- Widescreen cheats as upstream's table: original values checked where
  upstream does, matching by software name, 11 more Dreamcast games, the
  NAOMI table upstream's 35; a bad address is warned about and skipped
  instead of trapping. Game fixes applied every frame for Extreme Hunting
  2, King of Route 66, Dave Mirra, Samurai Shodown VI, Fur Fighters,
  Ooga Booga and the Dricas authentication of Aero Dancing i/i2/F,
  Daytona JP, Sega Tetris, Golf Shiyou Yo 2, Culdcept II, Outtrigger JP
  and F355 JP.
2026-10-14 02:21:51 +02:00
LibretroAdmin 5f0bc7f4b5 naomi: cartridge reset on a hard reset, LUT names, network packet number
- A hard reset now resets the cartridge's registers - PIO and DMA
  offsets, M1 decompression and decryption state, M4 and Atomiswave
  device state, the GD-ROM cartridge's DIMM address - without reloading
  ROMs, and any reset clears the I/O board program buffer: an upload cut
  off by a reset is not taken for part of the next one.
- The NAOMI per-game table is matched on the whole name: a name that
  was part of another entry's (an empty one: Samurai Spirits 6's) got
  that entry's settings.
- NaomiNetwork::receive waits for both bytes of the packet number, and
  does not report a half-received packet as received when stopping.
2026-10-13 12:26:55 +02:00
LibretroAdmin 2169c1bf26 libretro: m3u, failed loads, bounds, stick corner, per-game settings
- read_m3u counts discs itself (upstream f03faffd2) and an empty line no
  longer reads line[-1].
- retro_load_game clears the disc list, tray and game data first: a
  failed load has no unload. The tray is shut at unload, and the next
  load starts its own emulation thread (first_run).
- Bounds: the port is checked before device_type[] is read; keycodes
  past the keyboard table are ignored; VMU colours outside the list and
  render-to-texture scales outside 1..8 fall back.
- A stick pushed fully into the bottom-left corner overflowed an int
  and read as centred.
- Screen rotation is read at load: once on it never turned off again.
- A reset or an option change kept per-game cable and sorting settings;
  a second arcade game no longer inherits the first's I/O board setup.
- nullDC: no VGA for Caesar's Palace, no RGB for Roadsters, Shin Nihon
  Pro Wrestling 4 and The Grinch, forced NTSC/PAL for 13 games (the
  flash bytes are put back before saving), translucent depth mask for
  Monkey Ball (upstream's per-game list).
- The refresh-rate debounce is reset when timing is reported again.
2026-10-13 11:08:21 +02:00
LibretroAdmin e3ce1f292e gdrom: unknown commands end; replies no longer than there is of them
- A packet command the drive does not know set the error and left it
    at that: the drive stayed busy, no interrupt came, and nothing more
    was taken. It ends now (upstream).
  - REQ_MODE, REQ_STAT, REQ_ERROR, REQ_SES and GET_TOC send as many
    bytes as the program asks for, from as far into the reply as it
    says. Both numbers went straight into a copy out of ten bytes on
    the stack or thirty-two of a variable, and whatever came after in
    memory was sent. The program still gets the count it asked for:
    what there is of the reply, then zeroes. (Upstream bounds GET_TOC
    and stops with verify() on REQ_STAT.)
  - SET_MODE with an offset past the ten bytes that can be written had
    its count worked out as ten less the offset, unsigned: up to 255
    bytes written over whatever follows the drive's settings. Nothing
    is written from there now.
  - The built-in BIOS's drive state was never reset with the machine: a
    read by DMA that was under way went on into whatever was started
    next (upstream's gdrom_hle_reset()).
2026-10-10 14:38:46 +02:00
LibretroAdmin f23af78e11 rom_luts: no column for switching vmem32 off
Seventeen Windows CE games had it switched off for them in the table of
games; that was all their rows said, and the rows are gone with the
column. settings.dynarec.disable_vmem32 goes too.

They run as they did: through the table of pages, like every other
Windows CE game now.
2026-10-09 18:22:34 +02:00
LibretroAdmin bbc9d0ae3c vmem32 is gone
It has not been used since "the table of pages for every host", where
vmem32_init() was made to say no; this takes out what was left for it.

  - core/hw/mem/vmem32.cpp and .h, and the call to it from the fault
    handler - with the working out, for every fault, of whether it was a
    write and which SH4 instruction it was for, which only vmem32 asked.
  - Every "if (vmem32_enabled())": in the MMU (a translation forgotten,
    the TLB emptied, another address space), the block manager, the
    table of pages, the two 64-bit recompilers.
  - _vmem_enable_mmu(): it handed P0 and P3 of the host's layout of the
    SH4's memory over to vmem32 when the MMU came on and mapped them
    again when it went off. Nobody takes them now, so they are mapped
    once, with the rest (the two tables are one again).
  - x86-64 recompiler: a quick access to memory as the host has it
    mapped is for the MMU off only, so the store of the instruction's
    address ahead of it and the second "how far in is the move" are
    gone. Sh4Context keeps its exception_pc, unused, so that what
    follows it in the context is where it was.
  - bm_IsRamPageProtected(), which vmem32 asked before it mapped a page.

What stays is that with the MMU on a page of code is protected in P1
and P2 only, not in P0 and P3 as well: nothing reads or writes those as
the host has them then. The comments that said they were vmem32's say
that instead.

Why the host's mapping was given up - the numbers - was in a comment in
vmem32.cpp; it is in mmu.h now, with the table that replaced it.

No change in what runs: the whole of headless.sh passes on x86-64, on
ARM64 and 32-bit ARM under qemu and on the Windows core under wine, and
Sega Rally 2's sound over 3600 frames on x86-64 is what it was, byte
for byte.
2026-10-09 18:22:32 +02:00
LibretroAdmin a2343b3fbf naomi: a flat image starts without a BIOS
With no naomi.zip (naomi2.zip) a NAOMI game stopped at "cannot load
BIOS". A flat image now starts all the same: reios does for it what the
BIOS does. The BIOS is used as before when it is there, and a romset
still needs one.

What that turned out to be - found by letting the real BIOSes
(epr-21576h, and the NAOMI 2's epr-23605c) run up to a game's first
instruction and looking at what they leave, by reading the BIOS's code
for the parts games call, and by tracing Dead or Alive 2 and Virtua
Fighter 4 with the BIOS and without until the two runs went the same
way:

The start. The image's header says what to load where (up to eight
pieces at 0x360) and where the game begins (0x420). The BIOS hands over
with the display in its 640x480 mode, a copy of the header at 0c01f400,
the clock's time at 0c01f130, and registers pointing at both.

The system routines. The BIOS leaves a table of 24 routines at 0c018000
that games call for what every cabinet does alike: the operator's
settings and the check of them, the 28 coin settings, coins into
credits (bonus coins, service credits, the most credits, the meters,
the queue of sounds), what each player can do with the credits there
are, the board's region and the game's serial. They are written here
from what the BIOS's do; a routine's place in the table holds the trap
reios answers. The ones for the GD-ROM's DIMM board and the network
board answer 0 and say so once: no cartridge game seen calls them.
There being no test menu, the settings are the BIOS's defaults: one
coin, one credit.

The interrupt service. A game never takes the processor's vectors: the
vector base stays at the BIOS's 0c000000, and the game writes its
handlers into the BIOS's table at 0c0001c0. The vectors are traps here:
one puts the interrupted program's registers by, in the machine's own
memory so that a saved state has them, and sends the processor to the
handler; the handler returns to the other, which puts them back.

Two things a game looks at in the boot ROM itself:

- Before it will use the routines, a game compares 112 bytes at 1ffd00
  with a copy it carries - the BIOS's "COPYRIGHT (C)SEGA ENTERPRISES"
  lines and "NAOMI BOOT ROM" - and otherwise goes a way that never
  ends. Those bytes are put there. (Both games carry the same copy.)
- Dead or Alive 2's start-up code waits for a word that it reads
  through a pointer it has not yet set, which makes it the boot ROM's
  word at fc0. It is given the value the BIOS has there.

naomi_cart: bios_loaded is set when a flat image's BIOS was found; it
was only set for romsets, and nothing had asked until now.

Tested with no BIOS in the system directory (RetroArch nightly, glcore,
and the headless harness): Dead or Alive 2, Dead or Alive 2 Millennium
(from its RAR archive) and Virtua Fighter 4 (NAOMI 2) reach their
attract modes; a coin in Virtua Fighter 4 gives CREDIT(S) 1 and PRESS
START BUTTON, as it does with the BIOS; a reset starts the game again;
a state saved at frame 1400 and loaded at 1800 leaves the machine, 800
frames on, where 2200 frames straight leave it but for 270 bytes of
92 MB - and that state was taken inside an interrupt handler. With the
BIOS all three run as before, and the Dreamcast test disc passes.

Not done: romsets (the cartridge would have to be read through its
decryption), the test menu, and so any setting but the defaults. Of the
coin routines only the paths the defaults take have been run: bonus
coins, a chute to each player and service credits are as the BIOS's
code has them, and not exercised.
2026-10-08 06:45:09 +02:00
LibretroAdmin ad3ee3c7b1 naomi 2: the machine
A flat NAOMI image whose header says "Naomi2" is run as a NAOMI 2, with
the BIOS from naomi2.zip (or .7z). Until now every such image was taken
for a NAOMI 1, and the BIOS answered "this game is not acceptable by
main board" - which is what upstream flycast does with one as well.

What a NAOMI 2 has that a NAOMI does not:

  - The geometry processor of the last patch, in area 2: its registers
    at 08800000, its command port at 09000000, its 32 MB at 0a000000,
    and at 085f6800 and 085f8000 the holly's and the PowerVR's registers
    written to both PowerVRs at once (core/hw/pvr/elan_host.cpp). What
    it sends goes into the tile accelerator the way a store queue write
    does.

  - A second PowerVR. The two are one here, as in upstream: one set of
    registers, one video memory. What the second has of its own is its
    normal interrupt status, at 025f6900: the end of a list and the end
    of a render are raised in both, and the game clears each.

  - The second PowerVR's video memory is the mirror 32 MB above the
    first's, and games write every texture there too. The texture
    cache did not watch the mirror ("the mirrors don't seem to be
    used"), so a texture changed through it kept its old picture. On a
    NAOMI 2 it is watched.

  - Frames several times the size of anything a NAOMI sends: the tile
    accelerator's buffer and the lists a frame is parsed into are four
    times as big on a NAOMI 2, and a buffer kept from another machine
    is not reused.

Save states: version V20 adds, on a NAOMI 2 only, the second interrupt
status, the chip's state and memory, and the time its texture transfer
ends. Older states load as before.

"Is this a NAOMI" is asked in a dozen places: SYSTEM_IS_NAOMI() answers
for both.

Tested with a flat image of Virtua Fighter 4 and naomi2.7z, in the
RetroArch nightly of 7 October under Xvfb (Mesa llvmpipe):

  - The BIOS starts the game; the attract mode's temple scene and the
    demonstration fight are drawn - letterbox, coin text, the stage, the
    fighters and their shadows - by the OpenGL and the Vulkan renderer,
    per triangle and per pixel, and look alike in all four.
  - Against upstream flycast (which lights in vertex shaders), made to
    run the same image as a NAOMI 2 by a change of three lines: the same
    scenes, the same picture to the eye. The two do not reach a scene on
    the same frame, so they were not compared pixel by pixel.
  - What the chip sends the tile accelerator over 1500 frames is the
    same, byte for byte, from run to run.
  - Threaded rendering on and off: 138 pixels of a 1440x1080 frame
    differ, where two runs with the same setting differ in 54. Those
    are Mesa's: a texture with mirrored repeat comes out differently
    from run to run on llvmpipe, and identically with plain repeat.
  - A state saved, the game run 50 frames on, the state loaded and the
    game run on: 100 frames later its state differs from a straight
    run's in no more bytes than two straight runs differ from each
    other (about 900 of 92 MB).
  - tools/threads/headless.sh, the Dreamcast test disc: passes.

Not done here: the chip's memory is not in the recompiler's direct
mapping, so its accesses go by the handlers; NAOMI 2 games in MAME's
zip form are not in the game table; starting without a BIOS.
2026-10-08 03:50:06 +02:00
LibretroAdmin 783264f91c libretro: "Enable RTT (Render To Texture) Buffer" is gone, and the table of games that forced it
The option chose between two ways of treating what a game renders to
a texture: copy it to video memory after every such render, waiting
for the graphics card each time and losing Render To Texture
Upscaling, or leave it on the card and have the games that look for
it in video memory come out wrong. A table switched the first on for
the games known to need it.

Since the last three patches there is one way, and it is right for
both: the picture stays on the card and video memory gets it when it
is first touched there. Nothing is left for the option to choose.

- The core option, in every language, and settings.rend
  .RenderToTextureBuffer.
- The copy-after-every-render paths of all four renderers, with what
  only they used: the Vulkan drawers' second render target and its
  render pass variant, the check for the setting having changed, and
  WriteTextureToVRam() (rend/rtt_watch.cpp packs pixels now).
- The table's column, in the console's table and the arcade one, and
  the 51 rows that had nothing else in them.

Render To Texture Upscaling stays, and now applies to every game.
A frontend that still has the old option saved ignores it.

The live test passes on gl, glcore and vulkan, the headless test
passes.
2026-10-07 19:36:06 +02:00
LibretroAdmin 82f65906ba rend: 37 more games get their renders to a texture copied to video memory, and keep it
On the console what a game renders to a texture is in video memory
afterwards. Here it stays on the graphics card unless "Enable RTT
(Render To Texture) Buffer" is on, which it is not by default; games
that then go on to read that memory, write into it, or use it as a
texture of another shape come out wrong, and a table switches the
copy on for the ones known to.

- The table had 14 games. Upstream's list has 48 product numbers:
  the 37 missing here are added - Jet Grind Radio, Shenmue, Grandia
  II, Sakura Taisen 1 and 3, Silent Scope, Worms World Party and
  others. Two of the 14 were written without the region suffix the
  disc has (the European Tony Hawk's Skateboarding and Pro Skater 2)
  and matched nothing; the added rows have them as the disc does.

- What the table switched on was switched off again the next time
  any core option was changed, because the option's value alone was
  written to the setting each time. The table's word now holds.

Builds; the headless test passes. None of these games is here: the
rows are upstream's, not tested.
2026-10-07 17:43:25 +02:00
LibretroAdmin 2a65988852 rec-arm: the 32-bit ARM recompiler does the MMU
The 32-bit ARM recompiler knew nothing of the SH4's MMU, so a game
that needs it - the Windows CE ones - could not use it; the commit
before this one gave those games the interpreter. They now get the
recompiler, and that fallback is taken out again.

A block compiled with the MMU on differs in three ways, as it does on
the other recompilers:

- Every access to memory is a call that translates the address. Where
  translation fails, the SH4's exception is taken there - registers
  saved, PC at the handler - and longjmp() returns to the main loop,
  which goes on from the handler. Instructions the interpreter runs
  and store queue flushes, which can raise exceptions too, go through
  the same. A constant address is still read or written directly when
  it is one the MMU does not translate.
- It ends by handing its next PC to the main loop, which looks the
  block up: which block an address means depends on the mapping in
  force, so blocks are not chained to each other.
- It begins by checking that the PC is its own, and that the FPU is
  on if it uses it.

The main loop (ngen_arm.S) has the two entry points again for such
blocks, and a setjmp() where it starts, once a frame.

There is no access through the host's own mapping, as the 64-bit
recompilers have: a 32-bit host has no room to lay a translated
address space out. So with the MMU on every load and store is a call;
slower than without, a great deal faster than the interpreter.

Run headless on a 32-bit ARM core under qemu-arm, the live test's disc
passes with the MMU on - its MMU test, then everything else through
the MMU, including the TLB misses and the illegal instruction that
come back through longjmp() - under both SH4 timings; and without the
MMU as before. The x86-64 headless test passes as before. Built for
a device target and with NO_MMU defined.

Not run with a Windows CE game, and the path for an FPU instruction
with the FPU off is not reached by the disc.
2026-10-07 09:54:11 +02:00
LibretroAdmin ef7fd677d4 32-bit ARM: a game that needs the MMU is run by the interpreter
The 32-bit ARM recompiler knows nothing of the MMU. With it on, the
recompiler went on reading and writing at the addresses the program
gave as though they were physical ones, so a Windows CE game, which
lives behind the MMU, could not run on a 32-bit ARM build at all.

Where the core turns its MMU on for a game - a Windows CE disc, or
the "Force Windows CE Mode" option - a 32-bit ARM build now runs the
SH4 with the interpreter, which does the MMU properly, and says so in
the log. That will be slow on such a machine, but it is the game and
not a crash. x86-64 and ARM64, whose recompilers have the MMU, are as
they were.

Seen with the disc of the commit before, run headless on a 32-bit ARM
core under qemu-arm: the MMU run failed with the address not
translated, and passes with this.
2026-10-07 09:35:54 +02:00
LibretroAdmin 828a0671f9 an ELF file given as content is loaded and run
"elf" has been among the extensions the core says it takes, and the
HLE BIOS has had a loader for ELF files, but nothing connected them:
the setting that names the file was cleared at start-up and never set,
and an .elf was handed to the disc reader like any other content.

Now it works. Content ending in .elf is not taken for a disc. The HLE
BIOS is used for it whatever the HLE BIOS option says - it is what
loads the file, and the real BIOS would only find an empty drive - and
the drive is left empty, where before an empty drive made the core
switch the HLE BIOS off. The program is loaded into main memory and
started where the file says it starts, which is 0x8C010000 for
anything built the usual way, on every reset.

The loader no longer takes the file's word for where things are.
Before acting on them it checks that the file is long enough to have a
header, is a 32-bit ELF, and has its program header table inside it;
that each segment's bytes lie inside the file; and that where they go,
with the zeroes after them, lies inside main memory. Only segments
marked as loaded are loaded - a stack marker at address 0 used to draw
a warning. A file that fails any of this is refused, and the CPU is
then left idling at the start address, as it is for a missing disc,
and not run into whatever memory holds.

Checked with the live test's program built as an ELF by the compiler
and written as one by hand: both start and draw their scene. Five
damaged files - cut off inside the header, the header table outside
the file, a segment outside the file, one too long for memory, one
placed at the end of memory - are each refused and leave the core up;
before the checks the loader would have copied from or to wherever
they pointed.
2026-10-06 16:22:11 +02:00
LibretroAdmin 409cbff6e9 remove functions nothing calls
The linker was asked which functions of the core no code path reaches
(-ffunction-sections with --gc-sections --print-gc-sections), and each
one it named was then looked up by name across the whole source,
every platform's code and the tools included. These have no caller
anywhere:

  MemWrite32                         hw/pvr/pvr_mem.cpp
  pvr_ReadReg                        hw/pvr/pvr_regs.cpp
  rend_framePending                  hw/pvr/ta_ctx.cpp
  rend_init, rend_resize             hw/pvr/Renderer_if.cpp
  emit_WriteCodeCache                hw/sh4/dyna/driver.cpp
  bm_WriteBlockMap                   hw/sh4/dyna/blockmanager.cpp
  dec_Un_rNrN, dec_Un_frNfrM, dec_rz hw/sh4/sh4_opcode_list.cpp
  cpu_iWarn                          hw/sh4/interpr/sh4_opcodes.cpp
  get_nvmem_data                     hw/holly/sb_mem.cpp
  aica_Term                          hw/aica/aica_if.cpp
  reios_term                         reios/reios.cpp
  cryptoReset                        hw/naomi/decrypt.cpp
  printtoc                           imgread/common.cpp
  dc_start                           nullDC.cpp
  shutdown_thread_pool               rend/TexCache.cpp
  gl_DebugOutput                     rend/gl4/gles.cpp
  libPvr_GetRenderTarget,
  libPvr_GetRenderSurface            libretro/libretro.cpp
  VArray2::LockRegion                libretro/common.cpp
  LogManager::SetEnable              log/LogManagerLibretro.cpp
  set_user_data_dir,
  add_system_config_dir,
  add_system_data_dir                stdclass.cpp, with the three
                                     variables only they wrote

The two *_Term functions were empty. cryptoReset was not: but the
cartridge decryption it would have cleared starts itself afresh
whenever a key or an address is set.

Left alone, though the linker named them on x86-64: what the ARM,
ARM64 and x86 recompilers and the cached interpreter call (rdv_*,
emit_Write32, emit_SetBaseAddr, bm_GetStaleBlock, the block reference
counting, _vmem_ReadMem8SX32/16SX32, shil_opcode_name), cpu_iNimp,
which a build without an MMU calls, mmu_QACR_SQ, which is called but
inlined so that only its separate copy goes unused, and
archive_num_entries, which tools/archive uses.

libARM_Term, also empty and never called, stays for now: its file has
CRLF line endings, and a patch that touches it does not go through a
plain git am.
2026-10-06 14:52:51 +02:00
LibretroAdmin 85fc7c39f3 settings that only ever had one value are that value
A number of settings are given a value once, in LoadSettings(), and
never another: there is no core option behind them and nothing else
writes them. Each is replaced by its value where it was read, and the
branch that value never took is removed.

- dynarec.idleskip, always on: the idle loop detection in the block
  decoder is unconditional, and the branch for it being off is gone.
- dynarec.unstable_opt, always off: the store queue shortcut in the
  ARM recompiler that it guarded is removed.
- pvr.MaxThreads, always 3: a constant in the texture upscaler.
- pvr.subdivide_transp, always off, and only read inside an #if 0
  block of the triangle sorter, which goes with it.
- rend.Clipping, always on: tile clipping no longer asks.
- rend.AutoExtraDepthScale, always on.
- debug.SerialConsole, always off: the serial port's echo to stdout.
- imgread.PatchRegion, never set: the two functions that would have
  rewritten a disc's region strings returned at once, and are removed
  with the calls to them.
- validate.OpenGlChecks and pvr.Emulation.zMin/zMax were written and
  never read.

LoadSettings() also loses a block for builds that are not libretro,
which this core is not, and which set a field that no longer exists.

core/rend/soft/softrend.cpp, a software renderer that used one of
these settings, was not in any makefile. It is deleted, and its
declaration with it.
2026-10-06 12:08:50 +02:00
LibretroAdmin 3d6c8a86f6 remove the Synchronous Rendering and Delay Frame Swapping core options
Neither did anything. Both settings were written from their options
and read nowhere: what they chose between went with the old threaded
renderer, which could run ahead of the picture and had to either wait
for it or drop a frame. Threaded rendering now makes one frame for
each retro_run, the frame non-threaded rendering makes, and presents
it at vblank. The core had already been hiding both options for that
reason.

The options and their settings are removed.
2026-10-06 11:24:44 +02:00
LibretroAdmin 17c82ce087 settings: remove sound, VSync and OSD settings that nothing sets
settings.aica.LimitFPS was set to zero and never read.

settings.aica.NoSound was set to zero and read in one place, where a
non-zero value would have thrown every sample away. Sound is always
written.

settings.aica.CDDAMute was never set, so CD audio was always mixed in
and passed to the DSP. The branch for it being muted is gone.

That leaves settings.aica empty, and it is removed.

settings.pvr.Video (ResolutionMode, VSync) and settings.pvr.OSD
(ShowFPS, ShowStats) were declared and never mentioned again.
2026-10-06 11:24:40 +02:00
LibretroAdmin 3cb71849a7 Unwind dc_init on failure
Each exit of dc_init() tears down the stages it completed, and
plugins_Init() terminates the plugins it brought up before the one that
failed. A content load that fails no longer leaves the Win64 vectored
exception handler, the dynarec unwind table or the reserved address
space registered after the core is unloaded.
2026-10-06 11:04:49 +02:00
LibretroAdmin b622fea63c remove the Enable DSP core option: the AICA's DSP is always emulated
The option chose between two mixers. With it off, sound was mixed 32
samples at a time by a version that leaves the sound chip's DSP out:
no reverb, no chorus, none of the effects games route their channels
through, and a channel sent only to the DSP was dropped into the
output as it was instead. With it on, sound is mixed a sample at a
time with the DSP run for each.

The option, its setting and the batching setting that went with it are
removed, and so is the mixer without the DSP. Low-end builds, which
had it off by default, now run the DSP like every other.
2026-10-06 10:43:08 +02:00
LibretroAdmin e34d0028e4 remove the Fog Effects and Volume Modifier core options
Fog and modifier volumes are not enhancements: they are what the
PowerVR2 does, and a game that uses them looks wrong without them, with
no fog where there should be fog and no shadows where there should be
shadows. Both options defaulted to on and there is no reason to offer
"off".

The two options go, and settings.rend.Fog and
settings.rend.ModifierVolumes with them. The OpenGL, OpenGL per-pixel
and Vulkan renderers (both Vulkan drawers) always use the fog mode the
polygon asks for, always keep the fog table up to date, and always draw
the opaque and translucent modifier volumes.

The translated option tables still carry the two entries until they are
next regenerated; they are looked up by the English table's keys, so
they are not shown.
2026-10-06 03:07:31 +02:00
LibretroAdmin 171593b826 remove the Framerate core option, which did nothing
"Framerate" (Full Speed / Normal) chose when the core handed control
back to the frontend: after each rendered frame, or at each vblank. That
choice is gone. Since the threaded rendering rework every retro_run()
runs the machine to the next vblank and returns, with Threaded
Rendering on or off, with the frame deadline behind it for a game that
has the display off. The core never waits for the game to render.

What the option still set, settings.UpdateMode, was read in one place:
rend_end_sch(), and there only in a build made with NO_THREADS=1, which
no platform in the Makefile is. In every other build the option changed
nothing. In that one, its default (Full Speed) skipped the call that
presents the frame at the end of a render.

So the option is removed, with settings.UpdateMode. The per-game
override that forced it, settings.UpdateModeForced, goes too; the
updatemode_type column stays in rom_luts.h, unused, rather than every
row of the table being rewritten. rend_end_sch() presents
unconditionally.

The translated option tables still carry the entry until they are next
regenerated; they are looked up by the English table's keys, so it is
not shown.
2026-10-06 02:53:37 +02:00
LibretroAdmin 9b5d70d96b retro_atomic for the last std::atomic uses
Three were left in the core's own sources: the count of custom-texture
load requests in BaseTextureCacheData, the reset request flag, and an
unused <atomic> include in gles.h. They are retro_atomic like the rest;
the mirror of the texture counter in tools/threads/threads_test.cpp goes
the same way. No change in behaviour.
2026-10-06 02:34:21 +02:00
LibretroAdmin 4bd929c953 threaded rendering: make one frame per retro_run, the one non-threaded makes
With Threaded Rendering on, nothing tied the emulation to retro_run. The
emulation thread ran free, held back only by the audio it pushed to the
frontend itself, 512 samples at a time, from its own thread, whenever
that call blocked. retro_run waited up to 100 ms for whatever render
turned up and returned a duplicate frame if none did. A render the game
started while the last one was still being drawn was thrown away. So a
retro_run could last any length, carry a new frame or not, and its audio
arrived some other time from some other thread.

Each retro_run now makes exactly one frame, the same one it makes with
Threaded Rendering off:

  - retro_run wakes the emulation thread, which runs the machine up to
    vblank and goes back to sleep, as dc_run() does in retro_run itself
    when rendering is not threaded;
  - when the game starts a render the emulation thread hands it to the
    libretro thread and sleeps; that thread reads what the frame takes
    from the game's memory (display lists, textures, framebuffer), lets
    the emulation thread go on, and draws. A render to a texture, which
    ends up back in the game's memory, keeps it asleep until it is done;
  - every render is drawn. The emulation thread waits for the one before
    to be finished; none is dropped;
  - at vblank the frame's audio is sent from the libretro thread in one
    batch, together with its video.

The renderers read some PVR registers while they draw, by which time the
emulation has moved on and may have changed them. Each render now
carries a copy of the registers made when the game started it, and the
renderers' files read through that (PVR_REGS_FOR_RENDERER), so they draw
from what non-threaded rendering would have read.

EmuBaton (core/libretro/emu_baton.h) is that handshake. There is no
lock, no sleep and no timeout in it: every change of hands is one atomic
operation and a thread with nothing to do sleeps on an eventcount. A
save state from the frontend's task thread takes the machine between
two frames.

The "synchronous rendering" and "delay frame swapping" options have
nothing left to choose and are hidden.

tools/threads/live.sh runs the test disc for the same 300 frames with
Threaded Rendering on and off and requires the same screenshot, byte
for byte, and the same save state (live_same.py leaves out the host
addresses a state carries). The old threaded path fails that: its state
differs from the non-threaded one. tools/threads/run.sh puts the new
EmuBaton under ThreadSanitizer.
2026-10-05 13:39:02 +02:00
libretroadmin 4253dd8e59 control flow: hard backstop so dc_run() can never starve retro_run
The previous change routes non-threaded control-return through vblank in
os_DoEvents, which fixes the reported case (a static screen during disc loading
no longer blocks). But that still depends on the SPG raising vblank: rend_vblank
only runs when pvr_cur_scanline == SPG_VBLANK.vstart, so before video timing is
programmed, or if a title programs vstart out of range or a degenerate
Line_Cycles, vblank need not fire and dc_run() could still run unbounded.

Add a scheduler backstop that forces dc_stop() after at most SH4_MAIN_CLOCK/20
cycles (~2.5 frames), re-armed at every dc_run() entry. Under normal operation
vblank fires every frame, well inside that budget, so it never triggers; if
vblank stalls for any reason, dc_run() still returns and retro_run() is never
starved. Non-threaded only -- in threaded mode dc_run() is meant to loop
continuously on the emu thread, so the backstop is not armed there.

Verified by enumerating every CPU-stop source (dc_stop/Sh4_int_Stop): in
'Normal' mode with no rendering, none fired except the render-tied Present(),
confirming the reported stall; with these two changes control-return no longer
depends on either rendering or a well-behaved SPG.
2026-07-03 22:51:16 +02:00
libretroadmin 45ce654f6f win64: remove exception handler and unwind table on content close
dc_init() registers a vectored exception handler (AddVectoredExceptionHandler)
and a dynarec unwind function table (RtlAddFunctionTable, keyed to the code
cache) on every content load, but dc_term() removed neither. After Close
Content, dc_term() frees the code cache and vmem while both registrations still
point at that freed memory, so the next stack unwind or access violation -- even
one raised during teardown -- walks freed state inside the OS exception
machinery. That crashes on close/reload and is effectively undebuggable, since
the fault is in the unwinder itself rather than in steppable code. It only bites
on Win64 (VEH + RtlAddFunctionTable); other targets are unaffected.

Add common_libretro_cleanup(), called at the start of dc_term() before anything
is freed, to RemoveVectoredExceptionHandler() and RtlDeleteFunctionTable(). Keep
the VEH handle so it can be removed, make its install idempotent so reloads
don't stack handlers, and have setup_seh() drop any prior registration before
re-adding (the code cache is re-prepared each load).

Diagnosed by inspection; needs a Win64 runtime check to confirm.
2026-07-03 21:14:57 +02:00
libretroadmin 45c09f53aa libretro: make cross-thread emu flags atomic, stop spin-waits pegging a core
emu_in_thread, performed_serialization and reset_requested are written on one
thread and read on the other (emu thread vs. the libretro thread that drives
serialize/reset/unload). They were plain bools, i.e. a data race / UB. Make
them std::atomic<bool>; the mutex handshakes still provide the ordering that
matters, so relaxed semantics are sufficient for these standalone flags.

wait_until_dc_running() and acquire_mainloop_lock() hot-spun for up to five
seconds, burning a full core. The first has no work to do until the emu thread
reaches dc_run; the second must keep kicking the emu thread out of its render
wait but not at spin speed. Back both off by a millisecond per iteration with
retro_sleep(1); timeout and kick semantics are unchanged.
2026-07-03 20:17:01 +02:00
twinaphex 8d8eab8fd5 - Backport https://github.com/flyinghead/flycast/commit/889fc8256d720ae15484c8517537e1b19152bf6d#diff-fc1d651a6c7be7577e1a14b6e860c10115ef82091c1439ca291e945ba55a4220
- Also backport miniupnpc from another commit, necessary for BBA
  emulation
2021-04-14 19:50:23 +02:00
twinaphex 1493af65f7 Backport https://github.com/flyinghead/flycast/commit/1b04ef4cb11b286e7487683a8283512976579cb5#diff-3d7848c6ecc65ba29b69f95369a18a93d26feb5786259415bcc94f569b5b819c 2021-04-12 00:40:06 +02:00
twinaphex aac5eba64a Backport https://github.com/flyinghead/flycast/commit/75f7b92342b9099703d5a7ac205f23322d8f825b 2021-04-11 22:24:09 +02:00
twinaphex 44e06af888 CLeanups 2021-03-06 06:33:46 +01:00
barbudreadmon b580126710 (NAOMI) Backport https://github.com/flyinghead/flycast/commit/a41a81f5dde460c711e4e2874bd20df16ae1ac01
and some leftovers from https://github.com/flyinghead/flycast/commit/98b2a32eae0b7a98d7652ba6c368986656c6d656
2021-01-08 09:57:09 +01:00
barbudreadmon fa49de2997 (SH4) backport https://github.com/flyinghead/flycast/commit/333df13fce8a0bc577d6397c384abf86ea5daf24
(SH4) backport https://github.com/flyinghead/flycast/commit/a19c73de7b79b6c504798768a32922994f52c57f
sync soft/hard reset calls with upstream
2020-12-24 13:34:48 +01:00
barbudreadmon d41d526764 Backport Fog option
Backport https://github.com/flyinghead/flycast/commit/ec95c95b89178c9090c14b2f78c26a563593bef6

Backport https://github.com/flyinghead/flycast/commit/d524e3381d72b5b8d3c2c36863718e38b8b0ff51

Backport https://github.com/flyinghead/flycast/commit/b82a97853fd87607b51ca3b270b261f7d4c5aac0
2020-11-21 19:42:44 +01:00
barbudreadmon 81b4ede55f Revert "Backport Fogging option"
This reverts commit 73f0ee4e96.
2020-11-18 18:12:52 +01:00
twinaphex 73f0ee4e96 Backport Fogging option 2020-11-15 17:38:11 +01:00
Flyinghead 1bf642e90c maple: create devs after loading the game. aw: better analog mapping
Create maple devices after loading the game so the required arcade
controls are known (AW).
Map ftspeed and maxspeed accel and brake to triggers.

Issue #865
2020-06-22 13:40:41 +02:00
Flyinghead 2d439fa8c4 Fix crash when boot to bios is enabled and loading naomi/aw game
add emulator.h
2020-06-02 16:35:06 +02:00
Flyinghead 1d40e23654 get rid of wchar 2020-06-02 16:31:56 +02:00
Flyinghead a3f4818d87 don't reset frameskip to 0 at init
Issue #889
2020-05-12 10:29:20 +02:00
Flyinghead 07879f7a11 naomi: VMU support (vonot, sf3zu). Fix otrigger inputs. 2020-03-23 19:19:39 +01:00
Flyinghead aeadb7d6cd users now cannot force enable div matching
Issue #817
2020-02-17 18:13:37 +01:00
Flyinghead 9c41f48fb1 disable RGB component for some games
Issue #176
Issue #177
Issue #725
Issue #707
Issue #182
2020-02-03 23:43:38 +01:00
Flyinghead bc5be5e695 force HLE off to boot BIOS. Avoid crash when loading invalid content
Use file extension to select the right disc driver
Better error reporting, less memory leaks and crashes on invalid content
Force HLE off when booting BIOS
Don't crash if missing BIOS file
2020-01-31 12:32:05 +01:00
Flyinghead a53b52dd84 naomi/aw: load failure when mixing / and \ in content path on windows
Use libretro file path library.
Clean up
2019-12-20 10:34:29 +01:00
Flyinghead 605e2128cb Add anisotropic filtering option (vulkan). Remove RTT option. 2019-12-16 15:48:33 +01:00
Flyinghead 4f2abdd93f reios: allow hle savestates to be loaded in non-hle mode
Always initialize reios hooks during startup so hle savestates always work.
2019-09-19 12:09:29 +02:00
Flyinghead a774989cd9 Load roms in naomi and aw mode
Fixes naomi_boot.bin not working and atomiswave flash not being loaded
2019-09-17 11:26:08 +02:00
Flyinghead 5ceffc3ea2 Display lightgun xhair for Naomi and AW games
Only use HLE BIOS for dreamcast games
2019-09-12 18:19:00 +02:00