From 3c7b420e593282f9ad40da296e74ba9e4983e4f1 Mon Sep 17 00:00:00 2001 From: baldurk Date: Thu, 9 Aug 2018 16:52:14 +0100 Subject: [PATCH] Expand SPIR-V disassemblers to general shader processing tools * Instead of just configuring SPIR-V disassemblers and picking only the first one when we need to edit SPIR-V, we allow setting up any shader processor that goes between two shader encodings. * When editing, the default will still be to use embedded source, and then after that the first tool that goes from the native shader format to a text format, but the drop-down allows you to pick any of them. * Similarly in the shader viewer you can configure the compilation options and method, to choose the compiler you want to use. Embedded command line parameters in the shader are automatically appended. --- .../Code/Interface/PersistantConfig.cpp | 106 +++- qrenderdoc/Code/Interface/PersistantConfig.h | 132 ++++- .../Code/Interface/SPIRVDisassembler.cpp | 146 ------ .../Code/Interface/ShaderProcessingTool.cpp | 247 +++++++++ qrenderdoc/Code/pyrenderdoc/qrenderdoc.i | 2 +- .../Code/pyrenderdoc/qrenderdoc_stub.cpp | 16 +- qrenderdoc/Windows/Dialogs/SettingsDialog.cpp | 489 ++++++++++++------ qrenderdoc/Windows/Dialogs/SettingsDialog.h | 20 +- qrenderdoc/Windows/Dialogs/SettingsDialog.ui | 42 +- .../D3D11PipelineStateViewer.cpp | 40 +- .../PipelineState/D3D11PipelineStateViewer.h | 1 - .../PipelineState/D3D11PipelineStateViewer.ui | 64 ++- .../D3D12PipelineStateViewer.cpp | 38 +- .../PipelineState/D3D12PipelineStateViewer.h | 1 - .../PipelineState/D3D12PipelineStateViewer.ui | 18 + .../PipelineState/GLPipelineStateViewer.cpp | 40 +- .../PipelineState/GLPipelineStateViewer.h | 1 - .../PipelineState/GLPipelineStateViewer.ui | 18 + .../PipelineState/PipelineStateViewer.cpp | 178 ++++++- .../PipelineState/PipelineStateViewer.h | 24 +- .../VulkanPipelineStateViewer.cpp | 68 +-- .../PipelineState/VulkanPipelineStateViewer.h | 1 - .../VulkanPipelineStateViewer.ui | 18 + qrenderdoc/Windows/ShaderViewer.cpp | 207 +++++++- qrenderdoc/Windows/ShaderViewer.h | 6 +- qrenderdoc/Windows/ShaderViewer.ui | 147 +++++- qrenderdoc/qrenderdoc.pro | 2 +- qrenderdoc/qrenderdoc_local.vcxproj | 2 +- qrenderdoc/qrenderdoc_local.vcxproj.filters | 6 +- renderdoc/api/replay/replay_enums.h | 20 +- 30 files changed, 1485 insertions(+), 615 deletions(-) delete mode 100644 qrenderdoc/Code/Interface/SPIRVDisassembler.cpp create mode 100644 qrenderdoc/Code/Interface/ShaderProcessingTool.cpp diff --git a/qrenderdoc/Code/Interface/PersistantConfig.cpp b/qrenderdoc/Code/Interface/PersistantConfig.cpp index 87d7bc958..bd3540127 100644 --- a/qrenderdoc/Code/Interface/PersistantConfig.cpp +++ b/qrenderdoc/Code/Interface/PersistantConfig.cpp @@ -178,6 +178,7 @@ void PersistantConfig::applyValues(const QVariantMap &values) RENAMED_SETTING(QString, LastLogPath, LastCaptureFilePath); RENAMED_SETTING(QVariantList, RecentLogFiles, RecentCaptureFiles); RENAMED_SETTING(QDateTime, DegradedLog_LastUpdate, DegradedCapture_LastUpdate); + RENAMED_SETTING(QVariantList, SPIRVDisassemblers, ShaderProcessors); } int PersistantConfig::RemoteHostCount() @@ -310,25 +311,25 @@ bool PersistantConfig::Load(const rdcstr &filename) RemoteHosts.insert(0, host); } - bool tools[arraydim()] = {}; + bool tools[arraydim()] = {}; // see which known tools are registered - for(const SPIRVDisassembler &dis : SPIRVDisassemblers) + for(const ShaderProcessingTool &dis : ShaderProcessors) { // if it's declared - if(dis.tool != KnownSPIRVTool::Unknown) + if(dis.tool != KnownShaderTool::Unknown) tools[(size_t)dis.tool] = true; - for(KnownSPIRVTool tool : values()) + for(KnownShaderTool tool : values()) { if(QString(dis.executable).contains(ToolExecutable(tool))) tools[(size_t)tool] = true; } } - for(KnownSPIRVTool tool : values()) + for(KnownShaderTool tool : values()) { - if(tool == KnownSPIRVTool::Unknown || tools[(size_t)tool]) + if(tool == KnownShaderTool::Unknown || tools[(size_t)tool]) continue; QString exe = ToolExecutable(tool); @@ -341,14 +342,14 @@ bool PersistantConfig::Load(const rdcstr &filename) if(!path.isEmpty()) { - SPIRVDisassembler dis; - dis.name = ToQStr(tool); + ShaderProcessingTool s; + s.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; + s.executable = exe; + s.tool = tool; - SPIRVDisassemblers.push_back(dis); + ShaderProcessors.push_back(s); continue; } @@ -359,10 +360,15 @@ bool PersistantConfig::Load(const rdcstr &filename) QStringList searchPaths = {appDir.absoluteFilePath(lit("plugins/spirv/"))}; #if defined(Q_OS_WIN64) + // windows local searchPaths << appDir.absoluteFilePath(lit("../../plugins-win64/spirv/")); -#elif defined(Q_OS_WIN64) +#elif defined(Q_OS_WIN32) + // windows local searchPaths << appDir.absoluteFilePath(lit("../../plugins-win32/spirv/")); #elif defined(Q_OS_LINUX) + // linux installation + searchPaths << appDir.absoluteFilePath(lit("../share/renderdoc/plugins/spirv/")); + // linux local searchPaths << appDir.absoluteFilePath(lit("../../plugins-linux64/spirv/")); #endif @@ -372,17 +378,29 @@ bool PersistantConfig::Load(const rdcstr &filename) if(!path.isEmpty()) { - SPIRVDisassembler dis; - dis.name = ToQStr(tool); - dis.executable = path; - dis.tool = tool; + ShaderProcessingTool s; + s.name = ToQStr(tool); + s.executable = path; + s.tool = tool; - SPIRVDisassemblers.push_back(dis); + ShaderProcessors.push_back(s); continue; } } + // sanitisation pass, if a tool is declared as a known type ensure its inputs/outputs are correct. + // This is mostly for backwards compatibility with configs from before the inputs/outputs were + // added. + for(ShaderProcessingTool &dis : ShaderProcessors) + { + if(dis.tool != KnownShaderTool::Unknown) + { + dis.input = ToolInput(dis.tool); + dis.output = ToolOutput(dis.tool); + } + } + return ret; } @@ -467,20 +485,64 @@ rdcstr PersistantConfig::GetConfigSetting(const rdcstr &name) return ""; } -SPIRVDisassembler::SPIRVDisassembler(const QVariant &var) +ShaderProcessingTool::ShaderProcessingTool(const QVariant &var) { QVariantMap map = var.toMap(); if(map.contains(lit("tool"))) - tool = (KnownSPIRVTool)map[lit("tool")].toUInt(); + tool = (KnownShaderTool)map[lit("tool")].toUInt(); if(map.contains(lit("name"))) name = map[lit("name")].toString(); if(map.contains(lit("executable"))) executable = map[lit("executable")].toString(); if(map.contains(lit("args"))) - args = map[lit("args")].toString(); + { + QString a = map[lit("args")].toString(); + + // backwards compatibility + a.replace(lit("{spv_disasm}"), lit("{output_file}")); + a.replace(lit("{spv_bin}"), lit("{input_file}")); + + args = a; + } + + if(map.contains(lit("input"))) + { + input = (ShaderEncoding)map[lit("input")].toUInt(); + } + else + { + // backwards compatibility, it's a SPIR-V disassembler + input = ShaderEncoding::SPIRV; + } + + if(map.contains(lit("output"))) + { + output = (ShaderEncoding)map[lit("output")].toUInt(); + } + else + { + // backwards compatibility, we have to guess, assume GLSL as a sensible default + output = ShaderEncoding::GLSL; + } } -SPIRVDisassembler::operator QVariant() const +rdcstr ShaderProcessingTool::DefaultArguments() const +{ + if(tool == KnownShaderTool::SPIRV_Cross) + return "--output {output_file} {input_file} --vulkan-semantics"; + else if(tool == KnownShaderTool::spirv_dis) + return "--no-color -o {output_file} {input_file}"; + else if(tool == KnownShaderTool::glslangValidatorGLSL) + return "-g -V -o {output_file} {input_file} -S {glsl_stage4}"; + else if(tool == KnownShaderTool::glslangValidatorHLSL) + return "-D -g -V -o {output_file} {input_file} -S {glsl_stage4} -e {entry_point}"; + else if(tool == KnownShaderTool::spirv_as) + return "-o {output_file} {input_file}"; + + return args; +} + +ShaderProcessingTool::operator QVariant() const { QVariantMap map; @@ -488,6 +550,8 @@ SPIRVDisassembler::operator QVariant() const map[lit("name")] = name; map[lit("executable")] = executable; map[lit("args")] = args; + map[lit("input")] = (uint32_t)input; + map[lit("output")] = (uint32_t)output; return map; } diff --git a/qrenderdoc/Code/Interface/PersistantConfig.h b/qrenderdoc/Code/Interface/PersistantConfig.h index 63c973129..1c944485b 100644 --- a/qrenderdoc/Code/Interface/PersistantConfig.h +++ b/qrenderdoc/Code/Interface/PersistantConfig.h @@ -28,7 +28,7 @@ #include "QRDInterface.h" #include "RemoteHost.h" -DOCUMENT(R"(Identifies a particular known SPIR-V tool used for disassembly. +DOCUMENT(R"(Identifies a particular known tool used for shader processing. .. data:: Unknown @@ -41,39 +41,94 @@ DOCUMENT(R"(Identifies a particular known SPIR-V tool used for disassembly. .. data:: spirv_dis `spirv-dis from SPIRV-Tools `_. + +.. data:: glslangValidatorGLSL + + `glslang compiler (GLSL) `_. + +.. data:: glslangValidatorHLSL + + `glslang compiler (HLSL) `_. + +.. data:: spirv_as + + `spirv-as from SPIRV-Tools `_. + )"); -enum class KnownSPIRVTool : uint32_t +enum class KnownShaderTool : uint32_t { Unknown, First = Unknown, SPIRV_Cross, spirv_dis, + glslangValidatorGLSL, + glslangValidatorHLSL, + spirv_as, Count, }; -ITERABLE_OPERATORS(KnownSPIRVTool); +ITERABLE_OPERATORS(KnownShaderTool); -inline rdcstr ToolExecutable(KnownSPIRVTool tool) +inline rdcstr ToolExecutable(KnownShaderTool tool) { - if(tool == KnownSPIRVTool::SPIRV_Cross) + if(tool == KnownShaderTool::SPIRV_Cross) return "spirv-cross"; - else if(tool == KnownSPIRVTool::spirv_dis) + else if(tool == KnownShaderTool::spirv_dis) return "spirv-dis"; + else if(tool == KnownShaderTool::glslangValidatorGLSL) + return "glslangValidator"; + else if(tool == KnownShaderTool::glslangValidatorHLSL) + return "glslangValidator"; + else if(tool == KnownShaderTool::spirv_as) + return "spirv-as"; return ""; } -DOCUMENT("Describes an external program that can be used to disassemble SPIR-V."); -struct SPIRVDisassembler +inline ShaderEncoding ToolInput(KnownShaderTool tool) +{ + if(tool == KnownShaderTool::SPIRV_Cross || tool == KnownShaderTool::spirv_dis) + return ShaderEncoding::SPIRV; + else if(tool == KnownShaderTool::glslangValidatorGLSL) + return ShaderEncoding::GLSL; + else if(tool == KnownShaderTool::glslangValidatorHLSL) + return ShaderEncoding::HLSL; + else if(tool == KnownShaderTool::spirv_as) + return ShaderEncoding::SPIRVAsm; + + return ShaderEncoding::Unknown; +} + +inline ShaderEncoding ToolOutput(KnownShaderTool tool) +{ + if(tool == KnownShaderTool::SPIRV_Cross) + return ShaderEncoding::GLSL; + else if(tool == KnownShaderTool::spirv_dis) + return ShaderEncoding::SPIRVAsm; + else if(tool == KnownShaderTool::glslangValidatorGLSL || + tool == KnownShaderTool::glslangValidatorHLSL || tool == KnownShaderTool::spirv_as) + return ShaderEncoding::SPIRV; + + return ShaderEncoding::Unknown; +} + +DOCUMENT(R"(Describes an external program that can be used to process shaders, typically either +compiling from a high-level language to a binary format, or decompiling from the binary format to +a high-level language or textual representation. + +Commonly used with SPIR-V. +)"); +struct ShaderProcessingTool { DOCUMENT(""); - SPIRVDisassembler() = default; - VARIANT_CAST(SPIRVDisassembler); - bool operator==(const SPIRVDisassembler &o) const + ShaderProcessingTool() = default; + VARIANT_CAST(ShaderProcessingTool); + bool operator==(const ShaderProcessingTool &o) const { - return tool == o.tool && name == o.name && executable == o.executable && args == o.args; + return tool == o.tool && name == o.name && executable == o.executable && args == o.args && + input == o.input && output == o.output; } - bool operator<(const SPIRVDisassembler &o) const + bool operator<(const ShaderProcessingTool &o) const { if(tool != o.tool) return tool < o.tool; @@ -83,28 +138,61 @@ struct SPIRVDisassembler return executable < o.executable; if(args != o.args) return args < o.args; + if(input != o.input) + return input < o.input; + if(output != o.output) + return output < o.output; return false; } - DOCUMENT("The :class:`KnownSPIRVTool` identifying which known tool this disassembler is."); - KnownSPIRVTool tool = KnownSPIRVTool::Unknown; + DOCUMENT("The :class:`KnownShaderTool` identifying which known tool this program is."); + KnownShaderTool tool = KnownShaderTool::Unknown; DOCUMENT("The human-readable name of the program."); rdcstr name; DOCUMENT("The path to the executable to run for this program."); rdcstr executable; DOCUMENT("The command line argmuents to pass to the program."); rdcstr args; + DOCUMENT("The input that this program expects."); + ShaderEncoding input = ShaderEncoding::Unknown; + DOCUMENT("The output that this program provides."); + ShaderEncoding output = ShaderEncoding::Unknown; - DOCUMENT(R"(Runs this disassembler for a given shader reflection. + DOCUMENT(R"(Return the default arguments used when invoking this tool + +:return: The arguments specified for this tool. +:rtype: ``str`` +)"); + rdcstr DefaultArguments() const; + + DOCUMENT(R"(Runs this program to disassemble a given shader reflection. :param QWidget window: A handle to the window to use when showing a progress bar or error messages. :param ~renderdoc.ShaderReflection shader: The shader to disassemble. +:param str args: arguments to pass to the tool. The default arguments can be obtained using + :meth:`DefaultArguments` which can then be customised as desired. Passing an empty string uses the + default arguments. :return: The disassembly, or an empty string if something went wrong. :rtype: ``str`` )"); - rdcstr DisassembleShader(QWidget *window, const ShaderReflection *reflection) const; + rdcstr DisassembleShader(QWidget *window, const ShaderReflection *reflection, rdcstr args) const; + + DOCUMENT(R"(Runs this program to disassemble a given shader source. + +:param QWidget window: A handle to the window to use when showing a progress bar or error messages. +:param str source: The source code, preprocessed into a single file. +:param str entryPoint: The name of the entry point in the shader to compile. +:param ~renderdoc.ShaderStage stage: The pipeline stage that this shader represents. +:param str args: arguments to pass to the tool. The default arguments can be obtained using + :meth:`DefaultArguments` which can then be customised as desired. Passing an empty string uses the + default arguments. +:return: The compiled shader code, or an empty buffer if something went wrong. +:rtype: ``bytes`` +)"); + bytebuf CompileShader(QWidget *window, rdcstr source, rdcstr entryPoint, ShaderStage stage, + rdcstr args) const; }; -DECLARE_REFLECTION_STRUCT(SPIRVDisassembler); +DECLARE_REFLECTION_STRUCT(ShaderProcessingTool); #define BUGREPORT_URL "https://renderdoc.org/bugreporter" @@ -239,7 +327,7 @@ DECLARE_REFLECTION_STRUCT(BugReport); \ CONFIG_SETTING_VAL(public, bool, bool, AllowGlobalHook, false) \ \ - CONFIG_SETTING(public, QVariantList, rdcarray, SPIRVDisassemblers) \ + CONFIG_SETTING(public, QVariantList, rdcarray, ShaderProcessors) \ \ CONFIG_SETTING_VAL(public, bool, bool, Analytics_TotalOptOut, false) \ \ @@ -548,10 +636,10 @@ For more information about some of these settings that are user-facing see Defaults to ``False``. -.. data:: SPIRVDisassemblers +.. data:: ShaderProcessors - A list of :class:`SPIRVDisassembler` detailing the potential disassembler programs. The first one - in the list is the default. + A list of :class:`ShaderProcessingTool` detailing shader processing programs. The list comes in + priority order, with earlier processors preferred over later ones. .. data:: Analytics_TotalOptOut diff --git a/qrenderdoc/Code/Interface/SPIRVDisassembler.cpp b/qrenderdoc/Code/Interface/SPIRVDisassembler.cpp deleted file mode 100644 index ab2a8198f..000000000 --- a/qrenderdoc/Code/Interface/SPIRVDisassembler.cpp +++ /dev/null @@ -1,146 +0,0 @@ -/****************************************************************************** - * The MIT License (MIT) - * - * Copyright (c) 2016-2018 Baldur Karlsson - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - ******************************************************************************/ - -#include -#include -#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()) - return ""; - - QString spv_bin_file = QDir(QDir::tempPath()).absoluteFilePath(lit("spv_bin.spv")); - - QFile binHandle(spv_bin_file); - if(binHandle.open(QFile::WriteOnly | QIODevice::Truncate)) - { - binHandle.write( - QByteArray((const char *)shaderDetails->rawBytes.data(), shaderDetails->rawBytes.count())); - binHandle.close(); - } - else - { - RDDialog::critical( - window, QApplication::translate("SPIRVDisassembler", "Error writing temp file"), - QApplication::translate("SPIRVDisassembler", "Couldn't write temporary SPIR-V file %1.") - .arg(spv_bin_file)); - return ""; - } - - 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"), - QApplication::translate( - "SPIRVDisassembler", - "Please use {spv_bin} in the disassembler arguments to specify the input file.")); - return ""; - } - - QString glsl; - - 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 = programArguments; - - bool writesToFile = expandedargs.contains(lit("{spv_disas}")); - - expandedargs.replace(lit("{spv_bin}"), spv_bin_file); - expandedargs.replace(lit("{spv_disas}"), spv_disas_file); - - QStringList argList = ParseArgsList(expandedargs); - - QProcess process; - process.start(executable, argList); - process.waitForFinished(); - - if(process.exitStatus() != QProcess::NormalExit || process.exitCode() != 0) - { - if(window) - { - GUIInvoke::call(window, [window]() { - RDDialog::critical( - window, QApplication::translate("SPIRVDisassembler", "Error running disassembler"), - QApplication::translate( - "SPIRVDisassembler", - "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(window, QApplication::translate("SPIRVDisassembler", - "Please wait - running external disassembler"), - [thread]() { return !thread->isRunning(); }); - - thread->deleteLater(); - - return glsl; -} diff --git a/qrenderdoc/Code/Interface/ShaderProcessingTool.cpp b/qrenderdoc/Code/Interface/ShaderProcessingTool.cpp new file mode 100644 index 000000000..2011783a8 --- /dev/null +++ b/qrenderdoc/Code/Interface/ShaderProcessingTool.cpp @@ -0,0 +1,247 @@ +/****************************************************************************** + * The MIT License (MIT) + * + * Copyright (c) 2016-2018 Baldur Karlsson + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + ******************************************************************************/ + +#include +#include +#include "Code/QRDUtils.h" +#include "QRDInterface.h" + +template <> +std::string DoStringise(const KnownShaderTool &el) +{ + BEGIN_ENUM_STRINGISE(KnownShaderTool); + { + STRINGISE_ENUM_CLASS_NAMED(Unknown, "Custom Tool"); + STRINGISE_ENUM_CLASS_NAMED(SPIRV_Cross, "SPIRV-Cross"); + STRINGISE_ENUM_CLASS_NAMED(spirv_dis, "spirv-dis"); + STRINGISE_ENUM_CLASS_NAMED(glslangValidatorGLSL, "glslang (GLSL)"); + STRINGISE_ENUM_CLASS_NAMED(glslangValidatorHLSL, "glslang (HLSL)"); + STRINGISE_ENUM_CLASS_NAMED(spirv_as, "spirv-as"); + } + END_ENUM_STRINGISE(); +} + +rdcstr ShaderProcessingTool::DisassembleShader(QWidget *window, const ShaderReflection *shaderDetails, + rdcstr arguments) const +{ + if(executable.isEmpty()) + return ""; + + QString input_file = QDir(QDir::tempPath()).absoluteFilePath(lit("shader_input")); + QString output_file = QDir(QDir::tempPath()).absoluteFilePath(lit("shader_output")); + + QFile binHandle(input_file); + if(binHandle.open(QFile::WriteOnly | QIODevice::Truncate)) + { + binHandle.write( + QByteArray((const char *)shaderDetails->rawBytes.data(), shaderDetails->rawBytes.count())); + binHandle.close(); + } + else + { + RDDialog::critical( + window, QApplication::translate("ShaderProcessingTool", "Error writing temp file"), + QApplication::translate("ShaderProcessingTool", "Couldn't write temporary file %1.") + .arg(input_file)); + return ""; + } + + QString programArguments = arguments; + + if(programArguments.isEmpty()) + programArguments = DefaultArguments(); + + if(!programArguments.contains(lit("{input_file}"))) + { + RDDialog::critical( + window, QApplication::translate("ShaderProcessingTool", "Wrongly configured tool"), + QApplication::translate( + "ShaderProcessingTool", + "Please use {input_file} in the tool arguments to specify the input file.")); + return ""; + } + + QString outputData; + + QString expandedargs = programArguments; + + bool writesToFile = expandedargs.contains(lit("{output_file}")); + + expandedargs.replace(lit("{input_file}"), input_file); + expandedargs.replace(lit("{output_file}"), output_file); + + QStringList argList = ParseArgsList(expandedargs); + + LambdaThread *thread = + new LambdaThread([this, window, &outputData, argList, input_file, output_file, writesToFile]() { + QProcess process; + process.start(executable, argList); + process.waitForFinished(); + + if(process.exitStatus() != QProcess::NormalExit || process.exitCode() != 0) + { + if(window) + { + GUIInvoke::call(window, [window]() { + RDDialog::critical( + window, QApplication::translate("ShaderProcessingTool", "Error running tool"), + QApplication::translate( + "ShaderProcessingTool", + "There was an error invoking the external shader processing tool.")); + }); + } + } + + if(writesToFile) + { + QFile outputHandle(output_file); + if(outputHandle.open(QFile::ReadOnly)) + { + outputData = QString::fromUtf8(outputHandle.readAll()); + outputHandle.close(); + } + } + else + { + outputData = QString::fromUtf8(process.readAll()); + } + + QFile::remove(input_file); + QFile::remove(output_file); + }); + thread->start(); + + ShowProgressDialog(window, QApplication::translate("ShaderProcessingTool", + "Please wait - running external tool"), + [thread]() { return !thread->isRunning(); }); + + thread->deleteLater(); + + return outputData; +} + +bytebuf ShaderProcessingTool::CompileShader(QWidget *window, rdcstr source, rdcstr entryPoint, + ShaderStage stage, rdcstr arguments) const +{ + if(executable.isEmpty()) + return ""; + + QString input_file = QDir(QDir::tempPath()).absoluteFilePath(lit("shader_input")); + QString output_file = QDir(QDir::tempPath()).absoluteFilePath(lit("shader_output")); + + QFile binHandle(input_file); + if(binHandle.open(QFile::WriteOnly | QIODevice::Truncate)) + { + binHandle.write(QByteArray((const char *)source.c_str(), source.count())); + binHandle.close(); + } + else + { + RDDialog::critical( + window, QApplication::translate("ShaderProcessingTool", "Error writing temp file"), + QApplication::translate("ShaderProcessingTool", "Couldn't write temporary file %1.") + .arg(input_file)); + return ""; + } + + QString programArguments = arguments; + + if(programArguments.isEmpty()) + programArguments = DefaultArguments(); + + if(!programArguments.contains(lit("{input_file}"))) + { + RDDialog::critical( + window, QApplication::translate("ShaderProcessingTool", "Wrongly configured tool"), + QApplication::translate( + "ShaderProcessingTool", + "Please use {input_file} in the tool arguments to specify the input file.")); + return ""; + } + + bytebuf outputData; + + QString expandedargs = programArguments; + + bool writesToFile = expandedargs.contains(lit("{output_file}")); + + expandedargs.replace(lit("{input_file}"), input_file); + expandedargs.replace(lit("{entry_point}"), entryPoint); + expandedargs.replace(lit("{output_file}"), output_file); + + const QString glsl_stage4[ENUM_ARRAY_SIZE(ShaderStage)] = { + lit("vert"), lit("tesc"), lit("tese"), lit("geom"), lit("frag"), lit("comp"), + }; + + expandedargs.replace(lit("{glsl_stage4}"), glsl_stage4[int(stage)]); + + QStringList argList = ParseArgsList(expandedargs); + + LambdaThread *thread = + new LambdaThread([this, window, &outputData, argList, input_file, output_file, writesToFile]() { + QProcess process; + process.start(executable, argList); + process.waitForFinished(); + + if(process.exitStatus() != QProcess::NormalExit || process.exitCode() != 0) + { + if(window) + { + GUIInvoke::call(window, [window]() { + RDDialog::critical( + window, QApplication::translate("ShaderProcessingTool", "Error running tool"), + QApplication::translate( + "ShaderProcessingTool", + "There was an error invoking the external shader processing tool.")); + }); + } + } + + if(writesToFile) + { + QFile outputHandle(output_file); + if(outputHandle.open(QFile::ReadOnly)) + { + outputData = outputHandle.readAll(); + outputHandle.close(); + } + } + else + { + outputData = process.readAll(); + } + + QFile::remove(input_file); + QFile::remove(output_file); + }); + thread->start(); + + ShowProgressDialog(window, QApplication::translate("ShaderProcessingTool", + "Please wait - running external tool"), + [thread]() { return !thread->isRunning(); }); + + thread->deleteLater(); + + return outputData; +} diff --git a/qrenderdoc/Code/pyrenderdoc/qrenderdoc.i b/qrenderdoc/Code/pyrenderdoc/qrenderdoc.i index 4aa9cffce..ca7125ee4 100644 --- a/qrenderdoc/Code/pyrenderdoc/qrenderdoc.i +++ b/qrenderdoc/Code/pyrenderdoc/qrenderdoc.i @@ -80,7 +80,7 @@ TEMPLATE_ARRAY_DECLARE(rdcarray); DOCUMENT(""); TEMPLATE_ARRAY_INSTANTIATE(rdcarray, EventBookmark) -TEMPLATE_ARRAY_INSTANTIATE(rdcarray, SPIRVDisassembler) +TEMPLATE_ARRAY_INSTANTIATE(rdcarray, ShaderProcessingTool) TEMPLATE_ARRAY_INSTANTIATE(rdcarray, rdcstrpair) TEMPLATE_ARRAY_INSTANTIATE(rdcarray, BugReport) TEMPLATE_ARRAY_INSTANTIATE_PTR(rdcarray, ICaptureViewer) diff --git a/qrenderdoc/Code/pyrenderdoc/qrenderdoc_stub.cpp b/qrenderdoc/Code/pyrenderdoc/qrenderdoc_stub.cpp index 5cb5d206d..633dabfd8 100644 --- a/qrenderdoc/Code/pyrenderdoc/qrenderdoc_stub.cpp +++ b/qrenderdoc/Code/pyrenderdoc/qrenderdoc_stub.cpp @@ -69,14 +69,26 @@ extern "C" PyObject *QWidgetToPy(QWidget *widget) } //////////////////////////////////////////////////////////////////////////////// -// SPIRVDisassembler.cpp stubs +// ShaderProcessingTool.cpp stubs //////////////////////////////////////////////////////////////////////////////// -rdcstr SPIRVDisassembler::DisassembleShader(QWidget *window, const ShaderReflection *shaderDetails) const +rdcstr ShaderProcessingTool::DefaultArguments() const { return ""; } +rdcstr ShaderProcessingTool::DisassembleShader(QWidget *window, const ShaderReflection *shaderDetails, + rdcstr arguments) const +{ + return ""; +} + +bytebuf ShaderProcessingTool::CompileShader(QWidget *window, rdcstr source, rdcstr entryPoint, + ShaderStage stage, rdcstr arguments) const +{ + return bytebuf(); +} + //////////////////////////////////////////////////////////////////////////////// // PersistantConfig.cpp stubs //////////////////////////////////////////////////////////////////////////////// diff --git a/qrenderdoc/Windows/Dialogs/SettingsDialog.cpp b/qrenderdoc/Windows/Dialogs/SettingsDialog.cpp index 31e8121c7..e6c54d211 100644 --- a/qrenderdoc/Windows/Dialogs/SettingsDialog.cpp +++ b/qrenderdoc/Windows/Dialogs/SettingsDialog.cpp @@ -24,6 +24,8 @@ #include "SettingsDialog.h" #include +#include +#include #include "Code/Interface/QRDInterface.h" #include "Code/QRDUtils.h" #include "Styles/StyleData.h" @@ -31,61 +33,6 @@ #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) { @@ -135,25 +82,22 @@ SettingsDialog::SettingsDialog(ICaptureContext &ctx, QWidget *parent) ui->saveDirectory->setText(m_Ctx.Config().DefaultCaptureSaveDirectory); ui->tempDirectory->setText(m_Ctx.Config().TemporaryCaptureDirectory); - ui->disassemblers->setColumnCount(3); - ui->disassemblers->setHorizontalHeaderLabels(QStringList() << tr("Tool") << tr("Executable") - << tr("Arguments")); + ui->shaderTools->setColumnCount(2); + ui->shaderTools->setHorizontalHeaderLabels(QStringList() << tr("Tool") << tr("Process")); - ui->disassemblers->horizontalHeader()->setSectionResizeMode(0, QHeaderView::Interactive); - ui->disassemblers->horizontalHeader()->setSectionResizeMode(1, QHeaderView::Interactive); - ui->disassemblers->horizontalHeader()->setSectionResizeMode(2, QHeaderView::Stretch); + ui->shaderTools->horizontalHeader()->setSectionResizeMode(0, QHeaderView::Interactive); + ui->shaderTools->horizontalHeader()->setSectionResizeMode(1, QHeaderView::Stretch); - for(const SPIRVDisassembler &disasm : m_Ctx.Config().SPIRVDisassemblers) - addDisassembler(disasm); + for(const ShaderProcessingTool &tool : m_Ctx.Config().ShaderProcessors) + addProcessor(tool); - ui->disassemblers->horizontalHeader()->resizeSection(0, 100); + ui->shaderTools->horizontalHeader()->resizeSection(0, 100); - ui->disassemblers->verticalHeader()->setSectionsMovable(true); - ui->disassemblers->verticalHeader()->setMinimumWidth(20); + ui->shaderTools->verticalHeader()->setSectionsMovable(true); + ui->shaderTools->verticalHeader()->setMinimumWidth(20); - ui->disassemblers->setItemDelegateForColumn(0, new KnownSPIRVToolDelegate(this)); - - ui->deleteDisasm->setEnabled(false); + ui->deleteShaderTool->setEnabled(false); + ui->editShaderTool->setEnabled(false); ui->ExternalTool_RadeonGPUProfiler->setText(m_Ctx.Config().ExternalTool_RadeonGPUProfiler); @@ -230,8 +174,8 @@ SettingsDialog::SettingsDialog(ICaptureContext &ctx, QWidget *parent) m_Init = false; - QObject::connect(ui->disassemblers->verticalHeader(), &QHeaderView::sectionMoved, this, - &SettingsDialog::disassemblers_rowMoved); + QObject::connect(ui->shaderTools->verticalHeader(), &QHeaderView::sectionMoved, this, + &SettingsDialog::shaderTools_rowMoved); QObject::connect(ui->Formatter_MinFigures, OverloadedSlot::of(&QSpinBox::valueChanged), this, &SettingsDialog::formatter_valueChanged); QObject::connect(ui->Formatter_MaxFigures, OverloadedSlot::of(&QSpinBox::valueChanged), this, @@ -488,144 +432,349 @@ void SettingsDialog::on_ShaderViewer_FriendlyNaming_toggled(bool checked) m_Ctx.Config().Save(); } -void SettingsDialog::addDisassembler(const SPIRVDisassembler &disasm) +void SettingsDialog::addProcessor(const ShaderProcessingTool &tool) { - // prevent calling cellChanged - m_AddingDisassembler = true; + int row = ui->shaderTools->rowCount(); + ui->shaderTools->insertRow(row); - int row = ui->disassemblers->rowCount(); - ui->disassemblers->insertRow(row); + ui->shaderTools->setVerticalHeaderItem(row, new QTableWidgetItem(QString())); - ui->disassemblers->setVerticalHeaderItem(row, new QTableWidgetItem(QString())); + ui->shaderTools->setItem(row, 0, new QTableWidgetItem(tool.name)); + ui->shaderTools->setItem( + row, 1, + new QTableWidgetItem(QFormatStr("%1 -> %2").arg(ToQStr(tool.input)).arg(ToQStr(tool.output)))); +} - ui->disassemblers->setItem(row, 0, new QTableWidgetItem(disasm.name)); - ui->disassemblers->setItem(row, 1, new QTableWidgetItem(disasm.executable)); +bool SettingsDialog::editTool(int existing, ShaderProcessingTool &tool) +{ + QDialog dialog; + dialog.setWindowFlags(dialog.windowFlags() & ~Qt::WindowContextHelpButtonHint); - QTableWidgetItem *item = new QTableWidgetItem( - disasm.tool == KnownSPIRVTool::Unknown ? QString(disasm.args) : tr("Automatic")); - ui->disassemblers->setItem(row, 2, item); + dialog.resize(400, 0); - // make arguments non-editable for built-in tools - if(disasm.tool != KnownSPIRVTool::Unknown) + QGridLayout grid(&dialog); + + QLabel *lab; + + lab = new QLabel(tr("Name:"), &dialog); + lab->setAlignment(Qt::AlignRight | Qt::AlignTop); + grid.addWidget(lab, 0, 0, 1, 1); + + lab = new QLabel(tr("Tool Type:"), &dialog); + lab->setAlignment(Qt::AlignRight | Qt::AlignTop); + grid.addWidget(lab, 1, 0, 1, 1); + + lab = new QLabel(tr("Executable:"), &dialog); + lab->setAlignment(Qt::AlignRight | Qt::AlignTop); + grid.addWidget(lab, 2, 0, 1, 1); + + lab = new QLabel(tr("Command Line:"), &dialog); + lab->setAlignment(Qt::AlignRight | Qt::AlignTop); + grid.addWidget(lab, 3, 0, 1, 1); + + lab = new QLabel(tr("Input/Output:"), &dialog); + lab->setAlignment(Qt::AlignRight | Qt::AlignTop); + grid.addWidget(lab, 4, 0, 1, 1); + + QLineEdit nameEdit; + nameEdit.setPlaceholderText(lit("Tool Name")); + nameEdit.setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed); + nameEdit.setMinimumHeight(20); + + QStringList strs; + + for(KnownShaderTool t : values()) { - Qt::ItemFlags flags = item->flags() & ~Qt::ItemIsEditable; - item->setFlags(flags); + if(t == KnownShaderTool::Unknown) + strs << tr("Custom Tool"); + else + strs << ToQStr(t); } - m_AddingDisassembler = false; -} + QComboBox toolEdit; + toolEdit.addItems(strs); + toolEdit.setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed); -void SettingsDialog::on_addDisasm_clicked() -{ - 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); + QHBoxLayout executableLayout; - addDisassembler(disasm); + QLineEdit executableEdit; + executableEdit.setPlaceholderText(lit("tool")); + executableEdit.setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed); + executableEdit.setMinimumHeight(20); + QToolButton executableBrowse; + executableBrowse.setText(lit("...")); + executableBrowse.setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Fixed); - m_Ctx.Config().Save(); -} + executableLayout.addWidget(&executableEdit); + executableLayout.addWidget(&executableBrowse); -void SettingsDialog::on_deleteDisasm_clicked() -{ - int row = -1; + QTextEdit argsEdit; + argsEdit.setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); + argsEdit.setMinimumHeight(80); - QModelIndexList selected = ui->disassemblers->selectionModel()->selectedRows(); + strs.clear(); - 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) + for(ShaderEncoding enc : values()) { - ui->disassemblers->removeRow(row); - m_Ctx.Config().SPIRVDisassemblers.erase(row); + if(enc == ShaderEncoding::Unknown) + continue; + else + strs << ToQStr(enc); + } + + QHBoxLayout inputOutputLayout; + + QComboBox inputEdit; + inputEdit.addItems(strs); + + QComboBox outputEdit; + outputEdit.addItems(strs); + + inputOutputLayout.addWidget(&inputEdit); + inputOutputLayout.addWidget(&outputEdit); + + grid.addWidget(&nameEdit, 0, 1, 1, 1); + grid.addWidget(&toolEdit, 1, 1, 1, 1); + grid.addLayout(&executableLayout, 2, 1, 1, 1); + grid.addWidget(&argsEdit, 3, 1, 1, 1); + grid.addLayout(&inputOutputLayout, 4, 1, 1, 1); + + QDialogButtonBox buttons; + buttons.setStandardButtons(QDialogButtonBox::Ok | QDialogButtonBox::Cancel); + grid.addWidget(&buttons, 5, 0, 1, 2); + + QObject::connect(&buttons, &QDialogButtonBox::accepted, &dialog, &QDialog::accept); + QObject::connect(&buttons, &QDialogButtonBox::rejected, &dialog, &QDialog::reject); + + QObject::connect(&executableBrowse, &QToolButton::clicked, [&]() { + QString initDir; + + QFileInfo f(executableEdit.text()); + QDir dir = f.dir(); + if(f.isAbsolute() && dir.exists()) + { + initDir = dir.absolutePath(); + } + + QString filename = RDDialog::getExecutableFileName(this, tr("Choose executable"), initDir); + + if(!filename.isEmpty()) + executableEdit.setText(filename); + }); + + QObject::connect(&toolEdit, OverloadedSlot::of(&QComboBox::currentIndexChanged), + [&](int index) { + if(index > 0) + { + KnownShaderTool tool = KnownShaderTool(index); + + // -1 because we skip ShaderEncoding::Unknown + inputEdit.setCurrentIndex(int(ToolInput(tool)) - 1); + outputEdit.setCurrentIndex(int(ToolOutput(tool)) - 1); + argsEdit.setEnabled(false); + inputEdit.setEnabled(false); + outputEdit.setEnabled(false); + } + else + { + argsEdit.setEnabled(true); + inputEdit.setEnabled(true); + outputEdit.setEnabled(true); + } + }); + + // -1 because we skip ShaderEncoding::Unknown + inputEdit.setCurrentIndex(int(tool.input) - 1); + outputEdit.setCurrentIndex(int(tool.output) - 1); + executableEdit.setText(tool.executable); + argsEdit.setText(tool.args); + nameEdit.setText(tool.name); + toolEdit.setCurrentIndex(int(tool.tool)); + + bool invalid = false; + + do + { + RDDialog::show(&dialog); + + // don't validate if they cancelled + if(dialog.result() != QDialog::Accepted) + return false; + + tool.tool = KnownShaderTool(toolEdit.currentIndex()); + tool.name = nameEdit.text(); + tool.executable = executableEdit.text(); + tool.args = argsEdit.toPlainText(); + // +1 because we skip ShaderEncoding::Unknown + tool.input = ShaderEncoding(inputEdit.currentIndex() + 1); + tool.output = ShaderEncoding(outputEdit.currentIndex() + 1); + + QString message; + + // ensure we don't have an invalid name + if(tool.name == "Builtin") + { + invalid = true; + message = tr("'Builtin' is a reserved tool name, please select another."); + } + else if(tool.name.isEmpty()) + { + invalid = true; + message = tr("No tool name specified."); + } + else if(tool.executable.isEmpty()) + { + invalid = true; + message = tr("No tool executable selected."); + } + else if(tool.input == ShaderEncoding::Unknown) + { + invalid = true; + message = tr("Input type cannot be unknown."); + } + else if(tool.output == ShaderEncoding::Unknown) + { + invalid = true; + message = tr("Output type cannot be unknown."); + } + else if(tool.tool == KnownShaderTool::Unknown && + !QString(tool.args).contains(lit("{input_file}"))) + { + invalid = true; + message = tr("Custom tool arguments must include at least {input_file}."); + } + else + { + for(int i = 0; i < m_Ctx.Config().ShaderProcessors.count(); i++) + { + if(i == existing) + continue; + + if(tool.name == m_Ctx.Config().ShaderProcessors[i].name) + { + invalid = true; + message = tr("There's already a tool named '%1', please select another.").arg(tool.name); + break; + } + } + } + + if(invalid) + { + RDDialog::critical(this, tr("Invalid parameters specified"), message); + } + } while(invalid); + + return true; +} + +void SettingsDialog::on_addShaderTool_clicked() +{ + ShaderProcessingTool tool; + // start with example arguments + tool.args = lit("--input {input_file} --output {output_file} --mode foo"); + // impossible to pick a single default, but at least show the principle. + tool.input = ShaderEncoding::HLSL; + tool.output = ShaderEncoding::SPIRV; + + bool success = editTool(-1, tool); + + if(success) + { + m_Ctx.Config().ShaderProcessors.push_back(tool); + + addProcessor(tool); m_Ctx.Config().Save(); } } -void SettingsDialog::on_disassemblers_itemSelectionChanged() +void SettingsDialog::on_editShaderTool_clicked() { - ui->deleteDisasm->setEnabled(!ui->disassemblers->selectionModel()->selectedIndexes().empty()); -} + int row = -1; -void SettingsDialog::on_disassemblers_cellChanged(int row, int column) -{ - if(m_AddingDisassembler || row < 0 || row >= m_Ctx.Config().SPIRVDisassemblers.count()) + QModelIndexList selected = ui->shaderTools->selectionModel()->selectedRows(); + + if(!selected.isEmpty()) + row = selected[0].row(); + + if(row < 0 || row >= m_Ctx.Config().ShaderProcessors.count()) return; - SPIRVDisassembler &disasm = m_Ctx.Config().SPIRVDisassemblers[row]; + ShaderProcessingTool tool = m_Ctx.Config().ShaderProcessors[row]; - QString cellData = ui->disassemblers->item(row, column)->text(); + bool success = editTool(row, tool); - if(column == 0) + if(success) { - 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); - } + ui->shaderTools->setItem(row, 0, new QTableWidgetItem(tool.name)); + ui->shaderTools->setItem( + row, 1, new QTableWidgetItem( + QFormatStr("%1 -> %2").arg(ToQStr(tool.input)).arg(ToQStr(tool.output)))); + m_Ctx.Config().ShaderProcessors[row] = tool; + m_Ctx.Config().Save(); } - else if(column == 1) - { - disasm.executable = cellData; - } - else if(column == 2) - { - disasm.args = cellData; - } - - m_Ctx.Config().Save(); } -void SettingsDialog::on_disassemblers_keyPress(QKeyEvent *event) +void SettingsDialog::on_deleteShaderTool_clicked() +{ + int row = -1; + + QModelIndexList selected = ui->shaderTools->selectionModel()->selectedRows(); + + if(!selected.isEmpty()) + row = selected[0].row(); + + if(row < 0 || row >= m_Ctx.Config().ShaderProcessors.count()) + return; + + const ShaderProcessingTool &tool = m_Ctx.Config().ShaderProcessors[row]; + + QMessageBox::StandardButton res = RDDialog::question( + this, tr("Are you sure?"), tr("Are you sure you want to delete '%1'?").arg(tool.name), + QMessageBox::Yes | QMessageBox::No | QMessageBox::Cancel); + + if(res == QMessageBox::Yes) + { + ui->shaderTools->removeRow(row); + m_Ctx.Config().ShaderProcessors.erase(row); + + m_Ctx.Config().Save(); + } +} + +void SettingsDialog::on_shaderTools_itemSelectionChanged() +{ + ui->deleteShaderTool->setEnabled(!ui->shaderTools->selectionModel()->selectedIndexes().empty()); + ui->editShaderTool->setEnabled(ui->deleteShaderTool->isEnabled()); +} + +void SettingsDialog::on_shaderTools_keyPress(QKeyEvent *event) { if(event->key() == Qt::Key_Delete) { - ui->deleteDisasm->click(); + ui->deleteShaderTool->click(); + } + if(event->key() == Qt::Key_Enter || event->key() == Qt::Key_Return) + { + ui->editShaderTool->click(); } } -void SettingsDialog::disassemblers_rowMoved(int logicalIndex, int oldVisualIndex, int newVisualIndex) +void SettingsDialog::on_shaderTools_itemDoubleClicked(QTableWidgetItem *item) { - if(oldVisualIndex < 0 || oldVisualIndex >= m_Ctx.Config().SPIRVDisassemblers.count() || - newVisualIndex < 0 || newVisualIndex >= m_Ctx.Config().SPIRVDisassemblers.count()) + ui->editShaderTool->click(); +} + +void SettingsDialog::shaderTools_rowMoved(int logicalIndex, int oldVisualIndex, int newVisualIndex) +{ + if(oldVisualIndex < 0 || oldVisualIndex >= m_Ctx.Config().ShaderProcessors.count() || + newVisualIndex < 0 || newVisualIndex >= m_Ctx.Config().ShaderProcessors.count()) return; - SPIRVDisassembler disasm = m_Ctx.Config().SPIRVDisassemblers.at(oldVisualIndex); - m_Ctx.Config().SPIRVDisassemblers.erase(oldVisualIndex); - m_Ctx.Config().SPIRVDisassemblers.insert(newVisualIndex, disasm); + ShaderProcessingTool tool = m_Ctx.Config().ShaderProcessors.at(oldVisualIndex); + m_Ctx.Config().ShaderProcessors.erase(oldVisualIndex); + m_Ctx.Config().ShaderProcessors.insert(newVisualIndex, tool); m_Ctx.Config().Save(); } @@ -801,4 +950,4 @@ void SettingsDialog::on_UIStyle_currentIndexChanged(int index) } m_Ctx.Config().Save(); -} +} \ No newline at end of file diff --git a/qrenderdoc/Windows/Dialogs/SettingsDialog.h b/qrenderdoc/Windows/Dialogs/SettingsDialog.h index 04f0821ff..b59b57ac5 100644 --- a/qrenderdoc/Windows/Dialogs/SettingsDialog.h +++ b/qrenderdoc/Windows/Dialogs/SettingsDialog.h @@ -31,8 +31,9 @@ namespace Ui class SettingsDialog; } +class QTableWidgetItem; class QListWidgetItem; -struct SPIRVDisassembler; +struct ShaderProcessingTool; struct ICaptureContext; @@ -78,12 +79,13 @@ private slots: // shader viewer void on_ShaderViewer_FriendlyNaming_toggled(bool checked); - 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); + void on_addShaderTool_clicked(); + void on_editShaderTool_clicked(); + void on_deleteShaderTool_clicked(); + void on_shaderTools_itemSelectionChanged(); + void on_shaderTools_keyPress(QKeyEvent *event); + void on_shaderTools_itemDoubleClicked(QTableWidgetItem *item); + void shaderTools_rowMoved(int logicalIndex, int oldVisualIndex, int newVisualIndex); // event browser void on_EventBrowser_TimeUnit_currentIndexChanged(int index); @@ -112,9 +114,9 @@ private slots: private: Ui::SettingsDialog *ui; - void addDisassembler(const SPIRVDisassembler &disasm); + void addProcessor(const ShaderProcessingTool &disasm); + bool editTool(int existing, ShaderProcessingTool &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 e507eac0f..bfa84f795 100644 --- a/qrenderdoc/Windows/Dialogs/SettingsDialog.ui +++ b/qrenderdoc/Windows/Dialogs/SettingsDialog.ui @@ -73,7 +73,7 @@ QTabWidget::West - 0 + 3 true @@ -129,6 +129,12 @@ E.g. a value of 3 means 0.005 / 10 = 5E-4 Qt::Vertical + + + 0 + 0 + + @@ -699,16 +705,9 @@ e.g. a value of 5 means 0.123456789 will display as 0.12345 - Vulkan Disassemblers + Shader Processing Tools - - - - <html><head/><body><p>Available SPIR-V Disassemblers. Click items to edit, drag row header to change priority.</p></body></html> - - - @@ -723,9 +722,9 @@ e.g. a value of 5 means 0.123456789 will display as 0.12345 - + - QAbstractItemView::AnyKeyPressed|QAbstractItemView::DoubleClicked|QAbstractItemView::EditKeyPressed|QAbstractItemView::SelectedClicked + QAbstractItemView::NoEditTriggers true @@ -766,14 +765,21 @@ e.g. a value of 5 means 0.123456789 will display as 0.12345 - + Add - + + + Edit + + + + + Delete @@ -781,6 +787,16 @@ e.g. a value of 5 means 0.123456789 will display as 0.12345 + + + + <html><head/><body><p>Available Shader processing tools. These tools can be configured to translate from one form to another. Typically to e.g. disassemble from SPIR-V to GLSL or canonical assembly, or to compile from HLSL back to SPIR-V or DXBC.</p><p>Double click items to edit, drag row header to change relative priority.</p></body></html> + + + true + + + diff --git a/qrenderdoc/Windows/PipelineState/D3D11PipelineStateViewer.cpp b/qrenderdoc/Windows/PipelineState/D3D11PipelineStateViewer.cpp index c7d1150c1..762109a9a 100644 --- a/qrenderdoc/Windows/PipelineState/D3D11PipelineStateViewer.cpp +++ b/qrenderdoc/Windows/PipelineState/D3D11PipelineStateViewer.cpp @@ -146,7 +146,7 @@ D3D11PipelineStateViewer::D3D11PipelineStateViewer(ICaptureContext &ctx, } for(QToolButton *b : editButtons) - QObject::connect(b, &QToolButton::clicked, this, &D3D11PipelineStateViewer::shaderEdit_clicked); + QObject::connect(b, &QToolButton::clicked, &m_Common, &PipelineStateViewer::shaderEdit_clicked); for(QToolButton *b : saveButtons) QObject::connect(b, &QToolButton::clicked, this, &D3D11PipelineStateViewer::shaderSave_clicked); @@ -1449,7 +1449,9 @@ void D3D11PipelineStateViewer::setState() if(stage == NULL || stage->resourceId == ResourceId()) continue; - b->setEnabled(stage->reflection); + b->setEnabled(stage->reflection != NULL); + + m_Common.SetupShaderEditButton(b, ResourceId(), stage->resourceId, stage->reflection); } vs = ui->csUAVs->verticalScrollBar()->value(); @@ -2309,40 +2311,6 @@ void D3D11PipelineStateViewer::shaderView_clicked() m_Ctx.AddDockWindow(shad->Widget(), DockReference::AddTo, this); } -void D3D11PipelineStateViewer::shaderEdit_clicked() -{ - QWidget *sender = qobject_cast(QObject::sender()); - const D3D11Pipe::Shader *stage = stageForSender(sender); - - if(!stage || stage->resourceId == ResourceId()) - return; - - const ShaderReflection *shaderDetails = stage->reflection; - - if(!shaderDetails) - return; - - QString entryFunc = lit("EditedShader%1S").arg(ToQStr(stage->stage, GraphicsAPI::D3D11)[0]); - - rdcstrpairs files; - - bool hasOrigSource = m_Common.PrepareShaderEditing(shaderDetails, entryFunc, files); - - if(!hasOrigSource) - { - files.clear(); - files.push_back(make_rdcpair( - "generated.hlsl", m_Common.GenerateHLSLStub(shaderDetails, entryFunc))); - } - - if(files.empty()) - return; - - // we always consider the input HLSL, either the stub or the original source - m_Common.EditShader(stage->stage, stage->resourceId, shaderDetails, entryFunc, - ShaderEncoding::HLSL, files); -} - void D3D11PipelineStateViewer::shaderSave_clicked() { const D3D11Pipe::Shader *stage = stageForSender(qobject_cast(QObject::sender())); diff --git a/qrenderdoc/Windows/PipelineState/D3D11PipelineStateViewer.h b/qrenderdoc/Windows/PipelineState/D3D11PipelineStateViewer.h index 4e00c3614..f1e496610 100644 --- a/qrenderdoc/Windows/PipelineState/D3D11PipelineStateViewer.h +++ b/qrenderdoc/Windows/PipelineState/D3D11PipelineStateViewer.h @@ -68,7 +68,6 @@ private slots: // manual slots void shaderView_clicked(); - void shaderEdit_clicked(); void shaderSave_clicked(); void resource_itemActivated(RDTreeWidgetItem *item, int column); diff --git a/qrenderdoc/Windows/PipelineState/D3D11PipelineStateViewer.ui b/qrenderdoc/Windows/PipelineState/D3D11PipelineStateViewer.ui index 36a2f2250..6c7fafe95 100644 --- a/qrenderdoc/Windows/PipelineState/D3D11PipelineStateViewer.ui +++ b/qrenderdoc/Windows/PipelineState/D3D11PipelineStateViewer.ui @@ -645,6 +645,9 @@ :/page_white_edit.png:/page_white_edit.png + + QToolButton::MenuButtonPopup + Qt::ToolButtonTextBesideIcon @@ -938,6 +941,12 @@ QSizePolicy::MinimumExpanding + + + 0 + 0 + + @@ -1026,6 +1035,9 @@ :/page_white_edit.png:/page_white_edit.png + + QToolButton::MenuButtonPopup + Qt::ToolButtonTextBesideIcon @@ -1316,6 +1328,12 @@ QSizePolicy::MinimumExpanding + + + 0 + 0 + + @@ -1404,6 +1422,9 @@ :/page_white_edit.png:/page_white_edit.png + + QToolButton::MenuButtonPopup + Qt::ToolButtonTextBesideIcon @@ -1694,6 +1715,12 @@ QSizePolicy::MinimumExpanding + + + 0 + 0 + + @@ -1782,6 +1809,9 @@ :/page_white_edit.png:/page_white_edit.png + + QToolButton::MenuButtonPopup + Qt::ToolButtonTextBesideIcon @@ -2124,6 +2154,12 @@ QSizePolicy::MinimumExpanding + + + 0 + 0 + + @@ -2831,6 +2867,9 @@ :/page_white_edit.png:/page_white_edit.png + + QToolButton::MenuButtonPopup + Qt::ToolButtonTextBesideIcon @@ -3121,6 +3160,12 @@ QSizePolicy::MinimumExpanding + + + 0 + 0 + + @@ -3989,6 +4034,9 @@ :/page_white_edit.png:/page_white_edit.png + + QToolButton::MenuButtonPopup + Qt::ToolButtonTextBesideIcon @@ -4407,6 +4455,12 @@ QSizePolicy::MinimumExpanding + + + 0 + 0 + + @@ -4420,16 +4474,16 @@ - - RDTreeWidget - QTreeView -
Widgets/Extended/RDTreeWidget.h
-
RDLabel QLabel
Widgets/Extended/RDLabel.h
+ + RDTreeWidget + QTreeView +
Widgets/Extended/RDTreeWidget.h
+
PipelineFlowChart QFrame diff --git a/qrenderdoc/Windows/PipelineState/D3D12PipelineStateViewer.cpp b/qrenderdoc/Windows/PipelineState/D3D12PipelineStateViewer.cpp index 55d22ab93..5912d6ed5 100644 --- a/qrenderdoc/Windows/PipelineState/D3D12PipelineStateViewer.cpp +++ b/qrenderdoc/Windows/PipelineState/D3D12PipelineStateViewer.cpp @@ -184,7 +184,7 @@ D3D12PipelineStateViewer::D3D12PipelineStateViewer(ICaptureContext &ctx, } for(QToolButton *b : editButtons) - QObject::connect(b, &QToolButton::clicked, this, &D3D12PipelineStateViewer::shaderEdit_clicked); + QObject::connect(b, &QToolButton::clicked, &m_Common, &PipelineStateViewer::shaderEdit_clicked); for(QToolButton *b : saveButtons) QObject::connect(b, &QToolButton::clicked, this, &D3D12PipelineStateViewer::shaderSave_clicked); @@ -1468,6 +1468,8 @@ void D3D12PipelineStateViewer::setState() continue; b->setEnabled(stage->reflection && state.pipelineResourceId != ResourceId()); + + m_Common.SetupShaderEditButton(b, state.pipelineResourceId, stage->resourceId, stage->reflection); } bool streamoutSet = false; @@ -2137,40 +2139,6 @@ void D3D12PipelineStateViewer::shaderView_clicked() m_Ctx.AddDockWindow(shad->Widget(), DockReference::AddTo, this); } -void D3D12PipelineStateViewer::shaderEdit_clicked() -{ - QWidget *sender = qobject_cast(QObject::sender()); - const D3D12Pipe::Shader *stage = stageForSender(sender); - - if(!stage || stage->resourceId == ResourceId()) - return; - - const ShaderReflection *shaderDetails = stage->reflection; - - if(!shaderDetails) - return; - - QString entryFunc = lit("EditedShader%1S").arg(ToQStr(stage->stage, GraphicsAPI::D3D12)[0]); - - rdcstrpairs files; - - bool hasOrigSource = m_Common.PrepareShaderEditing(shaderDetails, entryFunc, files); - - if(!hasOrigSource) - { - files.clear(); - files.push_back(make_rdcpair( - "generated.hlsl", m_Common.GenerateHLSLStub(shaderDetails, entryFunc))); - } - - if(files.empty()) - return; - - // we always consider the input HLSL, either the stub or the original source - m_Common.EditShader(stage->stage, stage->resourceId, shaderDetails, entryFunc, - ShaderEncoding::HLSL, files); -} - void D3D12PipelineStateViewer::shaderSave_clicked() { const D3D12Pipe::Shader *stage = stageForSender(qobject_cast(QObject::sender())); diff --git a/qrenderdoc/Windows/PipelineState/D3D12PipelineStateViewer.h b/qrenderdoc/Windows/PipelineState/D3D12PipelineStateViewer.h index f738da7cb..861b1ef21 100644 --- a/qrenderdoc/Windows/PipelineState/D3D12PipelineStateViewer.h +++ b/qrenderdoc/Windows/PipelineState/D3D12PipelineStateViewer.h @@ -69,7 +69,6 @@ private slots: // manual slots void shaderView_clicked(); - void shaderEdit_clicked(); void shaderSave_clicked(); void resource_itemActivated(RDTreeWidgetItem *item, int column); void cbuffer_itemActivated(RDTreeWidgetItem *item, int column); diff --git a/qrenderdoc/Windows/PipelineState/D3D12PipelineStateViewer.ui b/qrenderdoc/Windows/PipelineState/D3D12PipelineStateViewer.ui index 9455e2460..7c3b83dc2 100644 --- a/qrenderdoc/Windows/PipelineState/D3D12PipelineStateViewer.ui +++ b/qrenderdoc/Windows/PipelineState/D3D12PipelineStateViewer.ui @@ -549,6 +549,9 @@ :/page_white_edit.png:/page_white_edit.png + + + QToolButton::MenuButtonPopup Qt::ToolButtonTextBesideIcon @@ -941,6 +944,9 @@ :/page_white_edit.png:/page_white_edit.png + + QToolButton::MenuButtonPopup + Qt::ToolButtonTextBesideIcon @@ -1326,6 +1332,9 @@ :/page_white_edit.png:/page_white_edit.png + + QToolButton::MenuButtonPopup + Qt::ToolButtonTextBesideIcon @@ -1711,6 +1720,9 @@ :/page_white_edit.png:/page_white_edit.png + + QToolButton::MenuButtonPopup + Qt::ToolButtonTextBesideIcon @@ -2695,6 +2707,9 @@ :/page_white_edit.png:/page_white_edit.png + + QToolButton::MenuButtonPopup + Qt::ToolButtonTextBesideIcon @@ -3748,6 +3763,9 @@ :/page_white_edit.png:/page_white_edit.png + + QToolButton::MenuButtonPopup + Qt::ToolButtonTextBesideIcon diff --git a/qrenderdoc/Windows/PipelineState/GLPipelineStateViewer.cpp b/qrenderdoc/Windows/PipelineState/GLPipelineStateViewer.cpp index 6002a741a..245c5c0ab 100644 --- a/qrenderdoc/Windows/PipelineState/GLPipelineStateViewer.cpp +++ b/qrenderdoc/Windows/PipelineState/GLPipelineStateViewer.cpp @@ -142,7 +142,7 @@ GLPipelineStateViewer::GLPipelineStateViewer(ICaptureContext &ctx, PipelineState } for(QToolButton *b : editButtons) - QObject::connect(b, &QToolButton::clicked, this, &GLPipelineStateViewer::shaderEdit_clicked); + QObject::connect(b, &QToolButton::clicked, &m_Common, &PipelineStateViewer::shaderEdit_clicked); for(QToolButton *b : saveButtons) QObject::connect(b, &QToolButton::clicked, this, &GLPipelineStateViewer::shaderSave_clicked); @@ -1384,7 +1384,9 @@ void GLPipelineStateViewer::setState() ShaderReflection *shaderDetails = stage->reflection; - b->setEnabled(shaderDetails); + b->setEnabled(shaderDetails != NULL); + + m_Common.SetupShaderEditButton(b, ResourceId(), stage->shaderResourceId, shaderDetails); } vs = ui->xfbBuffers->verticalScrollBar()->value(); @@ -2301,40 +2303,6 @@ void GLPipelineStateViewer::shaderView_clicked() m_Ctx.AddDockWindow(shad->Widget(), DockReference::AddTo, this); } -void GLPipelineStateViewer::shaderEdit_clicked() -{ - QWidget *sender = qobject_cast(QObject::sender()); - const GLPipe::Shader *stage = stageForSender(sender); - - if(!stage || stage->shaderResourceId == ResourceId()) - return; - - const ShaderReflection *shaderDetails = stage->reflection; - - if(!shaderDetails) - return; - - QString entryFunc = lit("EditedShader%1S").arg(ToQStr(stage->stage, GraphicsAPI::OpenGL)[0]); - - rdcstrpairs files; - - bool hasOrigSource = m_Common.PrepareShaderEditing(shaderDetails, entryFunc, files); - - if(!hasOrigSource) - { - // this would only happen if the GL program is uploading SPIR-V instead of GLSL. - files.clear(); - files.push_back(make_rdcpair("generated.glsl", "// TODO - disassemble SPIR-V")); - } - - if(files.empty()) - return; - - // we always consider the input GLSL - m_Common.EditShader(stage->stage, stage->shaderResourceId, shaderDetails, entryFunc, - ShaderEncoding::GLSL, files); -} - void GLPipelineStateViewer::shaderSave_clicked() { const GLPipe::Shader *stage = stageForSender(qobject_cast(QObject::sender())); diff --git a/qrenderdoc/Windows/PipelineState/GLPipelineStateViewer.h b/qrenderdoc/Windows/PipelineState/GLPipelineStateViewer.h index 98e450cf3..89cbdfd23 100644 --- a/qrenderdoc/Windows/PipelineState/GLPipelineStateViewer.h +++ b/qrenderdoc/Windows/PipelineState/GLPipelineStateViewer.h @@ -68,7 +68,6 @@ private slots: // manual slots void shaderView_clicked(); - void shaderEdit_clicked(); void shaderSave_clicked(); void resource_itemActivated(RDTreeWidgetItem *item, int column); void ubo_itemActivated(RDTreeWidgetItem *item, int column); diff --git a/qrenderdoc/Windows/PipelineState/GLPipelineStateViewer.ui b/qrenderdoc/Windows/PipelineState/GLPipelineStateViewer.ui index 13baef50e..c9a38bdb5 100644 --- a/qrenderdoc/Windows/PipelineState/GLPipelineStateViewer.ui +++ b/qrenderdoc/Windows/PipelineState/GLPipelineStateViewer.ui @@ -558,6 +558,9 @@ :/page_white_edit.png:/page_white_edit.png + + QToolButton::MenuButtonPopup + Qt::ToolButtonTextBesideIcon @@ -1003,6 +1006,9 @@ :/page_white_edit.png:/page_white_edit.png + + QToolButton::MenuButtonPopup + Qt::ToolButtonTextBesideIcon @@ -1448,6 +1454,9 @@ :/page_white_edit.png:/page_white_edit.png + + QToolButton::MenuButtonPopup + Qt::ToolButtonTextBesideIcon @@ -1893,6 +1902,9 @@ :/page_white_edit.png:/page_white_edit.png + + QToolButton::MenuButtonPopup + Qt::ToolButtonTextBesideIcon @@ -3316,6 +3328,9 @@ :/page_white_edit.png:/page_white_edit.png + + QToolButton::MenuButtonPopup + Qt::ToolButtonTextBesideIcon @@ -4268,6 +4283,9 @@ :/page_white_edit.png:/page_white_edit.png + + QToolButton::MenuButtonPopup + Qt::ToolButtonTextBesideIcon diff --git a/qrenderdoc/Windows/PipelineState/PipelineStateViewer.cpp b/qrenderdoc/Windows/PipelineState/PipelineStateViewer.cpp index f20bf6245..3a88456b6 100644 --- a/qrenderdoc/Windows/PipelineState/PipelineStateViewer.cpp +++ b/qrenderdoc/Windows/PipelineState/PipelineStateViewer.cpp @@ -23,9 +23,11 @@ ******************************************************************************/ #include "PipelineStateViewer.h" +#include #include #include #include +#include #include #include "3rdparty/toolwindowmanager/ToolWindowManager.h" #include "Code/QRDUtils.h" @@ -49,6 +51,9 @@ PipelineStateViewer::PipelineStateViewer(ICaptureContext &ctx, QWidget *parent) m_Current = NULL; + for(size_t i = 0; i < ARRAY_COUNT(editMenus); i++) + editMenus[i] = new QMenu(this); + setToD3D11(); m_Ctx.AddCaptureViewer(this); @@ -613,7 +618,7 @@ void PipelineStateViewer::MakeShaderVariablesHLSL(bool cbufferContents, QString PipelineStateViewer::GenerateHLSLStub(const ShaderReflection *shaderDetails, const QString &entryFunc) { - QString hlsl = lit("// No HLSL available - function stub generated\n\n"); + QString hlsl = lit("// HLSL function stub generated\n\n"); const QString textureDim[ENUM_ARRAY_SIZE(TextureType)] = { lit("Unknown"), lit("Buffer"), lit("Texture1D"), lit("Texture1DArray"), @@ -718,19 +723,27 @@ QString PipelineStateViewer::GenerateHLSLStub(const ShaderReflection *shaderDeta return hlsl; } -void PipelineStateViewer::EditShader(ShaderStage shaderType, ResourceId id, - const ShaderReflection *shaderDetails, const QString &entry, - ShaderEncoding encoding, const rdcstrpairs &files) +void PipelineStateViewer::shaderEdit_clicked() { - if(!shaderDetails) + QToolButton *sender = qobject_cast(QObject::sender()); + if(!sender) return; + // activate the first item in the menu, if there are any items, as the default action. + QMenu *menu = sender->menu(); + if(menu && !menu->actions().isEmpty()) + menu->actions()[0]->trigger(); +} + +void PipelineStateViewer::EditShader(ResourceId id, ShaderStage shaderType, const rdcstr &entry, + ShaderCompileFlags compileFlags, ShaderEncoding encoding, + const rdcstrpairs &files) +{ IShaderViewer *sv = m_Ctx.EditShader( - false, shaderDetails->stage, entry, files, encoding, shaderDetails->debugInfo.compileFlags, + false, shaderType, entry, files, encoding, compileFlags, // save callback - [shaderType, id, shaderDetails](ICaptureContext *ctx, IShaderViewer *viewer, - ShaderEncoding shaderEncoding, ShaderCompileFlags flags, - rdcstr entryFunc, bytebuf shaderBytes) { + [shaderType, id](ICaptureContext *ctx, IShaderViewer *viewer, ShaderEncoding shaderEncoding, + ShaderCompileFlags flags, rdcstr entryFunc, bytebuf shaderBytes) { if(shaderBytes.isEmpty()) return; @@ -740,7 +753,7 @@ void PipelineStateViewer::EditShader(ShaderStage shaderType, ResourceId id, // invoke off to the ReplayController to replace the capture's shader // with our edited one ctx->Replay().AsyncInvoke([ctx, entryFunc, shaderBytes, shaderEncoding, flags, shaderType, - id, shaderDetails, viewer](IReplayController *r) { + id, viewer](IReplayController *r) { rdcstr errs; ResourceId from = id; @@ -776,6 +789,151 @@ void PipelineStateViewer::EditShader(ShaderStage shaderType, ResourceId id, m_Ctx.AddDockWindow(sv->Widget(), DockReference::AddTo, this); } +void PipelineStateViewer::EditOriginalShaderSource(ResourceId id, + const ShaderReflection *shaderDetails) +{ + QSet uniqueFiles; + rdcstrpairs files; + + for(const ShaderSourceFile &s : shaderDetails->debugInfo.files) + { + QString filename = s.filename; + + uint filenameHash = qHash(filename.toLower()); + + if(uniqueFiles.contains(filenameHash)) + { + qWarning() << lit("Duplicate full filename") << filename; + continue; + } + uniqueFiles.insert(filenameHash); + + files.push_back(make_rdcpair(s.filename, s.contents)); + } + + EditShader(id, shaderDetails->stage, shaderDetails->entryPoint, + shaderDetails->debugInfo.compileFlags, shaderDetails->debugInfo.encoding, files); +} + +void PipelineStateViewer::EditDecompiledSource(const ShaderProcessingTool &tool, ResourceId id, + const ShaderReflection *shaderDetails) +{ + QString source = tool.DisassembleShader(this, shaderDetails, ""); + + if(source.isEmpty()) + return; + + rdcstrpairs files; + files.push_back(make_rdcpair("decompiled", source)); + + EditShader(id, shaderDetails->stage, shaderDetails->entryPoint, + shaderDetails->debugInfo.compileFlags, tool.output, files); +} + +void PipelineStateViewer::SetupShaderEditButton(QToolButton *button, ResourceId pipelineId, + ResourceId shaderId, + const ShaderReflection *shaderDetails) +{ + if(!shaderDetails || !button->isEnabled() || button->popupMode() != QToolButton::MenuButtonPopup) + return; + + QMenu *menu = editMenus[(int)shaderDetails->stage]; + + menu->clear(); + + rdcarray accepted = m_Ctx.TargetShaderEncodings(); + + // if we have original source and it's in a known format, display it as the first most preferred + // option + if(!shaderDetails->debugInfo.files.empty() && + shaderDetails->debugInfo.encoding != ShaderEncoding::Unknown) + { + QAction *action = + new QAction(tr("Edit Source - %1").arg(shaderDetails->debugInfo.files[0].filename), menu); + action->setIcon(Icons::page_white_edit()); + + QObject::connect(action, &QAction::triggered, [this, shaderId, shaderDetails]() { + EditOriginalShaderSource(shaderId, shaderDetails); + }); + + menu->addAction(action); + } + + // next up, try the shader processing tools in order - all the ones that will decompile from our + // native representation. We don't check here yet if we have a valid compiler to compile from the + // output to what we want. + for(const ShaderProcessingTool &tool : m_Ctx.Config().ShaderProcessors) + { + // skip tools that can't decode our shader, or doesn't produce a textual output + if(tool.input != shaderDetails->encoding || !IsTextRepresentation(tool.output)) + continue; + + QAction *action = new QAction(tr("Decompile with %1").arg(tool.name), menu); + action->setIcon(Icons::page_white_edit()); + + QObject::connect(action, &QAction::triggered, [this, tool, shaderId, shaderDetails]() { + EditDecompiledSource(tool, shaderId, shaderDetails); + }); + + menu->addAction(action); + } + + // if all else fails we can generate a stub for editing. Skip this for GLSL as it always has + // source above which is preferred. + if(shaderDetails->encoding != ShaderEncoding::GLSL) + { + QString label = tr("Edit Generated Stub"); + + if(shaderDetails->encoding == ShaderEncoding::SPIRV) + label = tr("Edit Pseudocode"); + + QAction *action = new QAction(label, menu); + action->setIcon(Icons::page_white_edit()); + + QObject::connect(action, &QAction::triggered, [this, pipelineId, shaderId, shaderDetails]() { + QString entry; + QString src; + + if(shaderDetails->encoding == ShaderEncoding::SPIRV) + { + m_Ctx.Replay().AsyncInvoke([this, pipelineId, shaderId, shaderDetails](IReplayController *r) { + rdcstr disasm = r->DisassembleShader(pipelineId, shaderDetails, ""); + + QString editeddisasm = + tr("#### PSEUDOCODE SPIR-V DISASSEMBLY ###\n") + + tr("#### Use a SPIR-V decompiler to get compileable source ###\n\n"); + + editeddisasm += disasm; + + GUIInvoke::call(this, [this, shaderId, shaderDetails, editeddisasm]() { + rdcstrpairs files; + files.push_back(make_rdcpair("pseudocode", editeddisasm)); + + EditShader(shaderId, shaderDetails->stage, shaderDetails->entryPoint, + ShaderCompileFlags(), ShaderEncoding::Unknown, files); + }); + }); + } + else if(shaderDetails->encoding == ShaderEncoding::DXBC) + { + entry = lit("EditedShader%1S").arg(ToQStr(shaderDetails->stage, GraphicsAPI::D3D11)[0]); + + rdcstrpairs files; + files.push_back(make_rdcpair("decompiled_stub.hlsl", + GenerateHLSLStub(shaderDetails, entry))); + + EditShader(shaderId, shaderDetails->stage, entry, ShaderCompileFlags(), + ShaderEncoding::HLSL, files); + } + + }); + + menu->addAction(action); + } + + button->setMenu(menu); +} + bool PipelineStateViewer::SaveShaderFile(const ShaderReflection *shader) { if(!shader) diff --git a/qrenderdoc/Windows/PipelineState/PipelineStateViewer.h b/qrenderdoc/Windows/PipelineState/PipelineStateViewer.h index e7fe9ba59..695ec14c7 100644 --- a/qrenderdoc/Windows/PipelineState/PipelineStateViewer.h +++ b/qrenderdoc/Windows/PipelineState/PipelineStateViewer.h @@ -35,6 +35,8 @@ class PipelineStateViewer; class QXmlStreamWriter; +class QToolButton; +class QMenu; class RDLabel; class D3D11PipelineStateViewer; @@ -63,14 +65,10 @@ public: void OnEventChanged(uint32_t eventId) override; QVariant persistData(); - void setPersistData(const QVariant &persistData); - bool PrepareShaderEditing(const ShaderReflection *shaderDetails, QString &entryFunc, - rdcstrpairs &files); - QString GenerateHLSLStub(const ShaderReflection *shaderDetails, const QString &entryFunc); - void EditShader(ShaderStage shaderType, ResourceId id, const ShaderReflection *shaderDetails, - const QString &entryFunc, ShaderEncoding encoding, const rdcstrpairs &files); + void SetupShaderEditButton(QToolButton *button, ResourceId pipelineId, ResourceId shaderId, + const ShaderReflection *shaderDetails); void setTopologyDiagram(QLabel *diagram, Topology topo); void setMeshViewPixmap(RDLabel *meshView); @@ -81,10 +79,24 @@ public: void exportHTMLTable(QXmlStreamWriter &xml, const QStringList &cols, const QVariantList &row); void endHTMLExport(QXmlStreamWriter *xml); +public slots: + void shaderEdit_clicked(); + private: Ui::PipelineStateViewer *ui; ICaptureContext &m_Ctx; + QMenu *editMenus[6] = {}; + + bool PrepareShaderEditing(const ShaderReflection *shaderDetails, QString &entryFunc, + rdcstrpairs &files); + QString GenerateHLSLStub(const ShaderReflection *shaderDetails, const QString &entryFunc); + void EditShader(ResourceId id, ShaderStage shaderType, const rdcstr &entry, + ShaderCompileFlags compileFlags, ShaderEncoding encoding, const rdcstrpairs &files); + void EditOriginalShaderSource(ResourceId id, const ShaderReflection *shaderDetails); + void EditDecompiledSource(const ShaderProcessingTool &tool, ResourceId id, + const ShaderReflection *shaderDetails); + void MakeShaderVariablesHLSL(bool cbufferContents, const rdcarray &vars, QString &struct_contents, QString &struct_defs); diff --git a/qrenderdoc/Windows/PipelineState/VulkanPipelineStateViewer.cpp b/qrenderdoc/Windows/PipelineState/VulkanPipelineStateViewer.cpp index f8390b761..c6a86af44 100644 --- a/qrenderdoc/Windows/PipelineState/VulkanPipelineStateViewer.cpp +++ b/qrenderdoc/Windows/PipelineState/VulkanPipelineStateViewer.cpp @@ -138,7 +138,7 @@ VulkanPipelineStateViewer::VulkanPipelineStateViewer(ICaptureContext &ctx, } for(QToolButton *b : editButtons) - QObject::connect(b, &QToolButton::clicked, this, &VulkanPipelineStateViewer::shaderEdit_clicked); + QObject::connect(b, &QToolButton::clicked, &m_Common, &PipelineStateViewer::shaderEdit_clicked); for(QToolButton *b : saveButtons) QObject::connect(b, &QToolButton::clicked, this, &VulkanPipelineStateViewer::shaderSave_clicked); @@ -1771,6 +1771,8 @@ void VulkanPipelineStateViewer::setState() : state.graphics.pipelineResourceId; b->setEnabled(shaderDetails && pipe != ResourceId()); + + m_Common.SetupShaderEditButton(b, pipe, stage->resourceId, shaderDetails); } //////////////////////////////////////////////// @@ -2412,70 +2414,6 @@ void VulkanPipelineStateViewer::shaderView_clicked() m_Ctx.AddDockWindow(shad->Widget(), DockReference::AddTo, this); } -void VulkanPipelineStateViewer::shaderEdit_clicked() -{ - QWidget *sender = qobject_cast(QObject::sender()); - const VKPipe::Shader *stage = stageForSender(sender); - - if(!stage || stage->resourceId == ResourceId()) - return; - - const ShaderReflection *shaderDetails = stage->reflection; - - ResourceId pipe = stage->stage == ShaderStage::Compute - ? m_Ctx.CurVulkanPipelineState()->compute.pipelineResourceId - : m_Ctx.CurVulkanPipelineState()->graphics.pipelineResourceId; - - if(!shaderDetails) - return; - - QString entryFunc = lit("EditedShader%1S").arg(ToQStr(stage->stage, GraphicsAPI::Vulkan)[0]); - - rdcstrpairs files; - - bool hasOrigSource = m_Common.PrepareShaderEditing(shaderDetails, entryFunc, files); - ShaderEncoding encoding = shaderDetails->debugInfo.encoding; - - if(hasOrigSource) - { - if(files.empty()) - return; - } - else - { - QString glsl; - - if(!m_Ctx.Config().SPIRVDisassemblers.isEmpty()) - glsl = m_Ctx.Config().SPIRVDisassemblers[0].DisassembleShader(this, shaderDetails); - - if(!glsl.isEmpty()) - { - // if we decompiled, we expect the entry point name to be the same and assume GLSL - // decompilation for now - entryFunc = shaderDetails->entryPoint; - encoding = ShaderEncoding::GLSL; - files.clear(); - files.push_back(make_rdcpair("decompiled", glsl)); - } - else - { - m_Ctx.Replay().AsyncInvoke([this, stage, pipe, shaderDetails, entryFunc](IReplayController *r) { - rdcstr disasm = r->DisassembleShader(pipe, shaderDetails, ""); - - GUIInvoke::call(this, [this, stage, shaderDetails, entryFunc, disasm]() { - rdcstrpairs fileMap; - fileMap.push_back(make_rdcpair("pseudo_disassembly", disasm)); - m_Common.EditShader(stage->stage, stage->resourceId, shaderDetails, entryFunc, - ShaderEncoding::Unknown, fileMap); - }); - }); - return; - } - } - - m_Common.EditShader(stage->stage, stage->resourceId, shaderDetails, entryFunc, encoding, files); -} - void VulkanPipelineStateViewer::shaderSave_clicked() { const VKPipe::Shader *stage = stageForSender(qobject_cast(QObject::sender())); diff --git a/qrenderdoc/Windows/PipelineState/VulkanPipelineStateViewer.h b/qrenderdoc/Windows/PipelineState/VulkanPipelineStateViewer.h index 0ef6e0234..35faae3bd 100644 --- a/qrenderdoc/Windows/PipelineState/VulkanPipelineStateViewer.h +++ b/qrenderdoc/Windows/PipelineState/VulkanPipelineStateViewer.h @@ -74,7 +74,6 @@ private slots: // manual slots void shaderView_clicked(); - void shaderEdit_clicked(); void shaderSave_clicked(); void resource_itemActivated(RDTreeWidgetItem *item, int column); diff --git a/qrenderdoc/Windows/PipelineState/VulkanPipelineStateViewer.ui b/qrenderdoc/Windows/PipelineState/VulkanPipelineStateViewer.ui index adf3f5b8e..44546fa1b 100644 --- a/qrenderdoc/Windows/PipelineState/VulkanPipelineStateViewer.ui +++ b/qrenderdoc/Windows/PipelineState/VulkanPipelineStateViewer.ui @@ -545,6 +545,9 @@ :/page_white_edit.png:/page_white_edit.png + + + QToolButton::MenuButtonPopup Qt::ToolButtonTextBesideIcon @@ -829,6 +832,9 @@ :/page_white_edit.png:/page_white_edit.png + + QToolButton::MenuButtonPopup + Qt::ToolButtonTextBesideIcon @@ -1112,6 +1118,9 @@ :/page_white_edit.png:/page_white_edit.png + + QToolButton::MenuButtonPopup + Qt::ToolButtonTextBesideIcon @@ -1395,6 +1404,9 @@ :/page_white_edit.png:/page_white_edit.png + + QToolButton::MenuButtonPopup + Qt::ToolButtonTextBesideIcon @@ -2438,6 +2450,9 @@ :/page_white_edit.png:/page_white_edit.png + + QToolButton::MenuButtonPopup + Qt::ToolButtonTextBesideIcon @@ -3254,6 +3269,9 @@ :/page_white_edit.png:/page_white_edit.png + + QToolButton::MenuButtonPopup + Qt::ToolButtonTextBesideIcon diff --git a/qrenderdoc/Windows/ShaderViewer.cpp b/qrenderdoc/Windows/ShaderViewer.cpp index 659b4cf1d..0570433b6 100644 --- a/qrenderdoc/Windows/ShaderViewer.cpp +++ b/qrenderdoc/Windows/ShaderViewer.cpp @@ -192,8 +192,29 @@ void ShaderViewer::editShader(bool customShader, ShaderStage stage, const QStrin m_Stage = stage; m_Flags = flags; - m_Encoding = shaderEncoding; - m_EntryPoint = entryPoint; + + // set up compilation parameters + QStringList strs; + strs.clear(); + for(ShaderEncoding i : values()) + strs << ToQStr(i); + + ui->encoding->addItems(strs); + ui->encoding->setCurrentIndex((int)shaderEncoding); + ui->entryFunc->setText(entryPoint); + + PopulateCompileTools(); + + QObject::connect(ui->encoding, OverloadedSlot::of(&QComboBox::currentIndexChanged), + [this](int) { PopulateCompileTools(); }); + QObject::connect(ui->compileTool, OverloadedSlot::of(&QComboBox::currentIndexChanged), + [this](int) { PopulateCompileToolParameters(); }); + + // if it's a custom shader, hide the group entirely (don't allow customisation of compile + // parameters). We can still use it to store the parameters passed in. When visible we collapse it + // by default. + if(customShader) + ui->compilationGroup->hide(); // hide debugging windows ui->watch->hide(); @@ -281,6 +302,17 @@ void ShaderViewer::editShader(bool customShader, ShaderStage stage, const QStrin ui->docking->areaOf(m_Scintillas.front()), 0.2f)); ui->docking->setToolWindowProperties( m_Errors, ToolWindowManager::HideCloseButton | ToolWindowManager::DisallowFloatWindow); + + if(!customShader) + { + ui->compilationGroup->setWindowTitle(tr("Compilation Settings")); + ui->docking->addToolWindow( + ui->compilationGroup, + ToolWindowManager::AreaReference(ToolWindowManager::AddTo, ui->docking->areaOf(m_Errors))); + ui->docking->setToolWindowProperties( + ui->compilationGroup, + ToolWindowManager::HideCloseButton | ToolWindowManager::DisallowFloatWindow); + } } void ShaderViewer::debugShader(const ShaderBindpointMapping *bind, const ShaderReflection *shader, @@ -294,6 +326,9 @@ void ShaderViewer::debugShader(const ShaderBindpointMapping *bind, const ShaderR m_Stage = ShaderStage::Vertex; m_DebugContext = debugContext; + // no recompilation happening, hide that group + ui->compilationGroup->hide(); + // no replacing allowed, stay in find mode m_FindReplace->allowUserModeChange(false); @@ -311,20 +346,23 @@ void ShaderViewer::debugShader(const ShaderBindpointMapping *bind, const ShaderR GUIInvoke::call(this, [this, targets, disasm]() { QStringList targetNames; - for(const rdcstr &t : targets) + for(int i = 0; i < targets.count(); i++) { - QString target = t; - targetNames << target; + QString target = targets[i]; + targetNames << QString(targets[i]); - // if we have a SPIR-V disassembly option, and the shader is natively SPIR-V, add our own - // SPIR-V disassemblers right after - if(target.contains(lit("SPIR-V")) && m_ShaderDetails->encoding == ShaderEncoding::SPIRV) + if(i == 0) { - for(const SPIRVDisassembler &d : m_Ctx.Config().SPIRVDisassemblers) - targetNames << targetName(d); + // add any custom decompiling tools we have after the first one + for(const ShaderProcessingTool &d : m_Ctx.Config().ShaderProcessors) + { + if(d.input == m_ShaderDetails->encoding) + targetNames << targetName(d); + } } } + m_DisassemblyType->clear(); m_DisassemblyType->addItems(targetNames); m_DisassemblyType->setCurrentIndex(0); QObject::connect(m_DisassemblyType, OverloadedSlot::of(&QComboBox::currentIndexChanged), @@ -1151,11 +1189,11 @@ void ShaderViewer::disassemble_typeChanged(int index) QString targetStr = m_DisassemblyType->currentText(); QByteArray target = targetStr.toUtf8(); - for(const SPIRVDisassembler &disasm : m_Ctx.Config().SPIRVDisassemblers) + for(const ShaderProcessingTool &disasm : m_Ctx.Config().ShaderProcessors) { if(targetStr == targetName(disasm)) { - QString result = disasm.DisassembleShader(this, m_ShaderDetails); + QString result = disasm.DisassembleShader(this, m_ShaderDetails, ""); m_DisassemblyView->setReadOnly(false); m_DisassemblyView->setText(result.toUtf8().data()); @@ -1499,9 +1537,9 @@ RDTreeWidgetItem *ShaderViewer::makeResourceRegister(const Bindpoint &bind, uint } } -QString ShaderViewer::targetName(const SPIRVDisassembler &disasm) +QString ShaderViewer::targetName(const ShaderProcessingTool &disasm) { - return lit("SPIR-V (%1)").arg(disasm.name); + return lit("%1 (%2)").arg(ToQStr(disasm.input)).arg(disasm.name); } void ShaderViewer::addFileList() @@ -2538,6 +2576,9 @@ void ShaderViewer::ShowErrors(const rdcstr &errors) m_Errors->setReadOnly(false); m_Errors->setText(errors.c_str()); m_Errors->setReadOnly(true); + + if(!errors.isEmpty()) + ToolWindowManager::raiseToolWindow(m_Errors); } } @@ -3186,6 +3227,80 @@ void ShaderViewer::on_findReplace_clicked() m_FindReplace->takeFocus(); } +void ShaderViewer::PopulateCompileTools() +{ + ShaderEncoding encoding = ShaderEncoding(ui->encoding->currentIndex()); + rdcarray accepted = m_Ctx.TargetShaderEncodings(); + + QStringList strs; + strs.clear(); + for(const ShaderProcessingTool &tool : m_Ctx.Config().ShaderProcessors) + { + // skip tools that can't accept our inputs, or doesn't produce a supported output + if(tool.input != encoding || accepted.indexOf(tool.output) < 0) + continue; + + strs << tool.name; + } + + // if we can pass in the shader source as-is, add a built-in option + if(accepted.indexOf(encoding) >= 0) + strs << tr("Builtin"); + + ui->compileTool->clear(); + ui->compileTool->addItems(strs); + + // pick the first option as highest priority + ui->compileTool->setCurrentIndex(0); + + // fill out parameters + PopulateCompileToolParameters(); + + if(strs.isEmpty()) + { + ShowErrors(tr("No compilation tool found that takes %1 as input and produces compatible output") + .arg(ToQStr(encoding))); + } +} + +void ShaderViewer::PopulateCompileToolParameters() +{ + ShaderEncoding encoding = ShaderEncoding(ui->encoding->currentIndex()); + rdcarray accepted = m_Ctx.TargetShaderEncodings(); + + ui->toolCommandLine->clear(); + + if(accepted.indexOf(encoding) >= 0 && + ui->compileTool->currentIndex() == ui->compileTool->count() - 1) + { + // if we're using the last Builtin tool, there are no default parameters + } + else + { + for(const ShaderProcessingTool &tool : m_Ctx.Config().ShaderProcessors) + { + if(QString(tool.name) == ui->compileTool->currentText()) + { + ui->toolCommandLine->setPlainText(tool.DefaultArguments()); + ui->toolCommandLine->setEnabled(true); + break; + } + } + } + + for(int i = 0; i < m_Flags.flags.count(); i++) + { + ShaderCompileFlag &flag = m_Flags.flags[i]; + if(flag.name == "@cmdline") + { + // append command line from saved flags + ui->toolCommandLine->setPlainText(ui->toolCommandLine->toPlainText() + + lit(" %1").arg(flag.value)); + break; + } + } +} + bool ShaderViewer::ProcessIncludeDirectives(QString &source, const rdcstrpairs &files) { // try and match up #includes against the files that we have. This isn't always @@ -3228,7 +3343,7 @@ bool ShaderViewer::ProcessIncludeDirectives(QString &source, const rdcstrpairs & if(source[ws] != QLatin1Char('<') && source[ws] != QLatin1Char('"')) { - ShowErrors(lit("Invalid #include directive found:\r\n") + line); + ShowErrors(tr("Invalid #include directive found:\r\n") + line); return false; } @@ -3238,7 +3353,7 @@ bool ShaderViewer::ProcessIncludeDirectives(QString &source, const rdcstrpairs & if(end == -1) { - ShowErrors(lit("Invalid #include directive found:\r\n") + line); + ShowErrors(tr("Invalid #include directive found:\r\n") + line); return false; } @@ -3299,9 +3414,18 @@ void ShaderViewer::on_refresh_clicked() return; } - if(m_SaveCallback) + // if we don't have any compile tools - even the 'builtin' one, this compilation is not going to + // succeed. + if(ui->compileTool->count() == 0) { - ShaderEncoding encoding = m_Encoding; + ShaderEncoding encoding = ShaderEncoding(ui->encoding->currentIndex()); + + ShowErrors(tr("No compilation tool found that takes %1 as input and produces compatible output") + .arg(ToQStr(encoding))); + } + else if(m_SaveCallback) + { + ShaderEncoding encoding = ShaderEncoding(ui->encoding->currentIndex()); rdcstrpairs files; for(ScintillaEdit *s : m_Scintillas) @@ -3325,7 +3449,52 @@ void ShaderViewer::on_refresh_clicked() bytebuf shaderBytes(source.toUtf8()); - m_SaveCallback(&m_Ctx, this, encoding, m_Flags, m_EntryPoint, shaderBytes); + rdcarray accepted = m_Ctx.TargetShaderEncodings(); + + if(accepted.indexOf(encoding) >= 0 && + ui->compileTool->currentIndex() == ui->compileTool->count() - 1) + { + // if using the builtin compiler, just pass through + } + else + { + for(const ShaderProcessingTool &tool : m_Ctx.Config().ShaderProcessors) + { + if(QString(tool.name) == ui->compileTool->currentText()) + { + bytebuf result = tool.CompileShader(this, source, ui->entryFunc->text(), m_Stage, + ui->toolCommandLine->toPlainText()); + + if(result.isEmpty()) + { + ShowErrors(tr("Error invoking '%1' to compile source").arg(tool.name)); + return; + } + + encoding = tool.output; + shaderBytes = result; + break; + } + } + } + + ShaderCompileFlags flags = m_Flags; + + bool found = false; + for(ShaderCompileFlag &f : flags.flags) + { + if(f.name == "@cmdline") + { + f.value = ui->toolCommandLine->toPlainText(); + found = true; + break; + } + } + + if(!found) + flags.flags.push_back({"@cmdline", ui->toolCommandLine->toPlainText()}); + + m_SaveCallback(&m_Ctx, this, encoding, flags, ui->entryFunc->text(), shaderBytes); } } diff --git a/qrenderdoc/Windows/ShaderViewer.h b/qrenderdoc/Windows/ShaderViewer.h index f9339566d..4c2f1be4d 100644 --- a/qrenderdoc/Windows/ShaderViewer.h +++ b/qrenderdoc/Windows/ShaderViewer.h @@ -160,6 +160,8 @@ private: ResourceId pipeline, ShaderDebugTrace *trace, const QString &debugContext); bool eventFilter(QObject *watched, QEvent *event) override; + void PopulateCompileTools(); + void PopulateCompileToolParameters(); bool ProcessIncludeDirectives(QString &source, const rdcstrpairs &files); const rdcarray *GetVariableList(VariableCategory varCat, int arrayIdx); @@ -186,8 +188,6 @@ private: ICaptureContext &m_Ctx; const ShaderBindpointMapping *m_Mapping = NULL; const ShaderReflection *m_ShaderDetails = NULL; - ShaderEncoding m_Encoding; - rdcstr m_EntryPoint; ShaderCompileFlags m_Flags; ShaderStage m_Stage; QString m_DebugContext; @@ -241,7 +241,7 @@ private: static const int INDICATOR_FINDRESULT = 0; static const int INDICATOR_REGHIGHLIGHT = 1; - QString targetName(const SPIRVDisassembler &disasm); + QString targetName(const ShaderProcessingTool &disasm); void addFileList(); diff --git a/qrenderdoc/Windows/ShaderViewer.ui b/qrenderdoc/Windows/ShaderViewer.ui index 5b4142af7..a33acfb0a 100644 --- a/qrenderdoc/Windows/ShaderViewer.ui +++ b/qrenderdoc/Windows/ShaderViewer.ui @@ -7,7 +7,7 @@ 0 0 963 - 574 + 746 @@ -592,8 +592,148 @@ true + + + + 60 + 560 + 441 + 141 + + + + + + + Compiler: + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + + 0 + 0 + + + + + 0 + 20 + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + Source Type: + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + + 0 + 0 + + + + + 0 + 20 + + + + QComboBox::NoInsert + + + QComboBox::AdjustToContents + + + + + + + + 0 + 20 + + + + QComboBox::NoInsert + + + QComboBox::AdjustToContents + + + + + + + Entry Point: + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + 0 + 20 + + + + false + + + + + + + RDTableWidget + QTableWidget +
Widgets/Extended/RDTableWidget.h
+
RDTreeWidget QTreeView @@ -604,11 +744,6 @@ QWidget
3rdparty/toolwindowmanager/ToolWindowManager.h
- - RDTableWidget - QTableWidget -
Widgets/Extended/RDTableWidget.h
-
diff --git a/qrenderdoc/qrenderdoc.pro b/qrenderdoc/qrenderdoc.pro index 6421e1f76..b186a5981 100644 --- a/qrenderdoc/qrenderdoc.pro +++ b/qrenderdoc/qrenderdoc.pro @@ -158,7 +158,7 @@ SOURCES += Code/qrenderdoc.cpp \ Code/pyrenderdoc/PythonContext.cpp \ Code/Interface/QRDInterface.cpp \ Code/Interface/Analytics.cpp \ - Code/Interface/SPIRVDisassembler.cpp \ + Code/Interface/ShaderProcessingTool.cpp \ Code/Interface/PersistantConfig.cpp \ Code/Interface/RemoteHost.cpp \ Styles/StyleData.cpp \ diff --git a/qrenderdoc/qrenderdoc_local.vcxproj b/qrenderdoc/qrenderdoc_local.vcxproj index 9516fcc8b..3b6fc3e0b 100644 --- a/qrenderdoc/qrenderdoc_local.vcxproj +++ b/qrenderdoc/qrenderdoc_local.vcxproj @@ -578,7 +578,7 @@ - + Create diff --git a/qrenderdoc/qrenderdoc_local.vcxproj.filters b/qrenderdoc/qrenderdoc_local.vcxproj.filters index 44d8f4c63..f4cf909a5 100644 --- a/qrenderdoc/qrenderdoc_local.vcxproj.filters +++ b/qrenderdoc/qrenderdoc_local.vcxproj.filters @@ -699,9 +699,6 @@ Windows\Dialogs - - Code\Interface - Widgets @@ -711,6 +708,9 @@ Generated Files + + Code\Interface + diff --git a/renderdoc/api/replay/replay_enums.h b/renderdoc/api/replay/replay_enums.h index 04b616267..7a2c39b9f 100644 --- a/renderdoc/api/replay/replay_enums.h +++ b/renderdoc/api/replay/replay_enums.h @@ -1320,6 +1320,12 @@ enum class GPUVendor : uint32_t DECLARE_REFLECTION_ENUM(GPUVendor); +DOCUMENT(R"(Get the GPUVendor for a given PCI Vendor ID. + +:param int vendorID: The PCI Vendor ID +:return: The vendor identified +:rtype: GPUVendor +)"); constexpr GPUVendor GPUVendorFromPCIVendor(uint32_t vendorID) { // temporarily disable clang-format to make this more readable. @@ -1416,6 +1422,18 @@ enum class ShaderEncoding : uint32_t ITERABLE_OPERATORS(ShaderEncoding); DECLARE_REFLECTION_ENUM(ShaderEncoding); +DOCUMENT(R"(Check whether or not this is a human readable text representation. + +:param ShaderEncoding e: The encoding to check. +:return: ``True`` if it describes a text representation, ``False`` for a bytecode representation. +:rtype: ``bool`` +)"); +constexpr inline bool IsTextRepresentation(ShaderEncoding encoding) +{ + return encoding == ShaderEncoding::HLSL || encoding == ShaderEncoding::GLSL || + encoding == ShaderEncoding::SPIRVAsm; +} + DOCUMENT(R"(A primitive topology used for processing vertex data. .. data:: Unknown @@ -1683,7 +1701,7 @@ DOCUMENT(R"(Check whether or not this is a strip-type topology. :param Topology t: The topology to check. :return: ``True`` if it describes a strip topology, ``False`` for a list. -:rtype: ``int`` +:rtype: ``bool`` )"); constexpr inline bool IsStrip(Topology topology) {