From 7fab2e0e0aa5e82b2681683ff42a6fe6e64641f1 Mon Sep 17 00:00:00 2001 From: baldurk Date: Tue, 12 May 2026 13:45:34 +0100 Subject: [PATCH] Add a forwarding ReplayController wrapper for python that auto invokes --- qrenderdoc/Code/CaptureContext.h | 1 + qrenderdoc/Code/Interface/QRDInterface.h | 25 ++ qrenderdoc/Code/ReplayManager.cpp | 6 + qrenderdoc/Windows/PythonShell.cpp | 386 ++++++++++++++++++++++- 4 files changed, 410 insertions(+), 8 deletions(-) diff --git a/qrenderdoc/Code/CaptureContext.h b/qrenderdoc/Code/CaptureContext.h index b17a36a77..34c993436 100644 --- a/qrenderdoc/Code/CaptureContext.h +++ b/qrenderdoc/Code/CaptureContext.h @@ -117,6 +117,7 @@ public: bool SaveCaptureTo(const rdcstr &captureFile) override; void RecompressCapture() override; void CloseCapture() override; + IReplayController *GetBlockingController() override { return NULL; } bool ImportCapture(const CaptureFileFormat &fmt, const rdcstr &importfile, const rdcstr &rdcfile) override; void ExportCapture(const CaptureFileFormat &fmt, const rdcstr &exportfile) override; diff --git a/qrenderdoc/Code/Interface/QRDInterface.h b/qrenderdoc/Code/Interface/QRDInterface.h index 0fd664b32..1c943eb86 100644 --- a/qrenderdoc/Code/Interface/QRDInterface.h +++ b/qrenderdoc/Code/Interface/QRDInterface.h @@ -2000,6 +2000,31 @@ time. DOCUMENT("Close the currently open capture file."); virtual void CloseCapture() = 0; + ////////////////////////////////////////////////////////////// + DOCUMENT(R"(Returns a blocking version of :class:`renderdoc.ReplayController`, which provides the +same interface but will block for all calls and run them on the correct replay thread. This is +provided only as a convenience to avoid calling :meth:`Replay` to obtain the +:class:`ReplayManager` and use that to directly invoke asynchronously or synchronously onto the +replay thread. + +This function will return ``None`` if no :class:`renderdoc.ReplayController` is available, for +example if there is no capture loaded. + +The returned object *must not* be used if the capture is closed for any reason. It is strongly +recommended that you do not cache this object and use it only in local areas of code when needed. + +.. warning:: + Care should be taken to ensure that this object is not confused with any other controller that is + non-blocking. + +:return: A blocking version of the :class:`renderdoc.ReplayController`. +:rtype: renderdoc.ReplayController +)"); + ////////////////////////////////////////////////////////////// + // This function is implemented only for python! it will return NULL unconditionally + // in C++. You should use Replay() to get IReplayManager and call BlockInvoke() + virtual IReplayController *GetBlockingController() = 0; + DOCUMENT(R"(Imports a capture file from a non-native format, via conversion to temporary rdc. This converts the file to a specified temporary .rdc and loads it, closing any existing capture. diff --git a/qrenderdoc/Code/ReplayManager.cpp b/qrenderdoc/Code/ReplayManager.cpp index 0fad56efc..5ad03eb81 100644 --- a/qrenderdoc/Code/ReplayManager.cpp +++ b/qrenderdoc/Code/ReplayManager.cpp @@ -292,6 +292,12 @@ void ReplayManager::AsyncInvoke(ReplayManager::ReplayInvokeCallback m) void ReplayManager::BlockInvoke(ReplayManager::ReplayInvokeCallback m) { + if(IsRunning() && m_Thread->isCurrentThread()) + { + m(m_Renderer); + return; + } + InvokeHandle *cmd = new InvokeHandle(m); PushInvoke(cmd); diff --git a/qrenderdoc/Windows/PythonShell.cpp b/qrenderdoc/Windows/PythonShell.cpp index 4a725605b..62e7d4a28 100644 --- a/qrenderdoc/Windows/PythonShell.cpp +++ b/qrenderdoc/Windows/PythonShell.cpp @@ -53,9 +53,9 @@ enum // operations. All invokes are blocking, so there can't be any times when the UI thread waits // on the python thread. template -struct ObjectForwarder : Obj +struct UIThreadInvoker : Obj { - ObjectForwarder(PythonShell *sh, Obj &o) : m_Shell(sh), m_Obj(o) {} + UIThreadInvoker(PythonShell *sh, Obj &o) : m_Shell(sh), m_Obj(o) {} PythonShell *m_Shell; Obj &m_Obj; @@ -96,9 +96,9 @@ struct ObjectForwarder : Obj } }; -struct MiniQtInvoker : ObjectForwarder +struct MiniQtInvoker : UIThreadInvoker { - MiniQtInvoker(PythonShell *shell, IMiniQtHelper &obj) : ObjectForwarder(shell, obj) {} + MiniQtInvoker(PythonShell *shell, IMiniQtHelper &obj) : UIThreadInvoker(shell, obj) {} virtual ~MiniQtInvoker() {} void InvokeOntoUIThread(std::function callback) { @@ -368,10 +368,10 @@ struct MiniQtInvoker : ObjectForwarder } }; -struct ExtensionInvoker : ObjectForwarder +struct ExtensionInvoker : UIThreadInvoker { MiniQtInvoker *m_MiniQt; - ExtensionInvoker(PythonShell *shell, IExtensionManager &obj) : ObjectForwarder(shell, obj) + ExtensionInvoker(PythonShell *shell, IExtensionManager &obj) : UIThreadInvoker(shell, obj) { m_MiniQt = new MiniQtInvoker(shell, obj.GetMiniQtHelper()); } @@ -457,10 +457,374 @@ struct ExtensionInvoker : ObjectForwarder } }; -struct CaptureContextInvoker : ObjectForwarder +struct ReplayControllerInvoker : IReplayController +{ + ReplayControllerInvoker(PythonShell *shell, ICaptureContext &ctx) : m_Shell(shell), m_Ctx(ctx) {} + PythonShell *m_Shell; + ICaptureContext &m_Ctx; + + template + void InvokeVoidFunction(F ptr, paramTypes... params) + { + PythonContext *scriptContext = m_Shell->GetScriptContext(); + if(scriptContext) + scriptContext->PausePythonThreading(); + m_Ctx.Replay().BlockInvoke( + [this, ptr, params...](IReplayController *replay) { (replay->*ptr)(params...); }); + if(scriptContext) + scriptContext->ResumePythonThreading(); + } + + template + R InvokeRetFunction(F ptr, paramTypes... params) + { + R ret = R(); + PythonContext *scriptContext = m_Shell->GetScriptContext(); + if(scriptContext) + scriptContext->PausePythonThreading(); + m_Ctx.Replay().BlockInvoke([this, &ret, ptr, params...](IReplayController *replay) { + ret = (replay->*ptr)(params...); + }); + if(scriptContext) + scriptContext->ResumePythonThreading(); + return ret; + } + + template + R &InvokeRetRefFunction(F ptr, paramTypes... params) + { + R *ret = NULL; + PythonContext *scriptContext = m_Shell->GetScriptContext(); + if(scriptContext) + scriptContext->PausePythonThreading(); + m_Ctx.Replay().BlockInvoke([this, &ret, ptr, params...](IReplayController *replay) { + ret = &(replay->*ptr)(params...); + }); + if(scriptContext) + scriptContext->ResumePythonThreading(); + return *ret; + } + + APIProperties GetAPIProperties() + { + return InvokeRetFunction(&IReplayController::GetAPIProperties); + } + + rdcarray GetSupportedWindowSystems() + { + return InvokeRetFunction>(&IReplayController::GetSupportedWindowSystems); + } + + IReplayOutput *CreateOutput(WindowingData window, ReplayOutputType type) + { + return InvokeRetFunction(&IReplayController::CreateOutput, window, type); + } + + void Shutdown() {} + + void ReplayLoop(WindowingData window, ResourceId texid) {} + + rdcstr CreateRGPProfile(WindowingData window) + { + return InvokeRetFunction(&IReplayController::CreateRGPProfile, window); + } + + void CancelReplayLoop() {} + + void FileChanged() {} + + void SetFrameEvent(uint32_t eventId, bool force) {} + + const D3D11Pipe::State *GetD3D11PipelineState() + { + return InvokeRetFunction(&IReplayController::GetD3D11PipelineState); + } + + const D3D12Pipe::State *GetD3D12PipelineState() + { + return InvokeRetFunction(&IReplayController::GetD3D12PipelineState); + } + + const GLPipe::State *GetGLPipelineState() + { + return InvokeRetFunction(&IReplayController::GetGLPipelineState); + } + + const VKPipe::State *GetVulkanPipelineState() + { + return InvokeRetFunction(&IReplayController::GetVulkanPipelineState); + } + + const PipeState &GetPipelineState() + { + return InvokeRetRefFunction(&IReplayController::GetPipelineState); + } + + rdcarray GetDescriptors(ResourceId descriptorStore, + const rdcarray &ranges) + { + return InvokeRetFunction>(&IReplayController::GetDescriptors, + descriptorStore, ranges); + } + + rdcarray GetSamplerDescriptors(ResourceId descriptorStore, + const rdcarray &ranges) + { + return InvokeRetFunction>(&IReplayController::GetSamplerDescriptors, + descriptorStore, ranges); + } + + const rdcarray &GetDescriptorAccess() + { + return InvokeRetRefFunction>( + &IReplayController::GetDescriptorAccess); + } + + rdcarray GetDescriptorLocations(ResourceId descriptorStore, + const rdcarray &ranges) + { + return InvokeRetFunction>( + &IReplayController::GetDescriptorLocations, descriptorStore, ranges); + } + + rdcarray GetDisassemblyTargets(bool withPipeline) + { + return InvokeRetFunction>(&IReplayController::GetDisassemblyTargets, + withPipeline); + } + + rdcstr DisassembleShader(ResourceId pipeline, const ShaderReflection *refl, const rdcstr &target) + { + return InvokeRetFunction(&IReplayController::DisassembleShader, pipeline, refl, target); + } + + void SetCustomShaderIncludes(const rdcarray &directories) {} + + rdcpair BuildCustomShader(const rdcstr &entry, ShaderEncoding sourceEncoding, + bytebuf source, + const ShaderCompileFlags &compileFlags, + ShaderStage type) + { + return InvokeRetFunction>( + &IReplayController::BuildCustomShader, entry, sourceEncoding, source, compileFlags, type); + } + + void FreeCustomShader(ResourceId id) {} + + rdcpair BuildTargetShader(const rdcstr &entry, ShaderEncoding sourceEncoding, + bytebuf source, + const ShaderCompileFlags &compileFlags, + ShaderStage type) + { + return InvokeRetFunction>( + &IReplayController::BuildTargetShader, entry, sourceEncoding, source, compileFlags, type); + } + + rdcarray GetTargetShaderEncodings() + { + return InvokeRetFunction>(&IReplayController::GetTargetShaderEncodings); + } + + rdcarray GetCustomShaderEncodings() + { + return InvokeRetFunction>(&IReplayController::GetCustomShaderEncodings); + } + + rdcarray GetCustomShaderSourcePrefixes() + { + return InvokeRetFunction>( + &IReplayController::GetCustomShaderSourcePrefixes); + } + + void ReplaceResource(ResourceId original, ResourceId replacement) {} + + void ClearReplayCache() {} + + void ReloadShaderDebugInformation() {} + + void RemoveReplacement(ResourceId id) {} + + void FreeTargetResource(ResourceId id) {} + + FrameDescription GetFrameInfo() + { + return InvokeRetFunction(&IReplayController::GetFrameInfo); + } + + const SDFile &GetStructuredFile() + { + return InvokeRetRefFunction(&IReplayController::GetStructuredFile); + } + + void AddFakeMarkers() {} + + const rdcarray &GetRootActions() + { + return InvokeRetRefFunction>(&IReplayController::GetRootActions); + } + + rdcarray FetchCounters(const rdcarray &counters) + { + return InvokeRetFunction>(&IReplayController::FetchCounters, counters); + } + + rdcarray EnumerateCounters() + { + return InvokeRetFunction>(&IReplayController::EnumerateCounters); + } + + CounterDescription DescribeCounter(GPUCounter counter) + { + return InvokeRetFunction(&IReplayController::DescribeCounter, counter); + } + + const rdcarray &GetResources() + { + return InvokeRetRefFunction>(&IReplayController::GetResources); + } + + const rdcarray &GetTextures() + { + return InvokeRetRefFunction>(&IReplayController::GetTextures); + } + + const rdcarray &GetBuffers() + { + return InvokeRetRefFunction>(&IReplayController::GetBuffers); + } + + const rdcarray &GetDescriptorStores() + { + return InvokeRetRefFunction>( + &IReplayController::GetDescriptorStores); + } + + rdcarray GetDebugMessages() + { + return InvokeRetFunction>(&IReplayController::GetDebugMessages); + } + + ResultDetails GetFatalErrorStatus() + { + return InvokeRetFunction(&IReplayController::GetFatalErrorStatus); + } + + rdcarray GetShaderEntryPoints(ResourceId shader) + { + return InvokeRetFunction>(&IReplayController::GetShaderEntryPoints, + shader); + } + + const ShaderReflection *GetShader(ResourceId pipeline, ResourceId shader, ShaderEntryPoint entry) + { + return InvokeRetFunction(&IReplayController::GetShader, pipeline, + shader, entry); + } + + PixelValue PickPixel(ResourceId textureId, uint32_t x, uint32_t y, const Subresource &sub, + CompType typeCast) + { + return InvokeRetFunction(&IReplayController::PickPixel, textureId, x, y, sub, + typeCast); + } + + rdcpair GetMinMax(ResourceId textureId, const Subresource &sub, + CompType typeCast) + { + return InvokeRetFunction>(&IReplayController::GetMinMax, + textureId, sub, typeCast); + } + + rdcarray GetHistogram(ResourceId textureId, const Subresource &sub, CompType typeCast, + float minval, float maxval, const rdcfixedarray &channels) + { + return InvokeRetFunction>(&IReplayController::GetHistogram, textureId, sub, + typeCast, minval, maxval, channels); + } + + rdcarray PixelHistory(ResourceId texture, uint32_t x, uint32_t y, + const Subresource &sub, CompType typeCast) + { + return InvokeRetFunction>(&IReplayController::PixelHistory, texture, + x, y, sub, typeCast); + } + + ShaderDebugTrace *DebugVertex(uint32_t vertid, uint32_t instid, uint32_t idx, uint32_t view) + { + return InvokeRetFunction(&IReplayController::DebugVertex, vertid, instid, + idx, view); + } + + ShaderDebugTrace *DebugPixel(uint32_t x, uint32_t y, const DebugPixelInputs &inputs) + { + return InvokeRetFunction(&IReplayController::DebugPixel, x, y, inputs); + } + + ShaderDebugTrace *DebugThread(const rdcfixedarray &groupid, + const rdcfixedarray &threadid) + { + return InvokeRetFunction(&IReplayController::DebugThread, groupid, threadid); + } + + ShaderDebugTrace *DebugMeshThread(const rdcfixedarray &groupid, + const rdcfixedarray &threadid) + { + return InvokeRetFunction(&IReplayController::DebugMeshThread, groupid, + threadid); + } + + rdcarray ContinueDebug(ShaderDebugger *debugger) + { + return InvokeRetFunction>(&IReplayController::ContinueDebug, debugger); + } + + void FreeTrace(ShaderDebugTrace *trace) + { + return InvokeVoidFunction(&IReplayController::FreeTrace, trace); + } + + rdcarray GetUsage(ResourceId id) + { + return InvokeRetFunction>(&IReplayController::GetUsage, id); + } + + rdcarray GetCBufferVariableContents(ResourceId pipeline, ResourceId shader, + ShaderStage stage, const rdcstr &entryPoint, + uint32_t cbufslot, ResourceId buffer, + uint64_t offset, uint64_t length) + { + return InvokeRetFunction>( + &IReplayController::GetCBufferVariableContents, pipeline, shader, stage, entryPoint, + cbufslot, buffer, offset, length); + } + + ResultDetails SaveTexture(const TextureSave &saveData, const rdcstr &path) + { + return InvokeRetFunction(&IReplayController::SaveTexture, saveData, path); + } + + MeshFormat GetPostVSData(uint32_t instance, uint32_t view, MeshDataStage stage) + { + return InvokeRetFunction(&IReplayController::GetPostVSData, instance, view, stage); + } + + bytebuf GetBufferData(ResourceId buff, uint64_t offset, uint64_t len) + { + return InvokeRetFunction(&IReplayController::GetBufferData, buff, offset, len); + } + + bytebuf GetTextureData(ResourceId tex, const Subresource &sub) + { + return InvokeRetFunction(&IReplayController::GetTextureData, tex, sub); + } +}; + +struct CaptureContextInvoker : UIThreadInvoker { ExtensionInvoker *m_Ext; - CaptureContextInvoker(PythonShell *shell, ICaptureContext &obj) : ObjectForwarder(shell, obj) + ReplayControllerInvoker m_ReplayController; + CaptureContextInvoker(PythonShell *shell, ICaptureContext &obj) + : UIThreadInvoker(shell, obj), m_ReplayController(shell, obj) { m_Ext = new ExtensionInvoker(shell, obj.Extensions()); } @@ -602,6 +966,12 @@ struct CaptureContextInvoker : ObjectForwarder InvokeVoidFunction(&ICaptureContext::RecompressCapture); } virtual void CloseCapture() override { InvokeVoidFunction(&ICaptureContext::CloseCapture); } + virtual IReplayController *GetBlockingController() override + { + if(!m_Obj.IsCaptureLoaded()) + return NULL; + return &m_ReplayController; + } virtual bool ImportCapture(const CaptureFileFormat &fmt, const rdcstr &importfile, const rdcstr &rdcfile) override {