/** * 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->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->phaseError = 0; scheduler->feedbackFrameSerial = 0; scheduler->feedbackSamples = 0; scheduler->feedbackDirty = false; } void lgFrameSchedulerObserveFrame(LGFrameScheduler * scheduler, uint32_t frameSerial, uint32_t generation, uint64_t readyTime) { if (!generation || (scheduler->readyFrameSerial == frameSerial && scheduler->readyGeneration == generation)) return; scheduler->readyFrameSerial = frameSerial; scheduler->readyGeneration = generation; scheduler->readyTime = readyTime; } void lgFrameSchedulerFeedback(LGFrameScheduler * scheduler, uint32_t frameSerial, uint32_t generation, uint64_t neededTime) { if (!scheduler->active || generation != scheduler->generation || frameSerial == scheduler->feedbackFrameSerial) return; uint64_t readyTime = neededTime; if (scheduler->readyFrameSerial == frameSerial && scheduler->readyGeneration == generation) readyTime = scheduler->readyTime; const uint64_t measuredPhase = neededTime > readyTime ? neededTime - readyTime : 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 period = (int64_t)scheduler->period; error %= period; if (error > period / 2) error -= period; else if (error < -period / 2) error += period; if (!scheduler->feedbackSamples) scheduler->phaseError = error; else scheduler->phaseError = (scheduler->phaseError * 7 + error) / 8; if (scheduler->feedbackSamples < 32) ++scheduler->feedbackSamples; scheduler->feedbackFrameSerial = frameSerial; 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 && now - scheduler->lastSend < interval) return; KVMFRFrameScheduleFlags flags = KVMFR_FRAME_SCHEDULE_ACTIVE; if (scheduler->resetPending) flags |= KVMFR_FRAME_SCHEDULE_RESET; const uint32_t feedbackFrameSerial = scheduler->feedbackFrameSerial; 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, .lease = FRAME_SCHEDULER_LEASE_MS, }; if (lgmpClientSendData(queue, &message, sizeof(message), NULL) != LGMP_OK) return; scheduler->active = true; scheduler->resetPending = false; scheduler->lastSend = now; if (scheduler->feedbackFrameSerial == feedbackFrameSerial) scheduler->feedbackDirty = false; }