[client/idd] align frame delivery to presentation

This commit is contained in:
Geoffrey McRae
2026-08-04 13:15:37 +10:00
parent 32ced2f24a
commit 97e7f439c2
7 changed files with 211 additions and 71 deletions

View File

@@ -31,6 +31,7 @@
#define FRAME_SCHEDULER_LEASE_MS 1000U
#define FRAME_SCHEDULER_RENEW_NS 250000000ULL
#define FRAME_SCHEDULER_FEEDBACK_NS 50000000ULL
#define FRAME_SCHEDULER_TARGET_SLACK_NS 500000ULL
#define FRAME_SCHEDULER_MIN_PERIOD_NS 2000000ULL
#define FRAME_SCHEDULER_MAX_PERIOD_NS 1000000000ULL
@@ -40,13 +41,18 @@ static struct
{
LG_Lock lock;
bool supported;
bool active;
bool controlPending;
uint32_t generation;
uint64_t period;
uint64_t lastSend;
uint64_t lastCadence;
_Atomic(bool) supported;
_Atomic(bool) active;
bool controlPending;
_Atomic(uint32_t) generation;
_Atomic(uint64_t) period;
uint64_t lastSend;
uint64_t lastCadence;
int64_t phaseError;
uint32_t feedbackFrameSerial;
unsigned feedbackSamples;
bool feedbackDirty;
LG_TransportControlToken controlToken;
@@ -79,7 +85,11 @@ static uint64_t fallbackPeriod(void)
return 0;
qsort(samples, count, sizeof(*samples), compareU64);
return samples[count / 2];
const uint64_t period = samples[count / 2];
if (samples[count * 3 / 4] - samples[count / 4] > period / 20)
return 0;
return period;
}
static uint64_t presentationPeriod(void)
@@ -89,7 +99,7 @@ static uint64_t presentationPeriod(void)
g_state.ds->getFramePeriod(&period))
return period;
return g_state.jitRender ? fallbackPeriod() : 0;
return fallbackPeriod();
}
static bool controlReady(void)
@@ -112,14 +122,23 @@ static bool sendSchedule(LG_TransportFrameScheduleFlags flags,
if (!controlReady())
return false;
int64_t phaseError;
uint32_t feedbackFrameSerial;
LG_LOCK(l_frameScheduler.lock);
phaseError = l_frameScheduler.phaseError;
feedbackFrameSerial = l_frameScheduler.feedbackFrameSerial;
LG_UNLOCK(l_frameScheduler.lock);
const LG_TransportControl control = {
.type = LG_TRANSPORT_CONTROL_FRAME_SCHEDULE,
.frameSchedule = {
.generation = l_frameScheduler.generation,
.flags = flags,
.period = period,
.targetSlack = FRAME_SCHEDULER_TARGET_SLACK_NS,
.lease = FRAME_SCHEDULER_LEASE_MS,
.generation = l_frameScheduler.generation,
.flags = flags,
.period = period,
.targetSlack = FRAME_SCHEDULER_TARGET_SLACK_NS,
.phaseError = phaseError,
.feedbackFrameSerial = feedbackFrameSerial,
.lease = FRAME_SCHEDULER_LEASE_MS,
},
};
@@ -134,6 +153,10 @@ static bool sendSchedule(LG_TransportFrameScheduleFlags flags,
}
l_frameScheduler.controlPending = true;
LG_LOCK(l_frameScheduler.lock);
if (l_frameScheduler.feedbackFrameSerial == feedbackFrameSerial)
l_frameScheduler.feedbackDirty = false;
LG_UNLOCK(l_frameScheduler.lock);
return true;
}
@@ -160,9 +183,13 @@ void frameScheduler_start(LG_TransportFeatureFlags features)
++l_frameScheduler.generation;
LG_LOCK(l_frameScheduler.lock);
l_frameScheduler.renderSampleIndex = 0;
l_frameScheduler.renderSampleCount = 0;
l_frameScheduler.lastRender = 0;
l_frameScheduler.renderSampleIndex = 0;
l_frameScheduler.renderSampleCount = 0;
l_frameScheduler.lastRender = 0;
l_frameScheduler.phaseError = 0;
l_frameScheduler.feedbackFrameSerial = 0;
l_frameScheduler.feedbackSamples = 0;
l_frameScheduler.feedbackDirty = false;
LG_UNLOCK(l_frameScheduler.lock);
}
@@ -213,9 +240,16 @@ void frameScheduler_update(void)
l_frameScheduler.period =
(l_frameScheduler.period * 7 + period) / 8;
if (l_frameScheduler.active && !reset &&
now - l_frameScheduler.lastSend < FRAME_SCHEDULER_RENEW_NS)
return;
if (l_frameScheduler.active && !reset)
{
LG_LOCK(l_frameScheduler.lock);
const bool feedbackDirty = l_frameScheduler.feedbackDirty;
LG_UNLOCK(l_frameScheduler.lock);
const uint64_t interval = feedbackDirty ?
FRAME_SCHEDULER_FEEDBACK_NS : FRAME_SCHEDULER_RENEW_NS;
if (now - l_frameScheduler.lastSend < interval)
return;
}
LG_TransportFrameScheduleFlags flags =
LG_TRANSPORT_FRAME_SCHEDULE_ACTIVE;
@@ -231,9 +265,6 @@ void frameScheduler_update(void)
void frameScheduler_observeRender(uint64_t timestamp)
{
if (!g_state.jitRender)
return;
LG_LOCK(l_frameScheduler.lock);
if (l_frameScheduler.lastRender &&
timestamp > l_frameScheduler.lastRender)
@@ -249,3 +280,40 @@ void frameScheduler_observeRender(uint64_t timestamp)
l_frameScheduler.lastRender = timestamp;
LG_UNLOCK(l_frameScheduler.lock);
}
void frameScheduler_feedback(uint64_t frameSerial, uint32_t generation,
uint64_t queueStart, uint64_t prepareStart)
{
if (!generation || !queueStart || prepareStart < queueStart)
return;
const uint64_t slack = prepareStart - queueStart;
LG_LOCK(l_frameScheduler.lock);
if (!l_frameScheduler.supported || !l_frameScheduler.active ||
generation != l_frameScheduler.generation)
{
LG_UNLOCK(l_frameScheduler.lock);
return;
}
int64_t error = slack > FRAME_SCHEDULER_TARGET_SLACK_NS ?
(int64_t)(slack - FRAME_SCHEDULER_TARGET_SLACK_NS) :
-(int64_t)(FRAME_SCHEDULER_TARGET_SLACK_NS - slack);
const int64_t limit = (int64_t)(l_frameScheduler.period / 2);
if (error > limit)
error = limit;
else if (error < -limit)
error = -limit;
if (!l_frameScheduler.feedbackSamples)
l_frameScheduler.phaseError = error;
else
l_frameScheduler.phaseError =
(l_frameScheduler.phaseError * 7 + error) / 8;
if (l_frameScheduler.feedbackSamples < 32)
++l_frameScheduler.feedbackSamples;
l_frameScheduler.feedbackFrameSerial = (uint32_t)frameSerial;
l_frameScheduler.feedbackDirty = true;
LG_UNLOCK(l_frameScheduler.lock);
}

