[idd] publish the latest frame before its deadline

This commit is contained in:
Geoffrey McRae
2026-08-04 13:10:02 +10:00
parent 1176e78a7f
commit 32ced2f24a
5 changed files with 166 additions and 25 deletions

View File

@@ -22,10 +22,13 @@
#include "CDebug.h"
#include <algorithm>
static const uint64_t MIN_PERIOD_NS = 2000000ULL;
static const uint64_t MAX_PERIOD_NS = 1000000000ULL;
static const uint32_t MIN_LEASE_MS = 100;
static const uint32_t MAX_LEASE_MS = 5000;
static const uint64_t MIN_SAFETY_NS = 250000ULL;
uint64_t CFrameScheduler::Nanotime()
{
@@ -105,6 +108,9 @@ void CFrameScheduler::ElectOwner(uint64_t now)
if (oldClientID != m_schedule.clientID ||
oldGeneration != m_schedule.generation)
{
m_nextDeadline = m_scheduling ? now + m_schedule.period : 0;
m_forceNext = m_scheduling;
if (m_scheduling)
DEBUG_INFO("Frame timing owner %u generation %u at %.3f Hz",
m_schedule.clientID, m_schedule.generation,
@@ -121,6 +127,15 @@ void CFrameScheduler::Reset()
client = {};
m_schedule = {};
m_scheduling = false;
m_forceNext = false;
m_lastArrival = 0;
m_guestPeriod = 0;
m_guestJitter = 0;
m_workEstimate = 0;
m_nextDeadline = 0;
m_arrivalSamples = 0;
m_timingSamples = 0;
ReleaseSRWLockExclusive(&m_lock);
}
@@ -214,3 +229,92 @@ bool CFrameScheduler::GetSchedule(Schedule& schedule) const
ReleaseSRWLockShared(&m_lock);
return result;
}
void CFrameScheduler::ObserveFrame(uint64_t now)
{
AcquireSRWLockExclusive(&m_lock);
if (m_lastArrival && now > m_lastArrival)
{
const uint64_t interval = now - m_lastArrival;
if (interval >= MIN_PERIOD_NS && interval <= MAX_PERIOD_NS)
{
if (!m_guestPeriod)
m_guestPeriod = interval;
else
{
const uint64_t error = m_guestPeriod > interval ?
m_guestPeriod - interval : interval - m_guestPeriod;
m_guestPeriod = (m_guestPeriod * 7 + interval) / 8;
m_guestJitter = (m_guestJitter * 7 + error) / 8;
}
if (m_arrivalSamples < 32)
++m_arrivalSamples;
}
}
m_lastArrival = now;
ReleaseSRWLockExclusive(&m_lock);
}
bool CFrameScheduler::SelectFrame(uint64_t now, bool force,
uint32_t& generation)
{
generation = 0;
AcquireSRWLockExclusive(&m_lock);
if (!m_scheduling)
{
ReleaseSRWLockExclusive(&m_lock);
return true;
}
generation = m_schedule.generation;
while (m_nextDeadline <= now)
m_nextDeadline += m_schedule.period;
if (force)
m_forceNext = true;
if (m_forceNext || m_arrivalSamples < 4 || m_timingSamples < 4)
{
ReleaseSRWLockExclusive(&m_lock);
return true;
}
const uint64_t safety =
std::max(MIN_SAFETY_NS,
std::max(m_guestJitter * 2, m_workEstimate / 8));
const uint64_t nextArrival = m_lastArrival + m_guestPeriod;
const bool process = nextArrival <= now ||
nextArrival + m_workEstimate + safety > m_nextDeadline;
ReleaseSRWLockExclusive(&m_lock);
return process;
}
void CFrameScheduler::FramePublished(uint32_t generation, uint64_t now)
{
AcquireSRWLockExclusive(&m_lock);
if (m_scheduling && generation == m_schedule.generation)
{
m_forceNext = false;
do
m_nextDeadline += m_schedule.period;
while (m_nextDeadline <= now);
}
ReleaseSRWLockExclusive(&m_lock);
}
void CFrameScheduler::RecordFrameTiming(uint64_t duration)
{
if (!duration)
return;
AcquireSRWLockExclusive(&m_lock);
if (!m_workEstimate || duration > m_workEstimate)
m_workEstimate = duration;
else
m_workEstimate = (m_workEstimate * 31 + duration) / 32;
if (m_timingSamples < 32)
++m_timingSamples;
ReleaseSRWLockExclusive(&m_lock);
}

View File

@@ -56,6 +56,15 @@ private:
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_guestJitter = 0;
uint64_t m_workEstimate = 0;
uint64_t m_nextDeadline = 0;
unsigned m_arrivalSamples = 0;
unsigned m_timingSamples = 0;
Client * FindClient(uint32_t clientID);
void ElectOwner(uint64_t now);
@@ -68,4 +77,8 @@ public:
uint64_t now);
bool UpdateSchedule(const KVMFRFrameSchedule& schedule, uint64_t now);
bool GetSchedule(Schedule& schedule) const;
void ObserveFrame(uint64_t now);
bool SelectFrame(uint64_t now, bool force, uint32_t& generation);
void FramePublished(uint32_t generation, uint64_t now);
void RecordFrameTiming(uint64_t duration);
};

