[client/host/idd/lgmp] add additional timing metrics

This commit is contained in:
Geoffrey McRae
2026-08-03 10:57:32 +10:00
parent 3035fa6282
commit 7533c855c2
18 changed files with 339 additions and 88 deletions

View File

@@ -33,13 +33,16 @@ using namespace Microsoft::WRL;
class CFrameBufferResource
{
private:
bool m_valid = false;
unsigned m_frameIndex = 0;
uint8_t * m_base = nullptr;
size_t m_size = 0;
size_t m_frameSize = 0;
bool m_valid = false;
unsigned m_frameIndex = 0;
uint8_t * m_base = nullptr;
size_t m_size = 0;
size_t m_frameSize = 0;
uint64_t m_captureTime = 0;
uint64_t m_postProcessTime = 0;
uint64_t m_copyStart = 0;
ComPtr<ID3D12Resource> m_res;
void * m_map = nullptr;
void * m_map = nullptr;
public:
bool Init(CSwapChainProcessor * swapChain, unsigned frameIndex, uint8_t * base, size_t size);
@@ -52,5 +55,16 @@ class CFrameBufferResource
size_t GetFrameSize() { return m_frameSize; }
void * GetMap() { return m_map; }
void SetTiming(uint64_t captureTime, uint64_t postProcessTime,
uint64_t copyStart)
{
m_captureTime = captureTime;
m_postProcessTime = postProcessTime;
m_copyStart = copyStart;
}
uint64_t GetCaptureTime () const { return m_captureTime; }
uint64_t GetPostProcessTime() const { return m_postProcessTime; }
uint64_t GetCopyStart () const { return m_copyStart; }
ComPtr<ID3D12Resource> Get() { return m_res; }
};

View File

