[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.
This commit is contained in:
Geoffrey McRae
2026-08-05 11:52:44 +10:00
parent 2c74f4a0f9
commit 12f6485e52
4 changed files with 25 additions and 25 deletions

View File

@@ -262,11 +262,11 @@ void frameScheduler_feedback(uint64_t frameSerial, uint32_t generation,
int64_t error = measuredPhase > FRAME_SCHEDULER_TARGET_SLACK_NS ? int64_t error = measuredPhase > FRAME_SCHEDULER_TARGET_SLACK_NS ?
(int64_t)(measuredPhase - FRAME_SCHEDULER_TARGET_SLACK_NS) : (int64_t)(measuredPhase - FRAME_SCHEDULER_TARGET_SLACK_NS) :
-(int64_t)(FRAME_SCHEDULER_TARGET_SLACK_NS - measuredPhase); -(int64_t)(FRAME_SCHEDULER_TARGET_SLACK_NS - measuredPhase);
error %= period; const int64_t limit = period / 2;
if (error > period / 2) if (error > limit)
error -= period; error = limit;
else if (error < -period / 2) else if (error < -limit)
error += period; error = -limit;
if (!l_frameScheduler.feedbackSamples) if (!l_frameScheduler.feedbackSamples)
l_frameScheduler.phaseError = error; l_frameScheduler.phaseError = error;

View File

@@ -434,17 +434,12 @@ static void frameTimingFinishRender(const LG_RendererFrameTiming * timing,
} }
LG_UNLOCK(l_frameTiming.lock); 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( frameScheduler_feedback(
feedbackFrameSerial, feedbackGeneration, measuredPhase); feedbackFrameSerial, feedbackGeneration,
prepareStart - feedbackQueueStart);
} }
} }

View File

@@ -98,11 +98,11 @@ void lgFrameSchedulerFeedback(LGFrameScheduler * scheduler,
(int64_t)(measuredPhase - FRAME_SCHEDULER_TARGET_SLACK_NS) : (int64_t)(measuredPhase - FRAME_SCHEDULER_TARGET_SLACK_NS) :
-(int64_t)(FRAME_SCHEDULER_TARGET_SLACK_NS - measuredPhase); -(int64_t)(FRAME_SCHEDULER_TARGET_SLACK_NS - measuredPhase);
const int64_t period = (int64_t)scheduler->period; const int64_t period = (int64_t)scheduler->period;
error %= period; const int64_t limit = period / 2;
if (error > period / 2) if (error > limit)
error -= period; error = limit;
else if (error < -period / 2) else if (error < -limit)
error += period; error = -limit;
if (!scheduler->feedbackSamples) if (!scheduler->feedbackSamples)
scheduler->phaseError = error; scheduler->phaseError = error;

View File

@@ -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) if (status == LGMP_OK)
{ {
LGMPMessage msg; LGMPMessage msg;
if (lgmpClientProcess(this->frameQueue, &msg) == LGMP_OK) if (lgmpClientProcess(this->frameQueue, &msg) == LGMP_OK)
{ {
const KVMFRFrame * frame = (const KVMFRFrame *)msg.mem; const KVMFRFrame * frame = (const KVMFRFrame *)msg.mem;
lgFrameSchedulerObserveFrame(&this->frameScheduler, const FrameBuffer * fb =
frame->frameSerial, msg.udata, now); (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; const KVMFRFrame * frame = (KVMFRFrame *)msg.mem;
lgFrameSchedulerObserveFrame(&this->frameScheduler,
frame->frameSerial, msg.udata, tickTime);
lgFrameSchedulerFeedback(&this->frameScheduler, lgFrameSchedulerFeedback(&this->frameScheduler,
frame->frameSerial, msg.udata, tickTime); frame->frameSerial, msg.udata, tickTime);