Libretro - D3D11/GL Delay Frame Swap Fix#2280
Draft
Immersion95 wants to merge 1 commit intoflyinghead:masterfrom
Draft
Libretro - D3D11/GL Delay Frame Swap Fix#2280Immersion95 wants to merge 1 commit intoflyinghead:masterfrom
Immersion95 wants to merge 1 commit intoflyinghead:masterfrom
Conversation
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #1615
I used AI to come up with a solution to a problem that had been bothering me a lot :p. I’m not expecting this to be merged, I just hope it helps you understand the issue I was running into and gives you some inspiration or direction for fixing it. Frankly I'm not able to help much more than than, but I really want this issue to be solved :).
Delay Frame Swap is indeed broken on Libretro - D3D11/GL - non-threaded rendering. I don't use threaded rendering as it adds 1 frame of input lag at the moment libretro/flycast#738.
Summary
This is a draft PR fixing DelayFrameSwapping in non-threaded Libretro mode for GL and DX11 backends. It was investigated and developed with the assistance of Claude (Anthropic).
Background: what DelayFrameSwapping does
On real Dreamcast hardware, a rendered frame is only displayed at the next vblank interrupt. DelayFrameSwapping emulates this behaviour by delaying the frame presentation by one vblank, adding exactly 1 frame of latency compared to having the option disabled.
This can be verified by counting frames in a capture:
This is the expected, hardware-accurate behaviour.
The problem
In non-threaded Libretro mode (GL and DX11), DelayFrameSwapping does not work correctly for all games. For some games such as Capcom vs SNK 2, it works as expected, enabling it correctly adds +1 frame of latency compared to having it disabled, matching real hardware behaviour.
However, for other games, enabling or disabling DelayFrameSwapping produces identical output, the intended +1 frame of hardware-accurate latency is never applied. The option is effectively broken for those games in this configuration.
SFIII Double Impact shows visible frame drops and stutter. Street Fighter Zero 3 has the image alternating between top and bottom of the screen every frame. Street Fighter 3rd Strike has no visible artifact, but DelayFrameSwapping has no effect, frame count is identical with it on or off.
Vulkan is not affected, it works correctly for all games and produces the expected +1 frame latency when DelayFrameSwapping is enabled.
Root cause
The fix for DelayFrameSwapping relies on
rend_swap_frame()detecting when the game flips its display buffer:This comparison fails due to a race condition: the game writes
FB_W_SOF1(next write buffer address, updatesfb_w_cur) before writingFB_R_SOF1(flip request). By the timerend_swap_frame()runs,fb_w_curhas already moved to the next frame's buffer address, the check fails andPresent()is never called.In threaded mode, this is not fatal:
rend_single_frame()loops continuously untilpresented = true, so a missed Present is retried automatically next cycle.In non-threaded mode, there is no retry loop. One call to
retro_run()must produce exactly one frame. IfPresent()is never called,retro_rend_present()is never called,is_dupestaystrue, and RetroArch receivesvideo_cb(NULL)and duplicates the previous frame.Vulkan is unaffected because it uses a fundamentally different presentation mechanism (
set_image()), RetroArch retrieves the image directly rather than waiting for a signal from Flycast.The fix
Two new boolean flags are introduced in
Renderer_if.cpp.swap_frame_calledis set totruewhenrend_swap_frame()is called, meaning the game has already flippedFB_R_SOF1before the vblank. It is reset tofalseat each vblank.presentedalready existed but was only reset inrend_single_frame()(threaded mode). It is now also reset at the start of eachrend_start_render()in non-threaded Libretro mode, so the vblank fallback can reliably detect whether Present already succeeded this cycle.At the vblank (
rend_vblank()), a fallback Present is fired if all of the following conditions are met:The
swap_frame_calledguard is critical: it prevents the fallback from firing on games like Capcom vs SNK 2, which flipFB_R_SOF1after the vblank scanline. Without this guard, the SH4 would be stopped too early for those games, causing micro-stutters that did not exist before the fix.renderer->Present()returnsfalseifframeRenderedis alreadyfalse(e.g. RTT frames, or Present already triggered viarend_swap_frame()), so there is no risk of double presentation.What is not affected
Vulkan is unchanged and already works correctly. Threaded rendering is unchanged, protected by
#ifdef LIBRETROand!config::ThreadedRenderingguards. Standalone is unchanged, protected by#ifdef LIBRETRO. Games that already work correctly such as CvS2 are unaffected, the!presentedguard ensures the fallback never fires when the normal path already succeeded.Files changed
core/hw/pvr/Renderer_if.cpp- only file modified.Tested
SFIII Double Impact: stutter resolved ✓
Street Fighter Zero 3: vertical alternation resolved ✓
Street Fighter 3rd Strike: DelayFrameSwapping now correctly adds +1 frame ✓
Capcom vs SNK 2: no regression, same smoothness as before ✓
Vulkan: unaffected ✓
Threaded rendering: unaffected ✓
Investigated and developed with the assistance of Claude (Anthropic).