From 5626cfa55b49776505b0264bc9ddd0ef974874f3 Mon Sep 17 00:00:00 2001 From: Geoffrey McRae Date: Tue, 4 Aug 2026 12:56:15 +1000 Subject: [PATCH] [common] add frame scheduling protocol --- client/include/interface/transport.h | 24 ++++++++++++++ client/transports/LGMP/lgmp.c | 30 ++++++++++++++--- common/include/common/KVMFR.h | 48 +++++++++++++++++++++++++--- 3 files changed, 94 insertions(+), 8 deletions(-) diff --git a/client/include/interface/transport.h b/client/include/interface/transport.h index e05887e0..ced5b1d8 100644 --- a/client/include/interface/transport.h +++ b/client/include/interface/transport.h @@ -59,6 +59,7 @@ enum { LG_TRANSPORT_FEATURE_SET_CURSOR_POS = 0x1, LG_TRANSPORT_FEATURE_WINDOW_SIZE = 0x2, + LG_TRANSPORT_FEATURE_FRAME_SCHEDULE = 0x4, }; typedef uint32_t LG_TransportFeatureFlags; @@ -133,6 +134,7 @@ typedef struct LG_TransportFrame { uint64_t serial; uint64_t timestamp; + uint32_t scheduleGeneration; LG_TransportFrameFlags flags; // Backend-owned immutable metadata, valid until releaseFrame. const LG_TransportFrameFormat * format; @@ -176,9 +178,20 @@ typedef enum LG_TransportControlType { LG_TRANSPORT_CONTROL_SET_CURSOR_POS, LG_TRANSPORT_CONTROL_WINDOW_SIZE, + LG_TRANSPORT_CONTROL_FRAME_SCHEDULE, } LG_TransportControlType; +enum +{ + LG_TRANSPORT_FRAME_SCHEDULE_ACTIVE = 0x1, + LG_TRANSPORT_FRAME_SCHEDULE_RELEASE = 0x2, + LG_TRANSPORT_FRAME_SCHEDULE_RESET = 0x4, + LG_TRANSPORT_FRAME_SCHEDULE_IMMEDIATE = 0x8, +}; + +typedef uint32_t LG_TransportFrameScheduleFlags; + typedef struct LG_TransportControl { LG_TransportControlType type; @@ -186,6 +199,17 @@ typedef struct LG_TransportControl { struct { int32_t x, y; } cursorPos; struct { uint32_t width, height; } windowSize; + struct + { + uint32_t generation; + LG_TransportFrameScheduleFlags flags; + uint64_t period; + uint64_t targetSlack; + int64_t phaseError; + uint32_t feedbackFrameSerial; + uint32_t lease; + } + frameSchedule; }; } LG_TransportControl; diff --git a/client/transports/LGMP/lgmp.c b/client/transports/LGMP/lgmp.c index 78299a93..e0de96f7 100644 --- a/client/transports/LGMP/lgmp.c +++ b/client/transports/LGMP/lgmp.c @@ -59,6 +59,7 @@ struct LG_Transport bool connected; bool framePending; const KVMFRFrame * pendingFrame; + uint32_t clientID; uint32_t frameSerial; bool formatValid; LG_TransportFrameFormat format; @@ -297,6 +298,8 @@ static bool lgmp_parseSession(const uint8_t * data, uint32_t size, session->features |= LG_TRANSPORT_FEATURE_SET_CURSOR_POS; if (header->features & KVMFR_FEATURE_WINDOWSIZE) session->features |= LG_TRANSPORT_FEATURE_WINDOW_SIZE; + if (header->features & KVMFR_FEATURE_FRAME_SCHEDULE) + session->features |= LG_TRANSPORT_FEATURE_FRAME_SCHEDULE; data += sizeof(*header); size -= sizeof(*header); @@ -359,8 +362,8 @@ static LG_TransportStatus lgmp_connect(LG_Transport * this, uint32_t size; uint8_t * data; uint32_t remoteVersion; - LGMP_STATUS status = lgmpClientSessionInit(this->client, &size, &data, NULL, - &remoteVersion); + LGMP_STATUS status = lgmpClientSessionInit(this->client, &size, &data, + &this->clientID, &remoteVersion); session->remoteVersion = remoteVersion; switch (status) { @@ -390,6 +393,7 @@ static void lgmp_disconnect(LG_Transport * this) lgmp_closeQueues(this); lgmp_closeDMA(this); this->connected = false; + this->clientID = 0; } static bool lgmp_sessionValid(LG_Transport * this) @@ -538,7 +542,8 @@ static LG_TransportStatus lgmp_nextFrame(LG_Transport * this, bool useDMA, this->frameSerial = frame->frameSerial; memset(result, 0, sizeof(*result)); - result->serial = frame->frameSerial; + result->serial = frame->frameSerial; + result->scheduleGeneration = message.udata; if (frame->flags & FRAME_FLAG_BLOCK_SCREENSAVER) result->flags |= LG_TRANSPORT_FRAME_BLOCK_SCREENSAVER; if (frame->flags & FRAME_FLAG_REQUEST_ACTIVATION) @@ -763,7 +768,7 @@ static void lgmp_releasePointer(LG_Transport * this, static LG_TransportStatus lgmp_sendControl(LG_Transport * this, const LG_TransportControl * control, LG_TransportControlToken * token) { - uint8_t buffer[sizeof(KVMFRWindowSize)]; + uint8_t buffer[LGMP_MSGS_SIZE]; uint32_t size; switch (control->type) { @@ -789,6 +794,23 @@ static LG_TransportStatus lgmp_sendControl(LG_Transport * this, size = sizeof(message); break; } + case LG_TRANSPORT_CONTROL_FRAME_SCHEDULE: + { + const KVMFRFrameSchedule message = { + .msg.type = KVMFR_MESSAGE_FRAME_SCHEDULE, + .clientID = this->clientID, + .generation = control->frameSchedule.generation, + .flags = control->frameSchedule.flags, + .period = control->frameSchedule.period, + .targetSlack = control->frameSchedule.targetSlack, + .phaseError = control->frameSchedule.phaseError, + .feedbackFrameSerial = control->frameSchedule.feedbackFrameSerial, + .lease = control->frameSchedule.lease, + }; + memcpy(buffer, &message, sizeof(message)); + size = sizeof(message); + break; + } default: return LG_TRANSPORT_ERROR; } diff --git a/common/include/common/KVMFR.h b/common/include/common/KVMFR.h index 3166d3a0..b4552477 100644 --- a/common/include/common/KVMFR.h +++ b/common/include/common/KVMFR.h @@ -30,7 +30,7 @@ #include "LGMPConfig.h" #define KVMFR_MAGIC "KVMFR---" -#define KVMFR_VERSION 25 +#define KVMFR_VERSION 26 // Fallback used by producers that cannot report the source display's SDR // white level. IDD frames override this with IDDCX_METADATA2::SdrWhiteLevel. @@ -59,8 +59,9 @@ typedef uint32_t KVMFRCursorFlags; enum { - KVMFR_FEATURE_SETCURSORPOS = 0x1, - KVMFR_FEATURE_WINDOWSIZE = 0x2 + KVMFR_FEATURE_SETCURSORPOS = 0x1, + KVMFR_FEATURE_WINDOWSIZE = 0x2, + KVMFR_FEATURE_FRAME_SCHEDULE = 0x4 }; typedef uint32_t KVMFRFeatureFlags; @@ -68,7 +69,8 @@ typedef uint32_t KVMFRFeatureFlags; enum { KVMFR_MESSAGE_SETCURSORPOS, - KVMFR_MESSAGE_WINDOWSIZE + KVMFR_MESSAGE_WINDOWSIZE, + KVMFR_MESSAGE_FRAME_SCHEDULE }; typedef uint32_t KVMFRMessageType; @@ -268,6 +270,44 @@ typedef struct KVMFRWindowSize } KVMFRWindowSize; +enum +{ + KVMFR_FRAME_SCHEDULE_ACTIVE = 0x1, + KVMFR_FRAME_SCHEDULE_RELEASE = 0x2, + KVMFR_FRAME_SCHEDULE_RESET = 0x4, + KVMFR_FRAME_SCHEDULE_IMMEDIATE = 0x8 +}; + +typedef uint32_t KVMFRFrameScheduleFlags; + +/* + * Client presentation scheduling is expressed entirely as durations. Client + * and producer monotonic clocks have unrelated epochs and must never be + * compared directly. + */ +typedef struct KVMFRFrameSchedule +{ + KVMFRMessage msg; + uint32_t clientID; + uint32_t generation; + KVMFRFrameScheduleFlags flags; + uint64_t period; + uint64_t targetSlack; + int64_t phaseError; + uint32_t feedbackFrameSerial; + uint32_t lease; + uint8_t reserved[16]; +} +KVMFRFrameSchedule; + +#if defined(__cplusplus) +static_assert(sizeof(KVMFRFrameSchedule) == 64, + "KVMFR frame schedule must fit in one LGMP control message"); +#elif defined(__STDC_VERSION__) && __STDC_VERSION__ >= 201112L +_Static_assert(sizeof(KVMFRFrameSchedule) == 64, + "KVMFR frame schedule must fit in one LGMP control message"); +#endif + #ifdef _MSC_VER #pragma warning(pop) #endif