diff --git a/qrenderdoc/Code/PersistantConfig.cpp b/qrenderdoc/Code/PersistantConfig.cpp index a583c7e34..515e78bab 100644 --- a/qrenderdoc/Code/PersistantConfig.cpp +++ b/qrenderdoc/Code/PersistantConfig.cpp @@ -39,6 +39,16 @@ variantType convertToVariant(const origType &val) return variantType(val); } +template +variantType convertToVariant(const QList &val) +{ + variantType ret; + ret.reserve(val.count()); + for(const innerType &s : val) + ret.push_back(s); + return ret; +} + template <> QVariantMap convertToVariant(const QStringMap &val) { @@ -50,32 +60,28 @@ QVariantMap convertToVariant(const QStringMap &val) return ret; } -template <> -QVariantList convertToVariant(const QList &val) -{ - QVariantList ret; - ret.reserve(val.count()); - for(const QString &s : val) - ret.push_back(s); - return ret; -} - -template <> -QVariantList convertToVariant(const QList &val) -{ - QVariantList ret; - ret.reserve(val.count()); - for(const RemoteHost &s : val) - ret.push_back(s); - return ret; -} - template origType convertFromVariant(const variantType &val) { return origType(val); } +template <> +QString convertFromVariant(const QVariant &val) +{ + return val.toString(); +} + +template +listType convertFromVariant(const QList &val) +{ + listType ret; + ret.reserve(val.count()); + for(const QVariant &s : val) + ret.push_back(convertFromVariant(s)); + return ret; +} + template <> QStringMap convertFromVariant(const QVariantMap &val) { @@ -87,26 +93,6 @@ QStringMap convertFromVariant(const QVariantMap &val) return ret; } -template <> -QList convertFromVariant(const QVariantList &val) -{ - QList ret; - ret.reserve(val.count()); - for(const QVariant &s : val) - ret.push_back(s.toString()); - return ret; -} - -template <> -QList convertFromVariant(const QVariantList &val) -{ - QList ret; - ret.reserve(val.count()); - for(const QVariant &s : val) - ret.push_back(RemoteHost(s)); - return ret; -} - bool PersistantConfig::Deserialize(const QString &filename) { QFile f(filename); diff --git a/qrenderdoc/Code/PersistantConfig.h b/qrenderdoc/Code/PersistantConfig.h index 7de4b4f3c..a6617c9cd 100644 --- a/qrenderdoc/Code/PersistantConfig.h +++ b/qrenderdoc/Code/PersistantConfig.h @@ -33,6 +33,36 @@ typedef QMap QStringMap; +struct SPIRVDisassembler +{ + SPIRVDisassembler() {} + SPIRVDisassembler(const QVariant &var) + { + QVariantMap map = var.toMap(); + if(map.contains("name")) + name = map["name"].toString(); + if(map.contains("executable")) + executable = map["executable"].toString(); + if(map.contains("args")) + args = map["args"].toString(); + } + + operator QVariant() const + { + QVariantMap map; + + map["name"] = name; + map["executable"] = executable; + map["args"] = args; + + return map; + } + + QString name; + QString executable; + QString args; +}; + #define CONFIG_SETTING_VAL(access, variantType, type, name, defaultValue) \ access: \ type name = defaultValue; @@ -108,6 +138,8 @@ typedef QMap QStringMap; \ CONFIG_SETTING_VAL(public, bool, bool, AllowGlobalHook, false) \ \ + CONFIG_SETTING(public, QVariantList, QList, SPIRVDisassemblers) \ + \ CONFIG_SETTING(private, QVariantMap, QStringMap, ConfigSettings) \ \ CONFIG_SETTING(private, QVariantList, QList, RemoteHostList) diff --git a/qrenderdoc/Code/QRDUtils.cpp b/qrenderdoc/Code/QRDUtils.cpp index 25fefaf77..5f9a22f0b 100644 --- a/qrenderdoc/Code/QRDUtils.cpp +++ b/qrenderdoc/Code/QRDUtils.cpp @@ -807,6 +807,103 @@ bool RunProcessAsAdmin(const QString &fullExecutablePath, const QStringList &par #endif } +QStringList ParseArgsList(const QString &args) +{ + QStringList ret; + + if(args.isEmpty()) + return ret; + +// on windows just use the function provided by the system +#if defined(Q_OS_WIN32) + std::wstring wargs = args.toStdWString(); + + int argc = 0; + wchar_t **argv = CommandLineToArgvW(wargs.c_str(), &argc); + + for(int i = 0; i < argc; i++) + ret << QString::fromWCharArray(argv[i]); + + LocalFree(argv); +#else + std::string argString = args.toStdString(); + + // perform some kind of sane parsing + bool dquot = false, squot = false; // are we inside ''s or ""s + + // current character + char *c = &argString[0]; + + // current argument we're building + std::string a; + + while(*c) + { + if(!dquot && !squot && (*c == ' ' || *c == '\t')) + { + if(!a.empty()) + ret << QString::fromStdString(a); + + a = ""; + } + else if(!dquot && *c == '"') + { + dquot = true; + } + else if(!squot && *c == '\'') + { + squot = true; + } + else if(dquot && *c == '"') + { + dquot = false; + } + else if(squot && *c == '\'') + { + squot = false; + } + else if(squot) + { + // single quotes don't escape, just copy literally until we leave single quote mode + a.push_back(*c); + } + else if(dquot) + { + // handle escaping + if(*c == '\\') + { + c++; + if(*c) + { + a.push_back(*c); + } + else + { + qCritical() << "Malformed args list:" << args; + return ret; + } + } + else + { + a.push_back(*c); + } + } + else + { + a.push_back(*c); + } + + c++; + } + + // if we were building an argument when we hit the end of the string + if(!a.empty()) + ret << QString::fromStdString(a); +#endif + + return ret; +} + void ShowProgressDialog(QWidget *window, const QString &labelText, ProgressFinishedMethod finished, ProgressUpdateMethod update) { diff --git a/qrenderdoc/Code/QRDUtils.h b/qrenderdoc/Code/QRDUtils.h index c8824aafe..6d227e853 100644 --- a/qrenderdoc/Code/QRDUtils.h +++ b/qrenderdoc/Code/QRDUtils.h @@ -687,6 +687,7 @@ class QProgressDialog; typedef std::function ProgressUpdateMethod; typedef std::function ProgressFinishedMethod; +QStringList ParseArgsList(const QString &args); bool RunProcessAsAdmin(const QString &fullExecutablePath, const QStringList ¶ms, std::function finishedCallback = std::function()); diff --git a/qrenderdoc/Windows/Dialogs/SettingsDialog.cpp b/qrenderdoc/Windows/Dialogs/SettingsDialog.cpp index 60c352d3c..1d69756cb 100644 --- a/qrenderdoc/Windows/Dialogs/SettingsDialog.cpp +++ b/qrenderdoc/Windows/Dialogs/SettingsDialog.cpp @@ -56,12 +56,11 @@ SettingsDialog::SettingsDialog(CaptureContext &ctx, QWidget *parent) ui->saveDirectory->setText(m_Ctx.Config.DefaultCaptureSaveDirectory); ui->tempDirectory->setText(m_Ctx.Config.TemporaryCaptureDirectory); - // TODO external disassembler - /* - ui->ExternalDisassemblerEnabled->setChecked(m_Ctx.Config.ExternalDisassemblerEnabled); - ui->externalDisassemblerArgs->setText(m_Ctx.Config.GetDefaultExternalDisassembler().args); - ui->externalDisassemblePath->setText(m_Ctx.Config.GetDefaultExternalDisassembler().executable); - */ + if(!m_Ctx.Config.SPIRVDisassemblers.isEmpty()) + { + ui->externalDisassemblerArgs->setText(m_Ctx.Config.SPIRVDisassemblers[0].args); + ui->externalDisassemblePath->setText(m_Ctx.Config.SPIRVDisassemblers[0].executable); + } ui->Android_AdbExecutablePath->setText(m_Ctx.Config.Android_AdbExecutablePath); ui->Android_MaxConnectTimeout->setValue(m_Ctx.Config.Android_MaxConnectTimeout); @@ -239,29 +238,40 @@ void SettingsDialog::on_ShaderViewer_FriendlyNaming_toggled(bool checked) m_Ctx.Config.Save(); } -void SettingsDialog::on_ExternalDisassemblerEnabled_toggled(bool checked) -{ - // TODO external disassembler - // m_Ctx.Config.ExternalDisassemblerEnabled = ui->ExternalDisassemblerEnabled->isChecked(); - - m_Ctx.Config.Save(); -} - void SettingsDialog::on_browseExtDisasemble_clicked() { // TODO external disassembler + QString filePath = RDDialog::getExecutableFileName(this, "Locate SPIR-V disassembler"); + + if(!filePath.isEmpty()) + { + ui->externalDisassemblePath->setText(filePath); + on_externalDisassemblePath_textEdited(filePath); + } } -void SettingsDialog::on_externalDisassemblePath_textEdited(const QString &disasm) +void SettingsDialog::on_externalDisassemblePath_textEdited(const QString &path) { - // TODO external disassembler + if(m_Ctx.Config.SPIRVDisassemblers.isEmpty()) + { + m_Ctx.Config.SPIRVDisassemblers.push_back(SPIRVDisassembler()); + m_Ctx.Config.SPIRVDisassemblers.back().name = "Unknown"; + } + + m_Ctx.Config.SPIRVDisassemblers.back().executable = path; m_Ctx.Config.Save(); } void SettingsDialog::on_externalDisassemblerArgs_textEdited(const QString &args) { - // TODO external disassembler + if(m_Ctx.Config.SPIRVDisassemblers.isEmpty()) + { + m_Ctx.Config.SPIRVDisassemblers.push_back(SPIRVDisassembler()); + m_Ctx.Config.SPIRVDisassemblers.back().name = "Unknown"; + } + + m_Ctx.Config.SPIRVDisassemblers.back().args = args; m_Ctx.Config.Save(); } diff --git a/qrenderdoc/Windows/Dialogs/SettingsDialog.h b/qrenderdoc/Windows/Dialogs/SettingsDialog.h index 96fca8f78..9a6c17958 100644 --- a/qrenderdoc/Windows/Dialogs/SettingsDialog.h +++ b/qrenderdoc/Windows/Dialogs/SettingsDialog.h @@ -69,7 +69,6 @@ private slots: // shader viewer void on_ShaderViewer_FriendlyNaming_toggled(bool checked); - void on_ExternalDisassemblerEnabled_toggled(bool checked); void on_browseExtDisasemble_clicked(); void on_externalDisassemblePath_textEdited(const QString &path); void on_externalDisassemblerArgs_textEdited(const QString &args); diff --git a/qrenderdoc/Windows/Dialogs/SettingsDialog.ui b/qrenderdoc/Windows/Dialogs/SettingsDialog.ui index a697692a7..55ab4c29d 100644 --- a/qrenderdoc/Windows/Dialogs/SettingsDialog.ui +++ b/qrenderdoc/Windows/Dialogs/SettingsDialog.ui @@ -61,7 +61,7 @@ QTabWidget::West - 5 + 3 true @@ -611,45 +611,7 @@ This option overrides that and will always replay locally if the local context i Vulkan Shaders - - - - Use an external tool to disassemble SPIR-V instead of RenderDoc's built-in disassembly. - -This is useful if you want to disassemble directly to a high level language like GLSL or HLSL that can be compiled for editing. - - - Use External Disassembler - - - - - - - Use an external tool to disassemble SPIR-V instead of RenderDoc's built-in disassembly. - -This is useful if you want to disassemble directly to a high level language like GLSL or HLSL that can be compiled for editing. - - - - - - - - - - Choose the executable file to invoke every time a shader needs to be disassembled - - - - - - - External Disassembler command line arguments - - - - + Choose the executable file to invoke every time a shader needs to be disassembled @@ -659,14 +621,49 @@ This is useful if you want to disassemble directly to a high level language like - + External Disassembler executable - + + + + Choose the executable file to invoke every time a shader needs to be disassembled + + + + + + + External Disassembler command line arguments + + + + + + + The command line arguments to the executable. + +The {spv_bin} and {spv_disas} tags indicate the (temporary) path to the SPIR-V binary file, and the expected SPIR-V disassembled file to create. + +If {spv_disas} is not used, the tool is expected to output the disassembly on stdout. + + + + + + + NOTE: Use the {spv_bin} and {spv_disas} tags to indicate to the external disassembler the input SPIR-V binary and the output SPIR-V disassembly respectively. + + + true + + + + Qt::Vertical @@ -679,27 +676,6 @@ This is useful if you want to disassemble directly to a high level language like - - - - The command line arguments to the executable. - -The {spv_bin} and {spv_disas} tags indicate the (temporary) path to the SPIR-V binary file, and the expected SPIR-V disassembled file to create. - -If {spv_disas} is not used, the tool is expected to output the disassembly on stdout. - - - - - - - NOTE: Use the {spv_bin} and {spv_disas} tags to indicate to the external disassembler the input SPIR-V binary and the output SPIR-V disassembly respectively. - - - true - - - diff --git a/qrenderdoc/Windows/PipelineState/VulkanPipelineStateViewer.cpp b/qrenderdoc/Windows/PipelineState/VulkanPipelineStateViewer.cpp index cdff45475..cd5677226 100644 --- a/qrenderdoc/Windows/PipelineState/VulkanPipelineStateViewer.cpp +++ b/qrenderdoc/Windows/PipelineState/VulkanPipelineStateViewer.cpp @@ -2385,7 +2385,13 @@ void VulkanPipelineStateViewer::shaderEdit_clicked() if(!hasOrigSource) { - QString glsl = "// TODO - disassemble SPIR-V"; + QString glsl; + + if(!m_Ctx.Config.SPIRVDisassemblers.isEmpty()) + glsl = disassembleSPIRV(shaderDetails); + + if(glsl.isEmpty()) + glsl = ToQStr(shaderDetails->Disassembly); mainfile = "generated.glsl"; @@ -2398,6 +2404,90 @@ void VulkanPipelineStateViewer::shaderEdit_clicked() m_Common.EditShader(stage->stage, stage->Shader, shaderDetails, entryFunc, files, mainfile); } +QString VulkanPipelineStateViewer::disassembleSPIRV(const ShaderReflection *shaderDetails) +{ + QString glsl; + + const SPIRVDisassembler &disasm = m_Ctx.Config.SPIRVDisassemblers[0]; + + if(disasm.executable.isEmpty()) + return ""; + + QString spv_bin_file = QDir(QDir::tempPath()).absoluteFilePath("spv_bin.spv"); + + QFile binHandle(spv_bin_file); + if(binHandle.open(QFile::WriteOnly | QIODevice::Truncate)) + { + binHandle.write( + QByteArray((const char *)shaderDetails->RawBytes.elems, shaderDetails->RawBytes.count)); + binHandle.close(); + } + else + { + RDDialog::critical(this, tr("Error writing temp file"), + tr("Couldn't write temporary SPIR-V file %1.").arg(spv_bin_file)); + return ""; + } + + if(!disasm.args.contains("{spv_bin}")) + { + RDDialog::critical( + this, tr("Wrongly configured disassembler"), + tr("Please use {spv_bin} in the disassembler arguments to specify the input file.")); + return ""; + } + + LambdaThread *thread = new LambdaThread([this, &glsl, &disasm, spv_bin_file]() { + QString spv_disas_file = QDir(QDir::tempPath()).absoluteFilePath("spv_disas.txt"); + + QString args = disasm.args; + + bool writesToFile = disasm.args.contains("{spv_disas}"); + + args.replace(QString::fromUtf8("{spv_bin}"), spv_bin_file); + args.replace(QString::fromUtf8("{spv_disas}"), spv_disas_file); + + QStringList argList = ParseArgsList(args); + + QProcess process; + process.start(disasm.executable, argList); + process.waitForFinished(); + + if(process.exitStatus() != QProcess::NormalExit || process.exitCode() != 0) + { + GUIInvoke::call([this]() { + RDDialog::critical(this, tr("Error running disassembler"), + tr("There was an error invoking the external SPIR-V disassembler.")); + }); + } + + if(writesToFile) + { + QFile outputHandle(spv_disas_file); + if(outputHandle.open(QFile::ReadOnly | QIODevice::Text)) + { + glsl = QString::fromUtf8(outputHandle.readAll()); + outputHandle.close(); + } + } + else + { + glsl = QString::fromUtf8(process.readAll()); + } + + QFile::remove(spv_bin_file); + QFile::remove(spv_disas_file); + }); + thread->start(); + + ShowProgressDialog(this, tr("Please wait - running external disassembler"), + [thread]() { return !thread->isRunning(); }); + + thread->deleteLater(); + + return glsl; +} + void VulkanPipelineStateViewer::shaderSave_clicked() { const VulkanPipelineState::ShaderStage *stage = diff --git a/qrenderdoc/Windows/PipelineState/VulkanPipelineStateViewer.h b/qrenderdoc/Windows/PipelineState/VulkanPipelineStateViewer.h index 6d1f966e3..2e1fd33ef 100644 --- a/qrenderdoc/Windows/PipelineState/VulkanPipelineStateViewer.h +++ b/qrenderdoc/Windows/PipelineState/VulkanPipelineStateViewer.h @@ -72,6 +72,7 @@ private slots: // manual slots void shaderView_clicked(); void shaderEdit_clicked(); + void shaderSave_clicked(); void resource_itemActivated(QTreeWidgetItem *item, int column); void ubo_itemActivated(QTreeWidgetItem *item, int column); @@ -107,6 +108,8 @@ private: const rdctype::array &vars); const VulkanPipelineState::ShaderStage *stageForSender(QWidget *widget); + QString disassembleSPIRV(const ShaderReflection *shaderDetails); + template void setViewDetails(QTreeWidgetItem *node, const viewType &view, FetchTexture *tex);