diff --git a/qrenderdoc/Code/Interface/PersistantConfig.cpp b/qrenderdoc/Code/Interface/PersistantConfig.cpp index f391f4c0d..47a7cb6dd 100644 --- a/qrenderdoc/Code/Interface/PersistantConfig.cpp +++ b/qrenderdoc/Code/Interface/PersistantConfig.cpp @@ -25,6 +25,7 @@ #include #include #include +#include #include "Code/QRDUtils.h" #include "Styles/StyleData.h" #include "QRDInterface.h" @@ -308,6 +309,66 @@ bool PersistantConfig::Load(const rdcstr &filename) RemoteHosts.insert(0, host); } + bool tools[arraydim()] = {}; + + // see which known tools are registered + for(const SPIRVDisassembler &dis : SPIRVDisassemblers) + { + // if it's declared + if(dis.tool != KnownSPIRVTool::Unknown) + tools[(size_t)dis.tool] = true; + + for(KnownSPIRVTool tool : values()) + { + if(QString(dis.executable).contains(ToolExecutable(tool))) + tools[(size_t)tool] = true; + } + } + + for(KnownSPIRVTool tool : values()) + { + if(tool == KnownSPIRVTool::Unknown || tools[(size_t)tool]) + continue; + + QString exe = ToolExecutable(tool); + + if(exe.isEmpty()) + continue; + + // try to find the tool in PATH + QString path = QStandardPaths::findExecutable(exe); + + if(!path.isEmpty()) + { + SPIRVDisassembler dis; + dis.name = ToQStr(tool); + // we store just the base name, so when we launch the process it will always find it in PATH, + // rather than baking in the current PATH result. + dis.executable = exe; + dis.tool = tool; + + SPIRVDisassemblers.push_back(dis); + + continue; + } + + // try to find it in our plugins folder + path = QStandardPaths::findExecutable( + exe, {QDir(QApplication::applicationDirPath()).absoluteFilePath(lit("plugins/spirv/"))}); + + if(!path.isEmpty()) + { + SPIRVDisassembler dis; + dis.name = ToQStr(tool); + dis.executable = path; + dis.tool = tool; + + SPIRVDisassemblers.push_back(dis); + + continue; + } + } + return ret; } @@ -393,6 +454,8 @@ rdcstr PersistantConfig::GetConfigSetting(const rdcstr &name) SPIRVDisassembler::SPIRVDisassembler(const QVariant &var) { QVariantMap map = var.toMap(); + if(map.contains(lit("tool"))) + tool = (KnownSPIRVTool)map[lit("tool")].toUInt(); if(map.contains(lit("name"))) name = map[lit("name")].toString(); if(map.contains(lit("executable"))) @@ -405,6 +468,7 @@ SPIRVDisassembler::operator QVariant() const { QVariantMap map; + map[lit("tool")] = (uint32_t)tool; map[lit("name")] = name; map[lit("executable")] = executable; map[lit("args")] = args; diff --git a/qrenderdoc/Code/Interface/PersistantConfig.h b/qrenderdoc/Code/Interface/PersistantConfig.h index 22d45ac58..c53767ccc 100644 --- a/qrenderdoc/Code/Interface/PersistantConfig.h +++ b/qrenderdoc/Code/Interface/PersistantConfig.h @@ -28,18 +28,55 @@ #include "QRDInterface.h" #include "RemoteHost.h" +DOCUMENT(R"(Identifies a particular known SPIR-V tool used for disassembly. + +.. data:: Unknown + + Corresponds to no known tool. + +.. data:: SPIRV_Cross + + `SPIRV-Cross `_. + +.. data:: spirv_dis + + `spirv-dis from SPIRV-Tools `_. +)"); +enum class KnownSPIRVTool : uint32_t +{ + Unknown, + First = Unknown, + SPIRV_Cross, + spirv_dis, + Count, +}; + +ITERABLE_OPERATORS(KnownSPIRVTool); + +inline rdcstr ToolExecutable(KnownSPIRVTool tool) +{ + if(tool == KnownSPIRVTool::SPIRV_Cross) + return "spirv-cross"; + else if(tool == KnownSPIRVTool::spirv_dis) + return "spirv-dis"; + + return ""; +} + DOCUMENT("Describes an external program that can be used to disassemble SPIR-V."); struct SPIRVDisassembler { DOCUMENT(""); - SPIRVDisassembler() {} + SPIRVDisassembler() = default; VARIANT_CAST(SPIRVDisassembler); bool operator==(const SPIRVDisassembler &o) const { - return name == o.name && executable == o.executable && args == o.args; + return tool == o.tool && name == o.name && executable == o.executable && args == o.args; } bool operator<(const SPIRVDisassembler &o) const { + if(tool != o.tool) + return tool < o.tool; if(name != o.name) return name < o.name; if(executable != o.executable) @@ -48,6 +85,8 @@ struct SPIRVDisassembler return args < o.args; return false; } + DOCUMENT("The :class:`KnownSPIRVTool` identifying which known tool this disassembler is."); + KnownSPIRVTool tool = KnownSPIRVTool::Unknown; DOCUMENT("The human-readable name of the program."); rdcstr name; DOCUMENT("The path to the executable to run for this program."); diff --git a/qrenderdoc/Code/Interface/SPIRVDisassembler.cpp b/qrenderdoc/Code/Interface/SPIRVDisassembler.cpp index ac1c43de9..c553494fc 100644 --- a/qrenderdoc/Code/Interface/SPIRVDisassembler.cpp +++ b/qrenderdoc/Code/Interface/SPIRVDisassembler.cpp @@ -1,4 +1,4 @@ -#/****************************************************************************** +/****************************************************************************** * The MIT License (MIT) * * Copyright (c) 2016-2018 Baldur Karlsson @@ -27,6 +27,18 @@ #include "Code/QRDUtils.h" #include "QRDInterface.h" +template <> +std::string DoStringise(const KnownSPIRVTool &el) +{ + BEGIN_ENUM_STRINGISE(KnownSPIRVTool); + { + STRINGISE_ENUM_CLASS_NAMED(Unknown, "Custom Tool"); + STRINGISE_ENUM_CLASS_NAMED(SPIRV_Cross, "SPIRV-Cross"); + STRINGISE_ENUM_CLASS_NAMED(spirv_dis, "spirv-dis"); + } + END_ENUM_STRINGISE(); +} + rdcstr SPIRVDisassembler::DisassembleShader(QWidget *window, const ShaderReflection *shaderDetails) const { if(executable.isEmpty()) @@ -50,7 +62,20 @@ rdcstr SPIRVDisassembler::DisassembleShader(QWidget *window, const ShaderReflect return ""; } - if(!QString(args).contains(lit("{spv_bin}"))) + QString programArguments = args; + + switch(tool) + { + case KnownSPIRVTool::SPIRV_Cross: + programArguments = lit("--output {spv_disas} {spv_bin} --vulkan-semantics"); + break; + case KnownSPIRVTool::spirv_dis: + programArguments = lit("--no-color -o {spv_disas} {spv_bin}"); + break; + default: break; + } + + if(!programArguments.contains(lit("{spv_bin}"))) { RDDialog::critical( window, QApplication::translate("SPIRVDisassembler", "Wrongly configured disassembler"), @@ -62,10 +87,10 @@ rdcstr SPIRVDisassembler::DisassembleShader(QWidget *window, const ShaderReflect QString glsl; - LambdaThread *thread = new LambdaThread([this, window, &glsl, spv_bin_file]() { + LambdaThread *thread = new LambdaThread([this, window, &glsl, programArguments, spv_bin_file]() { QString spv_disas_file = QDir(QDir::tempPath()).absoluteFilePath(lit("spv_disas.txt")); - QString expandedargs = args; + QString expandedargs = programArguments; bool writesToFile = expandedargs.contains(lit("{spv_disas}")); diff --git a/qrenderdoc/Windows/Dialogs/SettingsDialog.cpp b/qrenderdoc/Windows/Dialogs/SettingsDialog.cpp index be417b6b0..4fa9d0239 100644 --- a/qrenderdoc/Windows/Dialogs/SettingsDialog.cpp +++ b/qrenderdoc/Windows/Dialogs/SettingsDialog.cpp @@ -30,6 +30,61 @@ #include "CaptureDialog.h" #include "ui_SettingsDialog.h" +class KnownSPIRVToolDelegate : public QStyledItemDelegate +{ +public: + explicit KnownSPIRVToolDelegate(QWidget *parent = NULL) : QStyledItemDelegate(parent) {} + QWidget *createEditor(QWidget *parent, const QStyleOptionViewItem &option, + const QModelIndex &index) const override + { + QComboBox *editor = new QComboBox(parent); + + editor->setEditable(true); + editor->setInsertPolicy(QComboBox::NoInsert); + + QStringList items; + for(KnownSPIRVTool tool : values()) + items << ToQStr(tool); + editor->addItems(items); + + return editor; + } + + void setEditorData(QWidget *editor, const QModelIndex &index) const override + { + QComboBox *comboEditor = qobject_cast(editor); + if(comboEditor) + { + QString editData = index.data(Qt::EditRole).toString(); + + int idx = comboEditor->findText(editData); + + if(idx >= 0) + comboEditor->setCurrentIndex(idx); + else + comboEditor->setCurrentText(index.data(Qt::EditRole).toString()); + + return; + } + + QStyledItemDelegate::setEditorData(editor, index); + } + + void setModelData(QWidget *editor, QAbstractItemModel *model, const QModelIndex &index) const override + { + QComboBox *comboEditor = qobject_cast(editor); + if(comboEditor) + { + model->setData(index, comboEditor->currentText(), Qt::EditRole); + return; + } + + QStyledItemDelegate::setModelData(editor, model, index); + } + +private slots: +}; + SettingsDialog::SettingsDialog(ICaptureContext &ctx, QWidget *parent) : QDialog(parent), ui(new Ui::SettingsDialog), m_Ctx(ctx) { @@ -79,11 +134,26 @@ SettingsDialog::SettingsDialog(ICaptureContext &ctx, QWidget *parent) ui->saveDirectory->setText(m_Ctx.Config().DefaultCaptureSaveDirectory); ui->tempDirectory->setText(m_Ctx.Config().TemporaryCaptureDirectory); - 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->disassemblers->setColumnCount(3); + ui->disassemblers->setHorizontalHeaderLabels(QStringList() << tr("Tool") << tr("Executable") + << tr("Arguments")); + + ui->disassemblers->horizontalHeader()->setSectionResizeMode(0, QHeaderView::Interactive); + ui->disassemblers->horizontalHeader()->setSectionResizeMode(1, QHeaderView::Interactive); + ui->disassemblers->horizontalHeader()->setSectionResizeMode(2, QHeaderView::Stretch); + + for(const SPIRVDisassembler &disasm : m_Ctx.Config().SPIRVDisassemblers) + addDisassembler(disasm); + + ui->disassemblers->horizontalHeader()->resizeSection(0, 100); + + ui->disassemblers->verticalHeader()->setSectionsMovable(true); + ui->disassemblers->verticalHeader()->setMinimumWidth(20); + + ui->disassemblers->setItemDelegateForColumn(0, new KnownSPIRVToolDelegate(this)); + + ui->deleteDisasm->setEnabled(false); + ui->Android_SDKPath->setText(m_Ctx.Config().Android_SDKPath); ui->Android_JDKPath->setText(m_Ctx.Config().Android_JDKPath); ui->Android_MaxConnectTimeout->setValue(m_Ctx.Config().Android_MaxConnectTimeout); @@ -127,6 +197,8 @@ SettingsDialog::SettingsDialog(ICaptureContext &ctx, QWidget *parent) m_Init = false; + QObject::connect(ui->disassemblers->verticalHeader(), &QHeaderView::sectionMoved, this, + &SettingsDialog::disassemblers_rowMoved); QObject::connect(ui->Formatter_MinFigures, OverloadedSlot::of(&QSpinBox::valueChanged), this, &SettingsDialog::formatter_valueChanged); QObject::connect(ui->Formatter_MaxFigures, OverloadedSlot::of(&QSpinBox::valueChanged), this, @@ -302,39 +374,144 @@ void SettingsDialog::on_ShaderViewer_FriendlyNaming_toggled(bool checked) m_Ctx.Config().Save(); } -void SettingsDialog::on_browseExtDisasemble_clicked() +void SettingsDialog::addDisassembler(const SPIRVDisassembler &disasm) { - QString filePath = RDDialog::getExecutableFileName(this, tr("Locate SPIR-V disassembler")); + // prevent calling cellChanged + m_AddingDisassembler = true; - if(!filePath.isEmpty()) + int row = ui->disassemblers->rowCount(); + ui->disassemblers->insertRow(row); + + ui->disassemblers->setVerticalHeaderItem(row, new QTableWidgetItem(QString())); + + ui->disassemblers->setItem(row, 0, new QTableWidgetItem(disasm.name)); + ui->disassemblers->setItem(row, 1, new QTableWidgetItem(disasm.executable)); + + QTableWidgetItem *item = + new QTableWidgetItem(disasm.tool == KnownSPIRVTool::Unknown ? disasm.args : tr("Automatic")); + ui->disassemblers->setItem(row, 2, item); + + // make arguments non-editable for built-in tools + if(disasm.tool != KnownSPIRVTool::Unknown) { - ui->externalDisassemblePath->setText(filePath); - on_externalDisassemblePath_textEdited(filePath); + Qt::ItemFlags flags = item->flags() & ~Qt::ItemIsEditable; + item->setFlags(flags); } + + m_AddingDisassembler = false; } -void SettingsDialog::on_externalDisassemblePath_textEdited(const QString &path) +void SettingsDialog::on_addDisasm_clicked() { - if(m_Ctx.Config().SPIRVDisassemblers.isEmpty()) - { - m_Ctx.Config().SPIRVDisassemblers.push_back(SPIRVDisassembler()); - m_Ctx.Config().SPIRVDisassemblers.back().name = lit("Unknown"); - } + SPIRVDisassembler disasm; + disasm.name = tr("Custom Tool"); + disasm.executable = lit("path/to/executable"); + disasm.args = lit("--input {spv_bin} --output {spv_disasm}"); + m_Ctx.Config().SPIRVDisassemblers.push_back(disasm); - m_Ctx.Config().SPIRVDisassemblers.back().executable = path; + addDisassembler(disasm); m_Ctx.Config().Save(); } -void SettingsDialog::on_externalDisassemblerArgs_textEdited(const QString &args) +void SettingsDialog::on_deleteDisasm_clicked() { - if(m_Ctx.Config().SPIRVDisassemblers.isEmpty()) + int row = -1; + + QModelIndexList selected = ui->disassemblers->selectionModel()->selectedRows(); + + if(!selected.isEmpty()) + row = selected[0].row(); + + if(row < 0 || row >= m_Ctx.Config().SPIRVDisassemblers.count()) + return; + + const SPIRVDisassembler &disasm = m_Ctx.Config().SPIRVDisassemblers[row]; + + QMessageBox::StandardButton res = RDDialog::question( + this, tr("Are you sure?"), tr("Are you sure you want to delete '%1'?").arg(disasm.name), + QMessageBox::Yes | QMessageBox::No | QMessageBox::Cancel); + + if(res == QMessageBox::Yes) { - m_Ctx.Config().SPIRVDisassemblers.push_back(SPIRVDisassembler()); - m_Ctx.Config().SPIRVDisassemblers.back().name = lit("Unknown"); + ui->disassemblers->removeRow(row); + m_Ctx.Config().SPIRVDisassemblers.erase(row); + + m_Ctx.Config().Save(); + } +} + +void SettingsDialog::on_disassemblers_itemSelectionChanged() +{ + ui->deleteDisasm->setEnabled(!ui->disassemblers->selectionModel()->selectedIndexes().empty()); +} + +void SettingsDialog::on_disassemblers_cellChanged(int row, int column) +{ + if(m_AddingDisassembler || row < 0 || row >= m_Ctx.Config().SPIRVDisassemblers.count()) + return; + + SPIRVDisassembler &disasm = m_Ctx.Config().SPIRVDisassemblers[row]; + + QString cellData = ui->disassemblers->item(row, column)->text(); + + if(column == 0) + { + bool found = false; + + for(KnownSPIRVTool tool : values()) + { + if(ToQStr(tool) == cellData) + { + disasm.tool = tool; + disasm.name = cellData; + found = true; + + // make arguments non-editable + Qt::ItemFlags flags = ui->disassemblers->item(row, 2)->flags() & ~Qt::ItemIsEditable; + ui->disassemblers->item(row, 2)->setFlags(flags); + } + } + + if(!found) + { + disasm.tool = KnownSPIRVTool::Unknown; + disasm.name = cellData; + + // make arguments editable + Qt::ItemFlags flags = ui->disassemblers->item(row, 2)->flags() | Qt::ItemIsEditable; + ui->disassemblers->item(row, 2)->setFlags(flags); + } + } + else if(column == 1) + { + disasm.executable = cellData; + } + else if(column == 2) + { + disasm.args = cellData; } - m_Ctx.Config().SPIRVDisassemblers.back().args = args; + m_Ctx.Config().Save(); +} + +void SettingsDialog::on_disassemblers_keyPress(QKeyEvent *event) +{ + if(event->key() == Qt::Key_Delete) + { + ui->deleteDisasm->click(); + } +} + +void SettingsDialog::disassemblers_rowMoved(int logicalIndex, int oldVisualIndex, int newVisualIndex) +{ + if(oldVisualIndex < 0 || oldVisualIndex >= m_Ctx.Config().SPIRVDisassemblers.count() || + newVisualIndex < 0 || newVisualIndex >= m_Ctx.Config().SPIRVDisassemblers.count()) + return; + + SPIRVDisassembler disasm = m_Ctx.Config().SPIRVDisassemblers.at(oldVisualIndex); + m_Ctx.Config().SPIRVDisassemblers.erase(oldVisualIndex); + m_Ctx.Config().SPIRVDisassemblers.insert(newVisualIndex, disasm); m_Ctx.Config().Save(); } diff --git a/qrenderdoc/Windows/Dialogs/SettingsDialog.h b/qrenderdoc/Windows/Dialogs/SettingsDialog.h index 6732c55fc..dcd8b1fd5 100644 --- a/qrenderdoc/Windows/Dialogs/SettingsDialog.h +++ b/qrenderdoc/Windows/Dialogs/SettingsDialog.h @@ -70,9 +70,12 @@ private slots: // shader viewer void on_ShaderViewer_FriendlyNaming_toggled(bool checked); - void on_browseExtDisasemble_clicked(); - void on_externalDisassemblePath_textEdited(const QString &path); - void on_externalDisassemblerArgs_textEdited(const QString &args); + void on_addDisasm_clicked(); + void on_deleteDisasm_clicked(); + void on_disassemblers_itemSelectionChanged(); + void on_disassemblers_cellChanged(int row, int column); + void on_disassemblers_keyPress(QKeyEvent *event); + void disassemblers_rowMoved(int logicalIndex, int oldVisualIndex, int newVisualIndex); // event browser void on_EventBrowser_TimeUnit_currentIndexChanged(int index); @@ -100,6 +103,9 @@ private slots: private: Ui::SettingsDialog *ui; + void addDisassembler(const SPIRVDisassembler &disasm); + ICaptureContext &m_Ctx; bool m_Init = false; + bool m_AddingDisassembler = false; }; diff --git a/qrenderdoc/Windows/Dialogs/SettingsDialog.ui b/qrenderdoc/Windows/Dialogs/SettingsDialog.ui index fb5d14a23..8adc15e15 100644 --- a/qrenderdoc/Windows/Dialogs/SettingsDialog.ui +++ b/qrenderdoc/Windows/Dialogs/SettingsDialog.ui @@ -638,62 +638,17 @@ This option overrides that and will always replay locally if the local context i - Vulkan Shaders + Vulkan Disassemblers - - - - Choose the executable file to invoke every time a shader needs to be disassembled - - - Browse - - - - External Disassembler executable + <html><head/><body><p>Available SPIR-V Disassemblers. Click items to edit, drag row header to change priority.</p></body></html> - - - - 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 @@ -706,6 +661,65 @@ If {spv_disas} is not used, the tool is expected to output the disassembly on st + + + + QAbstractItemView::AnyKeyPressed|QAbstractItemView::DoubleClicked|QAbstractItemView::EditKeyPressed|QAbstractItemView::SelectedClicked + + + true + + + QAbstractItemView::SingleSelection + + + QAbstractItemView::SelectRows + + + Qt::ElideMiddle + + + false + + + false + + + false + + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Add + + + + + + + Delete + + + + + @@ -1082,6 +1096,13 @@ This can enable debugging of Vulkan apps that don't already contain the layer. + + + RDTableWidget + QTableWidget +
Widgets/Extended/RDTableWidget.h
+
+