mirror of
https://github.com/RetroPortingToolKit/psxrecomp
synced 2026-10-27 10:38:46 -04:00
Render thread: skip a present only when two newer frames wait
One frame behind is the normal pipelined state when the render thread is the slower side; skipping on it dropped nearly every present at 4K widescreen. rt_frames_ahead() counts the complete frames queued after the one being replayed; the backend skips only at two, where the emulation thread is at the in-flight bound waiting on the render thread. The unit test checks the count never exceeds the bound and that both cases occur under a slow consumer. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -12,8 +12,9 @@
|
||||
* ring end; payloads are copied, so the producer may reuse its buffers.
|
||||
* - rt_frame_end() closes a guest frame. At most max_frames closed frames may
|
||||
* wait in the ring; past that the emulation thread blocks (backpressure).
|
||||
* While replaying, rt_newer_frame_queued() tells the backend a later frame
|
||||
* is already recorded, so it may skip presenting a stale one.
|
||||
* While replaying, rt_frames_ahead() counts the complete frames already
|
||||
* recorded after the one being replayed, so the backend can skip
|
||||
* presenting a frame it is far behind on.
|
||||
* - rt_acquire() is the one sync point: it waits until every record has been
|
||||
* executed, has the render thread release the context, and makes it
|
||||
* current on the calling (emulation) thread. The emulation thread then
|
||||
@@ -22,7 +23,7 @@
|
||||
* thread at all.
|
||||
* - No lock is held while exec() runs. Locks only guard sleeping/waking.
|
||||
*
|
||||
* Every function except the rt_on_render_thread() / rt_newer_frame_queued()
|
||||
* Every function except the rt_on_render_thread() / rt_frames_ahead()
|
||||
* queries is emulation-thread only. */
|
||||
#ifndef PSX_RENDER_THREAD_H
|
||||
#define PSX_RENDER_THREAD_H
|
||||
@@ -81,9 +82,9 @@ void rt_cmd_commit(void);
|
||||
* max_frames closed frames are unconsumed. */
|
||||
void rt_frame_end(void);
|
||||
|
||||
/* Render-thread query while executing: a frame marker newer than the record
|
||||
* being executed is already queued (the frame being replayed is stale). */
|
||||
int rt_newer_frame_queued(void);
|
||||
/* Render-thread query while executing: how many complete frames are queued
|
||||
* after the frame whose record is being executed (0 = it is the newest). */
|
||||
int rt_frames_ahead(void);
|
||||
|
||||
/* Sync point. Drain, then move the context to the calling thread (held).
|
||||
* reason is a static string kept in the acquire ring. No-op when already held
|
||||
|
||||
@@ -9281,9 +9281,12 @@ static int rth_record_present(uint16_t op, int n, const int32_t *v) {
|
||||
static void rth_replay_present(const RtCmd *c, const uint8_t *p) {
|
||||
RthOvHdr hd;
|
||||
memcpy(&hd, p, sizeof hd);
|
||||
if (rt_newer_frame_queued()) {
|
||||
/* Behind: a later frame is already recorded. Drawing this one only
|
||||
* delays it; the guest never waits for a present. */
|
||||
if (rt_frames_ahead() >= 2) {
|
||||
/* Two later frames are already recorded, so the emulation thread is
|
||||
* at (or near) the in-flight bound waiting on us. Showing this frame
|
||||
* would only delay those; skip its present (its drawing has been
|
||||
* replayed and stays). One frame behind is the normal pipelined
|
||||
* state and still presents every frame. */
|
||||
s_rth_presents_stale++;
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -323,8 +323,10 @@ void rt_frame_end(void) {
|
||||
}
|
||||
}
|
||||
|
||||
int rt_newer_frame_queued(void) {
|
||||
return atomic_load(&R.frames_produced) >= atomic_load(&R.frames_consumed) + 2;
|
||||
int rt_frames_ahead(void) {
|
||||
int64_t d = (int64_t)(atomic_load(&R.frames_produced) -
|
||||
atomic_load(&R.frames_consumed)) - 1;
|
||||
return d < 0 ? 0 : (int)d;
|
||||
}
|
||||
|
||||
void rt_drain(void) {
|
||||
|
||||
@@ -41,7 +41,9 @@ static _Atomic int exec_errors;
|
||||
static _Atomic int exec_off_thread;
|
||||
static _Atomic int exec_without_ctx;
|
||||
static _Atomic int stale_seen;
|
||||
static _Atomic int far_behind_seen; /* two complete frames queued after this one */
|
||||
static int exec_delay_us;
|
||||
static int g_max_frames = 1;
|
||||
|
||||
static void sleep_us(int us) {
|
||||
struct timespec ts = { us / 1000000, (long)(us % 1000000) * 1000 };
|
||||
@@ -56,7 +58,9 @@ static void fake_exec(void *user, const RtCmd *c, const void *payload) {
|
||||
if (!rt_on_render_thread()) atomic_fetch_add(&exec_off_thread, 1);
|
||||
if (atomic_load(&ctx_owner) != 2) atomic_fetch_add(&exec_without_ctx, 1);
|
||||
if (c->op == OP_PRESENT) {
|
||||
if (rt_newer_frame_queued()) atomic_fetch_add(&stale_seen, 1);
|
||||
if (rt_frames_ahead() >= 1) atomic_fetch_add(&stale_seen, 1);
|
||||
if (rt_frames_ahead() >= 2) atomic_fetch_add(&far_behind_seen, 1);
|
||||
if (rt_frames_ahead() > g_max_frames) atomic_fetch_add(&exec_errors, 1); /* the bound */
|
||||
} else if (c->op == OP_DATA) {
|
||||
const uint64_t *p = (const uint64_t *)payload;
|
||||
if (c->payload < 8 || p[0] != exec_next) atomic_fetch_add(&exec_errors, 1);
|
||||
@@ -89,12 +93,14 @@ static void reset_state(void) {
|
||||
atomic_store(&exec_off_thread, 0);
|
||||
atomic_store(&exec_without_ctx, 0);
|
||||
atomic_store(&stale_seen, 0);
|
||||
atomic_store(&far_behind_seen, 0);
|
||||
atomic_store(&ctx_violations, 0);
|
||||
atomic_store(&ctx_owner, 1); /* the "context" starts current here */
|
||||
exec_delay_us = 0;
|
||||
}
|
||||
|
||||
static int start(size_t ring, int frames) {
|
||||
g_max_frames = frames;
|
||||
RtConfig cfg = { ring, frames, fake_exec, fake_ctx, NULL };
|
||||
return rt_start(&cfg);
|
||||
}
|
||||
@@ -157,7 +163,8 @@ static void test_backpressure(void) {
|
||||
RtStats st; rt_get_stats(&st);
|
||||
CHECK(worst <= 2, "in-flight bound held (worst %llu)", (unsigned long long)worst);
|
||||
CHECK(st.backpressure_waits > 0, "producer was held back");
|
||||
CHECK(atomic_load(&stale_seen) > 0, "stale presents detected while behind");
|
||||
CHECK(atomic_load(&stale_seen) > 0, "presents one frame behind seen");
|
||||
CHECK(atomic_load(&far_behind_seen) > 0, "presents two frames behind seen (skip case)");
|
||||
CHECK(atomic_load(&exec_errors) == 0, "order kept under backpressure");
|
||||
rt_stop();
|
||||
|
||||
|
||||
Reference in New Issue
Block a user