video: stop Scanline Sync latching itself off permanently

Scanline Sync stopped working ~121 frames into every session and never
resumed. Instrumenting video_driver_scanline_before_frame() showed the
spin in after_frame() running for the first ~31 frames and then never
again - 2400 consecutive frames with zero polls, while the menu still
reported the feature as on.

Two faults, compounding.

The disable tested "scanline_hold &&", so it fired only while a hold
was already in progress, and re-armed scanline_hold to refresh_rate / 2
on every frame of the hold it had just set. The tail decrements by one,
so hold sat permanently one below that value - the diagnostic reported
hold 59 in every window, never anything else. It therefore never
reached zero, the "Allow change" block is gated on !scanline_hold, and
scanline_next stayed at 0. after_frame() computes
scanline_target = 0 from that and takes its "scanline_target <= 0 ->
wait = false" exit, so nothing ever waited again.

The way in was frame_time_deviation, which compares
frame_time_samples[frame_time_count - 1] against
frame_time_target * 0.33f. That ring starts zeroed, and under
video_frame_time_sample_gated a sample failing the clean predicate is
not written and the count is not advanced, so the read can land on a
slot that was never populated. Zero satisfies the comparison, so an
unwritten sample reads as a permanent deviation. The diagnostic
confirmed it: the disable fired on 120 of 120 frames while
core_run_time exceeded its threshold on only 1.

Decide while the sync is running (!scanline_hold) so the countdown
completes uninterrupted, and gate the deviation test on frame_time > 0.
The two severities the old code intended but could not reach are now
distinguished: a core structurally over budget takes the long hold and
surrenders the target, a transient deviation takes the short one and
keeps it.

Verified on a 4K120 display: hold now shows 0..59 rather than pinned at
59, tune fires, scanline_next reaches 2156, and the spin runs again.

Two follow-ups this makes visible, neither addressed here. The short
"rearm" path still pre-empts the recovery on the frame hold expires, so
a trickle of deviations stalls tuning for long stretches. And with the
spin actually running it makes 25-41 D3DKMTGetScanLine calls per frame
at ~223 us each - 5.5 to 9.2 ms against an 8.31 ms frame period.
This commit is contained in:
LibretroAdmin authored and libretroadmin committed 2026-09-02 00:57:18 +02:00
1 parent d958315502
commit 0966f2f32d
1 file changed
+30 -6
+30 -6
View File
@@ -6280,15 +6280,39 @@ VIDEO_NOINLINE static void video_driver_scanline_before_frame(video_driver_state
uint16_t frame_time_index = video_st->frame_time_count & (MEASURE_FRAME_TIME_SAMPLES_COUNT - 1);
uint16_t sample_index = (uint16_t)((frame_time_index - 1) & (MEASURE_FRAME_TIME_SAMPLES_COUNT - 1));
retro_time_t frame_time = video_st->frame_time_samples[sample_index];
bool frame_time_deviation = frame_time >= frame_time_target * 1.66f || frame_time <= frame_time_target * 0.33f;
/* frame_time > 0 gates the comparison: the ring starts zeroed, and
* under video_frame_time_sample_gated a sample that fails the
* clean predicate is not written and frame_time_count is not
* advanced, so this can read an unwritten slot. Zero satisfies
* "<= target * 0.33" and would register as a permanent deviation. */
bool frame_time_deviation = frame_time > 0
&& ( frame_time >= frame_time_target * 1.66f
|| frame_time <= frame_time_target * 0.33f);
if (scanline_hold && (frame_time_deviation || core_run_time >= frame_time_target - 3000))
/* Only decide while the sync is actually running (hold == 0).
* This used to test "scanline_hold &&", so it re-fired on every
* frame of the hold it had just armed - hold was reset to
* refresh_rate / 2 and decremented by one at the tail, pinning it
* one below that value forever. It could therefore never reach
* zero, the "Allow change" block below is gated on !scanline_hold,
* and scanline_next stayed at 0, which makes
* video_driver_scanline_after_frame() take its
* "scanline_target <= 0 -> wait = false" exit every frame. Once
* the first deviation latched it, Scanline Sync did nothing at all
* for the rest of the session. */
if (!scanline_hold)
{
scanline_next = 0;
scanline_hold = refresh_rate / 2;
if (core_run_time >= frame_time_target - 3000)
{
/* Structural: the core cannot fit in the budget. Long hold,
* and give up the target so nothing waits meanwhile. */
scanline_next = 0;
scanline_hold = (int16_t)(refresh_rate / 2);
}
else if (frame_time_deviation)
/* Transient: ride it out briefly, keep the target. */
scanline_hold += 3;
}
else if (!scanline_hold && frame_time_deviation)
scanline_hold += 3;
}
/* Shift overflow */