diff --git a/client/include/interface/transport.h b/client/include/interface/transport.h index 6934ca21..d90b12b5 100644 --- a/client/include/interface/transport.h +++ b/client/include/interface/transport.h @@ -104,6 +104,8 @@ typedef struct LG_TransportFrameTiming uint64_t postProcessTime; uint64_t copyTime; uint64_t readyTime; + uint64_t holdTime; + uint64_t readyLeadTime; } LG_TransportFrameTiming; diff --git a/client/transports/LGMP/lgmp.c b/client/transports/LGMP/lgmp.c index 1e304947..458aa5cd 100644 --- a/client/transports/LGMP/lgmp.c +++ b/client/transports/LGMP/lgmp.c @@ -1085,6 +1085,8 @@ static void lgmp_getFrameTiming(LG_Transport * this, timing->postProcessTime = lease->frame->postProcessTime; timing->copyTime = lease->frame->copyTime; timing->readyTime = lease->frame->readyTime; + timing->holdTime = lease->frame->holdTime; + timing->readyLeadTime = lease->frame->readyLeadTime; LG_UNLOCK(this->frameLock); } diff --git a/common/include/common/KVMFR.h b/common/include/common/KVMFR.h index ca4f02ae..4a2deb29 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 28 +#define KVMFR_VERSION 29 // Fallback used by producers that cannot report the source display's SDR // white level. IDD frames override this with IDDCX_METADATA2::SdrWhiteLevel. @@ -210,24 +210,23 @@ typedef struct KVMFRFrame uint64_t captureTime; uint64_t postProcessTime; uint64_t copyTime; - // Time from copy completion until FrameBuffer::wp publishes readiness. + // Non-copy preparation and publication time, excluding cadence hold time. uint64_t readyTime; + // Time a prepared frame waited for its cadence publication target. + uint64_t holdTime; + // Time remaining until the cadence deadline when the frame was published. + uint64_t readyLeadTime; // Published after the timing fields and matched against frameSerial by the // client. timingValid is written last by the producer. uint32_t timingSerial; uint32_t timingValid; - // Producer cadence identity. Phase feedback is permitted only when the - // matching frame has KVMFR_FRAME_TIMING_PHASE_VALID set. KVMFRFrameTimingFlags timingFlags; - uint32_t scheduleGeneration; - uint32_t scheduleEpoch; - uint32_t scheduleDeadlineSerial; // Keep the conditional HDR block and damage rectangles on separate cache // lines from the producer timing fields. - uint8_t timingReserved[8]; + uint8_t timingReserved[4]; // HDR static metadata (valid when FRAME_FLAG_HDR_METADATA is set) // Display color primaries in 0.00002 units (SMPTE ST 2086 format) @@ -242,7 +241,13 @@ typedef struct KVMFRFrame uint32_t hdrMaxContentLightLevel; // MaxCLL (cd/m²) uint32_t hdrMaxFrameAverageLightLevel; // MaxFALL (cd/m²) - uint8_t hdrReserved[32]; + // Producer cadence identity. Phase feedback is permitted only when the + // matching frame has KVMFR_FRAME_TIMING_PHASE_VALID set. + uint32_t scheduleGeneration; + uint32_t scheduleEpoch; + uint32_t scheduleDeadlineSerial; + + uint8_t hdrReserved[20]; FrameDamageRect damageRects[KVMFR_MAX_DAMAGE_RECTS]; } @@ -251,9 +256,15 @@ KVMFRFrame; #if defined(__cplusplus) static_assert(offsetof(KVMFRFrame, captureTime) == 64, "KVMFRFrame hot fields must fit in one cache line"); -static_assert(offsetof(KVMFRFrame, timingFlags) == 104, +static_assert(offsetof(KVMFRFrame, holdTime) == 96, + "KVMFRFrame hold timing layout changed"); +static_assert(offsetof(KVMFRFrame, readyLeadTime) == 104, + "KVMFRFrame ready lead timing layout changed"); +static_assert(offsetof(KVMFRFrame, timingSerial) == 112, + "KVMFRFrame timing publication layout changed"); +static_assert(offsetof(KVMFRFrame, timingFlags) == 120, "KVMFRFrame timing flags layout changed"); -static_assert(offsetof(KVMFRFrame, scheduleDeadlineSerial) == 116, +static_assert(offsetof(KVMFRFrame, scheduleDeadlineSerial) == 168, "KVMFRFrame schedule identity layout changed"); static_assert(offsetof(KVMFRFrame, hdrDisplayPrimary) == 128, "KVMFRFrame HDR metadata must be cache-line aligned"); @@ -262,9 +273,15 @@ static_assert(offsetof(KVMFRFrame, damageRects) == 192, #elif defined(__STDC_VERSION__) && __STDC_VERSION__ >= 201112L _Static_assert(offsetof(KVMFRFrame, captureTime) == 64, "KVMFRFrame hot fields must fit in one cache line"); -_Static_assert(offsetof(KVMFRFrame, timingFlags) == 104, +_Static_assert(offsetof(KVMFRFrame, holdTime) == 96, + "KVMFRFrame hold timing layout changed"); +_Static_assert(offsetof(KVMFRFrame, readyLeadTime) == 104, + "KVMFRFrame ready lead timing layout changed"); +_Static_assert(offsetof(KVMFRFrame, timingSerial) == 112, + "KVMFRFrame timing publication layout changed"); +_Static_assert(offsetof(KVMFRFrame, timingFlags) == 120, "KVMFRFrame timing flags layout changed"); -_Static_assert(offsetof(KVMFRFrame, scheduleDeadlineSerial) == 116, +_Static_assert(offsetof(KVMFRFrame, scheduleDeadlineSerial) == 168, "KVMFRFrame schedule identity layout changed"); _Static_assert(offsetof(KVMFRFrame, hdrDisplayPrimary) == 128, "KVMFRFrame HDR metadata must be cache-line aligned"); diff --git a/host/src/app.c b/host/src/app.c index 359db0e6..cffd28e3 100644 --- a/host/src/app.c +++ b/host/src/app.c @@ -403,6 +403,8 @@ static bool sendFrame(CaptureResult result, bool * restart) fi->postProcessTime = 0; fi->copyTime = 0; fi->readyTime = 0; + fi->holdTime = 0; + fi->readyLeadTime = 0; fi->timingSerial = 0; fi->timingFlags = 0; fi->scheduleGeneration = 0; diff --git a/idd/LGIdd/CIndirectDeviceContext.cpp b/idd/LGIdd/CIndirectDeviceContext.cpp index a1fced17..7e0dc0ae 100644 --- a/idd/LGIdd/CIndirectDeviceContext.cpp +++ b/idd/LGIdd/CIndirectDeviceContext.cpp @@ -1973,6 +1973,8 @@ CIndirectDeviceContext::PreparedFrameBuffer CIndirectDeviceContext::PrepareFrame fi->postProcessTime = 0; fi->copyTime = 0; fi->readyTime = 0; + fi->holdTime = 0; + fi->readyLeadTime = 0; fi->timingSerial = 0; fi->timingFlags = 0; fi->scheduleGeneration = 0; @@ -2343,8 +2345,8 @@ void CIndirectDeviceContext::CompleteFrameBuffer( void CIndirectDeviceContext::SetFrameTiming(unsigned frameIndex, uint64_t captureTime, uint64_t postProcessTime, uint64_t copyTime, - uint64_t readyTime, const CFrameScheduler::Schedule& schedule, - uint64_t completedAt) + uint64_t readyTime, uint64_t holdTime, + const CFrameScheduler::Schedule& schedule, uint64_t completedAt) { if (frameIndex >= LGMP_Q_FRAME_BUFFER_LEN) return; @@ -2357,6 +2359,10 @@ void CIndirectDeviceContext::SetFrameTiming(unsigned frameIndex, frame->postProcessTime = postProcessTime; frame->copyTime = copyTime; frame->readyTime = readyTime; + frame->holdTime = holdTime; + frame->readyLeadTime = phaseValid && + schedule.deadline >= completedAt ? + schedule.deadline - completedAt : 0; frame->timingFlags = phaseValid ? KVMFR_FRAME_TIMING_PHASE_VALID : 0; frame->timingSerial = frame->frameSerial; diff --git a/idd/LGIdd/CIndirectDeviceContext.h b/idd/LGIdd/CIndirectDeviceContext.h index 16da6c8b..358ba8dd 100644 --- a/idd/LGIdd/CIndirectDeviceContext.h +++ b/idd/LGIdd/CIndirectDeviceContext.h @@ -299,7 +299,8 @@ public: void CompleteFrameBuffer(unsigned frameIndex, bool succeeded); void SetFrameTiming(unsigned frameIndex, uint64_t captureTime, uint64_t postProcessTime, uint64_t copyTime, uint64_t readyTime, - const CFrameScheduler::Schedule& schedule, uint64_t completedAt); + uint64_t holdTime, const CFrameScheduler::Schedule& schedule, + uint64_t completedAt); void WriteFrameBuffer(unsigned frameIndex, void* src, size_t offset, size_t len, bool setWritePos) const; void FinalizeFrameBuffer(unsigned frameIndex) const; diff --git a/idd/LGIdd/CSwapChainProcessor.cpp b/idd/LGIdd/CSwapChainProcessor.cpp index e86012e4..0d11ec12 100644 --- a/idd/LGIdd/CSwapChainProcessor.cpp +++ b/idd/LGIdd/CSwapChainProcessor.cpp @@ -766,12 +766,21 @@ void CSwapChainProcessor::CompletionFunction( // discarded. sc->m_devContext->FinalizeFrameBuffer(fbRes->GetFrameIndex()); const uint64_t publishedAt = CFrameScheduler::Nanotime(); - const uint64_t elapsed = publishedAt - postProcessStart; - const uint64_t measured = postProcessTime + copyTime; - const uint64_t readyTime = elapsed > measured ? elapsed - measured : 0; + const uint64_t prepareElapsed = prepareReady >= postProcessStart ? + prepareReady - postProcessStart : 0; + const uint64_t prepareMeasured = postProcessTime + prepareCopyTime; + const uint64_t prepareReadyTime = prepareElapsed > prepareMeasured ? + prepareElapsed - prepareMeasured : 0; + const uint64_t publishElapsed = publishedAt >= publishStart ? + publishedAt - publishStart : 0; + const uint64_t publishReadyTime = publishElapsed > publishCopyTime ? + publishElapsed - publishCopyTime : 0; + const uint64_t readyTime = prepareReadyTime + publishReadyTime; + const uint64_t holdTime = publishStart >= prepareReady ? + publishStart - prepareReady : 0; sc->m_devContext->SetFrameTiming(fbRes->GetFrameIndex(), - fbRes->GetCaptureTime(), postProcessTime, copyTime, readyTime, + fbRes->GetCaptureTime(), postProcessTime, copyTime, readyTime, holdTime, fbRes->GetSchedule(), publishedAt); sc->m_devContext->TryRecordFrameTiming( publishedAt - publishStart);