[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

@@ -92,6 +92,14 @@ enum
typedef uint32_t LG_TransportFrameFlags; typedef uint32_t LG_TransportFrameFlags;
typedef struct LG_TransportFrameTiming
{
uint64_t captureTime;
uint64_t postProcessTime;
uint64_t copyTime;
}
LG_TransportFrameTiming;
typedef struct LG_TransportFrameFormat typedef struct LG_TransportFrameFormat
{ {
uint32_t version; uint32_t version;
@@ -200,6 +208,11 @@ typedef struct LG_TransportOps
LG_TransportStatus (*nextFrame)(LG_Transport * transport, bool useDMA, LG_TransportStatus (*nextFrame)(LG_Transport * transport, bool useDMA,
LG_TransportFrame * frame); LG_TransportFrame * frame);
/* Read producer timings after the renderer has consumed the frame. Some
* transports publish the frame while its asynchronous copy is in progress,
* so these values are intentionally sampled late. */
void (*getFrameTiming)(LG_Transport * transport,
const LG_TransportFrame * frame, LG_TransportFrameTiming * timing);
void (*releaseFrame)(LG_Transport * transport, LG_TransportFrame * frame); void (*releaseFrame)(LG_Transport * transport, LG_TransportFrame * frame);
/* Called by the frame consumer as it exits. A backend may release transient /* Called by the frame consumer as it exits. A backend may release transient
* stream resources; nextFrame must reacquire them when the consumer restarts. */ * stream resources; nextFrame must reacquire them when the consumer restarts. */

View File

@@ -199,11 +199,21 @@ static bool tickTimerFn(void * unused)
return true; return true;
} }
struct RenderTiming
{
uint64_t renderStart;
uint64_t producerTime;
};
static void preSwapCallback(void * udata) static void preSwapCallback(void * udata)
{ {
const uint64_t * renderStart = (const uint64_t *)udata; const struct RenderTiming * timing = (const struct RenderTiming *)udata;
ringbuffer_push(g_state.renderDuration, const uint64_t renderTime = nanotime() - timing->renderStart;
&(float) {(nanotime() - *renderStart) * 1e-6f}); ringbuffer_push(g_state.renderDuration, &(float) {renderTime * 1e-6f});
if (timing->producerTime)
ringbuffer_push(g_state.endToEndDuration,
&(float) {(timing->producerTime + renderTime) * 1e-6f});
#ifdef ENABLE_TESTS #ifdef ENABLE_TESTS
if (!l_testCapture.enabled || l_testCapture.complete) if (!l_testCapture.enabled || l_testCapture.complete)
@@ -397,13 +407,17 @@ static int renderThread(void * unused)
const bool invalidate = atomic_exchange(&g_state.invalidateWindow, false); const bool invalidate = atomic_exchange(&g_state.invalidateWindow, false);
const uint64_t renderStart = nanotime(); const struct RenderTiming renderTiming = {
.renderStart = nanotime(),
.producerTime = newFrame ? atomic_load_explicit(
&g_state.producerFrameTime, memory_order_acquire) : 0,
};
LG_LOCK(g_state.lgrLock); LG_LOCK(g_state.lgrLock);
renderQueue_process(); renderQueue_process();
if (unlikely(!RENDERER(render, g_params.winRotate, newFrame, invalidate, if (unlikely(!RENDERER(render, g_params.winRotate, newFrame, invalidate,
preSwapCallback, (void *)&renderStart))) preSwapCallback, (void *)&renderTiming)))
{ {
LG_UNLOCK(g_state.lgrLock); LG_UNLOCK(g_state.lgrLock);
break; break;
@@ -778,6 +792,21 @@ int main_frameThread(void * unused)
break; break;
} }
LG_TransportFrameTiming timing = {};
if (g_state.transportOps->getFrameTiming)
g_state.transportOps->getFrameTiming(
g_state.transport, &frame, &timing);
ringbuffer_push(g_state.captureDuration,
&(float) {timing.captureTime * 1e-6f});
ringbuffer_push(g_state.postProcessDuration,
&(float) {timing.postProcessTime * 1e-6f});
ringbuffer_push(g_state.copyDuration,
&(float) {timing.copyTime * 1e-6f});
atomic_store_explicit(&g_state.producerFrameTime,
timing.captureTime + timing.postProcessTime + timing.copyTime,
memory_order_release);
overlaySplash_show(false); overlaySplash_show(false);
if ((frame.flags & LG_TRANSPORT_FRAME_REQUEST_ACTIVATION) && if ((frame.flags & LG_TRANSPORT_FRAME_REQUEST_ACTIVATION) &&
g_params.requestActivation) g_params.requestActivation)
@@ -1305,12 +1334,20 @@ static int lg_run(void)
DEBUG_INFO("Using font: %s", g_state.fontName); DEBUG_INFO("Using font: %s", g_state.fontName);
// initialize metrics ringbuffers // initialize metrics ringbuffers
g_state.renderTimings = ringbuffer_new(256, sizeof(float)); g_state.renderTimings = ringbuffer_new(256, sizeof(float));
g_state.uploadTimings = ringbuffer_new(256, sizeof(float)); g_state.uploadTimings = ringbuffer_new(256, sizeof(float));
g_state.renderDuration = ringbuffer_new(256, sizeof(float)); g_state.renderDuration = ringbuffer_new(256, sizeof(float));
overlayGraph_register("FRAME" , g_state.renderTimings , 0.0f, 50.0f, NULL); g_state.captureDuration = ringbuffer_new(256, sizeof(float));
overlayGraph_register("UPLOAD", g_state.uploadTimings , 0.0f, 50.0f, NULL); g_state.postProcessDuration = ringbuffer_new(256, sizeof(float));
overlayGraph_register("RENDER", g_state.renderDuration, 0.0f, 10.0f, NULL); g_state.copyDuration = ringbuffer_new(256, sizeof(float));
g_state.endToEndDuration = ringbuffer_new(256, sizeof(float));
overlayGraph_register("FRAME" , g_state.renderTimings , 0.0f, 50.0f, NULL);
overlayGraph_register("UPLOAD" , g_state.uploadTimings , 0.0f, 50.0f, NULL);
overlayGraph_register("RENDER" , g_state.renderDuration , 0.0f, 10.0f, NULL);
overlayGraph_register("CAPTURE" , g_state.captureDuration , 0.0f, 20.0f, NULL);
overlayGraph_register("POST PROCESS", g_state.postProcessDuration, 0.0f, 20.0f, NULL);
overlayGraph_register("COPY" , g_state.copyDuration , 0.0f, 20.0f, NULL);
overlayGraph_register("END TO END" , g_state.endToEndDuration , 0.0f, 50.0f, NULL);
// unknown guest OS at this time // unknown guest OS at this time
g_state.guestOS = LG_TRANSPORT_OS_OTHER; g_state.guestOS = LG_TRANSPORT_OS_OTHER;
@@ -1795,6 +1832,10 @@ static void lg_shutdown(void)
ringbuffer_free(&g_state.renderTimings); ringbuffer_free(&g_state.renderTimings);
ringbuffer_free(&g_state.uploadTimings); ringbuffer_free(&g_state.uploadTimings);
ringbuffer_free(&g_state.renderDuration); ringbuffer_free(&g_state.renderDuration);
ringbuffer_free(&g_state.captureDuration);
ringbuffer_free(&g_state.postProcessDuration);
ringbuffer_free(&g_state.copyDuration);
ringbuffer_free(&g_state.endToEndDuration);
free(g_state.fontName); free(g_state.fontName);
igDestroyContext(NULL); igDestroyContext(NULL);

View File

@@ -150,8 +150,13 @@ struct AppState
RingBuffer renderTimings; RingBuffer renderTimings;
RingBuffer renderDuration; RingBuffer renderDuration;
RingBuffer uploadTimings; RingBuffer uploadTimings;
RingBuffer captureDuration;
RingBuffer postProcessDuration;
RingBuffer copyDuration;
RingBuffer endToEndDuration;
atomic_uint_least64_t pendingCount; atomic_uint_least64_t pendingCount;
atomic_uint_least64_t producerFrameTime;
atomic_uint_least64_t renderCount, frameCount; atomic_uint_least64_t renderCount, frameCount;
_Atomic(float) fps, ups; _Atomic(float) fps, ups;

View File

@@ -122,17 +122,22 @@ int main(void)
wireFrame->formatVer = 1; wireFrame->formatVer = 1;
wireFrame->frameSerial = 1; wireFrame->frameSerial = 1;
wireFrame->type = FRAME_TYPE_BGRA; wireFrame->type = FRAME_TYPE_BGRA;
wireFrame->screenWidth = 1; wireFrame->screenWidth = 1;
wireFrame->screenHeight = 1; wireFrame->screenHeight = 1;
wireFrame->dataWidth = 1; wireFrame->dataWidth = 1;
wireFrame->dataHeight = 1; wireFrame->dataHeight = 1;
wireFrame->frameWidth = 1; wireFrame->frameWidth = 1;
wireFrame->frameHeight = 1; wireFrame->frameHeight = 1;
wireFrame->rotation = FRAME_ROT_0; wireFrame->rotation = FRAME_ROT_0;
wireFrame->stride = 1; wireFrame->stride = 1;
wireFrame->pitch = sizeof(uint32_t); wireFrame->pitch = sizeof(uint32_t);
wireFrame->offset = sizeof(*wireFrame); wireFrame->offset = sizeof(*wireFrame);
wireFrame->sdrWhiteLevel = KVMFR_SDR_WHITE_LEVEL_DEFAULT; wireFrame->sdrWhiteLevel = KVMFR_SDR_WHITE_LEVEL_DEFAULT;
wireFrame->captureTime = 100;
wireFrame->postProcessTime = 200;
wireFrame->copyTime = 300;
wireFrame->timingSerial = wireFrame->frameSerial;
wireFrame->timingValid = 1;
FrameBuffer * framebuffer = (FrameBuffer *)((uint8_t *)wireFrame + FrameBuffer * framebuffer = (FrameBuffer *)((uint8_t *)wireFrame +
wireFrame->offset); wireFrame->offset);
atomic_store(&framebuffer->wp, sizeof(uint32_t)); atomic_store(&framebuffer->wp, sizeof(uint32_t));
@@ -172,6 +177,11 @@ int main(void)
CHECK(lgmpHostQueuePost(pointerQueue, CURSOR_FLAG_POSITION, CHECK(lgmpHostQueuePost(pointerQueue, CURSOR_FLAG_POSITION,
pointerMemory) == LGMP_OK); pointerMemory) == LGMP_OK);
CHECK(LGT_LGMP.nextFrame(transport, false, &frame) == LG_TRANSPORT_OK); CHECK(LGT_LGMP.nextFrame(transport, false, &frame) == LG_TRANSPORT_OK);
LG_TransportFrameTiming timing;
LGT_LGMP.getFrameTiming(transport, &frame, &timing);
CHECK(timing.captureTime == wireFrame->captureTime);
CHECK(timing.postProcessTime == wireFrame->postProcessTime);
CHECK(timing.copyTime == wireFrame->copyTime);
LGT_LGMP.releaseFrame(transport, &frame); LGT_LGMP.releaseFrame(transport, &frame);
CHECK(LGT_LGMP.nextPointer(transport, &pointer) == LG_TRANSPORT_OK); CHECK(LGT_LGMP.nextPointer(transport, &pointer) == LG_TRANSPORT_OK);
LGT_LGMP.releasePointer(transport, &pointer); LGT_LGMP.releasePointer(transport, &pointer);
@@ -212,7 +222,8 @@ int main(void)
* Also recover when the host has already marked the cached handles bad. * Also recover when the host has already marked the cached handles bad.
* LGMP leaves a timed-out handle non-NULL when unsubscribe fails. * LGMP leaves a timed-out handle non-NULL when unsubscribe fails.
*/ */
wireFrame->frameSerial = 2; wireFrame->frameSerial = 2;
wireFrame->timingSerial = wireFrame->frameSerial;
CHECK(lgmpHostQueuePost(frameQueue, 0, frameMemory) == LGMP_OK); CHECK(lgmpHostQueuePost(frameQueue, 0, frameMemory) == LGMP_OK);
CHECK(lgmpHostQueuePost(pointerQueue, CURSOR_FLAG_POSITION, CHECK(lgmpHostQueuePost(pointerQueue, CURSOR_FLAG_POSITION,
pointerMemory) == LGMP_OK); pointerMemory) == LGMP_OK);

View File

@@ -56,6 +56,7 @@ struct LG_Transport
bool allowDMA; bool allowDMA;
bool connected; bool connected;
bool framePending; bool framePending;
const KVMFRFrame * pendingFrame;
uint32_t frameSerial; uint32_t frameSerial;
bool formatValid; bool formatValid;
LG_TransportFrameFormat format; LG_TransportFrameFormat format;
@@ -214,6 +215,7 @@ static void lgmp_stopFrame(struct LG_Transport * this)
lgmpStatusString(status)); lgmpStatusString(status));
} }
this->framePending = false; this->framePending = false;
this->pendingFrame = NULL;
LGMP_STATUS status = lgmpClientUnsubscribe(&this->frameQueue); LGMP_STATUS status = lgmpClientUnsubscribe(&this->frameQueue);
if (status != LGMP_OK) if (status != LGMP_OK)
{ {
@@ -602,14 +604,47 @@ static LG_TransportStatus lgmp_nextFrame(LG_Transport * this, bool useDMA,
DEBUG_WARN("Invalid damage rectangles, forcing a full update"); DEBUG_WARN("Invalid damage rectangles, forcing a full update");
this->framePending = true; this->framePending = true;
this->pendingFrame = frame;
return LG_TRANSPORT_OK; return LG_TRANSPORT_OK;
} }
static bool lgmp_frameTimingReady(const KVMFRFrame * frame)
{
return __atomic_load_n(&frame->timingValid, __ATOMIC_ACQUIRE) &&
frame->timingSerial == frame->frameSerial;
}
static void lgmp_getFrameTiming(LG_Transport * this,
const LG_TransportFrame * frame, LG_TransportFrameTiming * timing)
{
memset(timing, 0, sizeof(*timing));
if (!this->framePending || !this->pendingFrame ||
this->pendingFrame->frameSerial != frame->serial)
return;
/* The producer writes these immediately before completing the framebuffer.
* nextFrame can observe the header earlier, so sample them only after the
* renderer's onFrame call has consumed the framebuffer. */
for (unsigned i = 0;
!lgmp_frameTimingReady(this->pendingFrame) &&
i < 1000;
++i)
usleep(1);
if (!lgmp_frameTimingReady(this->pendingFrame))
return;
timing->captureTime = this->pendingFrame->captureTime;
timing->postProcessTime = this->pendingFrame->postProcessTime;
timing->copyTime = this->pendingFrame->copyTime;
}
static void lgmp_releaseFrame(LG_Transport * this, LG_TransportFrame * frame) static void lgmp_releaseFrame(LG_Transport * this, LG_TransportFrame * frame)
{ {
if (this->framePending && this->frameQueue) if (this->framePending && this->frameQueue)
lgmpClientMessageDone(this->frameQueue); lgmpClientMessageDone(this->frameQueue);
this->framePending = false; this->framePending = false;
this->pendingFrame = NULL;
memset(frame, 0, sizeof(*frame)); memset(frame, 0, sizeof(*frame));
} }
@@ -800,6 +835,7 @@ const LG_TransportOps LGT_LGMP =
.attachRenderer = lgmp_attachRenderer, .attachRenderer = lgmp_attachRenderer,
.detachRenderer = lgmp_detachRenderer, .detachRenderer = lgmp_detachRenderer,
.nextFrame = lgmp_nextFrame, .nextFrame = lgmp_nextFrame,
.getFrameTiming = lgmp_getFrameTiming,
.releaseFrame = lgmp_releaseFrame, .releaseFrame = lgmp_releaseFrame,
.stopFrame = lgmp_stopFrame, .stopFrame = lgmp_stopFrame,
.nextPointer = lgmp_nextPointer, .nextPointer = lgmp_nextPointer,

View File

@@ -25,11 +25,12 @@
#include <stdint.h> #include <stdint.h>
#include <stdbool.h> #include <stdbool.h>
#include <stddef.h>
#include "types.h" #include "types.h"
#include "LGMPConfig.h" #include "LGMPConfig.h"
#define KVMFR_MAGIC "KVMFR---" #define KVMFR_MAGIC "KVMFR---"
#define KVMFR_VERSION 23 #define KVMFR_VERSION 24
// 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.
@@ -165,6 +166,11 @@ typedef uint32_t KVMFRFrameFlags;
typedef struct KVMFRFrame typedef struct KVMFRFrame
{ {
/*
* Keep the fields consumed for every frame in the first cache line. The
* timing and HDR metadata below are diagnostic/conditional, while the large
* damage rectangle array is deliberately last.
*/
uint32_t formatVer; // the frame format version number uint32_t formatVer; // the frame format version number
uint32_t frameSerial; // the unique frame number uint32_t frameSerial; // the unique frame number
FrameType type; // the frame data type FrameType type; // the frame data type
@@ -178,9 +184,28 @@ typedef struct KVMFRFrame
uint32_t stride; // the row stride (zero if compressed data) uint32_t stride; // the row stride (zero if compressed data)
uint32_t pitch; // the row pitch (stride in bytes or the compressed frame size) uint32_t pitch; // the row pitch (stride in bytes or the compressed frame size)
uint32_t offset; // offset from the start of this header to the FrameBuffer header uint32_t offset; // offset from the start of this header to the FrameBuffer header
uint32_t damageRectsCount; // the number of damage rectangles (zero for full-frame damage)
FrameDamageRect damageRects[KVMFR_MAX_DAMAGE_RECTS];
KVMFRFrameFlags flags; // bit field combination of FRAME_FLAG_* KVMFRFrameFlags flags; // bit field combination of FRAME_FLAG_*
uint32_t damageRectsCount; // the number of damage rectangles (zero for full-frame damage)
// White level in nits for SDR content composited into an HDR frame, such as
// the hardware cursor. Valid for all frames; SDR modes normally report 80.
uint32_t sdrWhiteLevel;
/*
* Producer stage durations in nanoseconds. These are durations rather than
* absolute timestamps because the producer runs in a VM whose monotonic
* clock has a different epoch from the client. Durations can safely be
* combined with the client's local render duration for an end-to-end
* processing time.
*/
uint64_t captureTime;
uint64_t postProcessTime;
uint64_t copyTime;
// 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;
// HDR static metadata (valid when FRAME_FLAG_HDR_METADATA is set) // HDR static metadata (valid when FRAME_FLAG_HDR_METADATA is set)
// Display color primaries in 0.00002 units (SMPTE ST 2086 format) // Display color primaries in 0.00002 units (SMPTE ST 2086 format)
@@ -195,12 +220,22 @@ typedef struct KVMFRFrame
uint32_t hdrMaxContentLightLevel; // MaxCLL (cd/m²) uint32_t hdrMaxContentLightLevel; // MaxCLL (cd/m²)
uint32_t hdrMaxFrameAverageLightLevel; // MaxFALL (cd/m²) uint32_t hdrMaxFrameAverageLightLevel; // MaxFALL (cd/m²)
// White level in nits for SDR content composited into an HDR frame, such as FrameDamageRect damageRects[KVMFR_MAX_DAMAGE_RECTS];
// the hardware cursor. Valid for all frames; SDR modes normally report 80.
uint32_t sdrWhiteLevel;
} }
KVMFRFrame; KVMFRFrame;
#if defined(__cplusplus)
static_assert(offsetof(KVMFRFrame, captureTime) == 64,
"KVMFRFrame hot fields must fit in one cache line");
static_assert(offsetof(KVMFRFrame, damageRects) == 128,
"KVMFRFrame damage rectangles must be cache-line aligned");
#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, damageRects) == 128,
"KVMFRFrame damage rectangles must be cache-line aligned");
#endif
typedef struct KVMFRMessage typedef struct KVMFRMessage
{ {
KVMFRMessageType type; KVMFRMessageType type;

View File

@@ -97,6 +97,12 @@ typedef struct CaptureFrame
uint32_t damageRectsCount; uint32_t damageRectsCount;
FrameDamageRect damageRects[KVMFR_MAX_DAMAGE_RECTS]; FrameDamageRect damageRects[KVMFR_MAX_DAMAGE_RECTS];
// Producer-local durations. Absolute timestamps must not cross KVMFR as the
// client and producer use different monotonic clock domains.
uint64_t captureTime;
uint64_t postProcessTime;
uint64_t copyTime;
} }
CaptureFrame; CaptureFrame;
@@ -146,8 +152,9 @@ typedef struct CaptureInterface
CaptureFrame * frame, CaptureFrame * frame,
const size_t maxFrameSize); const size_t maxFrameSize);
CaptureResult (*getFrame )( CaptureResult (*getFrame )(
unsigned frameBufferIndex, unsigned frameBufferIndex,
FrameBuffer * frame, FrameBuffer * frame,
const size_t maxFrameSize); const size_t maxFrameSize,
CaptureFrame * captureFrame);
} }
CaptureInterface; CaptureInterface;

