Files
LookingGlass/idd/LGIdd/CFrameScheduler.h
Geoffrey McRae 0b9b805530 [idd] scheduler: retain frames for publication deadlines
Prepare each acquired content frame into one of two private GPU
candidates so the IddCx frame can be released without copying it to
IVSHMEM immediately.

Wake a high-resolution publisher at the client deadline and send the
newest completed candidate. Preserve accumulated damage across
superseded candidates, include the scheduling hold in end-to-end timing,
and keep transport work on the existing physical copy queue.
2026-08-05 12:38:07 +10:00

107 lines
3.0 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
*/
#pragma once
#include <Windows.h>
#include <stdint.h>
extern "C" {
#include <lgmp/lgmp.h>
}
#include "common/KVMFR.h"
class CFrameScheduler
{
public:
struct Schedule
{
uint32_t clientID;
uint32_t generation;
uint64_t period;
uint64_t targetSlack;
};
private:
struct Client
{
uint32_t clientID;
uint32_t generation;
uint64_t period;
uint64_t targetSlack;
uint64_t expiry;
uint32_t lastFeedbackFrameSerial;
bool subscribed;
bool active;
};
mutable SRWLOCK m_lock = SRWLOCK_INIT;
HANDLE m_wakeEvent = nullptr;
Client m_clients[LGMP_MAX_CLIENTS] = {};
Schedule m_schedule = {};
bool m_scheduling = false;
bool m_forceNext = false;
uint64_t m_lastArrival = 0;
uint64_t m_guestPeriod = 0;
uint64_t m_workEstimate = 0;
uint64_t m_nextDeadline = 0;
uint32_t m_lastPublishedFrameSerial = 0;
int64_t m_lastPhaseError = 0;
uint64_t m_acquiredFrames = 0;
uint64_t m_skippedFrames = 0;
uint64_t m_publishedFrames = 0;
uint64_t m_lastLog = 0;
uint64_t m_lastLogAcquired = 0;
uint64_t m_lastLogSkipped = 0;
uint64_t m_lastLogPublished = 0;
Client * FindClient(uint32_t clientID);
bool ElectOwner(uint64_t now);
bool ApplyFeedback(Client& client, const KVMFRFrameSchedule& schedule);
void AdvanceDeadline(uint64_t now);
void WakePublisher() const;
public:
CFrameScheduler();
~CFrameScheduler();
static uint64_t Nanotime();
void Reset();
void UpdateSubscribers(const uint32_t * clientIDs, unsigned count,
uint64_t now);
bool UpdateSchedule(const KVMFRFrameSchedule& schedule, uint64_t now);
bool GetSchedule(Schedule& schedule) const;
HANDLE GetWakeEvent() const { return m_wakeEvent; }
void ObserveFrame(uint64_t now);
void ForceFrame();
bool GetPublishTarget(uint64_t now, uint64_t& target,
uint32_t& generation, bool& periodic);
void FrameSuperseded();
void FramePublished(uint32_t generation, uint32_t frameSerial,
uint64_t now, bool periodic);
void RecordFrameTiming(uint64_t duration);
void LogStatistics(uint64_t now);
};