From f76a4cc33995785e940397e7d55dcb4be47f5d78 Mon Sep 17 00:00:00 2001 From: baldurk Date: Fri, 24 Feb 2017 17:34:02 +0000 Subject: [PATCH] Don't launch blocking execute call on UI thread, pop up progress ticker * This most commonly happens launching an Android program that takes a while to launch, or if you're launching a program with the delay for debugger option set. * Instead of the whole UI hanging, you'll get a progress dialog to appear while it's waiting. --- qrenderdoc/Code/CaptureContext.cpp | 9 +- qrenderdoc/Windows/Dialogs/CaptureDialog.cpp | 19 ++-- qrenderdoc/Windows/Dialogs/CaptureDialog.h | 9 +- qrenderdoc/Windows/MainWindow.cpp | 104 ++++++++++++------- qrenderdoc/Windows/MainWindow.h | 9 +- renderdocui/Windows/Dialogs/CaptureDialog.cs | 24 +++-- renderdocui/Windows/MainWindow.cs | 90 +++++++++++----- 7 files changed, 172 insertions(+), 92 deletions(-) diff --git a/qrenderdoc/Code/CaptureContext.cpp b/qrenderdoc/Code/CaptureContext.cpp index f09920a00..17150b762 100644 --- a/qrenderdoc/Code/CaptureContext.cpp +++ b/qrenderdoc/Code/CaptureContext.cpp @@ -685,11 +685,14 @@ CaptureDialog *CaptureContext::captureDialog() m_CaptureDialog = new CaptureDialog( *this, [this](const QString &exe, const QString &workingDir, const QString &cmdLine, - const QList &env, CaptureOptions opts) { - return m_MainWindow->OnCaptureTrigger(exe, workingDir, cmdLine, env, opts); + const QList &env, CaptureOptions opts, + std::function callback) { + return m_MainWindow->OnCaptureTrigger(exe, workingDir, cmdLine, env, opts, callback); }, [this](uint32_t PID, const QList &env, const QString &name, - CaptureOptions opts) { return m_MainWindow->OnInjectTrigger(PID, env, name, opts); }, + CaptureOptions opts, std::function callback) { + return m_MainWindow->OnInjectTrigger(PID, env, name, opts, callback); + }, m_MainWindow); m_CaptureDialog->setObjectName("capDialog"); m_CaptureDialog->setWindowIcon(*m_Icon); diff --git a/qrenderdoc/Windows/Dialogs/CaptureDialog.cpp b/qrenderdoc/Windows/Dialogs/CaptureDialog.cpp index 05aefa0b6..ace183375 100644 --- a/qrenderdoc/Windows/Dialogs/CaptureDialog.cpp +++ b/qrenderdoc/Windows/Dialogs/CaptureDialog.cpp @@ -745,10 +745,11 @@ void CaptureDialog::triggerCapture() QString name = m_ProcessModel->data(m_ProcessModel->index(item.row(), 0)).toString(); uint32_t PID = m_ProcessModel->data(m_ProcessModel->index(item.row(), 1)).toUInt(); - LiveCapture *live = m_InjectCallback(PID, settings().Environment, name, settings().Options); - - if(ui->queueFrameCap->isChecked() && live != NULL) - live->QueueCapture((int)ui->queuedFrame->value()); + m_InjectCallback(PID, settings().Environment, name, settings().Options, + [this](LiveCapture *live) { + if(ui->queueFrameCap->isChecked()) + live->QueueCapture((int)ui->queuedFrame->value()); + }); } } else @@ -781,10 +782,10 @@ void CaptureDialog::triggerCapture() QString cmdLine = ui->cmdline->text(); - LiveCapture *live = - m_CaptureCallback(exe, workingDir, cmdLine, settings().Environment, settings().Options); - - if(ui->queueFrameCap->isChecked() && live != NULL) - live->QueueCapture((int)ui->queuedFrame->value()); + m_CaptureCallback(exe, workingDir, cmdLine, settings().Environment, settings().Options, + [this](LiveCapture *live) { + if(ui->queueFrameCap->isChecked()) + live->QueueCapture((int)ui->queuedFrame->value()); + }); } } diff --git a/qrenderdoc/Windows/Dialogs/CaptureDialog.h b/qrenderdoc/Windows/Dialogs/CaptureDialog.h index c02ae8baa..0470b83dc 100644 --- a/qrenderdoc/Windows/Dialogs/CaptureDialog.h +++ b/qrenderdoc/Windows/Dialogs/CaptureDialog.h @@ -57,11 +57,12 @@ class CaptureDialog : public QFrame Q_OBJECT public: - typedef std::function &env, CaptureOptions opts)> + typedef std::function &env, CaptureOptions opts, + std::function callback)> OnCaptureMethod; - typedef std::function &env, - const QString &name, CaptureOptions opts)> + typedef std::function &env, const QString &name, + CaptureOptions opts, std::function callback)> OnInjectMethod; explicit CaptureDialog(CaptureContext &ctx, OnCaptureMethod captureCallback, diff --git a/qrenderdoc/Windows/MainWindow.cpp b/qrenderdoc/Windows/MainWindow.cpp index 48a643808..a27788d3a 100644 --- a/qrenderdoc/Windows/MainWindow.cpp +++ b/qrenderdoc/Windows/MainWindow.cpp @@ -324,63 +324,91 @@ void MainWindow::LoadFromFilename(const QString &filename) } } -LiveCapture *MainWindow::OnCaptureTrigger(const QString &exe, const QString &workingDir, - const QString &cmdLine, - const QList &env, - CaptureOptions opts) +void MainWindow::OnCaptureTrigger(const QString &exe, const QString &workingDir, + const QString &cmdLine, const QList &env, + CaptureOptions opts, std::function callback) { if(!PromptCloseLog()) - return NULL; + return; - QString logfile = m_Ctx.TempLogFilename(QFileInfo(exe).baseName()); + LambdaThread *th = new LambdaThread([this, exe, workingDir, cmdLine, env, opts, callback]() { + QString logfile = m_Ctx.TempLogFilename(QFileInfo(exe).baseName()); - uint32_t ret = m_Ctx.Renderer().ExecuteAndInject(exe, workingDir, cmdLine, env, logfile, opts); + uint32_t ret = m_Ctx.Renderer().ExecuteAndInject(exe, workingDir, cmdLine, env, logfile, opts); - if(ret == 0) + GUIInvoke::call([this, exe, ret, callback]() { + if(ret == 0) + { + RDDialog::critical(this, tr("Error kicking capture"), + tr("Error launching %1 for capture.\n\nCheck diagnostic log in Help " + "menu for more details.") + .arg(exe)); + return; + } + + LiveCapture *live = new LiveCapture( + m_Ctx, m_Ctx.Renderer().remote() ? m_Ctx.Renderer().remote()->Hostname : "", ret, this, + this); + ShowLiveCapture(live); + callback(live); + }); + }); + th->start(); + // wait a few ms before popping up a progress bar + th->wait(500); + if(th->isRunning()) { - RDDialog::critical( - this, tr("Error kicking capture"), - tr("Error launching %1 for capture.\n\nCheck diagnostic log in Help menu for more details.") - .arg(exe)); - return NULL; + ShowProgressDialog(this, tr("Launching %1, please wait...").arg(exe), + [th]() { return !th->isRunning(); }); } - - LiveCapture *live = new LiveCapture( - m_Ctx, m_Ctx.Renderer().remote() ? m_Ctx.Renderer().remote()->Hostname : "", ret, this, this); - ShowLiveCapture(live); - return live; + th->deleteLater(); } -LiveCapture *MainWindow::OnInjectTrigger(uint32_t PID, const QList &env, - const QString &name, CaptureOptions opts) +void MainWindow::OnInjectTrigger(uint32_t PID, const QList &env, + const QString &name, CaptureOptions opts, + std::function callback) { if(!PromptCloseLog()) - return NULL; + return; - QString logfile = m_Ctx.TempLogFilename(name); + LambdaThread *th = new LambdaThread([this, PID, env, name, opts, callback]() { + QString logfile = m_Ctx.TempLogFilename(name); - void *envList = RENDERDOC_MakeEnvironmentModificationList(env.size()); + void *envList = RENDERDOC_MakeEnvironmentModificationList(env.size()); - for(int i = 0; i < env.size(); i++) - RENDERDOC_SetEnvironmentModification(envList, i, env[i].variable.toUtf8().data(), - env[i].value.toUtf8().data(), env[i].type, env[i].separator); + for(int i = 0; i < env.size(); i++) + RENDERDOC_SetEnvironmentModification(envList, i, env[i].variable.toUtf8().data(), + env[i].value.toUtf8().data(), env[i].type, + env[i].separator); - uint32_t ret = RENDERDOC_InjectIntoProcess(PID, envList, logfile.toUtf8().data(), &opts, false); + uint32_t ret = RENDERDOC_InjectIntoProcess(PID, envList, logfile.toUtf8().data(), &opts, false); - RENDERDOC_FreeEnvironmentModificationList(envList); + RENDERDOC_FreeEnvironmentModificationList(envList); - if(ret == 0) + GUIInvoke::call([this, PID, ret, callback]() { + if(ret == 0) + { + RDDialog::critical( + this, tr("Error kicking capture"), + tr("Error injecting into process %1 for capture.\n\nCheck diagnostic log in " + "Help menu for more details.") + .arg(PID)); + return; + } + + LiveCapture *live = new LiveCapture(m_Ctx, "", ret, this, this); + ShowLiveCapture(live); + }); + }); + th->start(); + // wait a few ms before popping up a progress bar + th->wait(500); + if(th->isRunning()) { - RDDialog::critical(this, tr("Error kicking capture"), - tr("Error injecting into process %1 for capture.\n\nCheck diagnostic log in " - "Help menu for more details.") - .arg(PID)); - return NULL; + ShowProgressDialog(this, tr("Injecting into %1, please wait...").arg(PID), + [th]() { return !th->isRunning(); }); } - - LiveCapture *live = new LiveCapture(m_Ctx, "", ret, this, this); - ShowLiveCapture(live); - return live; + th->deleteLater(); } void MainWindow::LoadLogfile(const QString &filename, bool temporary, bool local) diff --git a/qrenderdoc/Windows/MainWindow.h b/qrenderdoc/Windows/MainWindow.h index 2a53f1915..0c3ceed69 100644 --- a/qrenderdoc/Windows/MainWindow.h +++ b/qrenderdoc/Windows/MainWindow.h @@ -63,10 +63,11 @@ public: void CloseLogfile(); QString GetSavePath(); - LiveCapture *OnCaptureTrigger(const QString &exe, const QString &workingDir, const QString &cmdLine, - const QList &env, CaptureOptions opts); - LiveCapture *OnInjectTrigger(uint32_t PID, const QList &env, - const QString &name, CaptureOptions opts); + void OnCaptureTrigger(const QString &exe, const QString &workingDir, const QString &cmdLine, + const QList &env, CaptureOptions opts, + std::function callback); + void OnInjectTrigger(uint32_t PID, const QList &env, const QString &name, + CaptureOptions opts, std::function callback); void PopulateRecentFiles(); diff --git a/renderdocui/Windows/Dialogs/CaptureDialog.cs b/renderdocui/Windows/Dialogs/CaptureDialog.cs index c7d2ddcd3..91f516800 100644 --- a/renderdocui/Windows/Dialogs/CaptureDialog.cs +++ b/renderdocui/Windows/Dialogs/CaptureDialog.cs @@ -259,8 +259,10 @@ namespace renderdocui.Windows.Dialogs #region Callbacks - public delegate LiveCapture OnCaptureMethod(string exe, string workingDir, string cmdLine, EnvironmentModification[] env, CaptureOptions opts); - public delegate LiveCapture OnInjectMethod(UInt32 PID, EnvironmentModification[] env, string name, CaptureOptions opts); + public delegate void OnConnectionEstablishedMethod(LiveCapture live); + + public delegate void OnCaptureMethod(string exe, string workingDir, string cmdLine, EnvironmentModification[] env, CaptureOptions opts, OnConnectionEstablishedMethod cb); + public delegate void OnInjectMethod(UInt32 PID, EnvironmentModification[] env, string name, CaptureOptions opts, OnConnectionEstablishedMethod cb); private OnCaptureMethod m_CaptureCallback = null; private OnInjectMethod m_InjectCallback = null; @@ -430,10 +432,11 @@ namespace renderdocui.Windows.Dialogs string cmdLine = cmdline.Text; - var live = m_CaptureCallback(exe, workingDir, cmdLine, GetSettings().Environment, GetSettings().Options); - - if (queueFrameCap.Checked && live != null) - live.QueueCapture((int)queuedCapFrame.Value); + m_CaptureCallback(exe, workingDir, cmdLine, GetSettings().Environment, GetSettings().Options, (LiveCapture live) => + { + if (queueFrameCap.Checked) + live.QueueCapture((int)queuedCapFrame.Value); + }); } private void OnInject() @@ -445,10 +448,11 @@ namespace renderdocui.Windows.Dialogs string name = item.SubItems[1].Text; UInt32 PID = (UInt32)item.Tag; - var live = m_InjectCallback(PID, GetSettings().Environment, name, GetSettings().Options); - - if (queueFrameCap.Checked && live != null) - live.QueueCapture((int)queuedCapFrame.Value); + m_InjectCallback(PID, GetSettings().Environment, name, GetSettings().Options, (LiveCapture live) => + { + if (queueFrameCap.Checked) + live.QueueCapture((int)queuedCapFrame.Value); + }); } } diff --git a/renderdocui/Windows/MainWindow.cs b/renderdocui/Windows/MainWindow.cs index 8245a8d26..16cc7628d 100644 --- a/renderdocui/Windows/MainWindow.cs +++ b/renderdocui/Windows/MainWindow.cs @@ -1019,48 +1019,90 @@ namespace renderdocui.Windows return ""; } - private LiveCapture OnCaptureTrigger(string exe, string workingDir, string cmdLine, EnvironmentModification[] env, CaptureOptions opts) + private void OnCaptureTrigger(string exe, string workingDir, string cmdLine, EnvironmentModification[] env, CaptureOptions opts, Dialogs.CaptureDialog.OnConnectionEstablishedMethod callback) { if (!PromptCloseLog()) - return null; + return; string logfile = m_Core.TempLogFilename(Path.GetFileNameWithoutExtension(exe)); StaticExports.SetConfigSetting("MaxConnectTimeout", m_Core.Config.MaxConnectTimeout.ToString()); - UInt32 ret = m_Core.Renderer.ExecuteAndInject(exe, workingDir, cmdLine, env, logfile, opts); - - if (ret == 0) + Thread th = Helpers.NewThread(new ThreadStart(() => { - MessageBox.Show(string.Format("Error launching {0} for capture.\n\nCheck diagnostic log in Help menu for more details.", exe), - "Error kicking capture", MessageBoxButtons.OK, MessageBoxIcon.Error); - return null; - } + UInt32 ret = m_Core.Renderer.ExecuteAndInject(exe, workingDir, cmdLine, env, logfile, opts); - var live = new LiveCapture(m_Core, m_Core.Renderer.Remote == null ? "" : m_Core.Renderer.Remote.Hostname, ret, this); - ShowLiveCapture(live); - return live; + this.BeginInvoke(new Action(() => + { + if (ret == 0) + { + MessageBox.Show(string.Format("Error launching {0} for capture.\n\nCheck diagnostic log in Help menu for more details.", exe), + "Error kicking capture", MessageBoxButtons.OK, MessageBoxIcon.Error); + return; + } + + var live = new LiveCapture(m_Core, m_Core.Renderer.Remote == null ? "" : m_Core.Renderer.Remote.Hostname, ret, this); + ShowLiveCapture(live); + callback(live); + })); + })); + th.Start(); + + // wait a few ms before popping up a progress bar + th.Join(500); + + if (th.IsAlive) + { + ProgressPopup modal = new ProgressPopup((ModalCloseCallback)delegate + { + return !th.IsAlive; + }, false); + modal.SetModalText(String.Format("Launching {0}, please wait...", exe)); + + modal.ShowDialog(); + } } - private LiveCapture OnInjectTrigger(UInt32 PID, EnvironmentModification[] env, string name, CaptureOptions opts) + private void OnInjectTrigger(UInt32 PID, EnvironmentModification[] env, string name, CaptureOptions opts, Dialogs.CaptureDialog.OnConnectionEstablishedMethod callback) { if (!PromptCloseLog()) - return null; + return; string logfile = m_Core.TempLogFilename(name); - UInt32 ret = StaticExports.InjectIntoProcess(PID, env, logfile, opts); - - if (ret == 0) + Thread th = Helpers.NewThread(new ThreadStart(() => { - MessageBox.Show(string.Format("Error injecting into process {0} for capture.\n\nCheck diagnostic log in Help menu for more details.", PID), - "Error kicking capture", MessageBoxButtons.OK, MessageBoxIcon.Error); - return null; - } + UInt32 ret = StaticExports.InjectIntoProcess(PID, env, logfile, opts); - var live = new LiveCapture(m_Core, "", ret, this); - ShowLiveCapture(live); - return live; + this.BeginInvoke(new Action(() => + { + if (ret == 0) + { + MessageBox.Show(string.Format("Error injecting into process {0} for capture.\n\nCheck diagnostic log in Help menu for more details.", PID), + "Error kicking capture", MessageBoxButtons.OK, MessageBoxIcon.Error); + return; + } + + var live = new LiveCapture(m_Core, m_Core.Renderer.Remote == null ? "" : m_Core.Renderer.Remote.Hostname, ret, this); + ShowLiveCapture(live); + callback(live); + })); + })); + th.Start(); + + // wait a few ms before popping up a progress bar + th.Join(500); + + if (th.IsAlive) + { + ProgressPopup modal = new ProgressPopup((ModalCloseCallback)delegate + { + return !th.IsAlive; + }, false); + modal.SetModalText(String.Format("Injecting into {0}, please wait...", PID)); + + modal.ShowDialog(); + } } private void captureLogToolStripMenuItem_Click(object sender, EventArgs e)