From 559656e0f62a775fd06fbda6b2b7fac160ff331b Mon Sep 17 00:00:00 2001 From: baldurk Date: Fri, 29 Dec 2017 14:31:18 +0000 Subject: [PATCH] Send capture progress over an active target control connection --- qrenderdoc/Windows/Dialogs/LiveCapture.cpp | 6 +++ renderdoc/api/replay/control_types.h | 6 +++ renderdoc/api/replay/replay_enums.h | 5 ++ renderdoc/core/core.h | 51 +++++++++++++++---- renderdoc/core/target_control.cpp | 42 ++++++++++++++- .../driver/d3d12/d3d12_command_queue_wrap.cpp | 3 ++ renderdoc/driver/d3d12/d3d12_device.cpp | 14 +++++ renderdoc/driver/d3d12/d3d12_device.h | 4 ++ renderdoc/driver/vulkan/vk_core.cpp | 2 + renderdoc/driver/vulkan/vk_core.h | 1 + .../driver/vulkan/wrappers/vk_queue_funcs.cpp | 9 ++++ 11 files changed, 132 insertions(+), 11 deletions(-) diff --git a/qrenderdoc/Windows/Dialogs/LiveCapture.cpp b/qrenderdoc/Windows/Dialogs/LiveCapture.cpp index f15274cb4..f52981f5e 100644 --- a/qrenderdoc/Windows/Dialogs/LiveCapture.cpp +++ b/qrenderdoc/Windows/Dialogs/LiveCapture.cpp @@ -1113,6 +1113,12 @@ void LiveCapture::connectionThreadEntry() }); } + if(msg.type == TargetControlMessageType::CaptureProgress) + { + float progress = msg.capProgress; + GUIInvoke::call([this, progress]() {}); + } + if(msg.type == TargetControlMessageType::NewCapture) { uint32_t capID = msg.newCapture.captureId; diff --git a/renderdoc/api/replay/control_types.h b/renderdoc/api/replay/control_types.h index 28b801333..676856fd4 100644 --- a/renderdoc/api/replay/control_types.h +++ b/renderdoc/api/replay/control_types.h @@ -476,6 +476,12 @@ struct TargetControlMessage BusyData busy; DOCUMENT("The :class:`new child process data `."); NewChildData newChild; + DOCUMENT(R"(The progress of an on-going capture. + +When valid, will be in the range of 0.0 to 1.0 (0 - 100%). If not valid when a capture isn't going +or has finished, it will be -1.0 +)"); + float capProgress = -1.0f; }; DECLARE_REFLECTION_STRUCT(TargetControlMessage); diff --git a/renderdoc/api/replay/replay_enums.h b/renderdoc/api/replay/replay_enums.h index 610ecd901..d906e0f0b 100644 --- a/renderdoc/api/replay/replay_enums.h +++ b/renderdoc/api/replay/replay_enums.h @@ -2929,6 +2929,10 @@ DOCUMENT(R"(The type of message received from or sent to an application target c .. data:: NewChild The target has created a child process. + +.. data:: CaptureProgress + + Progress update on an on-going frame capture. )"); enum class TargetControlMessageType : uint32_t { @@ -2940,6 +2944,7 @@ enum class TargetControlMessageType : uint32_t CaptureCopied, RegisterAPI, NewChild, + CaptureProgress, }; DECLARE_REFLECTION_ENUM(TargetControlMessageType); diff --git a/renderdoc/core/core.h b/renderdoc/core/core.h index f9380f9a9..0b76618f9 100644 --- a/renderdoc/core/core.h +++ b/renderdoc/core/core.h @@ -247,8 +247,14 @@ enum class CaptureProgress { PrepareInitialStates, First = PrepareInitialStates, - // frame capture goes here but we have no way to estimate its length, and the progress would be - // updated all over the place as every API call or every draw call would have to update it. + // In general we can't know how long the frame capture will take to have an explicit progress, but + // we can hack it by getting closer and closer to 100% without quite reaching it, with some + // heuristic for how far we expect to get. Some APIs will have no useful way to update progress + // during frame capture, but for explicit APIs like Vulkan we can update once per submission, and + // tune it so that it doesn't start crawling approaching 100% until well past the number of + // submissions we'd expect in a frame. + // Other APIs will simply skip this progress section entirely, which is fine. + FrameCapture, AddReferencedResources, SerialiseInitialStates, SerialiseFrameContents, @@ -259,18 +265,45 @@ enum class CaptureProgress DECLARE_REFLECTION_ENUM(CaptureProgress); ITERABLE_OPERATORS(CaptureProgress); +// different APIs spend their capture time in different places. So the weighting is roughly even for +// the potential hot-spots. So D3D11 might zoom past the PrepareInitialStates while Vulkan takes a +// couple of seconds, but then the situation is reversed for AddReferencedResources inline constexpr float ProgressWeight(CaptureProgress section) { // values must sum to 1.0 return section == CaptureProgress::PrepareInitialStates - ? 0.35f + ? 0.25f : section == CaptureProgress::AddReferencedResources - ? 0.1f - : section == CaptureProgress::SerialiseInitialStates - ? 0.5f - : section == CaptureProgress::SerialiseFrameContents - ? 0.04f - : section == CaptureProgress::FileWriting ? 0.01f : 0.0f; + ? 0.25f + : section == CaptureProgress::FrameCapture + ? 0.15f + : section == CaptureProgress::SerialiseInitialStates + ? 0.25f + : section == CaptureProgress::SerialiseFrameContents + ? 0.08f + : section == CaptureProgress::FileWriting ? 0.02f : 0.0f; +} + +// utility function to fake progress with x going from 0 to infinity, mapping to 0% to 100% in an +// inverse curve. For x from 0 to maxX the progress is reasonably spaced, past that it will be quite +// crushed. +// +// The equation is y = 1 - (1 / (x * param) + 1) +// +// => maxX will be when the curve reaches 80% +// 0.8 = 1 - (1 / (maxX * param) + 1) +// +// => gather constants on RHS +// 1 / (maxX * param) + 1 = 0.2 +// +// => switch denominators +// maxX * param + 1 = 5 +// +// => re-arrange for param +// param = 4 / maxX +inline constexpr float FakeProgress(uint32_t x, uint32_t maxX) +{ + return 1.0f - (1.0f / (x * (4.0f / float(maxX)) + 1)); } class IRemoteDriver; diff --git a/renderdoc/core/target_control.cpp b/renderdoc/core/target_control.cpp index d23784a2e..07ebd7f61 100644 --- a/renderdoc/core/target_control.cpp +++ b/renderdoc/core/target_control.cpp @@ -44,6 +44,7 @@ enum PacketType : uint32_t ePacket_DeleteCapture, ePacket_QueueCapture, ePacket_NewChild, + ePacket_CaptureProgress, }; DECLARE_REFLECTION_ENUM(PacketType); @@ -104,13 +105,18 @@ void RenderDoc::TargetControlClientThread(uint32_t version, Network::Socket *cli return; } - const int pingtime = 1000; // ping every 1000ms - const int ticktime = 10; // tick every 10ms + float captureProgress = -1.0f; + RenderDoc::Inst().SetProgressPointer(&captureProgress); + + const int pingtime = 1000; // ping every 1000ms + const int ticktime = 10; // tick every 10ms + const int progresstime = 100; // update capture progress every 100ms int curtime = 0; std::vector captures; std::vector > children; std::map drivers; + float prevCaptureProgress = captureProgress; while(client) { @@ -200,6 +206,26 @@ void RenderDoc::TargetControlClientThread(uint32_t version, Network::Socket *cli SERIALISE_ELEMENT(children.back().second); } } + else if(prevCaptureProgress != captureProgress) + { + if(captureProgress == 1.0f || captureProgress == -1.0f) + captureProgress = -1.0f; + + // send progress packets at reduced rate (not every tick), or if the progress is finished. + // we don't need to ping while we're sending capture progress, so we re-use curtime + if(captureProgress == -1.0f || curtime > progresstime) + { + curtime = 0; + + prevCaptureProgress = captureProgress; + + WRITE_DATA_SCOPE(); + { + SCOPED_SERIALISE_CHUNK(ePacket_CaptureProgress); + SERIALISE_ELEMENT(captureProgress); + } + } + } if(curtime > pingtime) { @@ -284,6 +310,8 @@ void RenderDoc::TargetControlClientThread(uint32_t version, Network::Socket *cli } } + RenderDoc::Inst().SetProgressPointer(NULL); + // give up our connection { SCOPED_LOCK(RenderDoc::Inst().m_SingleClientLock); @@ -610,6 +638,16 @@ public: reader.EndChunk(); return msg; } + else if(type == ePacket_CaptureProgress) + { + msg.type = TargetControlMessageType::CaptureProgress; + + READ_DATA_SCOPE(); + SERIALISE_ELEMENT(msg.capProgress).Named("Capture Progress"); + + reader.EndChunk(); + return msg; + } else if(type == ePacket_NewCapture) { msg.type = TargetControlMessageType::NewCapture; diff --git a/renderdoc/driver/d3d12/d3d12_command_queue_wrap.cpp b/renderdoc/driver/d3d12/d3d12_command_queue_wrap.cpp index e49261378..59b091fa0 100644 --- a/renderdoc/driver/d3d12/d3d12_command_queue_wrap.cpp +++ b/renderdoc/driver/d3d12/d3d12_command_queue_wrap.cpp @@ -333,6 +333,9 @@ void STDMETHODCALLTYPE WrappedID3D12CommandQueue::ExecuteCommandLists( RenderDoc::Inst().AddActiveDriver(RDCDriver::Vulkan, false); } + if(IsActiveCapturing(m_State)) + m_pDevice->AddCaptureSubmission(); + SERIALISE_TIME_CALL(m_pReal->ExecuteCommandLists(NumCommandLists, unwrapped)); if(IsCaptureMode(m_State)) diff --git a/renderdoc/driver/d3d12/d3d12_device.cpp b/renderdoc/driver/d3d12/d3d12_device.cpp index 3da27c2d4..4d12723b3 100644 --- a/renderdoc/driver/d3d12/d3d12_device.cpp +++ b/renderdoc/driver/d3d12/d3d12_device.cpp @@ -540,6 +540,18 @@ void WrappedID3D12Device::ApplyInitialContents() initStateCurList = NULL; } +void WrappedID3D12Device::AddCaptureSubmission() +{ + if(IsActiveCapturing(m_State)) + { + // 15 is quite a lot of submissions. + const int expectedMaxSubmissions = 15; + + RenderDoc::Inst().SetProgress(CaptureProgress::FrameCapture, FakeProgress(m_SubmitCounter, 15)); + m_SubmitCounter++; + } +} + void WrappedID3D12Device::CheckForDeath() { if(!m_Alive) @@ -1227,6 +1239,8 @@ void WrappedID3D12Device::StartFrameCapture(void *dev, void *wnd) m_AppControlledCapture = true; + m_SubmitCounter = 0; + m_FrameCounter = RDCMAX(1 + (uint32_t)m_CapturedFrames.size(), m_FrameCounter); FrameDescription frame; diff --git a/renderdoc/driver/d3d12/d3d12_device.h b/renderdoc/driver/d3d12/d3d12_device.h index 0e639fd5c..6e49483d1 100644 --- a/renderdoc/driver/d3d12/d3d12_device.h +++ b/renderdoc/driver/d3d12/d3d12_device.h @@ -299,6 +299,8 @@ private: Threading::CriticalSection m_CapTransitionLock; CaptureState m_State; + uint32_t m_SubmitCounter = 0; + D3D12InitParams m_InitParams; uint64_t m_SectionVersion; ID3D12InfoQueue *m_pInfoQueue; @@ -458,6 +460,8 @@ public: ID3D12Resource *GetUploadBuffer(uint64_t chunkOffset, uint64_t byteSize); void ApplyInitialContents(); + void AddCaptureSubmission(); + void ExecuteList(ID3D12GraphicsCommandList *list, ID3D12CommandQueue *queue = NULL); void ExecuteLists(ID3D12CommandQueue *queue = NULL); void FlushLists(bool forceSync = false, ID3D12CommandQueue *queue = NULL); diff --git a/renderdoc/driver/vulkan/vk_core.cpp b/renderdoc/driver/vulkan/vk_core.cpp index c02c0e334..e700581f6 100644 --- a/renderdoc/driver/vulkan/vk_core.cpp +++ b/renderdoc/driver/vulkan/vk_core.cpp @@ -881,6 +881,8 @@ void WrappedVulkan::StartFrameCapture(void *dev, void *wnd) m_AppControlledCapture = true; + m_SubmitCounter = 0; + m_FrameCounter = RDCMAX(1 + (uint32_t)m_CapturedFrames.size(), m_FrameCounter); FrameDescription frame; diff --git a/renderdoc/driver/vulkan/vk_core.h b/renderdoc/driver/vulkan/vk_core.h index 8d605e1f5..8cc434096 100644 --- a/renderdoc/driver/vulkan/vk_core.h +++ b/renderdoc/driver/vulkan/vk_core.h @@ -221,6 +221,7 @@ private: bool m_AppControlledCapture; bool m_MarkedActive = false; + uint32_t m_SubmitCounter = 0; uint64_t threadSerialiserTLSSlot; diff --git a/renderdoc/driver/vulkan/wrappers/vk_queue_funcs.cpp b/renderdoc/driver/vulkan/wrappers/vk_queue_funcs.cpp index be76631bf..6e2405153 100644 --- a/renderdoc/driver/vulkan/wrappers/vk_queue_funcs.cpp +++ b/renderdoc/driver/vulkan/wrappers/vk_queue_funcs.cpp @@ -416,6 +416,15 @@ VkResult WrappedVulkan::vkQueueSubmit(VkQueue queue, uint32_t submitCount, RenderDoc::Inst().AddActiveDriver(RDCDriver::Vulkan, false); } + if(IsActiveCapturing(m_State)) + { + // 15 is quite a lot of submissions. + const int expectedMaxSubmissions = 15; + + RenderDoc::Inst().SetProgress(CaptureProgress::FrameCapture, FakeProgress(m_SubmitCounter, 15)); + m_SubmitCounter++; + } + size_t tempmemSize = sizeof(VkSubmitInfo) * submitCount; // need to count how many semaphore and command buffer arrays to allocate for