View File

@@ -31,5 +31,7 @@ void frameScheduler_start(LG_TransportFeatureFlags features);
void frameScheduler_stop(void);
void frameScheduler_update(void);
void frameScheduler_observeRender(uint64_t timestamp);
void frameScheduler_feedback(uint64_t frameSerial, uint32_t generation,
uint64_t queueStart, uint64_t prepareStart);
#endif

View File

@@ -212,6 +212,8 @@ enum FrameTimingReady
struct FrameTimingRecord
{
LG_RendererFrameToken token;
uint64_t frameSerial;
uint32_t scheduleGeneration;
unsigned readyMask;
bool producerValid;
@@ -323,7 +325,8 @@ static void frameTimingCancel(LG_RendererFrameToken token)
});
}
static void frameTimingQueue(LG_RendererFrameToken token, uint64_t importTime,
static void frameTimingQueue(LG_RendererFrameToken token, uint64_t frameSerial,
uint32_t scheduleGeneration, uint64_t importTime,
uint64_t importWaitTime, uint64_t dispatchStart, uint64_t queueStart)
{
INTERLOCKED_SECTION(l_frameTiming.lock, {
@@ -333,10 +336,12 @@ static void frameTimingQueue(LG_RendererFrameToken token, uint64_t importTime,
const uint64_t elapsed = queueStart > dispatchStart ?
queueStart - dispatchStart : 0;
const uint64_t accounted = importTime + importWaitTime;
record->importTime = importTime;
record->importWaitTime = importWaitTime;
record->dispatchTime = elapsed > accounted ? elapsed - accounted : 0;
record->queueStart = queueStart;
record->importTime = importTime;
record->importWaitTime = importWaitTime;
record->dispatchTime = elapsed > accounted ? elapsed - accounted : 0;
record->queueStart = queueStart;
record->frameSerial = frameSerial;
record->scheduleGeneration = scheduleGeneration;
if (record->timestamp < queueStart)
record->timestamp = queueStart;
}
@@ -382,44 +387,55 @@ static void frameTimingFinishFrame(LG_RendererFrameToken token,
static void frameTimingFinishRender(const LG_RendererFrameTiming * timing,
uint64_t prepareStart, uint64_t prepareTime, uint64_t timestamp)
{
INTERLOCKED_SECTION(l_frameTiming.lock, {
if (l_frameTiming.retireToken <= timing->frameToken)
l_frameTiming.retireToken = timing->frameToken + 1;
uint64_t feedbackFrameSerial = 0;
uint64_t feedbackQueueStart = 0;
uint32_t feedbackGeneration = 0;
struct FrameTimingRecord * record = frameTimingRecord(timing->frameToken);
if (record->token == timing->frameToken)
LG_LOCK(l_frameTiming.lock);
if (l_frameTiming.retireToken <= timing->frameToken)
l_frameTiming.retireToken = timing->frameToken + 1;
struct FrameTimingRecord * record = frameTimingRecord(timing->frameToken);
if (record->token == timing->frameToken)
{
record->prepareStart = prepareStart;
record->prepareTime = prepareTime;
if (record->timestamp < timestamp)
record->timestamp = timestamp;
record->setupTime = timing->setupTime;
record->effectsTime = timing->effectsTime;
record->desktopTime = timing->desktopTime;
record->composeTime = timing->composeTime;
record->swapTime = timing->swapTime;
record->readyMask |= FRAME_TIMING_RENDER_READY;
feedbackFrameSerial = record->frameSerial;
feedbackGeneration = record->scheduleGeneration;
feedbackQueueStart = record->queueStart;
if (unlikely(
l_frameTiming.publishCount == FRAME_TIMING_RECORD_COUNT))
{
record->prepareStart = prepareStart;
record->prepareTime = prepareTime;
if (record->timestamp < timestamp)
record->timestamp = timestamp;
record->setupTime = timing->setupTime;
record->effectsTime = timing->effectsTime;
record->desktopTime = timing->desktopTime;
record->composeTime = timing->composeTime;
record->swapTime = timing->swapTime;
record->readyMask |= FRAME_TIMING_RENDER_READY;
if (unlikely(
l_frameTiming.publishCount == FRAME_TIMING_RECORD_COUNT))
if (!l_frameTiming.overflowWarning)
{
if (!l_frameTiming.overflowWarning)
{
DEBUG_WARN("Frame timing publish queue exhausted; sample lost");
l_frameTiming.overflowWarning = true;
}
*record = (struct FrameTimingRecord) {};
}
else
{
l_frameTiming.publishToken[l_frameTiming.publishWrite] =
timing->frameToken;
l_frameTiming.publishWrite =
(l_frameTiming.publishWrite + 1) % FRAME_TIMING_RECORD_COUNT;
++l_frameTiming.publishCount;
DEBUG_WARN("Frame timing publish queue exhausted; sample lost");
l_frameTiming.overflowWarning = true;
}
*record = (struct FrameTimingRecord) {};
}
});
else
{
l_frameTiming.publishToken[l_frameTiming.publishWrite] =
timing->frameToken;
l_frameTiming.publishWrite =
(l_frameTiming.publishWrite + 1) % FRAME_TIMING_RECORD_COUNT;
++l_frameTiming.publishCount;
}
}
LG_UNLOCK(l_frameTiming.lock);
frameScheduler_feedback(feedbackFrameSerial, feedbackGeneration,
feedbackQueueStart, prepareStart);
}
static void frameTimingPublishReady(void)
@@ -1083,8 +1099,9 @@ int main_frameThread(void * unused)
atomic_store_explicit(&l_testFrameSerial, frame.serial,
memory_order_release);
#endif
frameTimingQueue(frameToken, g_state.frameImportTime,
g_state.frameImportWaitTime, dispatchStart, queueStart);
frameTimingQueue(frameToken, frame.serial, frame.scheduleGeneration,
g_state.frameImportTime, g_state.frameImportWaitTime,
dispatchStart, queueStart);
if (g_state.jitRender)
{