mirror of
https://github.com/gnif/LookingGlass.git
synced 2026-08-04 06:12:04 +00:00
[idd] publish the latest frame before its deadline
This commit is contained in:
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
};
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user