@@ -212,7 +212,8 @@ void CIndirectDeviceContext::InitAdapter()
return;
}
if (InterlockedCompareExchange(&m_initInProgress, 1, 0) != 0)
LONG initExpected = 0;
if (!m_initInProgress.compare_exchange_strong(initExpected, 1))
{
DEBUG_TRACE("Adapter initialization skipped: initialization already in progress");
return;
@@ -227,7 +228,7 @@ void CIndirectDeviceContext::InitAdapter()
{
DEBUG_WARN("IVSHMEM not available yet, scheduling init retry");
ScheduleInitRetry();
InterlockedExchange(&m_initInProgress, 0);
m_initInProgress.store(0);
return;
}
m_ivshmemOpened = true;
@@ -370,7 +371,7 @@ void CIndirectDeviceContext::InitAdapter()
if (!NT_SUCCESS(status))
{
DEBUG_ERROR_HR(status, "IddCxAdapterInitAsync Failed");
InterlockedExchange(&m_initInProgress, 0);
m_initInProgress.store(0);
return;
}
@@ -378,7 +379,7 @@ void CIndirectDeviceContext::InitAdapter()
if (!m_adapter)
{
DEBUG_ERROR("IddCxAdapterInitAsync succeeded without returning an adapter object");
InterlockedExchange(&m_initInProgress, 0);
m_initInProgress.store(0);
return;
}
@@ -401,7 +402,7 @@ void CIndirectDeviceContext::InitAdapter()
// Adapter is up; no need to keep retrying.
StopInitRetry();
InterlockedExchange(&m_initInProgress, 0);
m_initInProgress.store(0);
DEBUG_INFO("Adapter initialization request complete; returning to IddCx");
}
@@ -501,7 +502,7 @@ void CIndirectDeviceContext::ReplugMonitor()
ReleaseSRWLockExclusive(&m_stateLock);
// Either no monitor yet, or one is already pending; build it now and
// cancel any queued rebuild so we do not create two.
InterlockedExchange(&m_finishInitQueued, 0);
m_finishInitQueued.store(0);
FinishInit(0);
return;
}
@@ -537,7 +538,7 @@ void CIndirectDeviceContext::ReplugMonitor()
// If there was no swap chain there will be no unassign callback to queue the
// rebuild. Otherwise OnSwapChainReleased does so after teardown has drained.
if (rebuild)
InterlockedExchange(&m_finishInitQueued, 1);
m_finishInitQueued.store(1);
}
void CIndirectDeviceContext::OnMonitorDestroyed(IDDCX_MONITOR monitor)
@@ -571,7 +572,7 @@ void CIndirectDeviceContext::OnSwapChainReleased()
ReleaseSRWLockExclusive(&m_stateLock);
if (rebuild)
InterlockedExchange(&m_finishInitQueued, 1);
m_finishInitQueued.store(1);
}
void CIndirectDeviceContext::OnSwapChainReady()
@@ -613,7 +614,7 @@ void CIndirectDeviceContext::OnSwapChainReady()
g_pipe.SetDeviceContext(this);
if (replug)
InterlockedExchange(&m_replugQueued, 1);
m_replugQueued.store(1);
else if (doSetMode)
g_pipe.SetDisplayMode(mode.width, mode.height, mode.refresh);
}
@@ -1100,7 +1101,7 @@ bool CIndirectDeviceContext::SetupLGMP(size_t alignSize)
void CIndirectDeviceContext::DeInitLGMP()
{
InterlockedExchange(&m_publishedFrameIndex, -1);
m_publishedFrameIndex.store(-1);
// The retry timer callback dereferences this context, so make sure it is
// stopped and drained before we tear anything down. Wait for any in-flight
@@ -1135,13 +1136,13 @@ void CIndirectDeviceContext::DeInitLGMP()
void CIndirectDeviceContext::LGMPTimer()
{
// Rebuild the monitor queued by ReplugMonitor, off the IddCx callback thread.
if (InterlockedExchange(&m_finishInitQueued, 0))
if (m_finishInitQueued.exchange(0))
{
FinishInit(0);
return;
}
if (InterlockedExchange(&m_replugQueued, 0))
if (m_replugQueued.exchange(0))
{
ReplugMonitor();
return;
@@ -1188,8 +1189,7 @@ void CIndirectDeviceContext::LGMPTimer()
if (lgmpHostQueueNewSubs(m_frameQueue) && m_monitor)
{
const LONG frameIndex =
InterlockedCompareExchange(&m_publishedFrameIndex, 0, 0);
const LONG frameIndex = m_publishedFrameIndex.load();
if (frameIndex >= 0)
lgmpHostQueuePost(m_frameQueue, 0, m_frameMemory[frameIndex]);
}
@@ -1299,6 +1299,11 @@ CIndirectDeviceContext::PreparedFrameBuffer CIndirectDeviceContext::PrepareFrame
// fi->offset is initialized at startup
fi->flags = flags;
fi->sdrWhiteLevel = dstFormat.sdrWhiteLevel;
fi->captureTime = 0;
fi->postProcessTime = 0;
fi->copyTime = 0;
fi->timingSerial = 0;
InterlockedExchange((volatile LONG *)&fi->timingValid, 0);
fi->rotation = FRAME_ROT_0;
fi->type = dstFormat.format;
@@ -1351,7 +1356,7 @@ bool CIndirectDeviceContext::PublishFrameBuffer(unsigned frameIndex)
/* Make resends select this submitted frame before posting it. This prevents
* a new subscriber racing publication from receiving the previous frame
* after the new one. */
InterlockedExchange(&m_publishedFrameIndex, (LONG)frameIndex);
m_publishedFrameIndex.store(static_cast<LONG>(frameIndex));
const LGMP_STATUS status =
lgmpHostQueuePost(m_frameQueue, 0, m_frameMemory[frameIndex]);
@@ -1364,6 +1369,20 @@ bool CIndirectDeviceContext::PublishFrameBuffer(unsigned frameIndex)
return true;
}
void CIndirectDeviceContext::SetFrameTiming(unsigned frameIndex,
uint64_t captureTime, uint64_t postProcessTime, uint64_t copyTime)
{
if (frameIndex >= LGMP_Q_FRAME_LEN)
return;
KVMFRFrame * frame = m_frame[frameIndex];
frame->captureTime = captureTime;
frame->postProcessTime = postProcessTime;
frame->copyTime = copyTime;
frame->timingSerial = frame->frameSerial;
InterlockedExchange((volatile LONG *)&frame->timingValid, 1);
}
void CIndirectDeviceContext::WriteFrameBuffer(unsigned frameIndex, void* src, size_t offset, size_t len, bool setWritePos) const
{
FrameBuffer * fb = m_frameBuffer[frameIndex];

View File

@@ -23,6 +23,7 @@
#include <Windows.h>
#include <wdf.h>
#include <IddCx.h>
#include <atomic>
#include <vector>
#include "CIVSHMEM.h"
@@ -78,9 +79,9 @@ private:
// Retry state for InitAdapter. At boot the IVSHMEM device may not have
// enumerated yet; if so we re-attempt from a timer instead of giving up.
WDFTIMER m_initTimer = nullptr;
bool m_ivshmemOpened = false;
volatile LONG m_initInProgress = 0;
WDFTIMER m_initTimer = nullptr;
bool m_ivshmemOpened = false;
std::atomic<LONG> m_initInProgress = 0;
CIVSHMEM m_ivshmem;
@@ -99,16 +100,16 @@ private:
bool m_cursorVisible = false;
int m_cursorX = 0, m_cursorY = 0;
size_t m_alignSize = 0;
size_t m_frameMemoryOffset = 0;
size_t m_maxFrameSize = 0;
int m_frameIndex = 0;
volatile LONG m_publishedFrameIndex = -1;
uint32_t m_formatVer = 0;
uint32_t m_frameSerial = 0;
PLGMPMemory m_frameMemory[LGMP_Q_FRAME_LEN] = {};
KVMFRFrame * m_frame [LGMP_Q_FRAME_LEN] = {};
FrameBuffer * m_frameBuffer[LGMP_Q_FRAME_LEN] = {};
size_t m_alignSize = 0;
size_t m_frameMemoryOffset = 0;
size_t m_maxFrameSize = 0;
int m_frameIndex = 0;
std::atomic<LONG> m_publishedFrameIndex = -1;
uint32_t m_formatVer = 0;
uint32_t m_frameSerial = 0;
PLGMPMemory m_frameMemory[LGMP_Q_FRAME_LEN] = {};
KVMFRFrame * m_frame [LGMP_Q_FRAME_LEN] = {};
FrameBuffer * m_frameBuffer[LGMP_Q_FRAME_LEN] = {};
unsigned m_width = 0;
unsigned m_height = 0;
@@ -164,8 +165,8 @@ private:
// Set by ReplugMonitor after a departure to rebuild the monitor from the LGMP
// timer, off the IddCx callback thread.
volatile LONG m_finishInitQueued = 0;
volatile LONG m_replugQueued = 0;
std::atomic<LONG> m_finishInitQueued = 0;
std::atomic<LONG> m_replugQueued = 0;
public:
CIndirectDeviceContext(_In_ WDFDEVICE wdfDevice) :
@@ -216,6 +217,8 @@ public:
bool FrameBufferAvailable() const;
PreparedFrameBuffer PrepareFrameBuffer(unsigned pitch, const D12FrameFormat& srcFormat, const D12FrameFormat& dstFormat, const RECT * dirtyRects, unsigned nbDirtyRects);
bool PublishFrameBuffer(unsigned frameIndex);
void SetFrameTiming(unsigned frameIndex, uint64_t captureTime,
uint64_t postProcessTime, uint64_t copyTime);
void WriteFrameBuffer(unsigned frameIndex, void* src, size_t offset, size_t len, bool setWritePos) const;
void FinalizeFrameBuffer(unsigned frameIndex) const;

View File

@@ -28,6 +28,22 @@
static const uint32_t HDR_PQ_MIN_LUMINANCE = 50;
static const uint32_t HDR_PQ_MAX_LUMINANCE = 10000;
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;
}
CSwapChainProcessor::CSwapChainProcessor(CIndirectMonitorContext * monitorContext,
UINT64 assignmentGeneration, IDDCX_MONITOR monitor,
CIndirectDeviceContext* devContext, IDDCX_SWAPCHAIN hSwapChain,
@@ -192,6 +208,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();
#ifdef HAS_IDDCX_110
if (m_devContext->HasIddCx110DDIs())
@@ -252,7 +269,8 @@ bool CSwapChainProcessor::SwapChainThreadCore()
if (frameNumber != lastFrameNumber)
{
lastFrameNumber = frameNumber;
if (!SwapChainNewFrame(surface, dirtyRectCount, colorSpace, sdrWhiteLevel))
if (!SwapChainNewFrame(surface, dirtyRectCount, colorSpace,
sdrWhiteLevel, Nanotime() - captureStart))
DEBUG_WARN("Failed to submit frame");
}
@@ -286,16 +304,21 @@ void CSwapChainProcessor::CompletionFunction(
// fail gracefully
if (!result)
{
sc->m_devContext->SetFrameTiming(fbRes->GetFrameIndex(),
fbRes->GetCaptureTime(), fbRes->GetPostProcessTime(),
Nanotime() - fbRes->GetCopyStart());
sc->m_devContext->FinalizeFrameBuffer(fbRes->GetFrameIndex());
return;
}
if (sc->m_dx12Device->IsIndirectCopy())
sc->m_devContext->WriteFrameBuffer(
fbRes->GetFrameIndex(),
fbRes->GetMap(), 0, fbRes->GetFrameSize(), true);
else
sc->m_devContext->FinalizeFrameBuffer(fbRes->GetFrameIndex());
fbRes->GetFrameIndex(), fbRes->GetMap(), 0, fbRes->GetFrameSize(), false);
const uint64_t copyTime = Nanotime() - fbRes->GetCopyStart();
sc->m_devContext->SetFrameTiming(fbRes->GetFrameIndex(),
fbRes->GetCaptureTime(), fbRes->GetPostProcessTime(), copyTime);
sc->m_devContext->FinalizeFrameBuffer(fbRes->GetFrameIndex());
}
@@ -470,8 +493,9 @@ bool CSwapChainProcessor::GetContentHDRMetadata(D12FrameFormat& format) const
#endif
}
bool CSwapChainProcessor::SwapChainNewFrame(ComPtr<IDXGIResource> acquiredBuffer, unsigned dirtyRectCount,
DXGI_COLOR_SPACE_TYPE colorSpace, UINT sdrWhiteLevel)
bool CSwapChainProcessor::SwapChainNewFrame(ComPtr<IDXGIResource> acquiredBuffer,
unsigned dirtyRectCount, DXGI_COLOR_SPACE_TYPE colorSpace,
UINT sdrWhiteLevel, uint64_t captureTime)
{
// 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
@@ -482,6 +506,8 @@ bool CSwapChainProcessor::SwapChainNewFrame(ComPtr<IDXGIResource> acquiredBuffer
return true;
}
const uint64_t postProcessStart = Nanotime();
ComPtr<ID3D11Texture2D> texture;
HRESULT hr = acquiredBuffer.As(&texture);
if (FAILED(hr))
@@ -708,6 +734,9 @@ bool CSwapChainProcessor::SwapChainNewFrame(ComPtr<IDXGIResource> acquiredBuffer
return false;
}
const uint64_t copyStart = Nanotime();
fbRes->SetTiming(captureTime, copyStart - postProcessStart, copyStart);
copyQueue->SetCompletionCallback(&CompletionFunction, this, fbRes);
D3D12_TEXTURE_COPY_LOCATION srcLoc = {};

View File

@@ -102,7 +102,7 @@ private:
#endif
bool GetContentHDRMetadata(D12FrameFormat& format) const;
bool SwapChainNewFrame(ComPtr<IDXGIResource> acquiredBuffer, unsigned dirtyRectCount,
DXGI_COLOR_SPACE_TYPE colorSpace, UINT sdrWhiteLevel);
DXGI_COLOR_SPACE_TYPE colorSpace, UINT sdrWhiteLevel, uint64_t captureTime);
public:
CSwapChainProcessor(CIndirectMonitorContext * monitorContext, UINT64 assignmentGeneration,