mirror of
https://github.com/baldurk/renderdoc.git
synced 2026-05-05 01:20:42 +00:00
Add support for configuring an external SPIR-V disassembler in Qt UI
This commit is contained in:
@@ -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);
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
|
||||
@@ -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 ¶ms,
|
||||
std::function<void()> finishedCallback = std::function<void()>());
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user