/** * 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_TARGET_SLACK_NS 1000000ULL #define FRAME_SCHEDULER_MIN_PERIOD_NS 2000000ULL #define FRAME_SCHEDULER_MAX_PERIOD_NS 1000000000ULL void lgFrameSchedulerInit(LGFrameScheduler * scheduler, bool supported, uint32_t clientID) { memset(scheduler, 0, sizeof(*scheduler)); scheduler->supported = supported; scheduler->immediatePending = supported; scheduler->clientID = clientID; } void lgFrameSchedulerSetPeriod(LGFrameScheduler * scheduler, uint64_t period) { if (!scheduler->supported || period < FRAME_SCHEDULER_MIN_PERIOD_NS || period > FRAME_SCHEDULER_MAX_PERIOD_NS) 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; scheduler->phaseError = 0; scheduler->feedbackFrameSerial = 0; scheduler->feedbackScheduleEpoch = 0; scheduler->feedbackSamples = 0; scheduler->feedbackDirty = false; } void lgFrameSchedulerRequestImmediate(LGFrameScheduler * scheduler) { if (scheduler->supported) scheduler->immediatePending = true; } void lgFrameSchedulerObserveFrame(LGFrameScheduler * scheduler, uint32_t frameSerial, uint32_t generation, uint32_t scheduleEpoch, uint64_t readyTime) { if (!generation || !scheduleEpoch || (scheduler->readyFrameSerial == frameSerial && scheduler->readyGeneration == generation && scheduler->readyScheduleEpoch == scheduleEpoch)) return; scheduler->readyFrameSerial = frameSerial; scheduler->readyGeneration = generation; scheduler->readyScheduleEpoch = scheduleEpoch; scheduler->readyTime = readyTime; } void lgFrameSchedulerFeedback(LGFrameScheduler * scheduler, uint32_t frameSerial, uint32_t generation, uint32_t scheduleEpoch, uint64_t tickTime) { if (!scheduler->active || generation != scheduler->generation || !scheduleEpoch || (frameSerial == scheduler->feedbackFrameSerial && scheduleEpoch == scheduler->feedbackScheduleEpoch) || scheduler->readyFrameSerial != frameSerial || scheduler->readyGeneration != generation || scheduler->readyScheduleEpoch != scheduleEpoch) 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->feedbackDirty = true; } void lgFrameSchedulerUpdate(LGFrameScheduler * scheduler, PLGMPClientQueue queue, uint64_t now) { if (!scheduler->supported || !scheduler->period || !queue) 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 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, .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->feedbackDirty = false; }