Allow for configuring multiple SPIR-V disassemblers, detect known tools

* Initially add support for spirv-cross and spirv-dis.
* When possible we'll auto-detect the tools in path or in the build's
  plugins folder.  Otherwise the user can add it and add their
  executable path.
* We still use the first disassembler for editing - in future it would
  be good to allow selecting the disassembler at edit time (as well
  as allowing multiple compilers).
This commit is contained in:
baldurk
2018-01-17 21:07:04 +00:00
parent 2354a0e71d
commit be0681f770
6 changed files with 411 additions and 79 deletions
@@ -25,6 +25,7 @@
#include <QApplication>
#include <QDebug>
#include <QDir>
#include <QStandardPaths>
#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<KnownSPIRVTool>()] = {};
// 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<KnownSPIRVTool>())
{
if(QString(dis.executable).contains(ToolExecutable(tool)))
tools[(size_t)tool] = true;
}
}
for(KnownSPIRVTool tool : values<KnownSPIRVTool>())
{
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;
+41 -2
View File
@@ -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 <https://github.com/KhronosGroup/SPIRV-Cross>`_.
.. data:: spirv_dis
`spirv-dis from SPIRV-Tools <https://github.com/KhronosGroup/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.");
@@ -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}"));
+199 -22
View File
@@ -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<KnownSPIRVTool>())
items << ToQStr(tool);
editor->addItems(items);
return editor;
}
void setEditorData(QWidget *editor, const QModelIndex &index) const override
{
QComboBox *comboEditor = qobject_cast<QComboBox *>(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<QComboBox *>(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<int>::of(&QSpinBox::valueChanged), this,
&SettingsDialog::formatter_valueChanged);
QObject::connect(ui->Formatter_MaxFigures, OverloadedSlot<int>::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<KnownSPIRVTool>())
{
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();
}
+9 -3
View File
@@ -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;
};
+69 -48
View File
@@ -638,62 +638,17 @@ This option overrides that and will always replay locally if the local context i
</sizepolicy>
</property>
<property name="title">
<string>Vulkan Shaders</string>
<string>Vulkan Disassemblers</string>
</property>
<layout class="QGridLayout" name="gridLayout_4">
<item row="1" column="1">
<widget class="QPushButton" name="browseExtDisasemble">
<property name="toolTip">
<string>Choose the executable file to invoke every time a shader needs to be disassembled</string>
</property>
<property name="text">
<string>Browse</string>
</property>
</widget>
</item>
<item row="0" column="0">
<widget class="QLabel" name="label_16">
<property name="text">
<string>External Disassembler executable</string>
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Available SPIR-V Disassemblers. Click items to edit, drag row header to change priority.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QLineEdit" name="externalDisassemblePath">
<property name="toolTip">
<string>Choose the executable file to invoke every time a shader needs to be disassembled</string>
</property>
</widget>
</item>
<item row="2" column="0">
<widget class="QLabel" name="label_17">
<property name="text">
<string>External Disassembler command line arguments</string>
</property>
</widget>
</item>
<item row="3" column="0">
<widget class="QLineEdit" name="externalDisassemblerArgs">
<property name="toolTip">
<string>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.</string>
</property>
</widget>
</item>
<item row="4" column="0">
<widget class="QLabel" name="label_18">
<property name="text">
<string>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.</string>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
</widget>
</item>
<item row="5" column="0">
<item row="3" column="0" colspan="2">
<spacer name="verticalSpacer_4">
<property name="orientation">
<enum>Qt::Vertical</enum>
@@ -706,6 +661,65 @@ If {spv_disas} is not used, the tool is expected to output the disassembly on st
</property>
</spacer>
</item>
<item row="1" column="0" colspan="2">
<widget class="RDTableWidget" name="disassemblers">
<property name="editTriggers">
<set>QAbstractItemView::AnyKeyPressed|QAbstractItemView::DoubleClicked|QAbstractItemView::EditKeyPressed|QAbstractItemView::SelectedClicked</set>
</property>
<property name="alternatingRowColors">
<bool>true</bool>
</property>
<property name="selectionMode">
<enum>QAbstractItemView::SingleSelection</enum>
</property>
<property name="selectionBehavior">
<enum>QAbstractItemView::SelectRows</enum>
</property>
<property name="textElideMode">
<enum>Qt::ElideMiddle</enum>
</property>
<property name="cornerButtonEnabled">
<bool>false</bool>
</property>
<attribute name="horizontalHeaderHighlightSections">
<bool>false</bool>
</attribute>
<attribute name="verticalHeaderHighlightSections">
<bool>false</bool>
</attribute>
</widget>
</item>
<item row="2" column="0" colspan="2">
<layout class="QHBoxLayout" name="horizontalLayout_6">
<item>
<spacer name="horizontalSpacer">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QPushButton" name="addDisasm">
<property name="text">
<string>Add</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="deleteDisasm">
<property name="text">
<string>Delete</string>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
</item>
@@ -1082,6 +1096,13 @@ This can enable debugging of Vulkan apps that don't already contain the layer.</
</item>
</layout>
</widget>
<customwidgets>
<customwidget>
<class>RDTableWidget</class>
<extends>QTableWidget</extends>
<header>Widgets/Extended/RDTableWidget.h</header>
</customwidget>
</customwidgets>
<resources/>
<connections/>
</ui>