/** * 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 #include #define FRAME_SCHEDULER_LEASE_MS 1000U #define FRAME_SCHEDULER_RENEW_NS 250000000ULL #define FRAME_SCHEDULER_FEEDBACK_NS 50000000ULL #define FRAME_SCHEDULER_TICK_GRACE_NS 500000000ULL #define FRAME_SCHEDULER_TARGET_SLACK_NS 1000000ULL #define FRAME_SCHEDULER_MIN_PERIOD_NS 2000000ULL #define FRAME_SCHEDULER_MAX_PERIOD_NS 1000000000ULL static void lgFrameSchedulerResetFeedback(LGFrameScheduler * scheduler) { scheduler->phaseError = 0; scheduler->feedbackFrameSerial = 0; scheduler->feedbackScheduleEpoch = 0; scheduler->feedbackDeadlineSerial = 0; scheduler->feedbackSamples = 0; scheduler->feedbackDirty = false; scheduler->readyFrameSerial = 0; scheduler->readyGeneration = 0; scheduler->readyScheduleEpoch = 0; scheduler->readyDeadlineSerial = 0; scheduler->readyTime = 0; } void lgFrameSchedulerInit(LGFrameScheduler * scheduler, bool supported, uint32_t clientID) { memset(scheduler, 0, sizeof(*scheduler)); scheduler->supported = supported; scheduler->immediatePending = supported; scheduler->clientID = clientID; } void lgFrameSchedulerSetActive(LGFrameScheduler * scheduler, bool active) { if (!scheduler->supported || scheduler->sourceActive == active) return; scheduler->sourceActive = active; if (!active) scheduler->lastTick = 0; } void lgFrameSchedulerSetPeriod(LGFrameScheduler * scheduler, uint64_t period, uint64_t tickTime) { if (!scheduler->supported) return; scheduler->lastTick = tickTime; if (period < FRAME_SCHEDULER_MIN_PERIOD_NS || period > FRAME_SCHEDULER_MAX_PERIOD_NS) { scheduler->period = 0; return; } bool reset = !scheduler->period; if (!reset) { const uint64_t delta = scheduler->period > period ? scheduler->period - period : period - scheduler->period; reset = delta > scheduler->period / 20; } scheduler->period = period; if (!reset) return; ++scheduler->generation; scheduler->resetPending = true; scheduler->immediatePending = true; lgFrameSchedulerResetFeedback(scheduler); } void lgFrameSchedulerRequestImmediate(LGFrameScheduler * scheduler) { if (scheduler->supported) scheduler->immediatePending = true; } void lgFrameSchedulerObserveFrame(LGFrameScheduler * scheduler, uint32_t frameSerial, uint32_t generation, uint32_t scheduleEpoch, uint32_t deadlineSerial, uint64_t readyTime) { if (!generation || !scheduleEpoch || !deadlineSerial || (scheduler->readyFrameSerial == frameSerial && scheduler->readyGeneration == generation && scheduler->readyScheduleEpoch == scheduleEpoch && scheduler->readyDeadlineSerial == deadlineSerial)) return; scheduler->readyFrameSerial = frameSerial; scheduler->readyGeneration = generation; scheduler->readyScheduleEpoch = scheduleEpoch; scheduler->readyDeadlineSerial = deadlineSerial; scheduler->readyTime = readyTime; } void lgFrameSchedulerFeedback(LGFrameScheduler * scheduler, uint32_t frameSerial, uint32_t generation, uint32_t scheduleEpoch, uint32_t deadlineSerial, uint64_t tickTime) { if (!scheduler->active || generation != scheduler->generation || !scheduleEpoch || !deadlineSerial || (frameSerial == scheduler->feedbackFrameSerial && scheduleEpoch == scheduler->feedbackScheduleEpoch && deadlineSerial == scheduler->feedbackDeadlineSerial) || scheduler->readyFrameSerial != frameSerial || scheduler->readyGeneration != generation || scheduler->readyScheduleEpoch != scheduleEpoch || scheduler->readyDeadlineSerial != deadlineSerial) return; if (scheduler->feedbackScheduleEpoch && scheduler->feedbackScheduleEpoch != scheduleEpoch) { scheduler->phaseError = 0; scheduler->feedbackSamples = 0; } const int64_t measuredPhase = tickTime >= scheduler->readyTime ? (int64_t)(tickTime - scheduler->readyTime) : -(int64_t)(scheduler->readyTime - tickTime); int64_t error = measuredPhase - (int64_t)FRAME_SCHEDULER_TARGET_SLACK_NS; const int64_t period = (int64_t)scheduler->period; const int64_t limit = period / 2; if (error > limit) error = limit; else if (error < -limit) error = -limit; if (!scheduler->feedbackSamples) scheduler->phaseError = error; else scheduler->phaseError = (scheduler->phaseError * 7 + error) / 8; if (scheduler->feedbackSamples < 32) ++scheduler->feedbackSamples; scheduler->feedbackFrameSerial = frameSerial; scheduler->feedbackScheduleEpoch = scheduleEpoch; scheduler->feedbackDeadlineSerial = deadlineSerial; scheduler->feedbackDirty = true; } void lgFrameSchedulerUpdate(LGFrameScheduler * scheduler, PLGMPClientQueue queue, uint64_t now) { if (!scheduler->supported || !queue) return; const bool tickStale = !scheduler->lastTick || now - scheduler->lastTick > FRAME_SCHEDULER_TICK_GRACE_NS; if (!scheduler->sourceActive || tickStale || !scheduler->period) { if (!scheduler->active) return; const KVMFRFrameSchedule message = { .msg.type = KVMFR_MESSAGE_FRAME_SCHEDULE, .clientID = scheduler->clientID, .generation = scheduler->generation, .flags = KVMFR_FRAME_SCHEDULE_RELEASE, }; if (lgmpClientSendData(queue, &message, sizeof(message), NULL) != LGMP_OK) return; scheduler->active = false; scheduler->resetPending = false; scheduler->immediatePending = true; scheduler->period = 0; scheduler->lastSend = now; lgFrameSchedulerResetFeedback(scheduler); return; } const uint64_t interval = scheduler->feedbackDirty ? FRAME_SCHEDULER_FEEDBACK_NS : FRAME_SCHEDULER_RENEW_NS; if (scheduler->active && !scheduler->resetPending && !scheduler->immediatePending && now - scheduler->lastSend < interval) return; KVMFRFrameScheduleFlags flags = KVMFR_FRAME_SCHEDULE_ACTIVE; if (scheduler->resetPending) flags |= KVMFR_FRAME_SCHEDULE_RESET; if (scheduler->immediatePending) flags |= KVMFR_FRAME_SCHEDULE_IMMEDIATE; const uint32_t feedbackFrameSerial = scheduler->feedbackFrameSerial; const uint32_t feedbackScheduleEpoch = scheduler->feedbackScheduleEpoch; const uint32_t feedbackDeadlineSerial = scheduler->feedbackDeadlineSerial; const KVMFRFrameSchedule message = { .msg.type = KVMFR_MESSAGE_FRAME_SCHEDULE, .clientID = scheduler->clientID, .generation = scheduler->generation, .flags = flags, .period = scheduler->period, .targetSlack = FRAME_SCHEDULER_TARGET_SLACK_NS, .phaseError = scheduler->phaseError, .feedbackFrameSerial = feedbackFrameSerial, .feedbackScheduleEpoch = feedbackScheduleEpoch, .feedbackDeadlineSerial = feedbackDeadlineSerial, .lease = FRAME_SCHEDULER_LEASE_MS, }; if (lgmpClientSendData(queue, &message, sizeof(message), NULL) != LGMP_OK) return; scheduler->active = true; scheduler->resetPending = false; scheduler->immediatePending = false; scheduler->lastSend = now; if (scheduler->feedbackFrameSerial == feedbackFrameSerial && scheduler->feedbackScheduleEpoch == feedbackScheduleEpoch && scheduler->feedbackDeadlineSerial == feedbackDeadlineSerial) scheduler->feedbackDirty = false; }