mirror of
https://github.com/libretro/parallel-gx2.git
synced 2026-11-07 12:37:54 -10:00
Pace AX by the frames the frontend asks for
The frame timing says where the quarter frame rate comes from: each retro_run spent ~100 ms in the audio callback and nothing anywhere else. With the cores running between frames, AX made audio by the wall clock, and RetroArch blocks in the audio callback until what it was given has played - so a slow frame meant more audio for the next one and a longer block in it, until the ring's 6144 frames capped it at ~10 retro_run a second. In a libretro core the audio has to follow the frames instead. Each retro_run now grants one frame's worth, 48000 / 60 = 800 samples, and AX queues a 3 ms frame only when there is budget for it; the wall-clock gate is not used in the core. The audio callback then paces retro_run at the reported 60 fps, a paused frontend grants nothing, and the budget is capped at three frames so a stalled title does not come back with a burst.
This commit is contained in:
1 parent
108d1282a2
commit
296e8e0101
3 files changed
+48
No files matched your search
@@ -394,6 +394,10 @@ namespace snd_core
|
||||
void AXOut_init();
|
||||
void AXOut_reset();
|
||||
void AXOut_update();
|
||||
#ifdef RETRO_CORE
|
||||
// One retro_run's worth of audio: how many samples AX may make next.
|
||||
void AXOut_LibretroGrantSamples(int32_t samples);
|
||||
#endif
|
||||
|
||||
COSModule* GetModuleSndCore1();
|
||||
COSModule* GetModuleSndCore2();
|
||||
|
||||
@@ -544,9 +544,44 @@ namespace snd_core
|
||||
std::atomic<uint64_t> g_ax_update_calls{0};
|
||||
std::atomic<uint64_t> g_ax_update_passed{0};
|
||||
|
||||
#ifdef RETRO_CORE
|
||||
// Samples the frontend has asked for and AX has not made yet. In a libretro
|
||||
// core the audio has to follow the frames the frontend asks for, not the
|
||||
// wall clock: RetroArch blocks in the audio callback until what it was
|
||||
// given has played, so audio made by the clock turns a slow frame into a
|
||||
// longer wait in the next one, and a longer wait into more audio - NNshi's
|
||||
// 60 fps settled at 15, this machine's at 10, each retro_run spending
|
||||
// ~100 ms handing over the ring's worth of audio. Paced by frames, the
|
||||
// audio callback is what paces retro_run, at the frame rate the core
|
||||
// reports. Capped at a few frames, so a title that stalls does not come
|
||||
// back with a burst to catch up.
|
||||
static std::atomic<int32_t> s_libretro_ax_budget{0};
|
||||
|
||||
void AXOut_LibretroGrantSamples(int32_t samples)
|
||||
{
|
||||
constexpr int32_t kCap = 3 * 800;
|
||||
int32_t cur = s_libretro_ax_budget.load(std::memory_order_relaxed);
|
||||
while (!s_libretro_ax_budget.compare_exchange_weak(cur, std::min(cur + samples, kCap), std::memory_order_relaxed))
|
||||
;
|
||||
}
|
||||
#endif
|
||||
|
||||
void AXOut_update()
|
||||
{
|
||||
g_ax_update_calls.fetch_add(1, std::memory_order_relaxed);
|
||||
#ifdef RETRO_CORE
|
||||
if (s_libretro_ax_budget.load(std::memory_order_relaxed) < AX_SAMPLES_PER_3MS_48KHZ)
|
||||
return;
|
||||
g_ax_update_passed.fetch_add(1, std::memory_order_relaxed);
|
||||
if (snd_core::isInitialized() && numQueuedFramesSndGeneric == snd_core::getNumProcessedFrames())
|
||||
{
|
||||
AXOut_updateDevicePlayState(true);
|
||||
snd_core::AXIst_QueueFrame();
|
||||
numQueuedFramesSndGeneric++;
|
||||
s_libretro_ax_budget.fetch_sub(AX_SAMPLES_PER_3MS_48KHZ, std::memory_order_relaxed);
|
||||
}
|
||||
return;
|
||||
#endif
|
||||
constexpr static auto kTimeout = std::chrono::duration_cast<std::chrono::nanoseconds>(std::chrono::milliseconds(((IAudioAPI::kBlockCount * 3) / 4) * (AX_FRAMES_PER_GROUP * 3)));
|
||||
constexpr static auto kWaitDuration = std::chrono::duration_cast<std::chrono::nanoseconds>(std::chrono::milliseconds(3));
|
||||
constexpr static auto kWaitDurationFast = std::chrono::duration_cast<std::chrono::nanoseconds>(std::chrono::microseconds(2900));
|
||||
|
||||
@@ -523,6 +523,12 @@ static std::atomic<unsigned> s_gate_hold_open{0};
|
||||
// waiting for the GPU thread, and the GPU thread swaps while it gets there -
|
||||
// so a gate that holds it then is holding it against a retro_run that cannot
|
||||
// be called yet, and the load never finishes. Boot swaps go straight through.
|
||||
namespace snd_core
|
||||
{
|
||||
// ax_out.cpp: one retro_run's worth of audio for AX to make.
|
||||
void AXOut_LibretroGrantSamples(int32_t samples);
|
||||
}
|
||||
|
||||
// Where a frame's time goes (cemu_log_audio): the GPU thread's wait for a
|
||||
// token at the swap, and retro_run's own phases, reported once a second.
|
||||
static std::atomic<uint64_t> s_prof_gate_wait_us{0};
|
||||
@@ -5155,6 +5161,9 @@ RETRO_API void retro_run()
|
||||
|
||||
// Ask for a frame: the GPU thread is parked at the gate after the last swap.
|
||||
libretro_frame_gate_grant();
|
||||
// And for the frame's audio: 48000 / 60, the rate and frame rate
|
||||
// retro_get_system_av_info reports.
|
||||
snd_core::AXOut_LibretroGrantSamples(800);
|
||||
|
||||
// Wait for frame from GPU thread
|
||||
{
|
||||
|
||||
Reference in new issue
Block a user