Skip to content

Commit 1cc2c22

Browse files
committed
Invoke CaptureContext function calls onto UI thread, from python thread
* Any work that might use Qt needs to happen on the UI thread, so when running a python script on the python thread we need to invoke across. We wrap the main ICaptureContext interface to block invoke onto the UI for any function calls that aren't just returning internal data.
1 parent 21836c2 commit 1cc2c22

4 files changed

Lines changed: 331 additions & 6 deletions

File tree

qrenderdoc/Code/QRDUtils.cpp

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -313,7 +313,7 @@ void GUIInvoke::init()
313313

314314
void GUIInvoke::call(const std::function<void()> &f)
315315
{
316-
if(qApp->thread() == QThread::currentThread())
316+
if(onUIThread())
317317
{
318318
f();
319319
return;
@@ -326,7 +326,7 @@ void GUIInvoke::call(const std::function<void()> &f)
326326

327327
void GUIInvoke::blockcall(const std::function<void()> &f)
328328
{
329-
if(qApp->thread() == QThread::currentThread())
329+
if(onUIThread())
330330
{
331331
f();
332332
return;
@@ -337,6 +337,11 @@ void GUIInvoke::blockcall(const std::function<void()> &f)
337337
invoke->metaObject()->method(methodIndex).invoke(invoke, Qt::BlockingQueuedConnection);
338338
}
339339

340+
bool GUIInvoke::onUIThread()
341+
{
342+
return qApp->thread() == QThread::currentThread();
343+
}
344+
340345
const QMessageBox::StandardButtons RDDialog::YesNoCancel =
341346
QMessageBox::StandardButtons(QMessageBox::Yes | QMessageBox::No | QMessageBox::Cancel);
342347

qrenderdoc/Code/QRDUtils.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -725,6 +725,7 @@ class GUIInvoke : public QObject
725725
static void init();
726726
static void call(const std::function<void()> &f);
727727
static void blockcall(const std::function<void()> &f);
728+
static bool onUIThread();
728729

729730
protected slots:
730731
void doInvoke()

qrenderdoc/Windows/PythonShell.cpp

Lines changed: 320 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -32,11 +32,324 @@
3232
#include "Code/pyrenderdoc/PythonContext.h"
3333
#include "ui_PythonShell.h"
3434

35+
// a forwarder that invokes onto the UI thread wherever necessary.
36+
// Note this does NOT make CaptureContext thread safe. We just invoke for any potentially UI
37+
// operations. All invokes are blocking, so there can't be any times when the UI thread waits
38+
// on the python thread.
39+
struct CaptureContextInvoker : ICaptureContext
40+
{
41+
ICaptureContext &m_Ctx;
42+
CaptureContextInvoker(ICaptureContext &ctx) : m_Ctx(ctx) {}
43+
//
44+
///////////////////////////////////////////////////////////////////////
45+
// pass-through functions that don't need the UI thread
46+
///////////////////////////////////////////////////////////////////////
47+
//
48+
virtual QString ConfigFilePath(const QString &filename) override
49+
{
50+
return m_Ctx.ConfigFilePath(filename);
51+
}
52+
virtual QString TempLogFilename(QString appname) override
53+
{
54+
return m_Ctx.TempLogFilename(appname);
55+
}
56+
virtual IReplayManager &Replay() override { return m_Ctx.Replay(); }
57+
virtual bool LogLoaded() override { return m_Ctx.LogLoaded(); }
58+
virtual bool IsLogLocal() override { return m_Ctx.IsLogLocal(); }
59+
virtual bool LogLoading() override { return m_Ctx.LogLoading(); }
60+
virtual QString LogFilename() override { return m_Ctx.LogFilename(); }
61+
virtual const FrameDescription &FrameInfo() override { return m_Ctx.FrameInfo(); }
62+
virtual const APIProperties &APIProps() override { return m_Ctx.APIProps(); }
63+
virtual uint32_t CurSelectedEvent() override { return m_Ctx.CurSelectedEvent(); }
64+
virtual uint32_t CurEvent() override { return m_Ctx.CurEvent(); }
65+
virtual const DrawcallDescription *CurSelectedDrawcall() override
66+
{
67+
return m_Ctx.CurSelectedDrawcall();
68+
}
69+
virtual const DrawcallDescription *CurDrawcall() override { return m_Ctx.CurDrawcall(); }
70+
virtual const rdctype::array<DrawcallDescription> &CurDrawcalls() override
71+
{
72+
return m_Ctx.CurDrawcalls();
73+
}
74+
virtual TextureDescription *GetTexture(ResourceId id) override { return m_Ctx.GetTexture(id); }
75+
virtual const rdctype::array<TextureDescription> &GetTextures() override
76+
{
77+
return m_Ctx.GetTextures();
78+
}
79+
virtual BufferDescription *GetBuffer(ResourceId id) override { return m_Ctx.GetBuffer(id); }
80+
virtual const rdctype::array<BufferDescription> &GetBuffers() override
81+
{
82+
return m_Ctx.GetBuffers();
83+
}
84+
virtual const DrawcallDescription *GetDrawcall(uint32_t eventID) override
85+
{
86+
return m_Ctx.GetDrawcall(eventID);
87+
}
88+
virtual WindowingSystem CurWindowingSystem() override { return m_Ctx.CurWindowingSystem(); }
89+
virtual void *FillWindowingData(uintptr_t winId) override
90+
{
91+
return m_Ctx.FillWindowingData(winId);
92+
}
93+
virtual const QVector<DebugMessage> &DebugMessages() override { return m_Ctx.DebugMessages(); }
94+
virtual int UnreadMessageCount() override { return m_Ctx.UnreadMessageCount(); }
95+
virtual void MarkMessagesRead() override { return m_Ctx.MarkMessagesRead(); }
96+
virtual D3D11Pipe::State &CurD3D11PipelineState() override
97+
{
98+
return m_Ctx.CurD3D11PipelineState();
99+
}
100+
virtual D3D12Pipe::State &CurD3D12PipelineState() override
101+
{
102+
return m_Ctx.CurD3D12PipelineState();
103+
}
104+
virtual GLPipe::State &CurGLPipelineState() override { return m_Ctx.CurGLPipelineState(); }
105+
virtual VKPipe::State &CurVulkanPipelineState() override
106+
{
107+
return m_Ctx.CurVulkanPipelineState();
108+
}
109+
virtual CommonPipelineState &CurPipelineState() override { return m_Ctx.CurPipelineState(); }
110+
virtual PersistantConfig &Config() override { return m_Ctx.Config(); }
111+
//
112+
///////////////////////////////////////////////////////////////////////
113+
// functions that invoke onto the UI thread
114+
///////////////////////////////////////////////////////////////////////
115+
//
116+
template <typename F, typename... paramTypes>
117+
void InvokeVoidFunction(F ptr, paramTypes... params)
118+
{
119+
if(!GUIInvoke::onUIThread())
120+
{
121+
GUIInvoke::blockcall([this, ptr, params...]() { (m_Ctx.*ptr)(params...); });
122+
123+
return;
124+
}
125+
126+
(m_Ctx.*ptr)(params...);
127+
}
128+
129+
template <typename R, typename F, typename... paramTypes>
130+
R InvokeRetFunction(F ptr, paramTypes... params)
131+
{
132+
if(!GUIInvoke::onUIThread())
133+
{
134+
R ret;
135+
GUIInvoke::blockcall([this, &ret, ptr, params...]() { ret = (m_Ctx.*ptr)(params...); });
136+
137+
return ret;
138+
}
139+
140+
return (m_Ctx.*ptr)(params...);
141+
}
142+
143+
virtual void LoadLogfile(const QString &logFile, const QString &origFilename, bool temporary,
144+
bool local) override
145+
{
146+
InvokeVoidFunction(&ICaptureContext::LoadLogfile, logFile, origFilename, temporary, local);
147+
}
148+
virtual void CloseLogfile() override { InvokeVoidFunction(&ICaptureContext::CloseLogfile); }
149+
virtual void SetEventID(const QVector<ILogViewer *> &exclude, uint32_t selectedEventID,
150+
uint32_t eventID, bool force = false)
151+
{
152+
InvokeVoidFunction(&ICaptureContext::SetEventID, exclude, selectedEventID, eventID, force);
153+
}
154+
virtual void RefreshStatus() override { InvokeVoidFunction(&ICaptureContext::RefreshStatus); }
155+
virtual void AddLogViewer(ILogViewer *viewer) override
156+
{
157+
InvokeVoidFunction(&ICaptureContext::AddLogViewer, viewer);
158+
}
159+
virtual void RemoveLogViewer(ILogViewer *viewer) override
160+
{
161+
InvokeVoidFunction(&ICaptureContext::RemoveLogViewer, viewer);
162+
}
163+
virtual void AddMessages(const rdctype::array<DebugMessage> &msgs) override
164+
{
165+
InvokeVoidFunction(&ICaptureContext::AddMessages, msgs);
166+
}
167+
virtual IMainWindow *GetMainWindow() override
168+
{
169+
return InvokeRetFunction<IMainWindow *>(&ICaptureContext::GetMainWindow);
170+
}
171+
virtual IEventBrowser *GetEventBrowser() override
172+
{
173+
return InvokeRetFunction<IEventBrowser *>(&ICaptureContext::GetEventBrowser);
174+
}
175+
virtual IAPIInspector *GetAPIInspector() override
176+
{
177+
return InvokeRetFunction<IAPIInspector *>(&ICaptureContext::GetAPIInspector);
178+
}
179+
virtual ITextureViewer *GetTextureViewer() override
180+
{
181+
return InvokeRetFunction<ITextureViewer *>(&ICaptureContext::GetTextureViewer);
182+
}
183+
virtual IBufferViewer *GetMeshPreview() override
184+
{
185+
return InvokeRetFunction<IBufferViewer *>(&ICaptureContext::GetMeshPreview);
186+
}
187+
virtual IPipelineStateViewer *GetPipelineViewer() override
188+
{
189+
return InvokeRetFunction<IPipelineStateViewer *>(&ICaptureContext::GetPipelineViewer);
190+
}
191+
virtual ICaptureDialog *GetCaptureDialog() override
192+
{
193+
return InvokeRetFunction<ICaptureDialog *>(&ICaptureContext::GetCaptureDialog);
194+
}
195+
virtual IDebugMessageView *GetDebugMessageView() override
196+
{
197+
return InvokeRetFunction<IDebugMessageView *>(&ICaptureContext::GetDebugMessageView);
198+
}
199+
virtual IStatisticsViewer *GetStatisticsViewer() override
200+
{
201+
return InvokeRetFunction<IStatisticsViewer *>(&ICaptureContext::GetStatisticsViewer);
202+
}
203+
virtual IPythonShell *GetPythonShell() override
204+
{
205+
return InvokeRetFunction<IPythonShell *>(&ICaptureContext::GetPythonShell);
206+
}
207+
virtual bool HasEventBrowser() override
208+
{
209+
return InvokeRetFunction<bool>(&ICaptureContext::HasEventBrowser);
210+
}
211+
virtual bool HasAPIInspector() override
212+
{
213+
return InvokeRetFunction<bool>(&ICaptureContext::HasAPIInspector);
214+
}
215+
virtual bool HasTextureViewer() override
216+
{
217+
return InvokeRetFunction<bool>(&ICaptureContext::HasTextureViewer);
218+
}
219+
virtual bool HasPipelineViewer() override
220+
{
221+
return InvokeRetFunction<bool>(&ICaptureContext::HasPipelineViewer);
222+
}
223+
virtual bool HasMeshPreview() override
224+
{
225+
return InvokeRetFunction<bool>(&ICaptureContext::HasMeshPreview);
226+
}
227+
virtual bool HasCaptureDialog() override
228+
{
229+
return InvokeRetFunction<bool>(&ICaptureContext::HasCaptureDialog);
230+
}
231+
virtual bool HasDebugMessageView() override
232+
{
233+
return InvokeRetFunction<bool>(&ICaptureContext::HasDebugMessageView);
234+
}
235+
virtual bool HasStatisticsViewer() override
236+
{
237+
return InvokeRetFunction<bool>(&ICaptureContext::HasStatisticsViewer);
238+
}
239+
virtual bool HasPythonShell() override
240+
{
241+
return InvokeRetFunction<bool>(&ICaptureContext::HasPythonShell);
242+
}
243+
244+
virtual void ShowEventBrowser() override
245+
{
246+
InvokeVoidFunction(&ICaptureContext::ShowEventBrowser);
247+
}
248+
virtual void ShowAPIInspector() override
249+
{
250+
InvokeVoidFunction(&ICaptureContext::ShowAPIInspector);
251+
}
252+
virtual void ShowTextureViewer() override
253+
{
254+
InvokeVoidFunction(&ICaptureContext::ShowTextureViewer);
255+
}
256+
virtual void ShowMeshPreview() override { InvokeVoidFunction(&ICaptureContext::ShowMeshPreview); }
257+
virtual void ShowPipelineViewer() override
258+
{
259+
InvokeVoidFunction(&ICaptureContext::ShowPipelineViewer);
260+
}
261+
virtual void ShowCaptureDialog() override
262+
{
263+
InvokeVoidFunction(&ICaptureContext::ShowCaptureDialog);
264+
}
265+
virtual void ShowDebugMessageView() override
266+
{
267+
InvokeVoidFunction(&ICaptureContext::ShowDebugMessageView);
268+
}
269+
virtual void ShowStatisticsViewer() override
270+
{
271+
InvokeVoidFunction(&ICaptureContext::ShowStatisticsViewer);
272+
}
273+
virtual void ShowPythonShell() override { InvokeVoidFunction(&ICaptureContext::ShowPythonShell); }
274+
virtual IShaderViewer *EditShader(bool customShader, const QString &entryPoint,
275+
const QStringMap &files, IShaderViewer::SaveCallback saveCallback,
276+
IShaderViewer::CloseCallback closeCallback) override
277+
{
278+
return InvokeRetFunction<IShaderViewer *>(&ICaptureContext::EditShader, customShader,
279+
entryPoint, files, saveCallback, closeCallback);
280+
}
281+
282+
virtual IShaderViewer *DebugShader(const ShaderBindpointMapping *bind,
283+
const ShaderReflection *shader, ShaderStage stage,
284+
ShaderDebugTrace *trace, const QString &debugContext) override
285+
{
286+
return InvokeRetFunction<IShaderViewer *>(&ICaptureContext::DebugShader, bind, shader, stage,
287+
trace, debugContext);
288+
}
289+
290+
virtual IShaderViewer *ViewShader(const ShaderBindpointMapping *bind,
291+
const ShaderReflection *shader, ShaderStage stage) override
292+
{
293+
return InvokeRetFunction<IShaderViewer *>(&ICaptureContext::ViewShader, bind, shader, stage);
294+
}
295+
296+
virtual IBufferViewer *ViewBuffer(uint64_t byteOffset, uint64_t byteSize, ResourceId id,
297+
const QString &format = QString()) override
298+
{
299+
return InvokeRetFunction<IBufferViewer *>(&ICaptureContext::ViewBuffer, byteOffset, byteSize,
300+
id, format);
301+
}
302+
303+
virtual IBufferViewer *ViewTextureAsBuffer(uint32_t arrayIdx, uint32_t mip, ResourceId id,
304+
const QString &format = QString()) override
305+
{
306+
return InvokeRetFunction<IBufferViewer *>(&ICaptureContext::ViewTextureAsBuffer, arrayIdx, mip,
307+
id, format);
308+
}
309+
310+
virtual IConstantBufferPreviewer *ViewConstantBuffer(ShaderStage stage, uint32_t slot,
311+
uint32_t idx) override
312+
{
313+
return InvokeRetFunction<IConstantBufferPreviewer *>(&ICaptureContext::ViewConstantBuffer,
314+
stage, slot, idx);
315+
}
316+
317+
virtual IPixelHistoryView *ViewPixelHistory(ResourceId texID, int x, int y,
318+
const TextureDisplay &display) override
319+
{
320+
return InvokeRetFunction<IPixelHistoryView *>(&ICaptureContext::ViewPixelHistory, texID, x, y,
321+
display);
322+
}
323+
324+
virtual QWidget *CreateBuiltinWindow(const QString &objectName) override
325+
{
326+
return InvokeRetFunction<QWidget *>(&ICaptureContext::CreateBuiltinWindow, objectName);
327+
}
328+
329+
virtual void BuiltinWindowClosed(QWidget *window) override
330+
{
331+
InvokeVoidFunction(&ICaptureContext::BuiltinWindowClosed, window);
332+
}
333+
334+
virtual void RaiseDockWindow(QWidget *dockWindow) override
335+
{
336+
InvokeVoidFunction(&ICaptureContext::RaiseDockWindow, dockWindow);
337+
}
338+
339+
virtual void AddDockWindow(QWidget *newWindow, DockReference ref, QWidget *refWindow,
340+
float percentage = 0.5f) override
341+
{
342+
InvokeVoidFunction(&ICaptureContext::AddDockWindow, newWindow, ref, refWindow, percentage);
343+
}
344+
};
345+
35346
PythonShell::PythonShell(ICaptureContext &ctx, QWidget *parent)
36347
: QFrame(parent), ui(new Ui::PythonShell), m_Ctx(ctx)
37348
{
38349
ui->setupUi(this);
39350

351+
m_ThreadCtx = new CaptureContextInvoker(m_Ctx);
352+
40353
QObject::connect(ui->lineInput, &RDLineEdit::keyPress, this, &PythonShell::interactive_keypress);
41354

42355
ui->lineInput->setFont(QFontDatabase::systemFont(QFontDatabase::FixedFont));
@@ -98,6 +411,8 @@ PythonShell::~PythonShell()
98411

99412
interactiveContext->Finish();
100413

414+
delete m_ThreadCtx;
415+
101416
delete ui;
102417
}
103418

@@ -211,9 +526,10 @@ void PythonShell::on_runScript_clicked()
211526
context->executeString(lit("script.py"), script);
212527
scriptContext = NULL;
213528

214-
context->Finish();
215-
216-
GUIInvoke::call([this]() { enableButtons(true); });
529+
GUIInvoke::call([this, context]() {
530+
context->Finish();
531+
enableButtons(true);
532+
});
217533
});
218534

219535
thread->selfDelete(true);
@@ -341,7 +657,7 @@ PythonContext *PythonShell::newContext()
341657
QObject::connect(ret, &PythonContext::exception, this, &PythonShell::exception);
342658
QObject::connect(ret, &PythonContext::textOutput, this, &PythonShell::textOutput);
343659

344-
ret->setGlobal("pyrenderdoc", &m_Ctx);
660+
ret->setGlobal("pyrenderdoc", (ICaptureContext *)m_ThreadCtx);
345661

346662
return ret;
347663
}

0 commit comments

Comments
 (0)