diff --git a/qrenderdoc/Code/RenderManager.cpp b/qrenderdoc/Code/RenderManager.cpp index 890237304..70a6ca542 100644 --- a/qrenderdoc/Code/RenderManager.cpp +++ b/qrenderdoc/Code/RenderManager.cpp @@ -122,6 +122,25 @@ void RenderManager::CloseThread() m_Thread = NULL; } +uint32_t RenderManager::ExecuteAndInject(const QString &exe, const QString &workingDir, + const QString &cmdLine, + const QList &env, + const QString &logfile, CaptureOptions opts) +{ + // if (m_Remote == null) + { + // TODO env + return RENDERDOC_ExecuteAndInject(exe.toUtf8().data(), workingDir.toUtf8().data(), + cmdLine.toUtf8().data(), NULL, logfile.toUtf8().data(), &opts, + false); + } + /* + else + { + } + */ +} + void RenderManager::PushInvoke(RenderManager::InvokeHandle *cmd) { if(m_Thread == NULL || !m_Thread->isRunning() || !m_Running) diff --git a/qrenderdoc/Code/RenderManager.h b/qrenderdoc/Code/RenderManager.h index 3bfcdbeeb..9caa28b92 100644 --- a/qrenderdoc/Code/RenderManager.h +++ b/qrenderdoc/Code/RenderManager.h @@ -29,6 +29,7 @@ #include #include #include +#include #include #include #include "renderdoc_replay.h" @@ -40,6 +41,61 @@ class LambdaThread; #define INVOKE_MEMFN(function) \ m_Ctx->Renderer()->AsyncInvoke([this](IReplayRenderer *r) { function(r); }); +struct EnvironmentModification +{ + QString variable; + QString value; + + EnvironmentModificationType type; + EnvironmentSeparator separator; + + QString GetTypeString() const + { + QString ret; + + if(type == eEnvMod_Append) + ret = QString("Append, %1").arg("TODO"); + else if(type == eEnvMod_Prepend) + ret = QString("Prepend, %1").arg("TODO"); + else + ret = "Set"; + + return ret; + } + + QString GetDescription() const + { + QString ret; + + if(type == eEnvMod_Append) + ret = QString("Append %1 with %2 using %3").arg(variable).arg(value).arg("TODO"); + else if(type == eEnvMod_Prepend) + ret = QString("Prepend %1 with %2 using %3").arg(variable).arg(value).arg("TODO"); + else + ret = QString("Set %1 to %2").arg(variable).arg(value); + + return ret; + } + + QVariantMap toJSON() const + { + QVariantMap ret; + ret["variable"] = variable; + ret["value"] = value; + ret["type"] = "Append"; // TODO + ret["separator"] = "Semi-colon"; // TODO + return ret; + } + + void fromJSON(const QVariantMap &data) + { + variable = data["variable"].toString(); + value = data["value"].toString(); + type = eEnvMod_Append; // TODO + separator = eEnvSep_SemiColon; // TODO + } +}; + class RenderManager { public: @@ -58,6 +114,10 @@ public: void CloseThread(); + uint32_t ExecuteAndInject(const QString &exe, const QString &workingDir, const QString &cmdLine, + const QList &env, const QString &logfile, + CaptureOptions opts); + private: struct InvokeHandle { diff --git a/qrenderdoc/Windows/Dialogs/CaptureDialog.cpp b/qrenderdoc/Windows/Dialogs/CaptureDialog.cpp new file mode 100644 index 000000000..3e4804b70 --- /dev/null +++ b/qrenderdoc/Windows/Dialogs/CaptureDialog.cpp @@ -0,0 +1,619 @@ +/****************************************************************************** + * The MIT License (MIT) + * + * Copyright (c) 2016 Baldur Karlsson + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + ******************************************************************************/ + +#include "CaptureDialog.h" +#include +#include +#include "Code/QRDUtils.h" +#include "FlowLayout.h" +#include "ui_CaptureDialog.h" + +#define JSON_ID "rdocCaptureSettings" +#define JSON_VER 1 + +Q_DECLARE_METATYPE(CaptureSettings); + +CaptureSettings::CaptureSettings() +{ + Inject = false; + AutoStart = false; + RENDERDOC_GetDefaultCaptureOptions(&Options); +} + +QVariantMap CaptureSettings::toJSON() const +{ + QVariantMap ret; + + ret["AutoStart"] = AutoStart; + + ret["Executable"] = Executable; + ret["WorkingDir"] = WorkingDir; + ret["CmdLine"] = CmdLine; + + QVariantList env; + for(int i = 0; i < Environment.size(); i++) + env.push_back(Environment[i].toJSON()); + ret["Environment"] = env; + + QVariantMap opts; + opts["AllowVSync"] = Options.AllowVSync; + opts["AllowFullscreen"] = Options.AllowFullscreen; + opts["APIValidation"] = Options.APIValidation; + opts["CaptureCallstacks"] = Options.CaptureCallstacks; + opts["CaptureCallstacksOnlyDraws"] = Options.CaptureCallstacksOnlyDraws; + opts["DelayForDebugger"] = Options.DelayForDebugger; + opts["VerifyMapWrites"] = Options.VerifyMapWrites; + opts["HookIntoChildren"] = Options.HookIntoChildren; + opts["RefAllResources"] = Options.RefAllResources; + opts["SaveAllInitials"] = Options.SaveAllInitials; + opts["CaptureAllCmdLists"] = Options.CaptureAllCmdLists; + opts["DebugOutputMute"] = Options.DebugOutputMute; + ret["Options"] = opts; + + return ret; +} + +void CaptureSettings::fromJSON(const QVariantMap &data) +{ + AutoStart = data["AutoStart"].toBool(); + + Executable = data["Executable"].toString(); + WorkingDir = data["WorkingDir"].toString(); + CmdLine = data["CmdLine"].toString(); + + QVariantList env = data["Environment"].toList(); + for(int i = 0; i < env.size(); i++) + { + EnvironmentModification e; + e.fromJSON(env[i].value()); + Environment.push_back(e); + } + + QVariantMap opts = data["Options"].toMap(); + + Options.AllowVSync = opts["AllowVSync"].toBool(); + Options.AllowFullscreen = opts["AllowFullscreen"].toBool(); + Options.APIValidation = opts["APIValidation"].toBool(); + Options.CaptureCallstacks = opts["CaptureCallstacks"].toBool(); + Options.CaptureCallstacksOnlyDraws = opts["CaptureCallstacksOnlyDraws"].toBool(); + Options.DelayForDebugger = opts["DelayForDebugger"].toUInt(); + Options.VerifyMapWrites = opts["VerifyMapWrites"].toBool(); + Options.HookIntoChildren = opts["HookIntoChildren"].toBool(); + Options.RefAllResources = opts["RefAllResources"].toBool(); + Options.SaveAllInitials = opts["SaveAllInitials"].toBool(); + Options.CaptureAllCmdLists = opts["CaptureAllCmdLists"].toBool(); + Options.DebugOutputMute = opts["DebugOutputMute"].toBool(); +} + +CaptureDialog::CaptureDialog(CaptureContext *ctx, OnCaptureMethod captureCallback, + OnInjectMethod injectCallback, QWidget *parent) + : QFrame(parent), ui(new Ui::CaptureDialog), m_Ctx(ctx) +{ + ui->setupUi(this); + + // setup FlowLayout for options group + { + QLayout *oldLayout = ui->optionsGroup->layout(); + + QObjectList options = ui->optionsGroup->children(); + options.removeOne((QObject *)oldLayout); + + delete oldLayout; + + FlowLayout *optionsFlow = new FlowLayout(ui->optionsGroup, -1, 3, 3); + + for(QObject *o : options) + optionsFlow->addWidget(qobject_cast(o)); + + ui->optionsGroup->setLayout(optionsFlow); + } + + ui->envVar->setEnabled(false); + + m_ProcessModel = new QStandardItemModel(0, 3, this); + + m_ProcessModel->setHeaderData(0, Qt::Horizontal, tr("Name")); + m_ProcessModel->setHeaderData(1, Qt::Horizontal, tr("PID")); + m_ProcessModel->setHeaderData(2, Qt::Horizontal, tr("Window Title")); + + QSortFilterProxyModel *proxy = new QSortFilterProxyModel(this); + + proxy->setSourceModel(m_ProcessModel); + // filter on all columns + proxy->setFilterKeyColumn(-1); + // allow updating the underlying model + proxy->setDynamicSortFilter(true); + + ui->processList->setModel(proxy); + ui->processList->setAlternatingRowColors(true); + + // sort by PID by default + ui->processList->sortByColumn(1, Qt::AscendingOrder); + + // TODO + ui->vulkanLayerWarn->setVisible(true); + + m_CaptureCallback = captureCallback; + m_InjectCallback = injectCallback; + + setSettings(CaptureSettings()); + + updateGlobalHook(); +} + +CaptureDialog::~CaptureDialog() +{ + delete ui; +} + +void CaptureDialog::setInjectMode(bool inject) +{ + m_Inject = inject; + + if(inject) + { + ui->injectGroup->setVisible(true); + ui->exeGroup->setVisible(false); + ui->topVerticalSpacer->spacerItem()->changeSize(0, 0, QSizePolicy::Minimum, QSizePolicy::Minimum); + ui->verticalLayout->invalidate(); + + ui->globalGroup->setVisible(false); + + fillProcessList(); + + ui->capture->setText("Inject"); + this->setWindowTitle("Inject into Process"); + } + else + { + ui->injectGroup->setVisible(false); + ui->exeGroup->setVisible(true); + ui->topVerticalSpacer->spacerItem()->changeSize(0, 0, QSizePolicy::Minimum, + QSizePolicy::Expanding); + ui->verticalLayout->invalidate(); + + ui->globalGroup->setVisible(m_Ctx->Config.AllowGlobalHook); + + ui->capture->setText("Capture"); + this->setWindowTitle("Capture Executable"); + } +} + +void CaptureDialog::on_CaptureCallstacks_toggled(bool checked) +{ + if(ui->CaptureCallstacks->isChecked()) + { + ui->CaptureCallstacksOnlyDraws->setEnabled(true); + } + else + { + ui->CaptureCallstacksOnlyDraws->setChecked(false); + ui->CaptureCallstacksOnlyDraws->setEnabled(false); + } +} + +void CaptureDialog::on_processFilter_textChanged(const QString &filter) +{ + QSortFilterProxyModel *model = (QSortFilterProxyModel *)ui->processList->model(); + + if(model == NULL) + return; + + model->setFilterFixedString(filter); +} + +void CaptureDialog::on_exePath_textChanged(const QString &exe) +{ + QFileInfo f(exe); + QDir dir = f.dir(); + bool valid = dir.makeAbsolute(); + + if(valid && f.isAbsolute()) + ui->workDirPath->setPlaceholderText(QDir::toNativeSeparators(dir.absolutePath())); + else if(exe == "") + ui->workDirPath->setPlaceholderText(""); + + updateGlobalHook(); +} + +void CaptureDialog::on_vulkanCapture_clicked() +{ + // TODO +} + +void CaptureDialog::on_processRefesh_clicked() +{ + fillProcessList(); +} + +void CaptureDialog::on_exePathBrowse_clicked() +{ + QString initDir = ""; + QString file = ""; + + QFileInfo f(ui->exePath->text()); + QDir dir = f.dir(); + if(ui->exePath->text() != "" && f.isAbsolute() && dir.exists()) + { + initDir = dir.absolutePath(); + } + else if(m_Ctx->Config.LastCapturePath != "") + { + initDir = m_Ctx->Config.LastCapturePath; + if(m_Ctx->Config.LastCaptureExe != "") + file = m_Ctx->Config.LastCaptureExe; + } + + // if(m_Core.Renderer.Remote == null) + { + QString filename = RDDialog::getOpenFileName(this, tr("Choose executable"), initDir, + "Executable files (*.exe);;All files (*.*)"); + + if(filename != "") + setExecutableFilename(filename); + } + /* + else + { + VirtualOpenFileDialog remoteBrowser = new VirtualOpenFileDialog(m_Core.Renderer); + remoteBrowser.Opened += new EventHandler(this.virtualExeBrowser_Opened); + + remoteBrowser.ShowDialog(this); + } + */ +} + +void CaptureDialog::on_workDirBrowse_clicked() +{ + QString initDir = ""; + + if(QDir(ui->workDirPath->text()).exists()) + { + initDir = ui->workDirPath->text(); + } + else + { + QDir dir = QFileInfo(ui->exePath->text()).dir(); + if(dir.exists()) + initDir = dir.absolutePath(); + else if(m_Ctx->Config.LastCapturePath != "") + initDir = m_Ctx->Config.LastCapturePath; + } + + // if(m_Core.Renderer.Remote == null) + { + QString dir = RDDialog::getExistingDirectory(this, "Choose working directory", initDir); + + if(dir != "") + ui->workDirPath->setText(dir); + } + /* + else + { + VirtualOpenFileDialog remoteBrowser = new VirtualOpenFileDialog(m_Core.Renderer); + remoteBrowser.Opened += new + EventHandler(this.virtualWorkDirBrowser_Opened); + + remoteBrowser.DirectoryBrowse = true; + + remoteBrowser.ShowDialog(this); + } + */ +} + +void CaptureDialog::on_envVarEdit_clicked() +{ + // TODO +} + +void CaptureDialog::on_toggleGlobal_clicked() +{ + if(ui->toggleGlobal->isEnabled()) + { + ui->toggleGlobal->setChecked(!ui->toggleGlobal->isChecked()); + + updateGlobalHook(); + } +} + +void CaptureDialog::on_saveSettings_clicked() +{ + QString filename = RDDialog::getSaveFileName(this, tr("Save Settings As"), QString(), + "Capture settings (*.cap)"); + + if(filename != "") + { + QDir dirinfo = QFileInfo(filename).dir(); + if(dirinfo.exists()) + { + saveSettings(filename); + PersistantConfig::AddRecentFile(m_Ctx->Config.RecentCaptureSettings, filename, 10); + } + } +} + +void CaptureDialog::on_loadSettings_clicked() +{ + QString filename = + RDDialog::getOpenFileName(this, tr("Open Settings"), QString(), "Capture settings (*.cap)"); + + if(filename != "" && QFileInfo::exists(filename)) + { + loadSettings(filename); + PersistantConfig::AddRecentFile(m_Ctx->Config.RecentCaptureSettings, filename, 10); + } +} + +void CaptureDialog::on_capture_clicked() +{ + triggerCapture(); +} + +void CaptureDialog::on_close_clicked() +{ + this->close(); +} + +void CaptureDialog::setSettings(CaptureSettings settings) +{ + setInjectMode(settings.Inject); + + ui->exePath->setText(settings.Executable); + ui->workDirPath->setText(settings.WorkingDir); + ui->cmdline->setText(settings.CmdLine); + + setEnvironmentModifications(settings.Environment); + + ui->AllowFullscreen->setChecked(settings.Options.AllowFullscreen); + ui->AllowVSync->setChecked(settings.Options.AllowVSync); + ui->HookIntoChildren->setChecked(settings.Options.HookIntoChildren); + ui->CaptureCallstacks->setChecked(settings.Options.CaptureCallstacks); + ui->CaptureCallstacksOnlyDraws->setChecked(settings.Options.CaptureCallstacksOnlyDraws); + ui->APIValidation->setChecked(settings.Options.APIValidation); + ui->RefAllResources->setChecked(settings.Options.RefAllResources); + ui->SaveAllInitials->setChecked(settings.Options.SaveAllInitials); + ui->DelayForDebugger->setValue(settings.Options.DelayForDebugger); + ui->VerifyMapWrites->setChecked(settings.Options.VerifyMapWrites); + ui->AutoStart->setChecked(settings.AutoStart); + + if(settings.AutoStart) + { + triggerCapture(); + } +} + +CaptureSettings CaptureDialog::settings() +{ + CaptureSettings ret; + + ret.Inject = injectMode(); + + ret.AutoStart = ui->AutoStart->isChecked(); + + ret.Executable = ui->exePath->text(); + ret.WorkingDir = ui->workDirPath->text(); + ret.CmdLine = ui->cmdline->text(); + + ret.Environment = m_EnvModifications; + + ret.Options.AllowFullscreen = ui->AllowFullscreen->isChecked(); + ret.Options.AllowVSync = ui->AllowVSync->isChecked(); + ret.Options.HookIntoChildren = ui->HookIntoChildren->isChecked(); + ret.Options.CaptureCallstacks = ui->CaptureCallstacks->isChecked(); + ret.Options.CaptureCallstacksOnlyDraws = ui->CaptureCallstacksOnlyDraws->isChecked(); + ret.Options.APIValidation = ui->APIValidation->isChecked(); + ret.Options.RefAllResources = ui->RefAllResources->isChecked(); + ret.Options.SaveAllInitials = ui->SaveAllInitials->isChecked(); + ret.Options.CaptureAllCmdLists = ui->CaptureAllCmdLists->isChecked(); + ret.Options.DelayForDebugger = (uint32_t)ui->DelayForDebugger->value(); + ret.Options.VerifyMapWrites = ui->VerifyMapWrites->isChecked(); + + return ret; +} + +void CaptureDialog::saveSettings(QString filename) +{ + QFile f(filename); + if(f.open(QIODevice::WriteOnly | QIODevice::Truncate | QIODevice::Text)) + { + QVariantMap values; + values["settings"] = settings().toJSON(); + SaveToJSON(values, f, JSON_ID, JSON_VER); + } + else + { + RDDialog::critical(this, "Error saving config", + tr("Couldn't open path %1 for write.").arg(filename)); + } +} + +void CaptureDialog::fillProcessList() +{ + m_ProcessModel->removeRows(0, m_ProcessModel->rowCount()); + + // no way of listing processes in Qt, fill with dummy data + m_ProcessModel->insertRows(0, 5); + +#define ROW(n, name, pid, title) \ + m_ProcessModel->setData(m_ProcessModel->index(n, 0), name); \ + m_ProcessModel->setData(m_ProcessModel->index(n, 1), pid); \ + m_ProcessModel->setData(m_ProcessModel->index(n, 2), title); + + ROW(0, "foo.exe", 123, "Foo Window"); + ROW(1, "magic.exe", 456, "Magic Window"); + ROW(2, "system", 999, "Scary System process"); + ROW(3, "chrome.exe", 4539, "Chrome - renderdoc.org"); + ROW(4, "firefox.exe", 8483, "Firefox - renderdoc.org"); +} + +void CaptureDialog::setExecutableFilename(QString filename) +{ + filename = QDir::toNativeSeparators(QFileInfo(filename).absoluteFilePath()); + ui->exePath->setText(filename); + + m_Ctx->Config.LastCapturePath = QFileInfo(filename).absolutePath(); + m_Ctx->Config.LastCaptureExe = QFileInfo(filename).completeBaseName(); +} + +void CaptureDialog::loadSettings(QString filename) +{ + QFile f(filename); + if(f.open(QIODevice::ReadOnly | QIODevice::Text)) + { + QVariantMap values; + + bool success = LoadFromJSON(values, f, JSON_ID, JSON_VER); + + if(success) + { + CaptureSettings settings; + settings.fromJSON(values["settings"].value()); + setSettings(settings); + } + else + { + RDDialog::critical(this, "Error loading config", + tr("Couldn't interpret settings in %1.").arg(filename)); + } + } + else + { + RDDialog::critical(this, "Error loading config", tr("Couldn't open path %1.").arg(filename)); + } +} + +void CaptureDialog::closeEvent(QCloseEvent *event) +{ + if(ui->toggleGlobal->isChecked()) + { + ui->toggleGlobal->setChecked(false); + + updateGlobalHook(); + } +} + +void CaptureDialog::updateGlobalHook() +{ + ui->globalGroup->setVisible(!injectMode() && m_Ctx->Config.AllowGlobalHook); + + if(ui->exePath->text().length() >= 4) + { + ui->toggleGlobal->setEnabled(true); + QString text = tr("Global hooking is risky!\nBe sure you know what you're doing."); + + if(ui->toggleGlobal->isChecked()) + text += tr("\nEmergency restore @ %TEMP%\\RenderDoc_RestoreGlobalHook.reg"); + + ui->globalLabel->setText(text); + } + else + { + ui->toggleGlobal->setEnabled(false); + ui->globalLabel->setText(tr("Global hooking requires an executable path, or filename")); + } +} + +void CaptureDialog::setEnvironmentModifications(const QList &modifications) +{ + m_EnvModifications = modifications; + + QString envModText = ""; + + for(const EnvironmentModification &mod : modifications) + { + if(envModText != "") + envModText += ", "; + + envModText += mod.GetDescription(); + } + + ui->envVar->setText(envModText); +} + +void CaptureDialog::triggerCapture() +{ + if(injectMode()) + { + QModelIndexList sel = ui->processList->selectionModel()->selectedRows(); + if(sel.size() == 1) + { + QModelIndex item = sel[0]; + + QSortFilterProxyModel *model = (QSortFilterProxyModel *)ui->processList->model(); + + item = model->mapToSource(item); + + 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(); + + // var live = ; + m_InjectCallback(PID, settings().Environment, name, settings().Options); + + /* + if(queueFrameCap.Checked && live != null) + live.QueueCapture((int)queuedCapFrame.Value); + */ + } + } + else + { + QString exe = ui->exePath->text(); + + // for non-remote captures, check the executable locally + // if(m_Core.Renderer.Remote == null) + { + if(!QFileInfo::exists(exe)) + { + RDDialog::critical(this, tr("Invalid executable"), + tr("Invalid application executable: %1").arg(exe)); + return; + } + } + + QString workingDir = ""; + + // for non-remote captures, check the directory locally + // if(m_Core.Renderer.Remote == null) + { + if(QDir(ui->workDirPath->text()).exists()) + workingDir = ui->workDirPath->text(); + } + /* + else + { + workingDir = RealWorkDir; + } + */ + + QString cmdLine = ui->cmdline->text(); + + // var live = + m_CaptureCallback(exe, workingDir, cmdLine, settings().Environment, settings().Options); + + /* + if(queueFrameCap.Checked && live != null) + live.QueueCapture((int)queuedCapFrame.Value); + */ + } +} diff --git a/qrenderdoc/Windows/Dialogs/CaptureDialog.h b/qrenderdoc/Windows/Dialogs/CaptureDialog.h new file mode 100644 index 000000000..d46176bd0 --- /dev/null +++ b/qrenderdoc/Windows/Dialogs/CaptureDialog.h @@ -0,0 +1,121 @@ +/****************************************************************************** + * The MIT License (MIT) + * + * Copyright (c) 2016 Baldur Karlsson + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + ******************************************************************************/ + +#pragma once + +#include +#include +#include "Code/CaptureContext.h" + +namespace Ui +{ +class CaptureDialog; +} + +class QStandardItemModel; + +struct CaptureSettings +{ + CaptureSettings(); + + CaptureOptions Options; + bool Inject; + bool AutoStart; + QString Executable; + QString WorkingDir; + QString CmdLine; + QList Environment; + + QVariantMap toJSON() const; + void fromJSON(const QVariantMap &data); +}; + +class CaptureDialog : public QFrame +{ + Q_OBJECT + +public: + typedef std::function &env, CaptureOptions opts)> + OnCaptureMethod; + typedef std::function &env, + const QString &name, CaptureOptions opts)> + OnInjectMethod; + + explicit CaptureDialog(CaptureContext *ctx, OnCaptureMethod captureCallback, + OnInjectMethod injectCallback, QWidget *parent = 0); + ~CaptureDialog(); + + bool injectMode() { return m_Inject; } + void setInjectMode(bool inject); + + void setExecutableFilename(QString filename); + void loadSettings(QString filename); + +private slots: + // automatic slots + void on_exePathBrowse_clicked(); + void on_exePath_textChanged(const QString &arg1); + void on_workDirBrowse_clicked(); + void on_envVarEdit_clicked(); + + void on_processFilter_textChanged(const QString &arg1); + void on_processRefesh_clicked(); + + void on_saveSettings_clicked(); + void on_loadSettings_clicked(); + + void on_capture_clicked(); + void on_close_clicked(); + + void on_toggleGlobal_clicked(); + + void on_vulkanCapture_clicked(); + + void on_CaptureCallstacks_toggled(bool checked); + +private: + void closeEvent(QCloseEvent *event) override; + + Ui::CaptureDialog *ui; + CaptureContext *m_Ctx; + + QStandardItemModel *m_ProcessModel; + + OnCaptureMethod m_CaptureCallback; + OnInjectMethod m_InjectCallback; + + void updateGlobalHook(); + void setEnvironmentModifications(const QList &modifications); + void triggerCapture(); + + void setSettings(CaptureSettings settings); + CaptureSettings settings(); + + void saveSettings(QString filename); + + QList m_EnvModifications; + bool m_Inject; + void fillProcessList(); +}; diff --git a/qrenderdoc/Windows/Dialogs/CaptureDialog.ui b/qrenderdoc/Windows/Dialogs/CaptureDialog.ui new file mode 100644 index 000000000..18aff67f3 --- /dev/null +++ b/qrenderdoc/Windows/Dialogs/CaptureDialog.ui @@ -0,0 +1,926 @@ + + + CaptureDialog + + + + 0 + 0 + 270 + 939 + + + + Capture Executable + + + + 4 + + + 6 + + + 6 + + + 6 + + + 6 + + + + + + 0 + 0 + + + + + 250 + 130 + + + + Program + + + + 4 + + + + + + 0 + 20 + + + + + 16777215 + 20 + + + + + + + + ... + + + + + + + Working Directory + + + + + + + ... + + + + + + + Command-line Arguments + + + + + + + Environment Variables + + + + + + + ... + + + + + + + Executable Path + + + + + + + + 0 + 20 + + + + + 16777215 + 20 + + + + + + + + + 0 + 20 + + + + + 16777215 + 20 + + + + + + + + + 16777215 + 20 + + + + true + + + + + + + + + + + 0 + 100 + + + + + 250 + 260 + + + + Process + + + + + + Refresh + + + + + + + + + + + + 255 + 0 + 0 + + + + + + + + + 255 + 0 + 0 + + + + + + + + + 106 + 104 + 100 + + + + + + + + + 18 + + + + NOTE: Injecting only works when the process has not used the target API + + + true + + + + + + + Filter process list by PID or name + + + + + + + + 0 + 0 + + + + + 0 + 100 + + + + QAbstractItemView::NoEditTriggers + + + true + + + 0 + + + false + + + false + + + true + + + + + + + + + + + 0 + 0 + + + + + 0 + 36 + + + + + + + + + 255 + 255 + 220 + + + + + + + + + 255 + 255 + 220 + + + + + + + + + 255 + 255 + 220 + + + + + + + + PointingHandCursor + + + true + + + Warning: Vulkan capture is not configured. +Click here to set up Vulkan capture. + + + + :/Resources/information.png:/Resources/information.png + + + QToolButton::DelayedPopup + + + Qt::ToolButtonTextBesideIcon + + + true + + + Qt::NoArrow + + + + + + + + 0 + 0 + + + + Capture Options + + + + + + + 150 + 0 + + + + + 150 + 16777215 + + + + Allow Fullscreen + + + + + + + + 150 + 0 + + + + + 150 + 16777215 + + + + Allow VSync + + + + + + + + 0 + 0 + + + + + 150 + 0 + + + + + 150 + 16777215 + + + + QFrame::NoFrame + + + QFrame::Plain + + + + 4 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + 0 + 0 + + + + + 40 + 0 + + + + secs + + + 0 + + + 120.000000000000000 + + + 0.000000000000000 + + + + + + + + 0 + 0 + + + + Debugger Delay + + + + + + + + + + + 150 + 0 + + + + + 150 + 16777215 + + + + Collect Callstacks + + + + + + + + 150 + 0 + + + + + 150 + 16777215 + + + + Only Drawcall stacks + + + + + + + + 150 + 0 + + + + + 150 + 16777215 + + + + Enable API Validation + + + + + + + + 150 + 0 + + + + + 150 + 16777215 + + + + Hook into Children + + + + + + + + 150 + 0 + + + + + 150 + 16777215 + + + + Save all Initials + + + + + + + + 150 + 0 + + + + + 150 + 16777215 + + + + Ref all Resources + + + + + + + + 150 + 0 + + + + + 150 + 16777215 + + + + Capture all Cmd Lists + + + + + + + + 150 + 0 + + + + + 150 + 16777215 + + + + Verify Map() Writes + + + + + + + + 150 + 0 + + + + + 150 + 16777215 + + + + Auto Start + + + + + + + + + + + 0 + 0 + + + + Actions + + + + + + Queue Capture + + + + + + + + 0 + 0 + + + + Frame + + + + + + 0 + + + + + + + Qt::Horizontal + + + + 596 + 20 + + + + + + + + + + + + 0 + 0 + + + + Global Process Hook + + + + + + + + + + + 255 + 0 + 0 + + + + + + + + + 255 + 0 + 0 + + + + + + + + + 106 + 104 + 100 + + + + + + + + Text Set by Code + + + + + + + Enable Global Hook + + + true + + + false + + + + + + + Qt::Horizontal + + + + 587 + 20 + + + + + + + + + + + + 0 + 0 + + + + QFrame::NoFrame + + + QFrame::Plain + + + + + + + 50 + 16777215 + + + + Save + + + + + + + + 0 + 0 + + + + + 50 + 16777215 + + + + Load + + + + + + + Qt::Horizontal + + + QSizePolicy::Expanding + + + + 556 + 20 + + + + + + + + + 60 + 16777215 + + + + Capture + + + + + + + + 50 + 16777215 + + + + Close + + + + + + + + + + Qt::Vertical + + + + 0 + 0 + + + + + + + + + + + diff --git a/qrenderdoc/Windows/MainWindow.cpp b/qrenderdoc/Windows/MainWindow.cpp index f5e9fab57..3c0376eb8 100644 --- a/qrenderdoc/Windows/MainWindow.cpp +++ b/qrenderdoc/Windows/MainWindow.cpp @@ -30,6 +30,7 @@ #include "Code/CaptureContext.h" #include "Code/QRDUtils.h" #include "Windows/Dialogs/AboutDialog.h" +#include "Windows/Dialogs/CaptureDialog.h" #include "EventBrowser.h" #include "TextureViewer.h" #include "ui_MainWindow.h" @@ -110,6 +111,11 @@ MainWindow::MainWindow(CaptureContext *ctx) : QMainWindow(NULL), ui(new Ui::Main eventBrowser->setObjectName("eventBrowser"); return eventBrowser; } + else if(objectName == "capDialog") + { + CaptureDialog *capDialog = this->createCaptureDialog(); + return capDialog; + } return NULL; }); @@ -131,6 +137,12 @@ MainWindow::MainWindow(CaptureContext *ctx) : QMainWindow(NULL), ui(new Ui::Main textureViewer, ToolWindowManager::AreaReference(ToolWindowManager::RightOf, ui->toolWindowManager->areaOf(eventBrowser), 0.75f)); + + CaptureDialog *capDialog = createCaptureDialog(); + + ui->toolWindowManager->addToolWindow( + capDialog, ToolWindowManager::AreaReference(ToolWindowManager::AddTo, + ui->toolWindowManager->areaOf(textureViewer))); } } @@ -173,17 +185,17 @@ void MainWindow::LoadFromFilename(const QString &filename) QFileInfo path(filename); QString ext = path.suffix().toLower(); - if(ext == ".rdc") + if(ext == "rdc") { LoadLogfile(filename, false, true); } - else if(ext == ".cap") + else if(ext == "cap") { - // OpenCaptureConfigFile(filename, false); + OpenCaptureConfigFile(filename, false); } - else if(ext == ".exe") + else if(ext == "exe") { - // OpenCaptureConfigFile(filename, true); + OpenCaptureConfigFile(filename, true); } else { @@ -192,6 +204,59 @@ void MainWindow::LoadFromFilename(const QString &filename) } } +void MainWindow::OnCaptureTrigger(const QString &exe, const QString &workingDir, + const QString &cmdLine, const QList &env, + CaptureOptions opts) +{ + if(!PromptCloseLog()) + return; + + QString logfile = m_Ctx->TempLogFilename(QFileInfo(exe).baseName()); + + uint32_t ret = m_Ctx->Renderer()->ExecuteAndInject(exe, workingDir, cmdLine, env, logfile, opts); + + 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; + } + + /* + var live = new LiveCapture(m_Core, m_Core.Renderer.Remote == null ? "" : + m_Core.Renderer.Remote.Hostname, ret, this); + ShowLiveCapture(live); + return live;*/ +} + +void MainWindow::OnInjectTrigger(uint32_t PID, const QList &env, + const QString &name, CaptureOptions opts) +{ + if(!PromptCloseLog()) + return; + + QString logfile = m_Ctx->TempLogFilename(name); + + // TODO - env + uint32_t ret = RENDERDOC_InjectIntoProcess(PID, NULL, logfile.toUtf8().data(), &opts, false); + + 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; + } + + /* + var live = new LiveCapture(m_Core, "", ret, this); + ShowLiveCapture(live); + return live;*/ +} + void MainWindow::LoadLogfile(const QString &filename, bool temporary, bool local) { if(PromptCloseLog()) @@ -352,13 +417,25 @@ void MainWindow::LoadLogfile(const QString &filename, bool temporary, bool local if(!remoteReplay) { - m_Ctx->Config.LastLogPath = QFileInfo(filename).dir().absolutePath(); + m_Ctx->Config.LastLogPath = QFileInfo(filename).absolutePath(); } statusText->setText(tr("Loading ") + origFilename + "..."); } } +void MainWindow::OpenCaptureConfigFile(const QString &filename, bool exe) +{ + CaptureDialog *capDialog = createCaptureDialog(); + + if(exe) + capDialog->setExecutableFilename(filename); + else + capDialog->loadSettings(filename); + + ui->toolWindowManager->addToolWindow(capDialog, ToolWindowManager::EmptySpace); +} + QString MainWindow::GetSavePath() { QString dir; @@ -490,6 +567,19 @@ void MainWindow::CloseLogfile() ui->action_Save_Log->setEnabled(false); } +CaptureDialog *MainWindow::createCaptureDialog() +{ + CaptureDialog *ret = new CaptureDialog( + m_Ctx, + [this](const QString &exe, const QString &workingDir, const QString &cmdLine, + const QList &env, + CaptureOptions opts) { this->OnCaptureTrigger(exe, workingDir, cmdLine, env, opts); }, + [this](uint32_t PID, const QList &env, const QString &name, + CaptureOptions opts) { this->OnInjectTrigger(PID, env, name, opts); }); + ret->setObjectName("capDialog"); + return ret; +} + void MainWindow::SetTitle(const QString &filename) { QString prefix = ""; @@ -595,7 +685,7 @@ void MainWindow::recentCapture(const QString &filename) { if(QFileInfo::exists(filename)) { - // OpenCaptureConfigFile(filename, false); + OpenCaptureConfigFile(filename, false); } else { @@ -715,6 +805,24 @@ void MainWindow::on_action_Texture_Viewer_triggered() ui->toolWindowManager->addToolWindow(textureViewer, ToolWindowManager::EmptySpace); } +void MainWindow::on_action_Capture_Log_triggered() +{ + CaptureDialog *capDialog = createCaptureDialog(); + + ui->toolWindowManager->addToolWindow(capDialog, ToolWindowManager::EmptySpace); + + capDialog->setInjectMode(false); +} + +void MainWindow::on_action_Inject_into_Process_triggered() +{ + CaptureDialog *capDialog = createCaptureDialog(); + + ui->toolWindowManager->addToolWindow(capDialog, ToolWindowManager::EmptySpace); + + capDialog->setInjectMode(true); +} + void MainWindow::saveLayout_triggered() { LoadSaveLayout(qobject_cast(QObject::sender()), true); diff --git a/qrenderdoc/Windows/MainWindow.h b/qrenderdoc/Windows/MainWindow.h index a3c5e024c..f83a36226 100644 --- a/qrenderdoc/Windows/MainWindow.h +++ b/qrenderdoc/Windows/MainWindow.h @@ -35,6 +35,7 @@ class MainWindow; class QLabel; class QProgressBar; +class CaptureDialog; class MainWindow : public QMainWindow, public ILogViewerForm { @@ -53,6 +54,11 @@ public: bool ownTemporaryLog() { return m_OwnTempLog; } void LoadFromFilename(const QString &filename); + void OnCaptureTrigger(const QString &exe, const QString &workingDir, const QString &cmdLine, + const QList &env, CaptureOptions opts); + void OnInjectTrigger(uint32_t PID, const QList &env, const QString &name, + CaptureOptions opts); + private slots: // automatic slots void on_action_Exit_triggered(); @@ -62,6 +68,8 @@ private slots: void on_action_Mesh_Output_triggered(); void on_action_Event_Viewer_triggered(); void on_action_Texture_Viewer_triggered(); + void on_action_Capture_Log_triggered(); + void on_action_Inject_into_Process_triggered(); // manual slots void saveLayout_triggered(); @@ -85,6 +93,8 @@ private: QString m_LastSaveCapturePath = ""; + CaptureDialog *createCaptureDialog(); + void SetTitle(const QString &filename); void SetTitle(); @@ -97,6 +107,7 @@ private: bool PromptCloseLog(); bool PromptSaveLog(); void LoadLogfile(const QString &filename, bool temporary, bool local); + void OpenCaptureConfigFile(const QString &filename, bool exe); QString GetSavePath(); void CloseLogfile(); diff --git a/qrenderdoc/qrenderdoc.pro b/qrenderdoc/qrenderdoc.pro index 0f1f50148..bbcd1b677 100644 --- a/qrenderdoc/qrenderdoc.pro +++ b/qrenderdoc/qrenderdoc.pro @@ -96,6 +96,7 @@ SOURCES += 3rdparty/toolwindowmanager/ToolWindowManager.cpp \ Widgets/TextureGoto.cpp \ Widgets/RangeHistogram.cpp \ Windows/Dialogs/TextureSaveDialog.cpp \ + Windows/Dialogs/CaptureDialog.cpp \ Code/QRDUtils.cpp HEADERS += 3rdparty/toolwindowmanager/ToolWindowManager.h \ @@ -120,6 +121,7 @@ HEADERS += 3rdparty/toolwindowmanager/ToolWindowManager.h \ Widgets/TextureGoto.h \ Widgets/RangeHistogram.h \ Windows/Dialogs/TextureSaveDialog.h \ + Windows/Dialogs/CaptureDialog.h \ Code/QRDUtils.h FORMS += Windows/Dialogs/AboutDialog.ui \ @@ -128,7 +130,8 @@ FORMS += Windows/Dialogs/AboutDialog.ui \ Windows/TextureViewer.ui \ Widgets/ResourcePreview.ui \ Widgets/ThumbnailStrip.ui \ - Windows/Dialogs/TextureSaveDialog.ui + Windows/Dialogs/TextureSaveDialog.ui \ + Windows/Dialogs/CaptureDialog.ui RESOURCES += \ resources.qrc diff --git a/qrenderdoc/qrenderdoc_local.vcxproj b/qrenderdoc/qrenderdoc_local.vcxproj index 4f3dcd1a8..2ec4f5f67 100644 --- a/qrenderdoc/qrenderdoc_local.vcxproj +++ b/qrenderdoc/qrenderdoc_local.vcxproj @@ -281,6 +281,7 @@ + @@ -309,6 +310,7 @@ + @@ -334,6 +336,7 @@ + @@ -350,6 +353,7 @@ + @@ -365,6 +369,7 @@ + diff --git a/qrenderdoc/qrenderdoc_local.vcxproj.filters b/qrenderdoc/qrenderdoc_local.vcxproj.filters index d3b8d5cca..32af009d5 100644 --- a/qrenderdoc/qrenderdoc_local.vcxproj.filters +++ b/qrenderdoc/qrenderdoc_local.vcxproj.filters @@ -169,6 +169,12 @@ Generated Files + + Generated Files + + + Windows\Dialogs + Code @@ -252,6 +258,12 @@ Generated Files + + Generated Files + + + Windows\Dialogs + Code @@ -459,6 +471,9 @@ Windows\Dialogs + + Windows\Dialogs +