Add support for configuring an external SPIR-V disassembler in Qt UI

This commit is contained in:
baldurk
2017-02-15 14:02:51 +00:00
parent 11a914bd59
commit 3398fcc9de
9 changed files with 316 additions and 122 deletions
+26 -40
View File
@@ -39,6 +39,16 @@ variantType convertToVariant(const origType &val)
return variantType(val);
}
template <typename variantType, typename innerType>
variantType convertToVariant(const QList<innerType> &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<QString> &val)
{
QVariantList ret;
ret.reserve(val.count());
for(const QString &s : val)
ret.push_back(s);
return ret;
}
template <>
QVariantList convertToVariant(const QList<RemoteHost> &val)
{
QVariantList ret;
ret.reserve(val.count());
for(const RemoteHost &s : val)
ret.push_back(s);
return ret;
}
template <typename origType, typename variantType>
origType convertFromVariant(const variantType &val)
{
return origType(val);
}
template <>
QString convertFromVariant(const QVariant &val)
{
return val.toString();
}
template <typename listType>
listType convertFromVariant(const QList<QVariant> &val)
{
listType ret;
ret.reserve(val.count());
for(const QVariant &s : val)
ret.push_back(convertFromVariant<decltype(ret.value(0))>(s));
return ret;
}
template <>
QStringMap convertFromVariant(const QVariantMap &val)
{
@@ -87,26 +93,6 @@ QStringMap convertFromVariant(const QVariantMap &val)
return ret;
}
template <>
QList<QString> convertFromVariant(const QVariantList &val)
{
QList<QString> ret;
ret.reserve(val.count());
for(const QVariant &s : val)
ret.push_back(s.toString());
return ret;
}
template <>
QList<RemoteHost> convertFromVariant(const QVariantList &val)
{
QList<RemoteHost> 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);
+32
View File
@@ -33,6 +33,36 @@
typedef QMap<QString, QString> 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<QString, QString> QStringMap;
\
CONFIG_SETTING_VAL(public, bool, bool, AllowGlobalHook, false) \
\
CONFIG_SETTING(public, QVariantList, QList<SPIRVDisassembler>, SPIRVDisassemblers) \
\
CONFIG_SETTING(private, QVariantMap, QStringMap, ConfigSettings) \
\
CONFIG_SETTING(private, QVariantList, QList<RemoteHost>, RemoteHostList)
+97
View File
@@ -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)
{
+1
View File
@@ -687,6 +687,7 @@ class QProgressDialog;
typedef std::function<float()> ProgressUpdateMethod;
typedef std::function<bool()> ProgressFinishedMethod;
QStringList ParseArgsList(const QString &args);
bool RunProcessAsAdmin(const QString &fullExecutablePath, const QStringList &params,
std::function<void()> finishedCallback = std::function<void()>());
+27 -17
View File
@@ -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();
}
@@ -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);
+39 -63
View File
@@ -61,7 +61,7 @@
<enum>QTabWidget::West</enum>
</property>
<property name="currentIndex">
<number>5</number>
<number>3</number>
</property>
<property name="documentMode">
<bool>true</bool>
@@ -611,45 +611,7 @@ This option overrides that and will always replay locally if the local context i
<string>Vulkan Shaders</string>
</property>
<layout class="QGridLayout" name="gridLayout_4">
<item row="0" column="0">
<widget class="QLabel" name="label_15">
<property name="toolTip">
<string>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.</string>
</property>
<property name="text">
<string>Use External Disassembler</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QCheckBox" name="ExternalDisassemblerEnabled">
<property name="toolTip">
<string>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.</string>
</property>
<property name="text">
<string/>
</property>
</widget>
</item>
<item row="2" 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="3" column="0">
<widget class="QLabel" name="label_17">
<property name="text">
<string>External Disassembler command line arguments</string>
</property>
</widget>
</item>
<item row="2" column="1">
<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>
@@ -659,14 +621,49 @@ This is useful if you want to disassemble directly to a high level language like
</property>
</widget>
</item>
<item row="1" column="0">
<item row="0" column="0">
<widget class="QLabel" name="label_16">
<property name="text">
<string>External Disassembler executable</string>
</property>
</widget>
</item>
<item row="6" column="0">
<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">
<spacer name="verticalSpacer_4">
<property name="orientation">
<enum>Qt::Vertical</enum>
@@ -679,27 +676,6 @@ This is useful if you want to disassemble directly to a high level language like
</property>
</spacer>
</item>
<item row="4" 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="5" 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>
</layout>
</widget>
</item>
@@ -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 =
@@ -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<ShaderConstant> &vars);
const VulkanPipelineState::ShaderStage *stageForSender(QWidget *widget);
QString disassembleSPIRV(const ShaderReflection *shaderDetails);
template <typename viewType>
void setViewDetails(QTreeWidgetItem *node, const viewType &view, FetchTexture *tex);