From 9482841ec87684cb6eb57ed5b20c133228f776fd Mon Sep 17 00:00:00 2001 From: hugol Date: Mon, 21 Apr 2025 11:06:13 -0700 Subject: [PATCH] Add support of NVIDIA DLSS4 flip-metering 1. Adds a custom build step in PresentMon.vcxproj to compile and embed the manifest of NV DisplayDriver (DD) ETW event in the PresentMon binary. The custom build is currently added for x64 build only. 2. Adds NVTraceConsumer for handling NV DD events that informs the metering of each flip. 3. Updates the CSV output to take into account flip metering. Metrics "DisplayedTime" and "MsBetweenDisplayChange" (V1 metrics) now reflect actual flip intervals when DLSS4 flip-metering is engaged. --- PresentData/ETW/NV_DD.h | 28 +++++++++ PresentData/NvidiaTraceConsumer.cpp | 79 ++++++++++++++++++++++++ PresentData/NvidiaTraceConsumer.hpp | 37 +++++++++++ PresentData/PresentData.vcxproj | 3 + PresentData/PresentData.vcxproj.filters | 5 ++ PresentData/PresentMonTraceConsumer.cpp | 12 +++- PresentData/PresentMonTraceConsumer.hpp | 8 +++ PresentData/PresentMonTraceSession.cpp | 15 +++++ PresentData/PresentMonTraceSession.hpp | 1 + PresentMon/CsvOutput.cpp | 29 +++++---- PresentMon/MainThread.cpp | 25 ++++++++ PresentMon/OutputThread.cpp | 53 ++++++++++++++++ PresentMon/PresentMon.hpp | 10 +++ PresentMon/PresentMon.vcxproj | 17 +++++ PresentMon/ddETWExternal.xml | Bin 0 -> 11436 bytes PresentMon/ddETWExternalRcWrapper.rc | 1 + 16 files changed, 310 insertions(+), 13 deletions(-) create mode 100644 PresentData/ETW/NV_DD.h create mode 100644 PresentData/NvidiaTraceConsumer.cpp create mode 100644 PresentData/NvidiaTraceConsumer.hpp create mode 100644 PresentMon/ddETWExternal.xml create mode 100644 PresentMon/ddETWExternalRcWrapper.rc diff --git a/PresentData/ETW/NV_DD.h b/PresentData/ETW/NV_DD.h new file mode 100644 index 00000000..879ffba1 --- /dev/null +++ b/PresentData/ETW/NV_DD.h @@ -0,0 +1,28 @@ +// Copyright (c) 2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved +// SPDX-License-Identifier: MIT + +#pragma once + +namespace NvidiaDisplayDriver_Events { + + enum class Keyword : uint64_t { + None = 0x00, + }; + + struct __declspec(uuid("{AE4F8626-8265-40D1-A70B-11B64240E8E9}")) GUID_STRUCT; + static const auto GUID = __uuidof(GUID_STRUCT); + + // Event descriptors: +#define EVENT_DESCRIPTOR_DECL(name_, id_, version_, channel_, level_, opcode_, task_, keyword_) struct name_ { \ + static uint16_t const Id = id_; \ + static uint8_t const Version = version_; \ + static uint8_t const Channel = channel_; \ + static uint8_t const Level = level_; \ + static uint8_t const Opcode = opcode_; \ + static uint16_t const Task = task_; \ + static Keyword const Keyword = (Keyword) keyword_; \ +}; + + EVENT_DESCRIPTOR_DECL(FlipRequest, 0x0001, 0x00, 0x13, 0x04, 0x0a, 0x0001, 0x1000000000000000) +#undef EVENT_DESCRIPTOR_DECL +} \ No newline at end of file diff --git a/PresentData/NvidiaTraceConsumer.cpp b/PresentData/NvidiaTraceConsumer.cpp new file mode 100644 index 00000000..cd535d02 --- /dev/null +++ b/PresentData/NvidiaTraceConsumer.cpp @@ -0,0 +1,79 @@ +// Copyright (c) 2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved +// SPDX-License-Identifier: MIT + +#include "PresentMonTraceConsumer.hpp" +#include "NvidiaTraceConsumer.hpp" + +NVTraceConsumer::NVTraceConsumer() +{ + // Nothing to do +} + +NVTraceConsumer::~NVTraceConsumer() +{ + // Nothing to do +} + +void NVTraceConsumer::HandleNvidiaDisplayDriverEvent(EVENT_RECORD* const pEventRecord, PMTraceConsumer* const pmConsumer) +{ + enum { + FlipRequest = 1 + }; + + auto const& hdr = pEventRecord->EventHeader; + switch (hdr.EventDescriptor.Id) { + case FlipRequest: { + auto alloc = pmConsumer->mMetadata.GetEventData(pEventRecord, L"alloc"); + auto vidPnSourceId = pmConsumer->mMetadata.GetEventData(pEventRecord, L"vidPnSourceId"); + auto& lastFlipTime = mLastFlipTimeByHead[vidPnSourceId]; + auto ts = pmConsumer->mMetadata.GetEventData(pEventRecord, L"ts"); + auto token = pmConsumer->mMetadata.GetEventData(pEventRecord, L"token"); + uint64_t proposedFlipTime = 0; + uint64_t delay = 0; + + if (token == mLastFlipToken) { + return; + } + mLastFlipToken = token; + + if (alloc == 0) { + assert(!delay); + assert(ts); + // proposedFlipTime in number of ticks + proposedFlipTime = ts; + auto t1 = *(uint64_t*)&hdr.TimeStamp; + if (proposedFlipTime >= t1) { + delay = proposedFlipTime - t1; + } + + if (proposedFlipTime && lastFlipTime && (proposedFlipTime < lastFlipTime)) { + delay = lastFlipTime - proposedFlipTime; + proposedFlipTime = lastFlipTime; + } + } + + lastFlipTime = proposedFlipTime; + + { + NvFlipRequest flipRequest; + flipRequest.FlipDelay = delay; + flipRequest.FlipToken = token; + mNvFlipRequestByThreadId.emplace(hdr.ThreadId, flipRequest); + } + break; + } + default: + break; + } +} + +void NVTraceConsumer::ApplyFlipDelay(PresentEvent* present, uint32_t threadId) +{ + auto flipIter = mNvFlipRequestByThreadId.find(threadId); + if (flipIter != mNvFlipRequestByThreadId.end()) { + present->FlipDelay = flipIter->second.FlipDelay; + present->FlipToken = flipIter->second.FlipToken; + // Clear the map (we want the whole map cleared, not just the element with the thread) + mNvFlipRequestByThreadId.clear(); + } +} \ No newline at end of file diff --git a/PresentData/NvidiaTraceConsumer.hpp b/PresentData/NvidiaTraceConsumer.hpp new file mode 100644 index 00000000..3760d8d2 --- /dev/null +++ b/PresentData/NvidiaTraceConsumer.hpp @@ -0,0 +1,37 @@ +// Copyright (c) 2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved +// SPDX-License-Identifier: MIT +#pragma once + +#ifndef NOMINMAX +#define NOMINMAX +#endif + +#include +#include +#include // must include after windows.h + +struct NvFlipRequest { + uint64_t FlipDelay; + uint32_t FlipToken; +}; + +struct NVTraceConsumer +{ + NVTraceConsumer(); + ~NVTraceConsumer(); + + NVTraceConsumer(const NVTraceConsumer&) = delete; + NVTraceConsumer& operator=(const NVTraceConsumer&) = delete; + NVTraceConsumer(NVTraceConsumer&&) = delete; + NVTraceConsumer& operator=(NVTraceConsumer&&) = delete; + + // ThreaId -> NV FlipRequest + std::unordered_map mNvFlipRequestByThreadId; + // vidPnSourceId -> flip qpcTime + std::unordered_map mLastFlipTimeByHead; + + void HandleNvidiaDisplayDriverEvent(EVENT_RECORD* const pEventRecord, PMTraceConsumer* const pmConsumer); + void ApplyFlipDelay(PresentEvent* present, uint32_t threadId); + + uint32_t mLastFlipToken = 0; +}; \ No newline at end of file diff --git a/PresentData/PresentData.vcxproj b/PresentData/PresentData.vcxproj index 5f2791f5..52cb7732 100644 --- a/PresentData/PresentData.vcxproj +++ b/PresentData/PresentData.vcxproj @@ -353,7 +353,9 @@ + + @@ -361,6 +363,7 @@ + diff --git a/PresentData/PresentData.vcxproj.filters b/PresentData/PresentData.vcxproj.filters index 461fc546..f6158e32 100644 --- a/PresentData/PresentData.vcxproj.filters +++ b/PresentData/PresentData.vcxproj.filters @@ -39,6 +39,10 @@ ETW + + ETW + + @@ -46,6 +50,7 @@ + diff --git a/PresentData/PresentMonTraceConsumer.cpp b/PresentData/PresentMonTraceConsumer.cpp index edd5f8fa..cf4cbbd5 100644 --- a/PresentData/PresentMonTraceConsumer.cpp +++ b/PresentData/PresentMonTraceConsumer.cpp @@ -1,4 +1,5 @@ // Copyright (C) 2017-2024 Intel Corporation +// Copyright (c) 2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved // SPDX-License-Identifier: MIT #include "PresentMonTraceConsumer.hpp" @@ -930,6 +931,9 @@ void PMTraceConsumer::HandleDXGKEvent(EVENT_RECORD* pEventRecord) auto present = FindPresentBySubmitSequence(submitSequence); if (present != nullptr) { + // Apply Nvidia FlipDelay, if any, to the presentEvent + mNvTraceConsumer.ApplyFlipDelay(present.get(), hdr.ThreadId); + // Complete the GPU tracking for this frame. // // For some present modes (e.g., Hardware_Legacy_Flip) this may be @@ -952,7 +956,7 @@ void PMTraceConsumer::HandleDXGKEvent(EVENT_RECORD* pEventRecord) // this the present screen time. if (FlipEntryStatusAfterFlip != (uint32_t) Microsoft_Windows_DxgKrnl::FlipEntryStatus::FlipWaitHSync) { - SetScreenTime(present, hdr.TimeStamp.QuadPart); + SetScreenTime(present, hdr.TimeStamp.QuadPart + present->FlipDelay); if (present->PresentMode == PresentMode::Hardware_Legacy_Flip) { CompletePresent(present); @@ -1353,6 +1357,12 @@ void PMTraceConsumer::HandleDXGKEvent(EVENT_RECORD* pEventRecord) assert(!mFilteredEvents); // Assert that filtering is working if expected } + +void PMTraceConsumer::HandleNvidiaDisplayDriverEvent(EVENT_RECORD* pEventRecord) +{ + mNvTraceConsumer.HandleNvidiaDisplayDriverEvent(pEventRecord, this); +} + void PMTraceConsumer::HandleWin7DxgkBlt(EVENT_RECORD* pEventRecord) { using namespace Microsoft_Windows_DxgKrnl::Win7; diff --git a/PresentData/PresentMonTraceConsumer.hpp b/PresentData/PresentMonTraceConsumer.hpp index b219c59c..78a27300 100644 --- a/PresentData/PresentMonTraceConsumer.hpp +++ b/PresentData/PresentMonTraceConsumer.hpp @@ -1,4 +1,5 @@ // Copyright (C) 2017-2024 Intel Corporation +// Copyright (c) 2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved // SPDX-License-Identifier: MIT #pragma once @@ -24,6 +25,7 @@ #include "Debug.hpp" #include "GpuTrace.hpp" #include "TraceConsumer.hpp" +#include "NvidiaTraceConsumer.hpp" #include "../IntelPresentMon/CommonUtilities/Hash.h" // PresentMode represents the different paths a present can take on windows. @@ -271,6 +273,9 @@ struct PresentEvent { // until a PresentFrameType_Info event with a different FrameId). bool WaitingForFrameId; + // Data from NV DisplayDriver event + uint64_t FlipDelay = 0; + uint32_t FlipToken = 0; PresentEvent(); PresentEvent(uint32_t fid); @@ -487,6 +492,8 @@ struct PMTraceConsumer std::unordered_map mRetrievedInput; // ProcessID -> InputData + // Trace consumer that handles events coming from Nvidia DisplayDriver + NVTraceConsumer mNvTraceConsumer; // ------------------------------------------------------------------------------------------- // Functions for decoding ETW and analysing process and present events. @@ -516,6 +523,7 @@ struct PMTraceConsumer void HandleDWMEvent(EVENT_RECORD* pEventRecord); void HandleMetadataEvent(EVENT_RECORD* pEventRecord); void HandleIntelPresentMonEvent(EVENT_RECORD* pEventRecord); + void HandleNvidiaDisplayDriverEvent(EVENT_RECORD* pEventRecord); void HandleWin7DxgkBlt(EVENT_RECORD* pEventRecord); void HandleWin7DxgkFlip(EVENT_RECORD* pEventRecord); diff --git a/PresentData/PresentMonTraceSession.cpp b/PresentData/PresentMonTraceSession.cpp index 71ca9bac..92c26320 100644 --- a/PresentData/PresentMonTraceSession.cpp +++ b/PresentData/PresentMonTraceSession.cpp @@ -1,9 +1,11 @@ // Copyright (C) 2017-2024 Intel Corporation +// Copyright (c) 2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved // SPDX-License-Identifier: MIT #include "Debug.hpp" #include "PresentMonTraceConsumer.hpp" #include "PresentMonTraceSession.hpp" +#include "NvidiaTraceConsumer.hpp" #include "ETW/Microsoft_Windows_D3D9.h" #include "ETW/Microsoft_Windows_Dwm_Core.h" @@ -16,6 +18,7 @@ #include "ETW/Microsoft_Windows_Win32k.h" #include "ETW/NT_Process.h" #include "ETW/Intel_PresentMon.h" +#include "ETW/NV_DD.h" namespace { @@ -280,6 +283,7 @@ void DisableProviders(TRACEHANDLE sessionHandle) status = EnableTraceEx2(sessionHandle, &Microsoft_Windows_DxgKrnl::Win7::GUID, EVENT_CONTROL_CODE_DISABLE_PROVIDER, 0, 0, 0, 0, nullptr); status = EnableTraceEx2(sessionHandle, &Microsoft_Windows_Kernel_Process::GUID, EVENT_CONTROL_CODE_DISABLE_PROVIDER, 0, 0, 0, 0, nullptr); status = EnableTraceEx2(sessionHandle, &Microsoft_Windows_Win32k::GUID, EVENT_CONTROL_CODE_DISABLE_PROVIDER, 0, 0, 0, 0, nullptr); + status = EnableTraceEx2(sessionHandle, &NvidiaDisplayDriver_Events::GUID, EVENT_CONTROL_CODE_DISABLE_PROVIDER, 0, 0, 0, 0, nullptr); } template< @@ -363,6 +367,10 @@ void CALLBACK EventRecordCallback(EVENT_RECORD* pEventRecord) session->mPMConsumer->HandleWin7DxgkMMIOFlip(pEventRecord); return; } + if (hdr.ProviderId == NvidiaDisplayDriver_Events::GUID) { + session->mPMConsumer->HandleNvidiaDisplayDriverEvent(pEventRecord); + return; + } } if constexpr (TRACK_PRESENTMON) { @@ -777,6 +785,13 @@ ULONG EnableProvidersListing( if (status != ERROR_SUCCESS) return status; } + // Nvidia_DisplayDriver + // + provider.ClearFilter(); + provider.AddEvent(); + status = provider.Enable(sessionHandle, NvidiaDisplayDriver_Events::GUID); + if (status != ERROR_SUCCESS) return status; + return ERROR_SUCCESS; } diff --git a/PresentData/PresentMonTraceSession.hpp b/PresentData/PresentMonTraceSession.hpp index 31f696bc..8cc8f119 100644 --- a/PresentData/PresentMonTraceSession.hpp +++ b/PresentData/PresentMonTraceSession.hpp @@ -1,4 +1,5 @@ // Copyright (C) 2017-2024 Intel Corporation +// Copyright (c) 2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved // SPDX-License-Identifier: MIT struct PMTraceConsumer; diff --git a/PresentMon/CsvOutput.cpp b/PresentMon/CsvOutput.cpp index 5850e10e..39c3f55d 100644 --- a/PresentMon/CsvOutput.cpp +++ b/PresentMon/CsvOutput.cpp @@ -1,4 +1,5 @@ // Copyright (C) 2017-2024 Intel Corporation +// Copyright (c) 2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved // SPDX-License-Identifier: MIT #include "PresentMon.hpp" @@ -156,7 +157,8 @@ void WriteCsvHeader(FILE* fp) L",PresentMode" L",msUntilRenderComplete" L",msUntilDisplayed" - L",msBetweenDisplayChange"); + L",msBetweenDisplayChange" + L",msFlipDelay"); } if (args.mTrackGPU) { fwprintf(fp, L",msUntilRenderStart" @@ -221,11 +223,12 @@ void WriteCsvRow( fwprintf(fp, L",%.*lf,%.*lf", DBL_DIG - 1, metrics.msInPresentApi, DBL_DIG - 1, metrics.msBetweenPresents); if (args.mTrackDisplay) { - fwprintf(fp, L",%d,%hs,%.*lf,%.*lf,%.*lf", p.SupportsTearing, - PresentModeToString(p.PresentMode), - DBL_DIG - 1, metrics.msUntilRenderComplete, - DBL_DIG - 1, metrics.msUntilDisplayed, - DBL_DIG - 1, metrics.msBetweenDisplayChange); + fwprintf(fp, L",%d,%hs,%.*lf,%.*lf,%.*lf,%.*lf", p.SupportsTearing, + PresentModeToString(p.PresentMode), + DBL_DIG - 1, metrics.msUntilRenderComplete, + DBL_DIG - 1, metrics.msUntilDisplayed, + DBL_DIG - 1, metrics.msBetweenDisplayChange, + DBL_DIG - 1, metrics.msFlipDelay); } if (args.mTrackGPU) { fwprintf(fp, L",%.*lf,%.*lf", DBL_DIG - 1, metrics.msUntilRenderStart, @@ -306,7 +309,8 @@ void WriteCsvHeader(FILE* fp) fwprintf(fp, L",DisplayLatency" L",DisplayedTime" L",AnimationError" - L",AnimationTime"); + L",AnimationTime" + L",FlipDelay"); } if (args.mTrackInput) { fwprintf(fp, L",AllInputToPhotonLatency"); @@ -397,12 +401,13 @@ void WriteCsvRow( } if (args.mTrackDisplay) { if (metrics.mDisplayedTime == 0.0) { - fwprintf(fp, L",NA,NA,NA,NA"); + fwprintf(fp, L",NA,NA,NA,NA,NA"); } else { - fwprintf(fp, L",%.4lf,%.4lf,%.4lf,%.4lf", metrics.mDisplayLatency, - metrics.mDisplayedTime, - metrics.mAnimationError, - metrics.mAnimationTime); + fwprintf(fp, L",%.4lf,%.4lf,%.4lf,%.4lf,%.4lf", metrics.mDisplayLatency, + metrics.mDisplayedTime, + metrics.mAnimationError, + metrics.mAnimationTime, + metrics.mFlipDelay); } } if (args.mTrackInput) { diff --git a/PresentMon/MainThread.cpp b/PresentMon/MainThread.cpp index 6fefe908..cb9485dc 100644 --- a/PresentMon/MainThread.cpp +++ b/PresentMon/MainThread.cpp @@ -1,6 +1,8 @@ // Copyright (C) 2017-2024 Intel Corporation +// Copyright (c) 2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved // SPDX-License-Identifier: MIT +#define WINVER _WIN32_WINNT_WIN10 // To make TdhLoadManifestFromBinary available #include "PresentMon.hpp" enum { @@ -15,6 +17,24 @@ static HWND gWnd = NULL; static bool gIsRecording = false; static uint32_t gHotkeyIgnoreCount = 0; +static bool LoadNVDDManifest() +{ + WCHAR CurrModuleDirW[MAX_PATH]; + if (!GetModuleFileNameW(NULL, CurrModuleDirW, MAX_PATH)) { + PrintError(L"error: failed to get exe fullpath\n"); + return false; + } + + std::wstring mypath = CurrModuleDirW; + auto status = TdhLoadManifestFromBinary(&mypath[0]); + if (ERROR_SUCCESS != status) + { + PrintError(L"error: failed to load manifest embedded in %s\n", mypath.c_str()); + return false; + } + return true; +} + static bool EnableScrollLock(bool enable) { auto const& args = GetCommandLineArgs(); @@ -179,6 +199,11 @@ int wmain(int argc, wchar_t** argv) LoadLibraryExA("tdh.dll", NULL, LOAD_LIBRARY_SEARCH_SYSTEM32); LoadLibraryExA("user32.dll", NULL, LOAD_LIBRARY_SEARCH_SYSTEM32); + // Load NVIDIA DisplayDriver event manifest embedded in the PresentMon binary + if (!LoadNVDDManifest()) { + return 1; + } + // Initialize console SetThreadDescription(GetCurrentThread(), L"PresentMon Consumer Thread"); InitializeConsole(); diff --git a/PresentMon/OutputThread.cpp b/PresentMon/OutputThread.cpp index 47d5f20f..16fed8df 100644 --- a/PresentMon/OutputThread.cpp +++ b/PresentMon/OutputThread.cpp @@ -1,4 +1,5 @@ // Copyright (C) 2017-2024 Intel Corporation +// Copyright (c) 2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved // SPDX-License-Identifier: MIT #include "PresentMon.hpp" @@ -250,11 +251,33 @@ static void UpdateChain( chain->mLastPresent->TimeInPresent; } chain->mLastDisplayedScreenTime = p->Displayed.empty() ? 0 : p->Displayed.back().second; + // Update last flipDelay. For NV GPU, size of p->Displayed can only be empty or 1 + chain->mLastDisplayedFlipDelay = p->Displayed.empty() ? 0 : p->FlipDelay; } chain->mLastPresent = p; } +static void AdjustScreenTimeForCollapsedPresentNV1( + SwapChainData* chain, + std::shared_ptr const& p, + uint64_t& screenTime) +{ + if (chain->mLastDisplayedFlipDelay > 0 && (chain->mLastDisplayedScreenTime > screenTime)) { + // If chain->mLastDisplayedScreenTime that is adjusted by flipDelay is larger than screenTime, + // it implies the last displayed present is a collapsed present, or a runt frame. + // So we adjust the screenTime and flipDelay of screenTime, + // effectively making screenTime equals to chain->mLastDisplayedScreenTime. + + // Cast away constness of p to adjust the screenTime and flipDelay. + PresentEvent* pp = const_cast(p.get()); + if (!pp->Displayed.empty()) { + pp->FlipDelay += chain->mLastDisplayedScreenTime - screenTime; + pp->Displayed[0].second = chain->mLastDisplayedScreenTime; + } + } +} + static void ReportMetrics1( PMTraceSession const& pmSession, ProcessInfo* processInfo, @@ -267,6 +290,9 @@ static void ReportMetrics1( uint64_t screenTime = p->Displayed.empty() ? 0 : p->Displayed[0].second; + // Special handling for NV flipDelay + AdjustScreenTimeForCollapsedPresentNV1(chain, p, screenTime); + FrameMetrics1 metrics; metrics.msBetweenPresents = chain->mLastPresent == nullptr ? 0 : pmSession.TimestampDeltaToUnsignedMilliSeconds(chain->mLastPresent->PresentStartTime, p->PresentStartTime); metrics.msInPresentApi = pmSession.TimestampDeltaToMilliSeconds(p->TimeInPresent); @@ -278,6 +304,7 @@ static void ReportMetrics1( metrics.msVideoDuration = pmSession.TimestampDeltaToMilliSeconds(p->GPUVideoDuration); metrics.msSinceInput = p->InputTime == 0 ? 0 : pmSession.TimestampDeltaToMilliSeconds(p->PresentStartTime - p->InputTime); metrics.qpcScreenTime = screenTime; + metrics.msFlipDelay = p->FlipDelay ? pmSession.TimestampDeltaToMilliSeconds(p->FlipDelay) : 0; if (isRecording) { UpdateCsv(pmSession, processInfo, *p, metrics); @@ -311,6 +338,28 @@ static void CalculateAnimationTime( } } + +static void AdjustScreenTimeForCollapsedPresentNV( + std::shared_ptr const& p, + PresentEvent const* nextDisplayedPresent, + uint64_t& screenTime, + uint64_t& nextScreenTime) +{ + // nextDisplayedPresent should always be non-null for NV GPU. + if (p->FlipDelay && screenTime > nextScreenTime && nextDisplayedPresent) { + // If screenTime that is adjusted by flipDelay is larger than nextScreenTime, + // it implies this present is a collapsed present, or a runt frame. + // So we adjust the screenTime and flipDelay of nextDisplayedPresent, + // effectively making nextScreenTime equals to screenTime. + + // Cast away constness of nextDisplayedPresent to adjust the screenTime and flipDelay. + PresentEvent* nextDispPresent = const_cast(nextDisplayedPresent); + nextDispPresent->FlipDelay += (screenTime - nextScreenTime); + nextScreenTime = screenTime; + nextDispPresent->Displayed[0].second = nextScreenTime; + } +} + static void ReportMetricsHelper( PMTraceSession const& pmSession, ProcessInfo* processInfo, @@ -405,6 +454,10 @@ static void ReportMetricsHelper( } if (displayed) { + // Special handling for NV flipDelay + AdjustScreenTimeForCollapsedPresentNV(p, nextDisplayedPresent, screenTime, nextScreenTime); + + metrics.mFlipDelay = p->FlipDelay ? pmSession.TimestampDeltaToMilliSeconds(p->FlipDelay) : 0; metrics.mDisplayLatency = pmSession.TimestampDeltaToUnsignedMilliSeconds(metrics.mCPUStart, screenTime); metrics.mDisplayedTime = pmSession.TimestampDeltaToUnsignedMilliSeconds(screenTime, nextScreenTime); metrics.mScreenTime = screenTime; diff --git a/PresentMon/PresentMon.hpp b/PresentMon/PresentMon.hpp index 3d9cbcca..65f91219 100644 --- a/PresentMon/PresentMon.hpp +++ b/PresentMon/PresentMon.hpp @@ -1,4 +1,5 @@ // Copyright (C) 2017-2024 Intel Corporation +// Copyright (c) 2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved // SPDX-License-Identifier: MIT #pragma once @@ -117,6 +118,9 @@ struct FrameMetrics { double mInstrumentedGpuLatency; double mReadyTimeToDisplayLatency; double mInstrumentedInputTime; + + // Internal NVIDIA Metrics + double mFlipDelay; }; struct FrameMetrics1 { @@ -130,6 +134,9 @@ struct FrameMetrics1 { double msVideoDuration; double msSinceInput; uint64_t qpcScreenTime; + + // Internal NVIDIA Metrics + double msFlipDelay; }; // We store SwapChainData per process and per swapchain, where we maintain: @@ -162,6 +169,9 @@ struct SwapChainData { float mAvgGPUDuration = 0.f; float mAvgDisplayLatency = 0.f; float mAvgDisplayedTime = 0.f; + + // Internal NVIDIA Metrics + uint64_t mLastDisplayedFlipDelay = 0; }; struct ProcessInfo { diff --git a/PresentMon/PresentMon.vcxproj b/PresentMon/PresentMon.vcxproj index b4af26d5..5d2a5457 100644 --- a/PresentMon/PresentMon.vcxproj +++ b/PresentMon/PresentMon.vcxproj @@ -94,6 +94,9 @@ false + + ClCompile + false @@ -180,6 +183,20 @@ {892028e5-32f6-45fc-8ab2-90fcbcac4bf6} + + + Document + mc.exe -um -v -z %(Filename)Events -h .\ %(FullPath) + Autogenerate header from DD events manifest + %(Filename)Events.rc;%(Filename)Events.h;%(Filename)_MSG00001.bin + mc.exe -um -v -z %(Filename)Events -h .\ %(FullPath) + Autogenerate header from DD events manifest + %(Filename)Events.rc;%(Filename)Events.h;%(Filename)_MSG00001.bin + + + + + diff --git a/PresentMon/ddETWExternal.xml b/PresentMon/ddETWExternal.xml new file mode 100644 index 0000000000000000000000000000000000000000..510ec3538b4f16c914abf19489d46185c646ffdc GIT binary patch literal 11436 zcmds7ZEqS!5T388)c=6;onXhVn_9F{Q|vZU?M5|e`^_R;Osv4+`pza+mA{_$d3IUf zz8xG0#9ei=Ic{cmXP$Z4**X6G(v_F;NhUIumfXvGxs^<=Wh$wBFHdD(p2;3QTcD&e z#PbByR7Q&WR{C-(ZEzid=cW80U&~k0RUDb*nClKRb7U?9{ONhmaG&D-3KVj+us+v3 z#{Z#wz|#phe#KftYZRsV0Pb9Fz}dlNb7zoqf;om*a|+57v*&n5i6`KlftKR)Ikef6 zL)`7j3;chtbf$cn(vb-88zo3Fo{}X>e~nR!@7tGzco*v(&!rF7)lwEnu8V?3CjnM7 z=;xy1%c5-+$}+>)84#Pv0sc>6htVV6`?&i{&S29E%ffAC6M6u#9s>E;TANtOpO>CF zP%DPBZ`!Lm(9f*)bRHp3md1#Vq$fg7+eeG%q}(WPzXy&9xNhM!DfD8*QLl6PLtaY{ ze?KBdd$`(i0>8=?FFD5w@e!hwGjPm5;5|WnoFY1o5G_X-^Fn=ogS$gSO%KuD2G_T^ ze}%h!T(7`)2rkl3z<(m&$!9fpCJ&J39sFTDmq|-|jn#Wt)0gLsrx7i83wasQcetaM zoI=_$KF=*(6E#;~{tRY(2FiG@DUr!=4d9#W&tuzQk$rsYwXm{_#wp~wh82H@G=D=s zoe!z=y|NCU&?@^1%P>3Z%*>q02ui_iP+o5N4s57>-~k@N%*l0$E7#HU=)7{X^>%>P)|!fNQMQ&|LBPdWvHJiCtUJ8g}*ysYNW6 zTjLy>QpYP;>k?>alwFHFZ=6MJt!QcA9zK1MHEdpE3xiaMxoZ(dg}zuTn0q&ls@W!t zlQOI5_^D%@*hp@}D%**W3&h6&*&v@s0W*QgRR@iW3|AXJE?nxOs3U+u?l zi|_0)yl4Fp%SA@tkY*@xB>YHSj)OlUwu3Ycg~G?H`GU*uBA_!xy%1mw8b9!(rR(gO zK9#X#=MLA4!fMC&pWm$d=(0jZ$f_jt;f^CiNH3Sr_twSo`P{B$c_!GbbgmyQmd)k$ zC1Z^c;<5-g`h<;}Z}~3jTDt=G>GTYj!}X0S=QLL9394guF&KHA&z#xT8Sf+L&Yq+4 zy+Jk3jOyE$%By=|urjJ{dKnD5-E^fMZ( zM)$1lbLh&_TWIbWZ%kSrjx-+ zlYHz~F^{sVlsla4Zyw_*JH6M41a?&t)s1G?JIryEtn(JYn5oZ*2y` zVchBW0L_TI=E!R5*lH5*+K-F4Ylggb+|^0q?N$WwlS}*r*z8(hySpnpg=U{&H!s%x zT=xWPduDUBU{6B-E`<9Ve}B+iU9NC$;O|NLuzB^~!uqG+ZF-lxd;j$4#dWvN=JI0k z9>biI{;ydvLl19Qxp%kg3W71luHb7`z2DYM?&ba$<8b@8