View File

@@ -299,10 +299,12 @@ static CaptureResult xcb_waitFrame(
} }
static CaptureResult xcb_getFrame( static CaptureResult xcb_getFrame(
unsigned frameBufferIndex, unsigned frameBufferIndex,
FrameBuffer * frame, FrameBuffer * frame,
const size_t maxFrameSize) const size_t maxFrameSize,
CaptureFrame * captureFrame)
{ {
(void)captureFrame;
DEBUG_ASSERT(this); DEBUG_ASSERT(this);
DEBUG_ASSERT(this->initialized); DEBUG_ASSERT(this->initialized);

View File

@@ -463,10 +463,12 @@ static CaptureResult pipewire_waitFrame(
} }
static CaptureResult pipewire_getFrame( static CaptureResult pipewire_getFrame(
unsigned frameBufferIndex, unsigned frameBufferIndex,
FrameBuffer * frame, FrameBuffer * frame,
const size_t maxFrameSize) const size_t maxFrameSize,
CaptureFrame * captureFrame)
{ {
(void)captureFrame;
if (this->stop || !this->frameData) if (this->stop || !this->frameData)
return CAPTURE_RESULT_REINIT; return CAPTURE_RESULT_REINIT;

View File

@@ -29,6 +29,7 @@
#include "common/rects.h" #include "common/rects.h"
#include "common/vector.h" #include "common/vector.h"
#include "common/display.h" #include "common/display.h"
#include "common/time.h"
#include "com_ref.h" #include "com_ref.h"
#include "backend.h" #include "backend.h"
@@ -771,8 +772,11 @@ exit:
return result; return result;
} }
static CaptureResult d12_getFrame(unsigned frameBufferIndex, static CaptureResult d12_getFrame(
FrameBuffer * frameBuffer, const size_t maxFrameSize) unsigned frameBufferIndex,
FrameBuffer * frameBuffer,
const size_t maxFrameSize,
CaptureFrame * captureFrame)
{ {
CaptureResult result = CAPTURE_RESULT_ERROR; CaptureResult result = CAPTURE_RESULT_ERROR;
comRef_scopePush(3); comRef_scopePush(3);
@@ -808,6 +812,7 @@ static CaptureResult d12_getFrame(unsigned frameBufferIndex,
if (result != CAPTURE_RESULT_OK) if (result != CAPTURE_RESULT_OK)
goto exit; goto exit;
const uint64_t postProcessStart = nanotime();
ID3D12Resource * next = *src; ID3D12Resource * next = *src;
D12Effect * effect; D12Effect * effect;
vector_forEach(effect, &this->effects) vector_forEach(effect, &this->effects)
@@ -934,6 +939,8 @@ static CaptureResult d12_getFrame(unsigned frameBufferIndex,
ID3D12CommandQueue_Wait(*this->copyQueue, ID3D12CommandQueue_Wait(*this->copyQueue,
*this->computeCommand.fence, this->computeCommand.fenceValue); *this->computeCommand.fence, this->computeCommand.fenceValue);
} }
captureFrame->postProcessTime = this->effectsActive ?
nanotime() - postProcessStart : 0;
// execute the copy commands // execute the copy commands
DEBUG_TRACE("Execute copy commands"); DEBUG_TRACE("Execute copy commands");

View File

@@ -1504,9 +1504,13 @@ static CaptureResult dxgi_waitFrame(unsigned frameBufferIndex,
return CAPTURE_RESULT_OK; return CAPTURE_RESULT_OK;
} }
static CaptureResult dxgi_getFrame(unsigned frameBufferIndex, static CaptureResult dxgi_getFrame(
FrameBuffer * frame, const size_t maxFrameSize) unsigned frameBufferIndex,
FrameBuffer * frame,
const size_t maxFrameSize,
CaptureFrame * captureFrame)
{ {
(void)captureFrame;
DEBUG_ASSERT(this); DEBUG_ASSERT(this);
DEBUG_ASSERT(this->initialized); DEBUG_ASSERT(this->initialized);

View File

@@ -718,9 +718,13 @@ static CaptureResult nvfbc_waitFrame(unsigned frameBufferIndex,
return CAPTURE_RESULT_OK; return CAPTURE_RESULT_OK;
} }
static CaptureResult nvfbc_getFrame(unsigned frameBufferIndex, static CaptureResult nvfbc_getFrame(
FrameBuffer * frame, const size_t maxFrameSize) unsigned frameBufferIndex,
FrameBuffer * frame,
const size_t maxFrameSize,
CaptureFrame * captureFrame)
{ {
(void)captureFrame;
const unsigned int h = DIFF_MAP_DIM(this->grabHeight, this->diffShift); const unsigned int h = DIFF_MAP_DIM(this->grabHeight, this->diffShift);
const unsigned int w = DIFF_MAP_DIM(this->grabWidth, this->diffShift); const unsigned int w = DIFF_MAP_DIM(this->grabWidth, this->diffShift);
uint8_t * frameData = framebuffer_get_data(frame); uint8_t * frameData = framebuffer_get_data(frame);

View File

@@ -109,6 +109,7 @@ struct app
bool frameValid; bool frameValid;
uint32_t frameSerial; uint32_t frameSerial;
uint32_t formatVer; uint32_t formatVer;
uint64_t captureTime[LGMP_Q_FRAME_LEN];
unsigned int captureFormatVer; unsigned int captureFormatVer;
bool hdr; bool hdr;
bool hdrPQ; bool hdrPQ;
@@ -291,6 +292,7 @@ static bool sendFrame(CaptureResult result, bool * restart)
} }
KVMFRFrame * fi = app.frame[app.captureIndex]; KVMFRFrame * fi = app.frame[app.captureIndex];
frame.captureTime = app.captureTime[app.captureIndex];
const uint32_t sdrWhiteLevel = frame.sdrWhiteLevel ? const uint32_t sdrWhiteLevel = frame.sdrWhiteLevel ?
frame.sdrWhiteLevel : KVMFR_SDR_WHITE_LEVEL_DEFAULT; frame.sdrWhiteLevel : KVMFR_SDR_WHITE_LEVEL_DEFAULT;
const bool metadataChanged = const bool metadataChanged =
@@ -396,6 +398,11 @@ static bool sendFrame(CaptureResult result, bool * restart)
// fi->offset is initialized at startup // fi->offset is initialized at startup
fi->flags = flags; fi->flags = flags;
fi->sdrWhiteLevel = sdrWhiteLevel; fi->sdrWhiteLevel = sdrWhiteLevel;
fi->captureTime = frame.captureTime;
fi->postProcessTime = 0;
fi->copyTime = 0;
fi->timingSerial = 0;
__atomic_store_n(&fi->timingValid, 0, __ATOMIC_RELAXED);
if (frame.hdrMetadata) if (frame.hdrMetadata)
{ {
memcpy(fi->hdrDisplayPrimary, frame.hdrDisplayPrimary, memcpy(fi->hdrDisplayPrimary, frame.hdrDisplayPrimary,
@@ -433,10 +440,20 @@ static bool sendFrame(CaptureResult result, bool * restart)
return true; return true;
} }
const uint64_t copyStart = nanotime();
app.iface->getFrame( app.iface->getFrame(
app.captureIndex, app.captureIndex,
app.frameBuffer[app.captureIndex], app.frameBuffer[app.captureIndex],
app.maxFrameSize); app.maxFrameSize,
&frame);
const uint64_t copyEnd = nanotime();
fi->postProcessTime = frame.postProcessTime;
frame.copyTime = copyEnd - copyStart > frame.postProcessTime ?
copyEnd - copyStart - frame.postProcessTime : 0;
fi->copyTime = frame.copyTime;
fi->timingSerial = fi->frameSerial;
__atomic_store_n(&fi->timingValid, 1, __ATOMIC_RELEASE);
app.readIndex = app.captureIndex; app.readIndex = app.captureIndex;
if (++app.captureIndex == LGMP_Q_FRAME_LEN) if (++app.captureIndex == LGMP_Q_FRAME_LEN)
@@ -1108,13 +1125,15 @@ int app_main(int argc, char * argv[])
nsleep(us * 1000); nsleep(us * 1000);
} }
const uint64_t captureStartTime = microtime(); const uint64_t captureStartTime = nanotime();
const CaptureResult result = app.iface->capture( const CaptureResult result = app.iface->capture(
app.captureIndex, app.frameBuffer[app.captureIndex]); app.captureIndex, app.frameBuffer[app.captureIndex]);
app.captureTime[app.captureIndex] = nanotime() - captureStartTime;
if (likely(result == CAPTURE_RESULT_OK)) if (likely(result == CAPTURE_RESULT_OK))
previousFrameTime = captureStartTime; previousFrameTime = captureStartTime / 1000;
else if (likely(result == CAPTURE_RESULT_TIMEOUT)) else if (likely(result == CAPTURE_RESULT_TIMEOUT))
{ {
if (!app.iface->asyncCapture) if (!app.iface->asyncCapture)

View File

@@ -33,13 +33,16 @@ using namespace Microsoft::WRL;
class CFrameBufferResource class CFrameBufferResource
{ {
private: private:
bool m_valid = false; bool m_valid = false;
unsigned m_frameIndex = 0; unsigned m_frameIndex = 0;
uint8_t * m_base = nullptr; uint8_t * m_base = nullptr;
size_t m_size = 0; size_t m_size = 0;
size_t m_frameSize = 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; ComPtr<ID3D12Resource> m_res;
void * m_map = nullptr; void * m_map = nullptr;
public: public:
bool Init(CSwapChainProcessor * swapChain, unsigned frameIndex, uint8_t * base, size_t size); bool Init(CSwapChainProcessor * swapChain, unsigned frameIndex, uint8_t * base, size_t size);
@@ -52,5 +55,16 @@ class CFrameBufferResource
size_t GetFrameSize() { return m_frameSize; } size_t GetFrameSize() { return m_frameSize; }
void * GetMap() { return m_map; } 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; } ComPtr<ID3D12Resource> Get() { return m_res; }
}; };

View File

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

View File

@@ -23,6 +23,7 @@
#include <Windows.h> #include <Windows.h>
#include <wdf.h> #include <wdf.h>
#include <IddCx.h> #include <IddCx.h>
#include <atomic>
#include <vector> #include <vector>
#include "CIVSHMEM.h" #include "CIVSHMEM.h"
@@ -78,9 +79,9 @@ private:
// Retry state for InitAdapter. At boot the IVSHMEM device may not have // 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. // enumerated yet; if so we re-attempt from a timer instead of giving up.
WDFTIMER m_initTimer = nullptr; WDFTIMER m_initTimer = nullptr;
bool m_ivshmemOpened = false; bool m_ivshmemOpened = false;
volatile LONG m_initInProgress = 0; std::atomic<LONG> m_initInProgress = 0;
CIVSHMEM m_ivshmem; CIVSHMEM m_ivshmem;
@@ -99,16 +100,16 @@ private:
bool m_cursorVisible = false; bool m_cursorVisible = false;
int m_cursorX = 0, m_cursorY = 0; int m_cursorX = 0, m_cursorY = 0;
size_t m_alignSize = 0; size_t m_alignSize = 0;
size_t m_frameMemoryOffset = 0; size_t m_frameMemoryOffset = 0;
size_t m_maxFrameSize = 0; size_t m_maxFrameSize = 0;
int m_frameIndex = 0; int m_frameIndex = 0;
volatile LONG m_publishedFrameIndex = -1; std::atomic<LONG> m_publishedFrameIndex = -1;
uint32_t m_formatVer = 0; uint32_t m_formatVer = 0;
uint32_t m_frameSerial = 0; uint32_t m_frameSerial = 0;
PLGMPMemory m_frameMemory[LGMP_Q_FRAME_LEN] = {}; PLGMPMemory m_frameMemory[LGMP_Q_FRAME_LEN] = {};
KVMFRFrame * m_frame [LGMP_Q_FRAME_LEN] = {}; KVMFRFrame * m_frame [LGMP_Q_FRAME_LEN] = {};
FrameBuffer * m_frameBuffer[LGMP_Q_FRAME_LEN] = {}; FrameBuffer * m_frameBuffer[LGMP_Q_FRAME_LEN] = {};
unsigned m_width = 0; unsigned m_width = 0;
unsigned m_height = 0; unsigned m_height = 0;
@@ -164,8 +165,8 @@ private:
// Set by ReplugMonitor after a departure to rebuild the monitor from the LGMP // Set by ReplugMonitor after a departure to rebuild the monitor from the LGMP
// timer, off the IddCx callback thread. // timer, off the IddCx callback thread.
volatile LONG m_finishInitQueued = 0; std::atomic<LONG> m_finishInitQueued = 0;
volatile LONG m_replugQueued = 0; std::atomic<LONG> m_replugQueued = 0;
public: public:
CIndirectDeviceContext(_In_ WDFDEVICE wdfDevice) : CIndirectDeviceContext(_In_ WDFDEVICE wdfDevice) :
@@ -216,6 +217,8 @@ public:
bool FrameBufferAvailable() const; bool FrameBufferAvailable() const;
PreparedFrameBuffer PrepareFrameBuffer(unsigned pitch, const D12FrameFormat& srcFormat, const D12FrameFormat& dstFormat, const RECT * dirtyRects, unsigned nbDirtyRects); PreparedFrameBuffer PrepareFrameBuffer(unsigned pitch, const D12FrameFormat& srcFormat, const D12FrameFormat& dstFormat, const RECT * dirtyRects, unsigned nbDirtyRects);
bool PublishFrameBuffer(unsigned frameIndex); 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 WriteFrameBuffer(unsigned frameIndex, void* src, size_t offset, size_t len, bool setWritePos) const;
void FinalizeFrameBuffer(unsigned frameIndex) 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_MIN_LUMINANCE = 50;
static const uint32_t HDR_PQ_MAX_LUMINANCE = 10000; 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, CSwapChainProcessor::CSwapChainProcessor(CIndirectMonitorContext * monitorContext,
UINT64 assignmentGeneration, IDDCX_MONITOR monitor, UINT64 assignmentGeneration, IDDCX_MONITOR monitor,
CIndirectDeviceContext* devContext, IDDCX_SWAPCHAIN hSwapChain, CIndirectDeviceContext* devContext, IDDCX_SWAPCHAIN hSwapChain,
@@ -192,6 +208,7 @@ bool CSwapChainProcessor::SwapChainThreadCore()
// path HDR is not available, so default to SDR. // path HDR is not available, so default to SDR.
DXGI_COLOR_SPACE_TYPE colorSpace = DXGI_COLOR_SPACE_RGB_FULL_G22_NONE_P709; DXGI_COLOR_SPACE_TYPE colorSpace = DXGI_COLOR_SPACE_RGB_FULL_G22_NONE_P709;
UINT sdrWhiteLevel = KVMFR_SDR_WHITE_LEVEL_DEFAULT; UINT sdrWhiteLevel = KVMFR_SDR_WHITE_LEVEL_DEFAULT;
const uint64_t captureStart = Nanotime();
#ifdef HAS_IDDCX_110 #ifdef HAS_IDDCX_110
if (m_devContext->HasIddCx110DDIs()) if (m_devContext->HasIddCx110DDIs())
@@ -252,7 +269,8 @@ bool CSwapChainProcessor::SwapChainThreadCore()
if (frameNumber != lastFrameNumber) if (frameNumber != lastFrameNumber)
{ {
lastFrameNumber = frameNumber; lastFrameNumber = frameNumber;
if (!SwapChainNewFrame(surface, dirtyRectCount, colorSpace, sdrWhiteLevel)) if (!SwapChainNewFrame(surface, dirtyRectCount, colorSpace,
sdrWhiteLevel, Nanotime() - captureStart))
DEBUG_WARN("Failed to submit frame"); DEBUG_WARN("Failed to submit frame");
} }
@@ -286,16 +304,21 @@ void CSwapChainProcessor::CompletionFunction(
// fail gracefully // fail gracefully
if (!result) if (!result)
{ {
sc->m_devContext->SetFrameTiming(fbRes->GetFrameIndex(),
fbRes->GetCaptureTime(), fbRes->GetPostProcessTime(),
Nanotime() - fbRes->GetCopyStart());
sc->m_devContext->FinalizeFrameBuffer(fbRes->GetFrameIndex()); sc->m_devContext->FinalizeFrameBuffer(fbRes->GetFrameIndex());
return; return;
} }
if (sc->m_dx12Device->IsIndirectCopy()) if (sc->m_dx12Device->IsIndirectCopy())
sc->m_devContext->WriteFrameBuffer( sc->m_devContext->WriteFrameBuffer(
fbRes->GetFrameIndex(), fbRes->GetFrameIndex(), fbRes->GetMap(), 0, fbRes->GetFrameSize(), false);
fbRes->GetMap(), 0, fbRes->GetFrameSize(), true);
else const uint64_t copyTime = Nanotime() - fbRes->GetCopyStart();
sc->m_devContext->FinalizeFrameBuffer(fbRes->GetFrameIndex()); 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 #endif
} }
bool CSwapChainProcessor::SwapChainNewFrame(ComPtr<IDXGIResource> acquiredBuffer, unsigned dirtyRectCount, bool CSwapChainProcessor::SwapChainNewFrame(ComPtr<IDXGIResource> acquiredBuffer,
DXGI_COLOR_SPACE_TYPE colorSpace, UINT sdrWhiteLevel) 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 // 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 // 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; return true;
} }
const uint64_t postProcessStart = Nanotime();
ComPtr<ID3D11Texture2D> texture; ComPtr<ID3D11Texture2D> texture;
HRESULT hr = acquiredBuffer.As(&texture); HRESULT hr = acquiredBuffer.As(&texture);
if (FAILED(hr)) if (FAILED(hr))
@@ -708,6 +734,9 @@ bool CSwapChainProcessor::SwapChainNewFrame(ComPtr<IDXGIResource> acquiredBuffer
return false; return false;
} }
const uint64_t copyStart = Nanotime();
fbRes->SetTiming(captureTime, copyStart - postProcessStart, copyStart);
copyQueue->SetCompletionCallback(&CompletionFunction, this, fbRes); copyQueue->SetCompletionCallback(&CompletionFunction, this, fbRes);
D3D12_TEXTURE_COPY_LOCATION srcLoc = {}; D3D12_TEXTURE_COPY_LOCATION srcLoc = {};

View File

@@ -102,7 +102,7 @@ private:
#endif #endif
bool GetContentHDRMetadata(D12FrameFormat& format) const; bool GetContentHDRMetadata(D12FrameFormat& format) const;
bool SwapChainNewFrame(ComPtr<IDXGIResource> acquiredBuffer, unsigned dirtyRectCount, 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: public:
CSwapChainProcessor(CIndirectMonitorContext * monitorContext, UINT64 assignmentGeneration, CSwapChainProcessor(CIndirectMonitorContext * monitorContext, UINT64 assignmentGeneration,