mirror of
https://github.com/gnif/LookingGlass.git
synced 2026-08-09 08:41:31 +00:00
Keep one accepted frame-schedule control in flight until LGMP reports that its serial has been consumed. Retain ambiguous tokens across pointer queue recovery instead of retransmitting RESET or IMMEDIATE messages. Back off acknowledgement polling and definite enqueue failures. Use modular serial comparison so acknowledgement remains correct across rollover.
399 lines
12 KiB
C
399 lines
12 KiB
C
/**
|
|
* Looking Glass
|
|
* Copyright © 2017-2026 The Looking Glass Authors
|
|
* https://looking-glass.io
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify it
|
|
* under the terms of the GNU General Public License as published by the Free
|
|
* Software Foundation; either version 2 of the License, or (at your option)
|
|
* any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful, but WITHOUT
|
|
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
|
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
|
* more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License along
|
|
* with this program; if not, write to the Free Software Foundation, Inc., 59
|
|
* Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
|
*/
|
|
|
|
#include "frame_scheduler.h"
|
|
#include "main.h"
|
|
|
|
#include "common/debug.h"
|
|
#include "common/locking.h"
|
|
#include "common/time.h"
|
|
|
|
#include <stdbool.h>
|
|
#include <string.h>
|
|
|
|
#define FRAME_SCHEDULER_LEASE_MS 1000U
|
|
#define FRAME_SCHEDULER_RENEW_NS 250000000ULL
|
|
#define FRAME_SCHEDULER_FEEDBACK_NS 50000000ULL
|
|
#define FRAME_SCHEDULER_CADENCE_GRACE_NS 500000000ULL
|
|
#define FRAME_SCHEDULER_TARGET_SLACK_NS 500000ULL
|
|
#define FRAME_SCHEDULER_MIN_PERIOD_NS 2000000ULL
|
|
#define FRAME_SCHEDULER_MAX_PERIOD_NS 1000000000ULL
|
|
#define FRAME_SCHEDULER_CONTROL_RETRY_NS 10000000ULL
|
|
#define FRAME_SCHEDULER_CONTROL_RETRY_MAX 250000000ULL
|
|
|
|
static struct
|
|
{
|
|
LG_Lock lock;
|
|
|
|
_Atomic(bool) supported;
|
|
_Atomic(bool) active;
|
|
bool controlPending;
|
|
bool controlFaulted;
|
|
bool enqueueFaulted;
|
|
bool immediatePending;
|
|
_Atomic(uint32_t) generation;
|
|
_Atomic(uint64_t) period;
|
|
uint64_t lastSend;
|
|
uint64_t nextControlCheck;
|
|
uint64_t controlRetryDelay;
|
|
_Atomic(uint64_t) lastCadence;
|
|
|
|
int64_t phaseError;
|
|
uint32_t feedbackFrameSerial;
|
|
uint32_t feedbackScheduleEpoch;
|
|
uint32_t feedbackDeadlineSerial;
|
|
unsigned feedbackSamples;
|
|
bool feedbackDirty;
|
|
|
|
LG_TransportControlToken controlToken;
|
|
}
|
|
l_frameScheduler;
|
|
|
|
static uint64_t presentationPeriod(void)
|
|
{
|
|
uint64_t period = 0;
|
|
if (g_state.ds->getFramePeriod &&
|
|
g_state.ds->getFramePeriod(&period))
|
|
return period;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void controlBackoff(uint64_t now)
|
|
{
|
|
uint64_t delay = l_frameScheduler.controlRetryDelay;
|
|
if (!delay)
|
|
delay = FRAME_SCHEDULER_CONTROL_RETRY_NS;
|
|
|
|
l_frameScheduler.nextControlCheck = now + delay;
|
|
l_frameScheduler.controlRetryDelay =
|
|
delay < FRAME_SCHEDULER_CONTROL_RETRY_MAX / 2 ?
|
|
delay * 2 : FRAME_SCHEDULER_CONTROL_RETRY_MAX;
|
|
}
|
|
|
|
static bool controlReady(uint64_t now)
|
|
{
|
|
if (!l_frameScheduler.controlPending)
|
|
return now >= l_frameScheduler.nextControlCheck;
|
|
|
|
if (now < l_frameScheduler.nextControlCheck)
|
|
return false;
|
|
|
|
const LG_TransportStatus status = g_state.transportOps->controlStatus(
|
|
g_state.transport, l_frameScheduler.controlToken);
|
|
if (status != LG_TRANSPORT_OK)
|
|
{
|
|
if (status != LG_TRANSPORT_UNAVAILABLE &&
|
|
!l_frameScheduler.controlFaulted)
|
|
{
|
|
DEBUG_WARN(
|
|
"Frame-schedule acknowledgement failed with status %d", status);
|
|
l_frameScheduler.controlFaulted = true;
|
|
}
|
|
controlBackoff(now);
|
|
return false;
|
|
}
|
|
|
|
l_frameScheduler.controlPending = false;
|
|
l_frameScheduler.nextControlCheck = 0;
|
|
l_frameScheduler.controlRetryDelay =
|
|
FRAME_SCHEDULER_CONTROL_RETRY_NS;
|
|
if (l_frameScheduler.controlFaulted)
|
|
{
|
|
l_frameScheduler.controlFaulted = false;
|
|
l_frameScheduler.immediatePending = true;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
static bool sendSchedule(LG_TransportFrameScheduleFlags flags,
|
|
uint64_t period, uint64_t now)
|
|
{
|
|
if (!controlReady(now))
|
|
return false;
|
|
|
|
int64_t phaseError;
|
|
uint32_t feedbackFrameSerial;
|
|
uint32_t feedbackScheduleEpoch;
|
|
uint32_t feedbackDeadlineSerial;
|
|
LG_LOCK(l_frameScheduler.lock);
|
|
phaseError = l_frameScheduler.phaseError;
|
|
feedbackFrameSerial = l_frameScheduler.feedbackFrameSerial;
|
|
feedbackScheduleEpoch = l_frameScheduler.feedbackScheduleEpoch;
|
|
feedbackDeadlineSerial = l_frameScheduler.feedbackDeadlineSerial;
|
|
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,
|
|
.phaseError = phaseError,
|
|
.feedbackFrameSerial = feedbackFrameSerial,
|
|
.feedbackScheduleEpoch = feedbackScheduleEpoch,
|
|
.feedbackDeadlineSerial = feedbackDeadlineSerial,
|
|
.lease = FRAME_SCHEDULER_LEASE_MS,
|
|
},
|
|
};
|
|
|
|
const LG_TransportStatus status = g_state.transportOps->sendControl(
|
|
g_state.transport, &control, &l_frameScheduler.controlToken);
|
|
if (status != LG_TRANSPORT_OK)
|
|
{
|
|
if (status != LG_TRANSPORT_UNAVAILABLE &&
|
|
!l_frameScheduler.enqueueFaulted)
|
|
{
|
|
DEBUG_WARN("Frame-schedule control failed with status %d", status);
|
|
l_frameScheduler.enqueueFaulted = true;
|
|
}
|
|
controlBackoff(now);
|
|
return false;
|
|
}
|
|
|
|
l_frameScheduler.controlPending = true;
|
|
l_frameScheduler.enqueueFaulted = false;
|
|
l_frameScheduler.nextControlCheck =
|
|
now + FRAME_SCHEDULER_CONTROL_RETRY_NS;
|
|
l_frameScheduler.controlRetryDelay =
|
|
FRAME_SCHEDULER_CONTROL_RETRY_NS;
|
|
if (flags & LG_TRANSPORT_FRAME_SCHEDULE_IMMEDIATE)
|
|
l_frameScheduler.immediatePending = false;
|
|
LG_LOCK(l_frameScheduler.lock);
|
|
if (l_frameScheduler.feedbackFrameSerial == feedbackFrameSerial &&
|
|
l_frameScheduler.feedbackScheduleEpoch == feedbackScheduleEpoch &&
|
|
l_frameScheduler.feedbackDeadlineSerial == feedbackDeadlineSerial)
|
|
l_frameScheduler.feedbackDirty = false;
|
|
LG_UNLOCK(l_frameScheduler.lock);
|
|
return true;
|
|
}
|
|
|
|
void frameScheduler_init(void)
|
|
{
|
|
memset(&l_frameScheduler, 0, sizeof(l_frameScheduler));
|
|
LG_LOCK_INIT(l_frameScheduler.lock);
|
|
}
|
|
|
|
void frameScheduler_free(void)
|
|
{
|
|
LG_LOCK_FREE(l_frameScheduler.lock);
|
|
}
|
|
|
|
void frameScheduler_start(LG_TransportFeatureFlags features)
|
|
{
|
|
l_frameScheduler.supported =
|
|
features & LG_TRANSPORT_FEATURE_FRAME_SCHEDULE;
|
|
l_frameScheduler.active = false;
|
|
l_frameScheduler.controlPending = false;
|
|
l_frameScheduler.controlFaulted = false;
|
|
l_frameScheduler.enqueueFaulted = false;
|
|
l_frameScheduler.immediatePending = true;
|
|
l_frameScheduler.lastSend = 0;
|
|
l_frameScheduler.nextControlCheck = 0;
|
|
l_frameScheduler.controlRetryDelay =
|
|
FRAME_SCHEDULER_CONTROL_RETRY_NS;
|
|
l_frameScheduler.lastCadence = 0;
|
|
++l_frameScheduler.generation;
|
|
|
|
LG_LOCK(l_frameScheduler.lock);
|
|
l_frameScheduler.phaseError = 0;
|
|
l_frameScheduler.feedbackFrameSerial = 0;
|
|
l_frameScheduler.feedbackScheduleEpoch = 0;
|
|
l_frameScheduler.feedbackDeadlineSerial = 0;
|
|
l_frameScheduler.feedbackSamples = 0;
|
|
l_frameScheduler.feedbackDirty = false;
|
|
LG_UNLOCK(l_frameScheduler.lock);
|
|
}
|
|
|
|
void frameScheduler_stop(void)
|
|
{
|
|
if (l_frameScheduler.supported && l_frameScheduler.active)
|
|
{
|
|
// Poll an accepted message once more before abandoning it with the
|
|
// session. A definite enqueue failure is safe to retry immediately.
|
|
l_frameScheduler.nextControlCheck = 0;
|
|
sendSchedule(
|
|
LG_TRANSPORT_FRAME_SCHEDULE_RELEASE, 0, nanotime());
|
|
}
|
|
|
|
l_frameScheduler.supported = false;
|
|
l_frameScheduler.active = false;
|
|
l_frameScheduler.controlPending = false;
|
|
l_frameScheduler.controlFaulted = false;
|
|
l_frameScheduler.enqueueFaulted = false;
|
|
l_frameScheduler.immediatePending = false;
|
|
l_frameScheduler.nextControlCheck = 0;
|
|
}
|
|
|
|
void frameScheduler_observeCadence(void)
|
|
{
|
|
atomic_store_explicit(
|
|
&l_frameScheduler.lastCadence, nanotime(), memory_order_release);
|
|
}
|
|
|
|
void frameScheduler_update(void)
|
|
{
|
|
if (!l_frameScheduler.supported)
|
|
return;
|
|
|
|
const uint64_t lastCadence = atomic_load_explicit(
|
|
&l_frameScheduler.lastCadence, memory_order_acquire);
|
|
const uint64_t now = nanotime();
|
|
if (!lastCadence ||
|
|
now - lastCadence > FRAME_SCHEDULER_CADENCE_GRACE_NS)
|
|
{
|
|
if (l_frameScheduler.active &&
|
|
sendSchedule(
|
|
LG_TRANSPORT_FRAME_SCHEDULE_RELEASE, 0, now))
|
|
{
|
|
l_frameScheduler.active = false;
|
|
l_frameScheduler.period = 0;
|
|
l_frameScheduler.immediatePending = true;
|
|
LG_LOCK(l_frameScheduler.lock);
|
|
l_frameScheduler.phaseError = 0;
|
|
l_frameScheduler.feedbackFrameSerial = 0;
|
|
l_frameScheduler.feedbackScheduleEpoch = 0;
|
|
l_frameScheduler.feedbackDeadlineSerial = 0;
|
|
l_frameScheduler.feedbackSamples = 0;
|
|
l_frameScheduler.feedbackDirty = false;
|
|
LG_UNLOCK(l_frameScheduler.lock);
|
|
}
|
|
return;
|
|
}
|
|
|
|
const uint64_t period = presentationPeriod();
|
|
if (period < FRAME_SCHEDULER_MIN_PERIOD_NS ||
|
|
period > FRAME_SCHEDULER_MAX_PERIOD_NS)
|
|
return;
|
|
|
|
bool reset = !l_frameScheduler.period;
|
|
if (!reset)
|
|
{
|
|
const uint64_t delta = l_frameScheduler.period > period ?
|
|
l_frameScheduler.period - period : period - l_frameScheduler.period;
|
|
reset = delta > l_frameScheduler.period / 20;
|
|
}
|
|
|
|
if (reset)
|
|
{
|
|
l_frameScheduler.period = period;
|
|
++l_frameScheduler.generation;
|
|
l_frameScheduler.immediatePending = true;
|
|
LG_LOCK(l_frameScheduler.lock);
|
|
l_frameScheduler.phaseError = 0;
|
|
l_frameScheduler.feedbackFrameSerial = 0;
|
|
l_frameScheduler.feedbackScheduleEpoch = 0;
|
|
l_frameScheduler.feedbackDeadlineSerial = 0;
|
|
l_frameScheduler.feedbackSamples = 0;
|
|
l_frameScheduler.feedbackDirty = false;
|
|
LG_UNLOCK(l_frameScheduler.lock);
|
|
}
|
|
else
|
|
l_frameScheduler.period =
|
|
(l_frameScheduler.period * 7 + period) / 8;
|
|
|
|
if (l_frameScheduler.active && !reset &&
|
|
!l_frameScheduler.immediatePending)
|
|
{
|
|
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;
|
|
if (reset)
|
|
flags |= LG_TRANSPORT_FRAME_SCHEDULE_RESET;
|
|
if (l_frameScheduler.immediatePending)
|
|
flags |= LG_TRANSPORT_FRAME_SCHEDULE_IMMEDIATE;
|
|
|
|
if (sendSchedule(flags, l_frameScheduler.period, now))
|
|
{
|
|
l_frameScheduler.active = true;
|
|
l_frameScheduler.lastSend = now;
|
|
}
|
|
}
|
|
|
|
void frameScheduler_feedback(uint64_t frameSerial, uint32_t generation,
|
|
uint32_t scheduleEpoch, uint32_t deadlineSerial,
|
|
uint64_t measuredPhase)
|
|
{
|
|
if (!generation || !scheduleEpoch || !deadlineSerial)
|
|
return;
|
|
|
|
LG_LOCK(l_frameScheduler.lock);
|
|
if (!l_frameScheduler.supported || !l_frameScheduler.active ||
|
|
generation != l_frameScheduler.generation)
|
|
{
|
|
LG_UNLOCK(l_frameScheduler.lock);
|
|
return;
|
|
}
|
|
|
|
if (l_frameScheduler.feedbackFrameSerial == (uint32_t)frameSerial &&
|
|
l_frameScheduler.feedbackScheduleEpoch == scheduleEpoch &&
|
|
l_frameScheduler.feedbackDeadlineSerial == deadlineSerial)
|
|
{
|
|
LG_UNLOCK(l_frameScheduler.lock);
|
|
return;
|
|
}
|
|
|
|
const int64_t period = (int64_t)l_frameScheduler.period;
|
|
if (!period)
|
|
{
|
|
LG_UNLOCK(l_frameScheduler.lock);
|
|
return;
|
|
}
|
|
|
|
if (l_frameScheduler.feedbackScheduleEpoch &&
|
|
l_frameScheduler.feedbackScheduleEpoch != scheduleEpoch)
|
|
{
|
|
l_frameScheduler.phaseError = 0;
|
|
l_frameScheduler.feedbackSamples = 0;
|
|
}
|
|
|
|
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);
|
|
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;
|
|
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.feedbackScheduleEpoch = scheduleEpoch;
|
|
l_frameScheduler.feedbackDeadlineSerial = deadlineSerial;
|
|
l_frameScheduler.feedbackDirty = true;
|
|
LG_UNLOCK(l_frameScheduler.lock);
|
|
}
|