From 12f6485e528f8d1dba3d89f6de502be2e6cd3722 Mon Sep 17 00:00:00 2001 From: Geoffrey McRae Date: Wed, 5 Aug 2026 11:52:44 +1000 Subject: [PATCH] [client/obs] scheduler: correct phase feedback Only report the client queue lead when JIT rendering provides a valid measurement. Clamp linear phase errors instead of wrapping them into the opposite direction at half of a refresh period. Timestamp OBS frames after the shared framebuffer becomes ready. --- client/src/frame_scheduler.c | 10 +++++----- client/src/main.c | 13 ++++--------- obs/frame_scheduler.c | 10 +++++----- obs/lg.c | 17 +++++++++++------ 4 files changed, 25 insertions(+), 25 deletions(-) diff --git a/client/src/frame_scheduler.c b/client/src/frame_scheduler.c index b6a0f8dc..2e1c3748 100644 --- a/client/src/frame_scheduler.c +++ b/client/src/frame_scheduler.c @@ -262,11 +262,11 @@ void frameScheduler_feedback(uint64_t frameSerial, uint32_t generation, int64_t error = measuredPhase > FRAME_SCHEDULER_TARGET_SLACK_NS ? (int64_t)(measuredPhase - FRAME_SCHEDULER_TARGET_SLACK_NS) : -(int64_t)(FRAME_SCHEDULER_TARGET_SLACK_NS - measuredPhase); - error %= period; - if (error > period / 2) - error -= period; - else if (error < -period / 2) - error += period; + const int64_t limit = period / 2; + if (error > limit) + error = limit; + else if (error < -limit) + error = -limit; if (!l_frameScheduler.feedbackSamples) l_frameScheduler.phaseError = error; diff --git a/client/src/main.c b/client/src/main.c index 1e2cf021..716f6da5 100644 --- a/client/src/main.c +++ b/client/src/main.c @@ -434,17 +434,12 @@ static void frameTimingFinishRender(const LG_RendererFrameTiming * timing, } LG_UNLOCK(l_frameTiming.lock); - if (feedbackFrameSerial && feedbackGeneration) + if (g_state.jitRender && feedbackFrameSerial && feedbackGeneration && + feedbackQueueStart && prepareStart >= feedbackQueueStart) { - uint64_t measuredPhase = timing->swapTime; - if (g_state.jitRender) - { - if (!feedbackQueueStart || prepareStart < feedbackQueueStart) - return; - measuredPhase = prepareStart - feedbackQueueStart; - } frameScheduler_feedback( - feedbackFrameSerial, feedbackGeneration, measuredPhase); + feedbackFrameSerial, feedbackGeneration, + prepareStart - feedbackQueueStart); } } diff --git a/obs/frame_scheduler.c b/obs/frame_scheduler.c index 0cd57426..4627d641 100644 --- a/obs/frame_scheduler.c +++ b/obs/frame_scheduler.c @@ -98,11 +98,11 @@ void lgFrameSchedulerFeedback(LGFrameScheduler * scheduler, (int64_t)(measuredPhase - FRAME_SCHEDULER_TARGET_SLACK_NS) : -(int64_t)(FRAME_SCHEDULER_TARGET_SLACK_NS - measuredPhase); const int64_t period = (int64_t)scheduler->period; - error %= period; - if (error > period / 2) - error -= period; - else if (error < -period / 2) - error += period; + const int64_t limit = period / 2; + if (error > limit) + error = limit; + else if (error < -limit) + error = -limit; if (!scheduler->feedbackSamples) scheduler->phaseError = error; diff --git a/obs/lg.c b/obs/lg.c index 496f79cf..45c2110f 100644 --- a/obs/lg.c +++ b/obs/lg.c @@ -505,15 +505,22 @@ static void * frameThread(void * data) } } - const uint64_t now = os_gettime_ns(); + uint64_t now = os_gettime_ns(); if (status == LGMP_OK) { LGMPMessage msg; if (lgmpClientProcess(this->frameQueue, &msg) == LGMP_OK) { - const KVMFRFrame * frame = (const KVMFRFrame *)msg.mem; - lgFrameSchedulerObserveFrame(&this->frameScheduler, - frame->frameSerial, msg.udata, now); + const KVMFRFrame * frame = (const KVMFRFrame *)msg.mem; + const FrameBuffer * fb = + (const FrameBuffer *)((const uint8_t *)frame + frame->offset); + if (framebuffer_wait( + fb, (size_t)frame->dataHeight * frame->pitch)) + { + now = os_gettime_ns(); + lgFrameSchedulerObserveFrame(&this->frameScheduler, + frame->frameSerial, msg.udata, now); + } } } @@ -1287,8 +1294,6 @@ static void lgVideoTick(void * data, float seconds) } const KVMFRFrame * frame = (KVMFRFrame *)msg.mem; - lgFrameSchedulerObserveFrame(&this->frameScheduler, - frame->frameSerial, msg.udata, tickTime); lgFrameSchedulerFeedback(&this->frameScheduler, frame->frameSerial, msg.udata, tickTime);