View File

@@ -1503,14 +1503,16 @@ CIndirectDeviceContext::PreparedFrameBuffer CIndirectDeviceContext::PrepareFrame
return result;
}
bool CIndirectDeviceContext::PublishFrameBuffer(unsigned frameIndex)
bool CIndirectDeviceContext::PublishFrameBuffer(unsigned frameIndex,
uint32_t scheduleGeneration)
{
if (!m_frameQueue || frameIndex >= LGMP_Q_FRAME_LEN)
return false;
AcquireSRWLockExclusive(&m_framePublishLock);
const LGMP_STATUS status =
lgmpHostQueuePost(m_frameQueue, 0, m_frameMemory[frameIndex]);
lgmpHostQueuePost(
m_frameQueue, scheduleGeneration, m_frameMemory[frameIndex]);
if (status == LGMP_OK)
{
m_publishedFrameIndex.store(
@@ -1519,6 +1521,10 @@ bool CIndirectDeviceContext::PublishFrameBuffer(unsigned frameIndex)
}
ReleaseSRWLockExclusive(&m_framePublishLock);
if (status == LGMP_OK)
m_frameScheduler.FramePublished(
scheduleGeneration, CFrameScheduler::Nanotime());
if (status != LGMP_OK)
{
DEBUG_ERROR("Failed to publish frame: %s", lgmpStatusString(status));
@@ -1528,6 +1534,22 @@ bool CIndirectDeviceContext::PublishFrameBuffer(unsigned frameIndex)
return true;
}
void CIndirectDeviceContext::ObserveFrame(uint64_t now)
{
m_frameScheduler.ObserveFrame(now);
}
bool CIndirectDeviceContext::SelectFrame(uint64_t now, bool force,
uint32_t& generation)
{
return m_frameScheduler.SelectFrame(now, force, generation);
}
void CIndirectDeviceContext::RecordFrameTiming(uint64_t duration)
{
m_frameScheduler.RecordFrameTiming(duration);
}
void CIndirectDeviceContext::AbortFrameBuffer(unsigned frameIndex)
{
if (frameIndex >= LGMP_Q_FRAME_LEN)

View File

@@ -225,7 +225,7 @@ public:
bool FrameBufferAvailable() const;
PreparedFrameBuffer PrepareFrameBuffer(unsigned pitch, const D12FrameFormat& srcFormat, const D12FrameFormat& dstFormat, const RECT * dirtyRects, unsigned nbDirtyRects);
bool PublishFrameBuffer(unsigned frameIndex);
bool PublishFrameBuffer(unsigned frameIndex, uint32_t scheduleGeneration);
void AbortFrameBuffer(unsigned frameIndex);
void FailFrameBuffer(unsigned frameIndex);
void CompleteFrameBuffer(unsigned frameIndex);
@@ -234,6 +234,10 @@ public:
void WriteFrameBuffer(unsigned frameIndex, void* src, size_t offset, size_t len, bool setWritePos) const;
void FinalizeFrameBuffer(unsigned frameIndex) const;
void ObserveFrame(uint64_t now);
bool SelectFrame(uint64_t now, bool force, uint32_t& generation);
void RecordFrameTiming(uint64_t duration);
void SendCursor(const IDARG_OUT_QUERY_HWCURSOR & info, const BYTE * data,
UINT sdrWhiteLevel);

View File

@@ -31,22 +31,6 @@ static const uint32_t HDR_PQ_MAX_LUMINANCE = 10000;
static_assert(LGMP_Q_FRAME_LEN == 2,
"IDD damage repair assumes two alternating frame buffers");
static uint64_t Nanotime()
{
static const uint64_t frequency = []()
{
LARGE_INTEGER value;
QueryPerformanceFrequency(&value);
return (uint64_t)value.QuadPart;
}();
LARGE_INTEGER counter;
QueryPerformanceCounter(&counter);
const uint64_t ticks = (uint64_t)counter.QuadPart;
return ticks / frequency * 1000000000ULL +
ticks % frequency * 1000000000ULL / frequency;
}
static bool FrameMetadataChanged(const D12FrameFormat& previous,
const D12FrameFormat& current)
{
@@ -258,7 +242,7 @@ bool CSwapChainProcessor::SwapChainThreadCore()
// path HDR is not available, so default to SDR.
DXGI_COLOR_SPACE_TYPE colorSpace = DXGI_COLOR_SPACE_RGB_FULL_G22_NONE_P709;
UINT sdrWhiteLevel = KVMFR_SDR_WHITE_LEVEL_DEFAULT;
const uint64_t captureStart = Nanotime();
const uint64_t captureStart = CFrameScheduler::Nanotime();
#ifdef HAS_IDDCX_110
if (m_devContext->HasIddCx110DDIs())
@@ -374,7 +358,7 @@ void CSwapChainProcessor::CompletionFunction(
// Publish readiness before sampling the endpoint. Timing has its own valid
// flag and is published immediately afterwards.
sc->m_devContext->FinalizeFrameBuffer(fbRes->GetFrameIndex());
const uint64_t readyEnd = Nanotime();
const uint64_t readyEnd = CFrameScheduler::Nanotime();
uint64_t postProcessTime = cpuCopyStart - fbRes->GetPostProcessStart();
uint64_t copyTime = readyEnd - cpuCopyStart;
@@ -391,6 +375,8 @@ void CSwapChainProcessor::CompletionFunction(
sc->m_postProcessors[fbRes->GetFrameIndex()].RecordTiming(
fbRes->GetTimingEffectIndex(), fbRes->GetTimingToken(),
fbRes->IsFullCopy(), postProcessTime + copyTime + readyTime);
sc->m_devContext->RecordFrameTiming(
postProcessTime + copyTime + readyTime);
sc->m_devContext->SetFrameTiming(fbRes->GetFrameIndex(),
fbRes->GetCaptureTime(), postProcessTime, copyTime, readyTime);
sc->m_devContext->CompleteFrameBuffer(fbRes->GetFrameIndex());
@@ -643,9 +629,11 @@ bool CSwapChainProcessor::SwapChainNewFrame(ComPtr<IDXGIResource> acquiredBuffer
DXGI_COLOR_SPACE_TYPE colorSpace, UINT sdrWhiteLevel,
uint64_t captureStart)
{
const uint64_t postProcessStart = Nanotime();
const uint64_t postProcessStart = CFrameScheduler::Nanotime();
const uint64_t captureTime = postProcessStart - captureStart;
m_devContext->ObserveFrame(postProcessStart);
// Preserve the fast drop path: never hold an IddCx frame while waiting for
// a slow or disconnected client. We have not read its rectangles, so force
// the next published frame to invalidate the entire image.
@@ -874,12 +862,21 @@ bool CSwapChainProcessor::SwapChainNewFrame(ComPtr<IDXGIResource> acquiredBuffer
SetFullPendingDamage();
// Adaptive effects need comparable full-frame samples until they lock.
if (m_postProcessors[0].RequiresFullDamage())
const bool requiresFullDamage =
m_postProcessors[0].RequiresFullDamage();
if (requiresFullDamage)
SetFullPendingDamage();
if (noImageUpdate && !m_hasPendingDamage)
return true;
uint32_t scheduleGeneration = 0;
if (!m_devContext->SelectFrame(CFrameScheduler::Nanotime(),
needsReconfigure || postProcessFormatChanged ||
frameMetadataChanged || requiresFullDamage,
scheduleGeneration))
return true;
const D12FrameFormat& dstFormat =
m_postProcessors[0].GetOutputFormat();
const unsigned pitch = m_postProcessors[0].GetOutputPitch();
@@ -1000,7 +997,7 @@ bool CSwapChainProcessor::SwapChainNewFrame(ComPtr<IDXGIResource> acquiredBuffer
ClipDirtyRects(currentDirtyRects, &nbDirtyRects,
dstFormat.width, dstFormat.height);
const uint64_t copyStart = Nanotime();
const uint64_t copyStart = CFrameScheduler::Nanotime();
fbRes->SetTiming(captureTime, postProcessStart, copyStart);
copySlot->SetCompletionCallback(&CompletionFunction, this, fbRes);
@@ -1070,7 +1067,8 @@ bool CSwapChainProcessor::SwapChainNewFrame(ComPtr<IDXGIResource> acquiredBuffer
return false;
}
if (!m_devContext->PublishFrameBuffer(buffer.frameIndex))
if (!m_devContext->PublishFrameBuffer(
buffer.frameIndex, scheduleGeneration))
{
SetFullPendingDamage();
return false;