Account for full refresh-cycle GUI acknowledgement latency

This commit is contained in:
evgeny committed 2026-10-20 00:12:26 +02:00
1 parent d8d9f7c4a4
commit 0a73d140e6
2 files changed
+52 -11

No files matched your search

+3 -1
View File
@@ -170,7 +170,9 @@ void call_video_playout_presented(struct call_video_playout *p, int64_t pts, int
const struct call_video_playout_frame *f = &p->pending;
int64_t av = p->pending_av, lead = now - p->selected_us;
// GUI freeze не становится постоянным опережением dispatch для последующих кадров.
if (lead <= f->interval_us / 2) p->lead_us = (p->lead_us * 7 + lead) / 8;
// ACK TextureView/композитора может занимать целый период экрана. Его надо вычесть
// из dispatch следующего кадра; иначе один ACK на 60 Hz искусственно снижает FPS.
if (lead <= f->interval_us + f->interval_us / 4 + 1000) p->lead_us = (p->lead_us * 7 + lead) / 8;
int64_t error = now - p->head_show_us;
if (!p->anchored || error > f->interval_us / 2 || error < -(int64_t)f->interval_us / 2) p->last_deadline = now;
else {
+49 -10
View File
@@ -16,33 +16,35 @@ static void simulate(unsigned pause_ms, int cpu, int64_t quantum, int accelerate
arrival = origin + 1000000 + pause_ms * 1000ll;
frames[i] = (struct call_video_playout_frame){origin + i * 33333ll, arrival, 33333};
}
unsigned selected = 0, shown = 0, available = 0, accelerated = 0;
unsigned selected = 0, shown = 0, skipped = 0, available = 0, accelerated = 0;
int64_t last_show = 0, ack_at = 0, recovered_lag = INT64_MAX, audio_pts = origin;
for (int64_t now = origin; now < origin + 22000000 && shown < COUNT; now += quantum) {
for (int64_t now = origin; now < origin + 22000000 && shown + skipped < COUNT; now += quantum) {
uint32_t rate = accelerated_audio && now >= origin + 1500000 && now < origin + 2500000 ? 1100000 : 1000000;
if (now > origin) audio_pts += quantum * rate / 1000000;
if (cpu && now >= origin + 1000000 && now < origin + 1000000 + pause_ms * 1000ll) continue;
while (available < COUNT && frames[available].received_us <= now) ++available;
if (ack_at && now >= ack_at) {
call_video_playout_presented(&p, frames[shown].pts_us, now);
call_video_playout_presented(&p, frames[selected - 1].pts_us, now);
CHECK(!last_show || now > last_show); last_show = now; ++shown; ack_at = 0;
}
struct call_clock_snapshot audio = {.pts_us = audio_pts, .rate_ppm = rate,
.valid = !(now >= origin + 4000000 && now < origin + 4100000)};
const struct call_video_playout_frame *f = selected < available ? frames + selected : NULL;
int action = call_video_playout_poll(&p, f, now, &audio, available - shown, 1);
if (now >= origin + 6000000 && shown) recovered_lag = audio.pts_us - frames[shown - 1].pts_us;
int action = call_video_playout_poll(&p, f, now, &audio, available - shown - skipped, 1);
if (now >= origin + 6000000 && shown) recovered_lag = audio.pts_us - p.last_show_pts;
if (action == CALL_VIDEO_PLAYOUT_WAIT) continue;
if (action == CALL_VIDEO_PLAYOUT_DROP_LATE) { ++selected; ++skipped; continue; }
CHECK(action == CALL_VIDEO_PLAYOUT_SHOW && audio.valid && !ack_at);
CHECK(f->pts_us - audio.pts_us <= 40000);
if (p.speed_ppm > 1050000) ++accelerated;
++selected; ack_at = now + 3000; // Не мгновенный GUI: кадр остаётся в общем запасе до ACK.
CHECK(call_video_playout_poll(&p, selected < available ? frames + selected : NULL, now, &audio,
available - shown, 1) == CALL_VIDEO_PLAYOUT_WAIT);
available - shown - skipped, 1) == CALL_VIDEO_PLAYOUT_WAIT);
}
DEBUG_INFO(DEBUG_CATEGORY_CALL, "presentation recovery pause_ms=%u cpu=%d quantum_us=%lld audio_fast=%d shown=%u/%u lag_us=%lld",
pause_ms, cpu, (long long)quantum, accelerated_audio, shown, COUNT, (long long)recovered_lag);
CHECK(shown == COUNT && accelerated);
DEBUG_INFO(DEBUG_CATEGORY_CALL, "presentation recovery pause_ms=%u cpu=%d quantum_us=%lld audio_fast=%d shown=%u skipped=%u total=%u lag_us=%lld",
pause_ms, cpu, (long long)quantum, accelerated_audio, shown, skipped, COUNT, (long long)recovered_lag);
CHECK(shown + skipped == COUNT && accelerated);
if (quantum < 2000 || pause_ms <= 100) CHECK(skipped == 0);
CHECK(recovered_lag < (quantum < 2000 ? 100000 : 220000));
}
@@ -129,6 +131,42 @@ static void render_latency_change(void) {
DEBUG_INFO(DEBUG_CATEGORY_CALL, "PASS render latency 3->9ms: phase_us=%lld; no accumulated per-frame GUI delay", (long long)phase);
}
/* Вход 60 fps, показ ограничен 60 Hz: после паузы ускориться физически нельзя.
* Каждый вход считается decoded; теряется лишь показ, ACK остаётся единственным владельцем часов. */
static void limited_display(unsigned pause_ms) {
enum { COUNT = 600 };
struct call_video_playout p; call_video_playout_init(&p, 14);
unsigned consumed = 0, shown = 0, skipped = 0;
int64_t ack_at = 0, pending_pts = 0, recovered = INT64_MAX;
for (int64_t now = origin; now < origin + 12000000 && consumed < COUNT; now += 1000) {
unsigned available = (unsigned)((now - origin) * 60 / 1000000) + 1;
if (available > COUNT) available = COUNT;
if (ack_at && now >= ack_at) {
call_video_playout_presented(&p, pending_pts, now); ++shown; ack_at = 0;
}
if (now >= origin + 1000000 && now < origin + 1000000 + pause_ms * 1000ll) continue;
if (consumed >= available) continue;
struct call_video_playout_frame f = {origin + consumed * 1000000ll / 60, origin, 16666};
struct call_clock_snapshot audio = {.pts_us = now, .rate_ppm = 1000000, .valid = 1};
const int64_t before = p.last_show_us, deadline = p.last_deadline;
int action = call_video_playout_poll(&p, &f, now, &audio, available - consumed + (ack_at != 0), 1);
if (action == CALL_VIDEO_PLAYOUT_SHOW) {
++consumed; pending_pts = f.pts_us;
ack_at = origin + ((now - origin) * 60 / 1000000 + 1) * 1000000 / 60;
} else if (action == CALL_VIDEO_PLAYOUT_DROP_LATE) {
++consumed; ++skipped;
CHECK(p.last_show_us == before && p.last_deadline == deadline && !p.awaiting);
} else CHECK(action == CALL_VIDEO_PLAYOUT_WAIT);
if (now > origin + 6000000) recovered = now - f.pts_us;
}
if (ack_at) { call_video_playout_presented(&p, pending_pts, ack_at); ++shown; }
DEBUG_INFO(DEBUG_CATEGORY_CALL, "60Hz recovery pause_ms=%u decoded=%u shown=%u skipped=%u lag_us=%lld",
pause_ms, consumed, shown, skipped, (long long)recovered);
CHECK(consumed == COUNT && shown + skipped == COUNT);
if (pause_ms <= 100) CHECK(skipped == 0);
else CHECK(skipped > 0 && recovered < 180000);
}
int main(void) {
debug_config_init(); debug_set_level(DEBUG_LEVEL_INFO);
const unsigned pauses[] = {50, 100, 200, 500};
@@ -137,5 +175,6 @@ int main(void) {
simulate(pauses[i], cpu, 1000, 0);
simulate(pauses[i], cpu, 15625, 1);
}
presentation_deadline(); queued_capture_gap(); slowing_with_queue(); render_latency_change(); return 0;
presentation_deadline(); queued_capture_gap(); slowing_with_queue(); render_latency_change();
limited_display(50); limited_display(100); limited_display(500); return 0;
}