[common] add frame scheduling protocol

This commit is contained in:
Geoffrey McRae
2026-08-04 12:56:15 +10:00
parent 84d1427cab
commit 5626cfa55b
3 changed files with 94 additions and 8 deletions

View File

@@ -59,6 +59,7 @@ enum
{ {
LG_TRANSPORT_FEATURE_SET_CURSOR_POS = 0x1, LG_TRANSPORT_FEATURE_SET_CURSOR_POS = 0x1,
LG_TRANSPORT_FEATURE_WINDOW_SIZE = 0x2, LG_TRANSPORT_FEATURE_WINDOW_SIZE = 0x2,
LG_TRANSPORT_FEATURE_FRAME_SCHEDULE = 0x4,
}; };
typedef uint32_t LG_TransportFeatureFlags; typedef uint32_t LG_TransportFeatureFlags;
@@ -133,6 +134,7 @@ typedef struct LG_TransportFrame
{ {
uint64_t serial; uint64_t serial;
uint64_t timestamp; uint64_t timestamp;
uint32_t scheduleGeneration;
LG_TransportFrameFlags flags; LG_TransportFrameFlags flags;
// Backend-owned immutable metadata, valid until releaseFrame. // Backend-owned immutable metadata, valid until releaseFrame.
const LG_TransportFrameFormat * format; const LG_TransportFrameFormat * format;
@@ -176,9 +178,20 @@ typedef enum LG_TransportControlType
{ {
LG_TRANSPORT_CONTROL_SET_CURSOR_POS, LG_TRANSPORT_CONTROL_SET_CURSOR_POS,
LG_TRANSPORT_CONTROL_WINDOW_SIZE, LG_TRANSPORT_CONTROL_WINDOW_SIZE,
LG_TRANSPORT_CONTROL_FRAME_SCHEDULE,
} }
LG_TransportControlType; 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 typedef struct LG_TransportControl
{ {
LG_TransportControlType type; LG_TransportControlType type;
@@ -186,6 +199,17 @@ typedef struct LG_TransportControl
{ {
struct { int32_t x, y; } cursorPos; struct { int32_t x, y; } cursorPos;
struct { uint32_t width, height; } windowSize; 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; LG_TransportControl;

View File

@@ -59,6 +59,7 @@ struct LG_Transport
bool connected; bool connected;
bool framePending; bool framePending;
const KVMFRFrame * pendingFrame; const KVMFRFrame * pendingFrame;
uint32_t clientID;
uint32_t frameSerial; uint32_t frameSerial;
bool formatValid; bool formatValid;
LG_TransportFrameFormat format; 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; session->features |= LG_TRANSPORT_FEATURE_SET_CURSOR_POS;
if (header->features & KVMFR_FEATURE_WINDOWSIZE) if (header->features & KVMFR_FEATURE_WINDOWSIZE)
session->features |= LG_TRANSPORT_FEATURE_WINDOW_SIZE; session->features |= LG_TRANSPORT_FEATURE_WINDOW_SIZE;
if (header->features & KVMFR_FEATURE_FRAME_SCHEDULE)
session->features |= LG_TRANSPORT_FEATURE_FRAME_SCHEDULE;
data += sizeof(*header); data += sizeof(*header);
size -= sizeof(*header); size -= sizeof(*header);
@@ -359,8 +362,8 @@ static LG_TransportStatus lgmp_connect(LG_Transport * this,
uint32_t size; uint32_t size;
uint8_t * data; uint8_t * data;
uint32_t remoteVersion; uint32_t remoteVersion;
LGMP_STATUS status = lgmpClientSessionInit(this->client, &size, &data, NULL, LGMP_STATUS status = lgmpClientSessionInit(this->client, &size, &data,
&remoteVersion); &this->clientID, &remoteVersion);
session->remoteVersion = remoteVersion; session->remoteVersion = remoteVersion;
switch (status) switch (status)
{ {
@@ -390,6 +393,7 @@ static void lgmp_disconnect(LG_Transport * this)
lgmp_closeQueues(this); lgmp_closeQueues(this);
lgmp_closeDMA(this); lgmp_closeDMA(this);
this->connected = false; this->connected = false;
this->clientID = 0;
} }
static bool lgmp_sessionValid(LG_Transport * this) 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; this->frameSerial = frame->frameSerial;
memset(result, 0, sizeof(*result)); memset(result, 0, sizeof(*result));
result->serial = frame->frameSerial; result->serial = frame->frameSerial;
result->scheduleGeneration = message.udata;
if (frame->flags & FRAME_FLAG_BLOCK_SCREENSAVER) if (frame->flags & FRAME_FLAG_BLOCK_SCREENSAVER)
result->flags |= LG_TRANSPORT_FRAME_BLOCK_SCREENSAVER; result->flags |= LG_TRANSPORT_FRAME_BLOCK_SCREENSAVER;
if (frame->flags & FRAME_FLAG_REQUEST_ACTIVATION) 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, static LG_TransportStatus lgmp_sendControl(LG_Transport * this,
const LG_TransportControl * control, LG_TransportControlToken * token) const LG_TransportControl * control, LG_TransportControlToken * token)
{ {
uint8_t buffer[sizeof(KVMFRWindowSize)]; uint8_t buffer[LGMP_MSGS_SIZE];
uint32_t size; uint32_t size;
switch (control->type) switch (control->type)
{ {
@@ -789,6 +794,23 @@ static LG_TransportStatus lgmp_sendControl(LG_Transport * this,
size = sizeof(message); size = sizeof(message);
break; 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: default:
return LG_TRANSPORT_ERROR; return LG_TRANSPORT_ERROR;
} }

View File

@@ -30,7 +30,7 @@
#include "LGMPConfig.h" #include "LGMPConfig.h"
#define KVMFR_MAGIC "KVMFR---" #define KVMFR_MAGIC "KVMFR---"
#define KVMFR_VERSION 25 #define KVMFR_VERSION 26
// Fallback used by producers that cannot report the source display's SDR // Fallback used by producers that cannot report the source display's SDR
// white level. IDD frames override this with IDDCX_METADATA2::SdrWhiteLevel. // white level. IDD frames override this with IDDCX_METADATA2::SdrWhiteLevel.
@@ -59,8 +59,9 @@ typedef uint32_t KVMFRCursorFlags;
enum enum
{ {
KVMFR_FEATURE_SETCURSORPOS = 0x1, KVMFR_FEATURE_SETCURSORPOS = 0x1,
KVMFR_FEATURE_WINDOWSIZE = 0x2 KVMFR_FEATURE_WINDOWSIZE = 0x2,
KVMFR_FEATURE_FRAME_SCHEDULE = 0x4
}; };
typedef uint32_t KVMFRFeatureFlags; typedef uint32_t KVMFRFeatureFlags;
@@ -68,7 +69,8 @@ typedef uint32_t KVMFRFeatureFlags;
enum enum
{ {
KVMFR_MESSAGE_SETCURSORPOS, KVMFR_MESSAGE_SETCURSORPOS,
KVMFR_MESSAGE_WINDOWSIZE KVMFR_MESSAGE_WINDOWSIZE,
KVMFR_MESSAGE_FRAME_SCHEDULE
}; };
typedef uint32_t KVMFRMessageType; typedef uint32_t KVMFRMessageType;
@@ -268,6 +270,44 @@ typedef struct KVMFRWindowSize
} }
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 #ifdef _MSC_VER
#pragma warning(pop) #pragma warning(pop)
#endif #endif