From 8630f82db30c4dec3857d4b2ee4a5f29c5ef8a7e Mon Sep 17 00:00:00 2001 From: baldurk Date: Fri, 10 Feb 2017 21:48:07 +0000 Subject: [PATCH] Update shader viewer to support editing and debugging of shaders --- .../D3D11PipelineStateViewer.cpp | 38 +- .../D3D12PipelineStateViewer.cpp | 35 +- .../PipelineState/GLPipelineStateViewer.cpp | 35 +- .../PipelineState/PipelineStateViewer.cpp | 185 ++++ .../PipelineState/PipelineStateViewer.h | 4 + .../VulkanPipelineStateViewer.cpp | 35 +- qrenderdoc/Windows/PixelHistoryView.cpp | 38 +- qrenderdoc/Windows/ShaderViewer.cpp | 945 ++++++++++++++++-- qrenderdoc/Windows/ShaderViewer.h | 95 +- qrenderdoc/Windows/ShaderViewer.ui | 407 +++++++- qrenderdoc/Windows/TextureViewer.cpp | 47 + qrenderdoc/Windows/TextureViewer.h | 1 + 12 files changed, 1741 insertions(+), 124 deletions(-) diff --git a/qrenderdoc/Windows/PipelineState/D3D11PipelineStateViewer.cpp b/qrenderdoc/Windows/PipelineState/D3D11PipelineStateViewer.cpp index 6af1c5d48..a3421083e 100644 --- a/qrenderdoc/Windows/PipelineState/D3D11PipelineStateViewer.cpp +++ b/qrenderdoc/Windows/PipelineState/D3D11PipelineStateViewer.cpp @@ -2149,11 +2149,13 @@ void D3D11PipelineStateViewer::shaderView_clicked() { ShaderStageType shaderStage = eShaderStage_Vertex; ShaderReflection *shaderDetails = NULL; + const ShaderBindpointMapping *bindMap = NULL; QWidget *sender = qobject_cast(QObject::sender()); if(sender == ui->iaBytecode || sender == ui->iaBytecodeViewButton) { shaderDetails = m_Ctx.CurD3D11PipelineState.m_IA.Bytecode; + bindMap = NULL; } else { @@ -2162,11 +2164,13 @@ void D3D11PipelineStateViewer::shaderView_clicked() if(stage == NULL || stage->Shader == ResourceId()) return; + bindMap = &stage->BindpointMapping; shaderDetails = stage->ShaderDetails; shaderStage = stage->stage; } - ShaderViewer *shad = new ShaderViewer(m_Ctx, shaderDetails, shaderStage, NULL, ""); + ShaderViewer *shad = + ShaderViewer::viewShader(m_Ctx, bindMap, shaderDetails, shaderStage, m_Ctx.mainWindow()); m_Ctx.setupDockWindow(shad); @@ -2178,6 +2182,38 @@ void D3D11PipelineStateViewer::shaderView_clicked() void D3D11PipelineStateViewer::shaderEdit_clicked() { + QWidget *sender = qobject_cast(QObject::sender()); + const D3D11PipelineState::ShaderStage *stage = stageForSender(sender); + + if(!stage || stage->Shader == ResourceId()) + return; + + const ShaderReflection *shaderDetails = stage->ShaderDetails; + + if(!shaderDetails) + return; + + QString entryFunc = QString("EditedShader%1S").arg(ToQStr(stage->stage, eGraphicsAPI_D3D11)[0]); + + QString mainfile = ""; + + QStringMap files; + + bool hasOrigSource = m_Common.PrepareShaderEditing(shaderDetails, entryFunc, files, mainfile); + + if(!hasOrigSource) + { + QString hlsl = "// TODO - generate stub HLSL"; + + mainfile = "generated.hlsl"; + + files[mainfile] = hlsl; + } + + if(files.empty()) + return; + + m_Common.EditShader(stage->stage, stage->Shader, shaderDetails, entryFunc, files, mainfile); } void D3D11PipelineStateViewer::shaderSave_clicked() diff --git a/qrenderdoc/Windows/PipelineState/D3D12PipelineStateViewer.cpp b/qrenderdoc/Windows/PipelineState/D3D12PipelineStateViewer.cpp index 495447c4d..d4510f137 100644 --- a/qrenderdoc/Windows/PipelineState/D3D12PipelineStateViewer.cpp +++ b/qrenderdoc/Windows/PipelineState/D3D12PipelineStateViewer.cpp @@ -2160,7 +2160,8 @@ void D3D12PipelineStateViewer::shaderView_clicked() if(stage == NULL || stage->Shader == ResourceId()) return; - ShaderViewer *shad = new ShaderViewer(m_Ctx, stage->ShaderDetails, stage->stage, NULL, ""); + ShaderViewer *shad = ShaderViewer::viewShader( + m_Ctx, &stage->BindpointMapping, stage->ShaderDetails, stage->stage, m_Ctx.mainWindow()); m_Ctx.setupDockWindow(shad); @@ -2172,6 +2173,38 @@ void D3D12PipelineStateViewer::shaderView_clicked() void D3D12PipelineStateViewer::shaderEdit_clicked() { + QWidget *sender = qobject_cast(QObject::sender()); + const D3D12PipelineState::ShaderStage *stage = stageForSender(sender); + + if(!stage || stage->Shader == ResourceId()) + return; + + const ShaderReflection *shaderDetails = stage->ShaderDetails; + + if(!shaderDetails) + return; + + QString entryFunc = QString("EditedShader%1S").arg(ToQStr(stage->stage, eGraphicsAPI_D3D12)[0]); + + QString mainfile = ""; + + QStringMap files; + + bool hasOrigSource = m_Common.PrepareShaderEditing(shaderDetails, entryFunc, files, mainfile); + + if(!hasOrigSource) + { + QString hlsl = "// TODO - generate stub HLSL"; + + mainfile = "generated.hlsl"; + + files[mainfile] = hlsl; + } + + if(files.empty()) + return; + + m_Common.EditShader(stage->stage, stage->Shader, shaderDetails, entryFunc, files, mainfile); } void D3D12PipelineStateViewer::shaderSave_clicked() diff --git a/qrenderdoc/Windows/PipelineState/GLPipelineStateViewer.cpp b/qrenderdoc/Windows/PipelineState/GLPipelineStateViewer.cpp index f37efdc8a..3c0eb9b14 100644 --- a/qrenderdoc/Windows/PipelineState/GLPipelineStateViewer.cpp +++ b/qrenderdoc/Windows/PipelineState/GLPipelineStateViewer.cpp @@ -2257,7 +2257,8 @@ void GLPipelineStateViewer::shaderView_clicked() ShaderReflection *shaderDetails = stage->ShaderDetails; - ShaderViewer *shad = new ShaderViewer(m_Ctx, shaderDetails, stage->stage, NULL, ""); + ShaderViewer *shad = ShaderViewer::viewShader(m_Ctx, &stage->BindpointMapping, shaderDetails, + stage->stage, m_Ctx.mainWindow()); m_Ctx.setupDockWindow(shad); @@ -2269,6 +2270,38 @@ void GLPipelineStateViewer::shaderView_clicked() void GLPipelineStateViewer::shaderEdit_clicked() { + QWidget *sender = qobject_cast(QObject::sender()); + const GLPipelineState::ShaderStage *stage = stageForSender(sender); + + if(!stage || stage->Shader == ResourceId()) + return; + + const ShaderReflection *shaderDetails = stage->ShaderDetails; + + if(!shaderDetails) + return; + + QString entryFunc = QString("EditedShader%1S").arg(ToQStr(stage->stage, eGraphicsAPI_OpenGL)[0]); + + QString mainfile = ""; + + QStringMap files; + + bool hasOrigSource = m_Common.PrepareShaderEditing(shaderDetails, entryFunc, files, mainfile); + + if(!hasOrigSource) + { + QString glsl = "// TODO - disassemble SPIR-V"; + + mainfile = "generated.glsl"; + + files[mainfile] = glsl; + } + + if(files.empty()) + return; + + m_Common.EditShader(stage->stage, stage->Shader, shaderDetails, entryFunc, files, mainfile); } void GLPipelineStateViewer::shaderSave_clicked() diff --git a/qrenderdoc/Windows/PipelineState/PipelineStateViewer.cpp b/qrenderdoc/Windows/PipelineState/PipelineStateViewer.cpp index 184ec5719..72bf97f81 100644 --- a/qrenderdoc/Windows/PipelineState/PipelineStateViewer.cpp +++ b/qrenderdoc/Windows/PipelineState/PipelineStateViewer.cpp @@ -23,6 +23,9 @@ ******************************************************************************/ #include "PipelineStateViewer.h" +#include "3rdparty/toolwindowmanager/ToolWindowManager.h" +#include "Windows/MainWindow.h" +#include "Windows/ShaderViewer.h" #include "D3D11PipelineStateViewer.h" #include "D3D12PipelineStateViewer.h" #include "GLPipelineStateViewer.h" @@ -183,6 +186,188 @@ void PipelineStateViewer::setToVulkan() ui->layout->addWidget(m_Vulkan); m_Current = m_Vulkan; m_Ctx.CurPipelineState.DefaultType = eGraphicsAPI_Vulkan; +} + +bool PipelineStateViewer::PrepareShaderEditing(const ShaderReflection *shaderDetails, + QString &entryFunc, QStringMap &files, + QString &mainfile) +{ + if(!shaderDetails->DebugInfo.entryFunc.empty() && !shaderDetails->DebugInfo.files.empty()) + { + entryFunc = ToQStr(shaderDetails->DebugInfo.entryFunc); + + QStringList uniqueFiles; + + for(auto &s : shaderDetails->DebugInfo.files) + { + QString filename = ToQStr(s.first); + if(uniqueFiles.contains(filename.toLower())) + { + qWarning() << "Duplicate full filename" << ToQStr(s.first); + continue; + } + uniqueFiles.push_back(filename.toLower()); + + files[filename] = ToQStr(s.second); + } + + int entryFile = shaderDetails->DebugInfo.entryFile; + if(entryFile < 0 || entryFile >= shaderDetails->DebugInfo.files.count) + entryFile = 0; + + mainfile = ToQStr(shaderDetails->DebugInfo.files[entryFile].first); + + return true; + } + + return false; +} + +void PipelineStateViewer::EditShader(ShaderStageType shaderType, ResourceId id, + const ShaderReflection *shaderDetails, const QString &entryFunc, + const QStringMap &files, const QString &mainfile) +{ + ShaderViewer *sv = ShaderViewer::editShader( + m_Ctx, false, entryFunc, files, + // save callback + [entryFunc, mainfile, shaderType, id, shaderDetails]( + CaptureContext *ctx, ShaderViewer *viewer, const QStringMap &updatedfiles) { + QString compileSource = updatedfiles[mainfile]; + + // try and match up #includes against the files that we have. This isn't always + // possible as fxc only seems to include the source for files if something in + // that file was included in the compiled output. So you might end up with + // dangling #includes - we just have to ignore them + int offs = compileSource.indexOf("#include"); + + while(offs >= 0) + { + // search back to ensure this is a valid #include (ie. not in a comment). + // Must only see whitespace before, then a newline. + int ws = qMax(0, offs - 1); + while(ws >= 0 && (compileSource[ws] == ' ' || compileSource[ws] == '\t')) + ws--; + + // not valid? jump to next. + if(ws > 0 && compileSource[ws] != '\n') + { + offs = compileSource.indexOf("#include", offs + 1); + continue; + } + + int start = ws + 1; + + bool tail = true; + + int lineEnd = compileSource.indexOf("\n", start + 1); + if(lineEnd == -1) + { + lineEnd = compileSource.length(); + tail = false; + } + + ws = offs + sizeof("#include") - 1; + while(compileSource[ws] == ' ' || compileSource[ws] == '\t') + ws++; + + QString line = compileSource.mid(offs, lineEnd - offs + 1); + + if(compileSource[ws] != '<' && compileSource[ws] != '"') + { + viewer->showErrors("Invalid #include directive found:\r\n" + line); + return; + } + + // find matching char, either <> or ""; + int end = compileSource.indexOf(compileSource[ws] == '"' ? '"' : '>', ws + 1); + + if(end == -1) + { + viewer->showErrors("Invalid #include directive found:\r\n" + line); + return; + } + + QString fname = compileSource.mid(ws + 1, end - ws - 1); + + QString fileText; + + // look for exact match first + if(updatedfiles.contains(fname)) + { + fileText = updatedfiles[fname]; + } + else + { + QString search = QFileInfo(fname).fileName(); + // if not, try and find the same filename (this is not proper include handling!) + for(const QString &k : updatedfiles.keys()) + { + if(QFileInfo(k).fileName().compare(search, Qt::CaseInsensitive) == 0) + { + fileText = updatedfiles[k]; + break; + } + } + + if(fileText == "") + fileText = "// Can't find file " + fname + "\n"; + } + + compileSource = compileSource.left(offs) + "\n\n" + fileText + "\n\n" + + (tail ? compileSource.mid(lineEnd + 1) : ""); + + // need to start searching from the beginning - wasteful but allows nested includes to + // work + offs = compileSource.indexOf("#include"); + } + + if(updatedfiles.contains("@cmdline")) + compileSource = updatedfiles["@cmdline"] + "\n\n" + compileSource; + + // invoke off to the ReplayRenderer to replace the log's shader + // with our edited one + ctx->Renderer().AsyncInvoke([ctx, entryFunc, compileSource, shaderType, id, shaderDetails, + viewer](IReplayRenderer *r) { + rdctype::str errs; + + uint flags = shaderDetails->DebugInfo.compileFlags; + + ResourceId from = id; + ResourceId to = r->BuildTargetShader( + entryFunc.toUtf8().data(), compileSource.toUtf8().data(), flags, shaderType, &errs); + + GUIInvoke::call([viewer, errs]() { viewer->showErrors(ToQStr(errs)); }); + if(to == ResourceId()) + { + r->RemoveReplacement(from); + GUIInvoke::call([ctx]() { ctx->RefreshStatus(); }); + } + else + { + r->ReplaceResource(from, to); + GUIInvoke::call([ctx]() { ctx->RefreshStatus(); }); + } + }); + }, + + // Close Callback + [id](CaptureContext *ctx) { + // remove the replacement on close (we could make this more sophisticated if there + // was a place to control replaced resources/shaders). + ctx->Renderer().AsyncInvoke([ctx, id](IReplayRenderer *r) { + r->RemoveReplacement(id); + GUIInvoke::call([ctx] { ctx->RefreshStatus(); }); + }); + }, + m_Ctx.mainWindow()); + + m_Ctx.setupDockWindow(sv); + + ToolWindowManager *manager = ToolWindowManager::managerOf(this); + + ToolWindowManager::AreaReference ref(ToolWindowManager::AddTo, manager->areaOf(this)); + manager->addToolWindow(sv, ref); +} bool PipelineStateViewer::SaveShaderFile(const ShaderReflection *shader) { diff --git a/qrenderdoc/Windows/PipelineState/PipelineStateViewer.h b/qrenderdoc/Windows/PipelineState/PipelineStateViewer.h index 9c0b85519..c9637837c 100644 --- a/qrenderdoc/Windows/PipelineState/PipelineStateViewer.h +++ b/qrenderdoc/Windows/PipelineState/PipelineStateViewer.h @@ -56,6 +56,10 @@ public: void setPersistData(const QVariant &persistData); bool SaveShaderFile(const ShaderReflection *shader); + bool PrepareShaderEditing(const ShaderReflection *shaderDetails, QString &entryFunc, + QStringMap &files, QString &mainfile); + void EditShader(ShaderStageType shaderType, ResourceId id, const ShaderReflection *shaderDetails, + const QString &entryFunc, const QStringMap &files, const QString &mainfile); private: Ui::PipelineStateViewer *ui; diff --git a/qrenderdoc/Windows/PipelineState/VulkanPipelineStateViewer.cpp b/qrenderdoc/Windows/PipelineState/VulkanPipelineStateViewer.cpp index c0b9a6575..fc4bbd9e6 100644 --- a/qrenderdoc/Windows/PipelineState/VulkanPipelineStateViewer.cpp +++ b/qrenderdoc/Windows/PipelineState/VulkanPipelineStateViewer.cpp @@ -2359,7 +2359,8 @@ void VulkanPipelineStateViewer::shaderView_clicked() ShaderReflection *shaderDetails = stage->ShaderDetails; - ShaderViewer *shad = new ShaderViewer(m_Ctx, shaderDetails, stage->stage, NULL, ""); + ShaderViewer *shad = ShaderViewer::viewShader(m_Ctx, &stage->BindpointMapping, shaderDetails, + stage->stage, m_Ctx.mainWindow()); m_Ctx.setupDockWindow(shad); @@ -2371,6 +2372,38 @@ void VulkanPipelineStateViewer::shaderView_clicked() void VulkanPipelineStateViewer::shaderEdit_clicked() { + QWidget *sender = qobject_cast(QObject::sender()); + const VulkanPipelineState::ShaderStage *stage = stageForSender(sender); + + if(!stage || stage->Shader == ResourceId()) + return; + + const ShaderReflection *shaderDetails = stage->ShaderDetails; + + if(!shaderDetails) + return; + + QString entryFunc = QString("EditedShader%1S").arg(ToQStr(stage->stage, eGraphicsAPI_Vulkan)[0]); + + QString mainfile = ""; + + QStringMap files; + + bool hasOrigSource = m_Common.PrepareShaderEditing(shaderDetails, entryFunc, files, mainfile); + + if(!hasOrigSource) + { + QString glsl = "// TODO - disassemble SPIR-V"; + + mainfile = "generated.glsl"; + + files[mainfile] = glsl; + } + + if(files.empty()) + return; + + m_Common.EditShader(stage->stage, stage->Shader, shaderDetails, entryFunc, files, mainfile); } void VulkanPipelineStateViewer::shaderSave_clicked() diff --git a/qrenderdoc/Windows/PixelHistoryView.cpp b/qrenderdoc/Windows/PixelHistoryView.cpp index 8f566975d..440fba715 100644 --- a/qrenderdoc/Windows/PixelHistoryView.cpp +++ b/qrenderdoc/Windows/PixelHistoryView.cpp @@ -29,6 +29,7 @@ #include #include "3rdparty/toolwindowmanager/ToolWindowManager.h" #include "Windows/BufferViewer.h" +#include "Windows/ShaderViewer.h" #include "ui_PixelHistoryView.h" struct EventTag @@ -635,7 +636,42 @@ void PixelHistoryView::setHistory(const rdctype::array &histo void PixelHistoryView::startDebug(EventTag tag) { m_Ctx.SetEventID({this}, tag.eventID, tag.eventID); - // TODO Shader debugging + + ShaderDebugTrace *trace = new ShaderDebugTrace; + + bool success = false; + + m_Ctx.Renderer().BlockInvoke([this, &success, trace](IReplayRenderer *r) { + success = + r->DebugPixel((uint32_t)m_Pixel.x(), (uint32_t)m_Pixel.y(), m_Display.sampleIdx, ~0U, trace); + }); + + if(!success || trace->states.count == 0) + { + RDDialog::critical(this, tr("Debug Error"), tr("Error debugging pixel.")); + delete trace; + return; + } + + GUIInvoke::call([this, trace]() { + QString debugContext = QString("Pixel %1,%2").arg(m_Pixel.x()).arg(m_Pixel.y()); + + const ShaderReflection *shaderDetails = + m_Ctx.CurPipelineState.GetShaderReflection(eShaderStage_Pixel); + const ShaderBindpointMapping &bindMapping = + m_Ctx.CurPipelineState.GetBindpointMapping(eShaderStage_Pixel); + + // viewer takes ownership of the trace + ShaderViewer *s = ShaderViewer::debugShader(m_Ctx, &bindMapping, shaderDetails, + eShaderStage_Pixel, trace, debugContext, this); + + m_Ctx.setupDockWindow(s); + + ToolWindowManager *manager = ToolWindowManager::managerOf(this); + + ToolWindowManager::AreaReference ref(ToolWindowManager::AddTo, manager->areaOf(this)); + manager->addToolWindow(s, ref); + }); } void PixelHistoryView::jumpToPrimitive(EventTag tag) diff --git a/qrenderdoc/Windows/ShaderViewer.cpp b/qrenderdoc/Windows/ShaderViewer.cpp index 848f0c8bc..0001934cb 100644 --- a/qrenderdoc/Windows/ShaderViewer.cpp +++ b/qrenderdoc/Windows/ShaderViewer.cpp @@ -24,51 +24,66 @@ #include "ShaderViewer.h" #include +#include #include "3rdparty/scintilla/include/SciLexer.h" #include "3rdparty/scintilla/include/qt/ScintillaEdit.h" #include "3rdparty/toolwindowmanager/ToolWindowManager.h" #include "Code/ScintillaSyntax.h" +#include "Windows/PipelineState/PipelineStateViewer.h" #include "ui_ShaderViewer.h" -ShaderViewer::ShaderViewer(CaptureContext &ctx, ShaderReflection *shader, ShaderStageType stage, - ShaderDebugTrace *trace, const QString &debugContext, QWidget *parent) +struct CBufferTag +{ + CBufferTag() {} + CBufferTag(uint32_t c, uint32_t r) : cb(c), reg(r) {} + uint32_t cb = 0; + uint32_t reg = 0; +}; + +struct ResourceTag +{ + ResourceTag() {} + ResourceTag(uint32_t r) : reg(r) {} + uint32_t reg = 0; +}; + +Q_DECLARE_METATYPE(CBufferTag); +Q_DECLARE_METATYPE(ResourceTag); +Q_DECLARE_METATYPE(ShaderVariable); + +ShaderViewer::ShaderViewer(CaptureContext &ctx, QWidget *parent) : QFrame(parent), ui(new Ui::ShaderViewer), m_Ctx(ctx) { ui->setupUi(this); - m_ShaderDetails = shader; - m_Trace = trace; - - if(trace != NULL) - setWindowTitle( - QString("Debugging %1 - %2").arg(m_Ctx.CurPipelineState.GetShaderName(stage)).arg(debugContext)); - else - setWindowTitle(m_Ctx.CurPipelineState.GetShaderName(stage)); - - QString disasm = shader != NULL ? ToQStr(shader->Disassembly) : ""; - { - m_DisassemblyView = MakeEditor("scintillaDisassem", disasm, - m_Ctx.APIProps().pipelineType == eGraphicsAPI_Vulkan); + m_DisassemblyView = + MakeEditor("scintillaDisassem", "", m_Ctx.APIProps().pipelineType == eGraphicsAPI_Vulkan); m_DisassemblyView->setReadOnly(true); m_DisassemblyView->setWindowTitle(tr("Disassembly")); - QObject::connect(m_DisassemblyView, &ScintillaEdit::keyPressed, this, - &ShaderViewer::disassembly_keyPressed); + m_DisassemblyView->usePopUp(SC_POPUP_NEVER); + QObject::connect(m_DisassemblyView, &ScintillaEdit::keyPressed, this, &ShaderViewer::readonly_keyPressed); // C# LightCoral m_DisassemblyView->markerSetBack(CURRENT_MARKER, SCINTILLA_COLOUR(240, 128, 128)); + m_DisassemblyView->markerSetBack(CURRENT_MARKER + 1, SCINTILLA_COLOUR(240, 128, 128)); m_DisassemblyView->markerDefine(CURRENT_MARKER, SC_MARK_SHORTARROW); + m_DisassemblyView->markerDefine(CURRENT_MARKER + 1, SC_MARK_BACKGROUND); // C# LightSlateGray m_DisassemblyView->markerSetBack(FINISHED_MARKER, SCINTILLA_COLOUR(119, 136, 153)); + m_DisassemblyView->markerSetBack(FINISHED_MARKER + 1, SCINTILLA_COLOUR(119, 136, 153)); m_DisassemblyView->markerDefine(FINISHED_MARKER, SC_MARK_ROUNDRECT); + m_DisassemblyView->markerDefine(FINISHED_MARKER + 1, SC_MARK_BACKGROUND); // C# Red m_DisassemblyView->markerSetBack(BREAKPOINT_MARKER, SCINTILLA_COLOUR(255, 0, 0)); + m_DisassemblyView->markerSetBack(BREAKPOINT_MARKER + 1, SCINTILLA_COLOUR(255, 0, 0)); m_DisassemblyView->markerDefine(BREAKPOINT_MARKER, SC_MARK_CIRCLE); + m_DisassemblyView->markerDefine(BREAKPOINT_MARKER + 1, SC_MARK_BACKGROUND); m_Scintillas.push_back(m_DisassemblyView); @@ -76,9 +91,129 @@ ShaderViewer::ShaderViewer(CaptureContext &ctx, ShaderReflection *shader, Shader ui->docking->setToolWindowProperties(m_DisassemblyView, ToolWindowManager::HideCloseButton); } - if(shader != NULL && shader->DebugInfo.entryFunc.count > 0 && shader->DebugInfo.files.count > 0) + ui->docking->setAllowFloatingWindow(false); + ui->docking->setRubberBandLineWidth(50); + + QVBoxLayout *layout = new QVBoxLayout(this); + layout->setSpacing(0); + layout->setMargin(0); + layout->addWidget(ui->toolbar); + layout->addWidget(ui->docking); + + m_Ctx.AddLogViewer(this); +} + +void ShaderViewer::editShader(bool customShader, const QString &entryPoint, const QStringMap &files) +{ + m_Scintillas.removeOne(m_DisassemblyView); + ui->docking->removeToolWindow(m_DisassemblyView); + + // hide watch, constants, variables + ui->watch->hide(); + ui->variables->hide(); + ui->constants->hide(); + + // hide debugging toolbar buttons + ui->stepBack->hide(); + ui->stepNext->hide(); + ui->runToCursor->hide(); + ui->runToSample->hide(); + ui->runToNaNOrInf->hide(); + ui->regFormatSep->hide(); + ui->intView->hide(); + ui->floatView->hide(); + + // hide signatures + ui->inputSig->hide(); + ui->outputSig->hide(); + + QString title; + + QWidget *sel = NULL; + for(const QString &f : files.keys()) { - if(trace != NULL) + QString name = QFileInfo(f).fileName(); + QString text = files[f]; + + ScintillaEdit *scintilla = AddFileScintilla(name, text); + + scintilla->setReadOnly(false); + QObject::connect(m_DisassemblyView, &ScintillaEdit::keyPressed, this, + &ShaderViewer::editable_keyPressed); + + // TODO - shortcuts + QObject::connect(new QShortcut(QKeySequence(Qt::Key_S | Qt::ControlModifier), scintilla), + &QShortcut::activated, this, &ShaderViewer::on_save_clicked); + + QWidget *w = (QWidget *)scintilla; + w->setProperty("filename", f); + + if(text.contains(entryPoint)) + sel = scintilla; + + if(sel == scintilla || title.isEmpty()) + title = tr("%1 - Edit (%2)").arg(entryPoint).arg(name); + } + + if(sel != NULL) + ToolWindowManager::raiseToolWindow(sel); + + setWindowTitle(title); + + // TODO File list + // if(files.count() > 2) + // addFileList(); + + m_Errors = MakeEditor("errors", "", false); + m_Errors->setReadOnly(true); + m_Errors->setWindowTitle("Errors"); + + // remove margins + m_Errors->setMarginWidthN(0, 0); + m_Errors->setMarginWidthN(1, 0); + m_Errors->setMarginWidthN(2, 0); + + QObject::connect(m_Errors, &ScintillaEdit::keyPressed, this, &ShaderViewer::readonly_keyPressed); + + ui->docking->addToolWindow( + m_Errors, ToolWindowManager::AreaReference(ToolWindowManager::BottomOf, + ui->docking->areaOf(m_Scintillas.front()), 0.2f)); + ui->docking->setToolWindowProperties(m_Errors, ToolWindowManager::HideCloseButton); +} + +void ShaderViewer::debugShader(const ShaderBindpointMapping *bind, const ShaderReflection *shader, + ShaderStageType stage, ShaderDebugTrace *trace, + const QString &debugContext) +{ + m_Mapping = bind; + m_ShaderDetails = shader; + m_Trace = trace; + m_Stage = stage; + + if(!shader || !bind) + m_Trace = NULL; + + if(trace) + setWindowTitle( + QString("Debugging %1 - %2").arg(m_Ctx.CurPipelineState.GetShaderName(stage)).arg(debugContext)); + else + setWindowTitle(m_Ctx.CurPipelineState.GetShaderName(stage)); + + if(shader) + { + // read-only applies to us too! + m_DisassemblyView->setReadOnly(false); + m_DisassemblyView->setText(shader->Disassembly.c_str()); + m_DisassemblyView->setReadOnly(true); + } + + if(trace) + QObject::connect(m_DisassemblyView, &ScintillaEdit::buttonReleased, this, + &ShaderViewer::disassembly_buttonReleased); + + if(shader && shader->DebugInfo.entryFunc.count > 0 && shader->DebugInfo.files.count > 0) + { + if(trace) setWindowTitle( QString("Debug %1() - %2").arg(ToQStr(shader->DebugInfo.entryFunc)).arg(debugContext)); else @@ -92,20 +227,7 @@ ShaderViewer::ShaderViewer(CaptureContext &ctx, ShaderReflection *shader, Shader QString name = QFileInfo(ToQStr(f.first)).fileName(); QString text = ToQStr(f.second); - ScintillaEdit *scintilla = MakeEditor("scintilla" + name, text, true); - scintilla->setReadOnly(true); - scintilla->setWindowTitle(name); - ((QWidget *)scintilla)->setProperty("name", name); - - QObject::connect(scintilla, &ScintillaEdit::keyPressed, this, - &ShaderViewer::readonly_keyPressed); - - ui->docking->addToolWindow( - scintilla, ToolWindowManager::AreaReference(ToolWindowManager::AddTo, - ui->docking->areaOf(m_DisassemblyView))); - ui->docking->setToolWindowProperties(scintilla, ToolWindowManager::HideCloseButton); - - m_Scintillas.push_back(scintilla); + ScintillaEdit *scintilla = AddFileScintilla(name, text); if(shader->DebugInfo.entryFile >= 0 && shader->DebugInfo.entryFile < shader->DebugInfo.files.count) @@ -121,87 +243,171 @@ ShaderViewer::ShaderViewer(CaptureContext &ctx, ShaderReflection *shader, Shader fileIdx++; } + if(trace) + sel = m_DisassemblyView; + + // TODO File list // if(shader->DebugInfo.files.count > 2) - // AddFileList(); + // addFileList(); ToolWindowManager::raiseToolWindow(sel); } - for(int i = 0; i < ui->inputSig->header()->count(); i++) - ui->inputSig->header()->setSectionResizeMode(i, QHeaderView::ResizeToContents); + ui->snippets->hide(); - for(int i = 0; i < ui->outputSig->header()->count(); i++) - ui->outputSig->header()->setSectionResizeMode(i, QHeaderView::ResizeToContents); - - if(m_ShaderDetails != NULL) + if(trace) { - for(SigParameter &s : m_ShaderDetails->InputSig) + // hide signatures + ui->inputSig->hide(); + ui->outputSig->hide(); + + ui->variables->header()->setSectionResizeMode(0, QHeaderView::Stretch); + ui->variables->header()->setSectionResizeMode(1, QHeaderView::ResizeToContents); + ui->variables->header()->setSectionResizeMode(2, QHeaderView::Stretch); + + ui->constants->header()->setSectionResizeMode(0, QHeaderView::Stretch); + ui->constants->header()->setSectionResizeMode(1, QHeaderView::ResizeToContents); + ui->constants->header()->setSectionResizeMode(2, QHeaderView::Stretch); + + ui->watch->setWindowTitle(tr("Watch")); + ui->docking->addToolWindow( + ui->watch, ToolWindowManager::AreaReference(ToolWindowManager::BottomOf, + ui->docking->areaOf(m_DisassemblyView), 0.25f)); + ui->docking->setToolWindowProperties(ui->watch, ToolWindowManager::HideCloseButton); + + ui->variables->setWindowTitle(tr("Variables")); + ui->docking->addToolWindow( + ui->variables, + ToolWindowManager::AreaReference(ToolWindowManager::AddTo, ui->docking->areaOf(ui->watch))); + ui->docking->setToolWindowProperties(ui->variables, ToolWindowManager::HideCloseButton); + + ui->constants->setWindowTitle(tr("Constants && Resources")); + ui->docking->addToolWindow( + ui->constants, ToolWindowManager::AreaReference(ToolWindowManager::LeftOf, + ui->docking->areaOf(ui->variables), 0.5f)); + ui->docking->setToolWindowProperties(ui->constants, ToolWindowManager::HideCloseButton); + + m_DisassemblyView->setMarginWidthN(1, 20); + + // display current line in margin 2, distinct from breakpoint in margin 1 + sptr_t markMask = (1 << CURRENT_MARKER) | (1 << FINISHED_MARKER); + + m_DisassemblyView->setMarginMaskN(1, m_DisassemblyView->marginMaskN(1) & ~markMask); + m_DisassemblyView->setMarginMaskN(2, m_DisassemblyView->marginMaskN(2) | markMask); + + QObject::connect(ui->stepBack, &QToolButton::clicked, this, &ShaderViewer::stepBack); + QObject::connect(ui->stepNext, &QToolButton::clicked, this, &ShaderViewer::stepNext); + QObject::connect(ui->runToCursor, &QToolButton::clicked, this, &ShaderViewer::runToCursor); + QObject::connect(ui->runToSample, &QToolButton::clicked, this, &ShaderViewer::runToSample); + QObject::connect(ui->runToNaNOrInf, &QToolButton::clicked, this, &ShaderViewer::runToNanOrInf); + + QObject::connect(new QShortcut(QKeySequence(Qt::Key_F10), m_DisassemblyView), + &QShortcut::activated, this, &ShaderViewer::stepNext); + QObject::connect(new QShortcut(QKeySequence(Qt::Key_F10 | Qt::ShiftModifier), m_DisassemblyView), + &QShortcut::activated, this, &ShaderViewer::stepBack); + QObject::connect( + new QShortcut(QKeySequence(Qt::Key_F10 | Qt::ControlModifier), m_DisassemblyView), + &QShortcut::activated, this, &ShaderViewer::runToCursor); + QObject::connect(new QShortcut(QKeySequence(Qt::Key_F5), m_DisassemblyView), + &QShortcut::activated, this, &ShaderViewer::run); + QObject::connect(new QShortcut(QKeySequence(Qt::Key_F5 | Qt::ShiftModifier), m_DisassemblyView), + &QShortcut::activated, this, &ShaderViewer::runBack); + QObject::connect(new QShortcut(QKeySequence(Qt::Key_F9), m_DisassemblyView), + &QShortcut::activated, [this]() { toggleBreakpoint(); }); + + setCurrentStep(0); + } + else + { + // hide watch, constants, variables + ui->watch->hide(); + ui->variables->hide(); + ui->constants->hide(); + + // hide debugging toolbar buttons + ui->stepBack->hide(); + ui->stepNext->hide(); + ui->runToCursor->hide(); + ui->runToSample->hide(); + ui->runToNaNOrInf->hide(); + ui->regFormatSep->hide(); + ui->intView->hide(); + ui->floatView->hide(); + + // show input and output signatures + for(int i = 0; i < ui->inputSig->header()->count(); i++) + ui->inputSig->header()->setSectionResizeMode(i, QHeaderView::ResizeToContents); + + for(int i = 0; i < ui->outputSig->header()->count(); i++) + ui->outputSig->header()->setSectionResizeMode(i, QHeaderView::ResizeToContents); + + if(shader) { - QString name = s.varName.count == 0 - ? ToQStr(s.semanticName) - : QString("%1 (%2)").arg(ToQStr(s.varName)).arg(ToQStr(s.semanticName)); - if(s.semanticName.count == 0) - name = s.varName; - - QString semIdx = s.needSemanticIndex ? QString::number(s.semanticIndex) : ""; - - ui->inputSig->addTopLevelItem(makeTreeNode( - {name, semIdx, s.regIndex, TypeString(s), ToQStr(s.systemValue), - GetComponentString(s.regChannelMask), GetComponentString(s.channelUsedMask)})); - } - - bool multipleStreams = false; - for(const SigParameter &s : m_ShaderDetails->OutputSig) - { - if(s.stream > 0) + for(const SigParameter &s : shader->InputSig) { - multipleStreams = true; - break; + QString name = s.varName.count == 0 + ? ToQStr(s.semanticName) + : QString("%1 (%2)").arg(ToQStr(s.varName)).arg(ToQStr(s.semanticName)); + if(s.semanticName.count == 0) + name = s.varName; + + QString semIdx = s.needSemanticIndex ? QString::number(s.semanticIndex) : ""; + + ui->inputSig->addTopLevelItem(makeTreeNode( + {name, semIdx, s.regIndex, TypeString(s), ToQStr(s.systemValue), + GetComponentString(s.regChannelMask), GetComponentString(s.channelUsedMask)})); + } + + bool multipleStreams = false; + for(const SigParameter &s : shader->OutputSig) + { + if(s.stream > 0) + { + multipleStreams = true; + break; + } + } + + for(const SigParameter &s : shader->OutputSig) + { + QString name = s.varName.count == 0 + ? ToQStr(s.semanticName) + : QString("%1 (%2)").arg(ToQStr(s.varName)).arg(ToQStr(s.semanticName)); + if(s.semanticName.count == 0) + name = s.varName; + + if(multipleStreams) + name = QString("Stream %1 : %2").arg(s.stream).arg(name); + + QString semIdx = s.needSemanticIndex ? QString::number(s.semanticIndex) : ""; + + ui->outputSig->addTopLevelItem(makeTreeNode( + {name, semIdx, s.regIndex, TypeString(s), ToQStr(s.systemValue), + GetComponentString(s.regChannelMask), GetComponentString(s.channelUsedMask)})); } } - for(const SigParameter &s : m_ShaderDetails->OutputSig) - { - QString name = s.varName.count == 0 - ? ToQStr(s.semanticName) - : QString("%1 (%2)").arg(ToQStr(s.varName)).arg(ToQStr(s.semanticName)); - if(s.semanticName.count == 0) - name = s.varName; + ui->inputSig->setWindowTitle(tr("Input Signature")); + ui->docking->addToolWindow(ui->inputSig, ToolWindowManager::AreaReference( + ToolWindowManager::BottomOf, + ui->docking->areaOf(m_DisassemblyView), 0.2f)); + ui->docking->setToolWindowProperties(ui->inputSig, ToolWindowManager::HideCloseButton); - if(multipleStreams) - name = QString("Stream %1 : %2").arg(s.stream).arg(name); - - QString semIdx = s.needSemanticIndex ? QString::number(s.semanticIndex) : ""; - - ui->outputSig->addTopLevelItem(makeTreeNode( - {name, semIdx, s.regIndex, TypeString(s), ToQStr(s.systemValue), - GetComponentString(s.regChannelMask), GetComponentString(s.channelUsedMask)})); - } + ui->outputSig->setWindowTitle(tr("Output Signature")); + ui->docking->addToolWindow( + ui->outputSig, ToolWindowManager::AreaReference(ToolWindowManager::RightOf, + ui->docking->areaOf(ui->inputSig), 0.5f)); + ui->docking->setToolWindowProperties(ui->outputSig, ToolWindowManager::HideCloseButton); } - - ui->inputSig->setWindowTitle(tr("Input Signature")); - ui->docking->addToolWindow( - ui->inputSig, ToolWindowManager::AreaReference(ToolWindowManager::BottomOf, - ui->docking->areaOf(m_DisassemblyView), 0.2f)); - ui->docking->setToolWindowProperties(ui->inputSig, ToolWindowManager::HideCloseButton); - - ui->outputSig->setWindowTitle(tr("Output Signature")); - ui->docking->addToolWindow( - ui->outputSig, ToolWindowManager::AreaReference(ToolWindowManager::RightOf, - ui->docking->areaOf(ui->inputSig), 0.5f)); - ui->docking->setToolWindowProperties(ui->outputSig, ToolWindowManager::HideCloseButton); - - ui->docking->setAllowFloatingWindow(false); - ui->docking->setRubberBandLineWidth(50); - - QHBoxLayout *layout = new QHBoxLayout(this); - layout->addWidget(ui->docking); - - m_Ctx.AddLogViewer(this); } ShaderViewer::~ShaderViewer() { + delete m_Trace; + + if(m_CloseCallback) + m_CloseCallback(&m_Ctx); + m_Ctx.RemoveLogViewer(this); delete ui; } @@ -219,12 +425,27 @@ void ShaderViewer::OnEventChanged(uint32_t eventID) { } -void ShaderViewer::disassembly_keyPressed(QKeyEvent *event) +ScintillaEdit *ShaderViewer::AddFileScintilla(const QString &name, const QString &text) { -} + ScintillaEdit *scintilla = MakeEditor("scintilla" + name, text, true); + scintilla->setReadOnly(true); + scintilla->setWindowTitle(name); + ((QWidget *)scintilla)->setProperty("name", name); -void ShaderViewer::readonly_keyPressed(QKeyEvent *event) -{ + QObject::connect(scintilla, &ScintillaEdit::keyPressed, this, &ShaderViewer::readonly_keyPressed); + + ToolWindowManager::AreaReference ref(ToolWindowManager::EmptySpace); + + if(!m_Scintillas.empty()) + ref = ToolWindowManager::AreaReference(ToolWindowManager::AddTo, + ui->docking->areaOf(m_Scintillas[0])); + + ui->docking->addToolWindow(scintilla, ref); + ui->docking->setToolWindowProperties(scintilla, ToolWindowManager::HideCloseButton); + + m_Scintillas.push_back(scintilla); + + return scintilla; } ScintillaEdit *ShaderViewer::MakeEditor(const QString &name, const QString &text, bool src) @@ -265,3 +486,527 @@ ScintillaEdit *ShaderViewer::MakeEditor(const QString &name, const QString &text return ret; } + +void ShaderViewer::readonly_keyPressed(QKeyEvent *event) +{ + // TODO find +} + +void ShaderViewer::editable_keyPressed(QKeyEvent *event) +{ + // TODO replace +} + +void ShaderViewer::disassembly_buttonReleased(QMouseEvent *event) +{ + // TODO context menu +} + +bool ShaderViewer::stepBack() +{ + if(!m_Trace) + return false; + + if(currentStep() == 0) + return false; + + setCurrentStep(currentStep() - 1); + + return true; +} + +bool ShaderViewer::stepNext() +{ + if(!m_Trace) + return false; + + if(currentStep() + 1 >= m_Trace->states.count) + return false; + + setCurrentStep(currentStep() + 1); + + return true; +} + +void ShaderViewer::runToCursor() +{ + if(!m_Trace) + return; + + sptr_t i = m_DisassemblyView->lineFromPosition(m_DisassemblyView->currentPos()); + + for(; i < m_DisassemblyView->lineCount(); i++) + { + int line = instructionForLine(i); + if(line >= 0) + { + runTo(line, true); + break; + } + } +} + +int ShaderViewer::instructionForLine(sptr_t line) +{ + QString trimmed = m_DisassemblyView->getLine(line).trimmed(); + + int colon = trimmed.indexOf(QChar(':')); + + if(colon > 0) + { + trimmed.truncate(colon); + + bool ok = false; + int instruction = trimmed.toInt(&ok); + + if(ok && instruction >= 0) + return instruction; + } + + return -1; +} + +void ShaderViewer::runToSample() +{ + runTo(-1, true, eShaderDbg_SampleLoadGather); +} + +void ShaderViewer::runToNanOrInf() +{ + runTo(-1, true, eShaderDbg_GeneratedNanOrInf); +} + +void ShaderViewer::runBack() +{ + runTo(-1, false); +} + +void ShaderViewer::run() +{ + runTo(-1, true); +} + +void ShaderViewer::runTo(int runToInstruction, bool forward, ShaderDebugStateFlags condition) +{ + if(!m_Trace) + return; + + int step = currentStep(); + + int inc = forward ? 1 : -1; + + bool firstStep = true; + + while(step < m_Trace->states.count) + { + if(runToInstruction >= 0 && m_Trace->states[step].nextInstruction == (uint32_t)runToInstruction) + break; + + if(!firstStep && (m_Trace->states[step + inc].flags & condition)) + break; + + if(!firstStep && m_Breakpoints.contains((int)m_Trace->states[step].nextInstruction)) + break; + + firstStep = false; + + if(step + inc < 0 || step + inc >= m_Trace->states.count) + break; + + step += inc; + } + + setCurrentStep(step); +} + +QString ShaderViewer::stringRep(const ShaderVariable &var, bool useType) +{ + if(ui->intView->isChecked() || (useType && var.type == eVar_Int)) + return RowString(var, 0, eVar_Int); + + if(useType && var.type == eVar_UInt) + return RowString(var, 0, eVar_UInt); + + return RowString(var, 0, eVar_Float); +} + +QTreeWidgetItem *ShaderViewer::makeResourceRegister(const BindpointMap &bind, uint32_t idx, + const BoundResource &bound, + const ShaderResource &res) +{ + QString name = QString(" (%1)").arg(ToQStr(res.name)); + + const FetchTexture *tex = m_Ctx.GetTexture(bound.Id); + const FetchBuffer *buf = m_Ctx.GetBuffer(bound.Id); + + if(res.IsSampler) + return NULL; + + QChar regChar('u'); + + if(res.IsSRV) + regChar = QChar('t'); + + // %1 = reg prefix (t or u for D3D11) + // %2 = bind slot + // %3 = bind set + // %4 = array index + + const char *fmt = "%1%2"; + + if(m_Ctx.APIProps().pipelineType == eGraphicsAPI_D3D12) + fmt = bind.arraySize == 1 ? "t%3:%2" : "t%3:%2[%4]"; + + QString regname = QString(fmt).arg(regChar).arg(bind.bind).arg(bind.bindset).arg(idx); + + if(tex) + { + QString type = QString("%1x%2x%3[%4] @ %5 - %6") + .arg(tex->width) + .arg(tex->height) + .arg(tex->depth > 1 ? tex->depth : tex->arraysize) + .arg(tex->mips) + .arg(ToQStr(tex->format.strname)) + .arg(ToQStr(tex->name)); + + return makeTreeNode({regname + name, "Texture", type}); + } + else if(buf) + { + QString type = QString("%1 - %2").arg(buf->length).arg(ToQStr(buf->name)); + + return makeTreeNode({regname + name, "Buffer", type}); + } + else + { + return makeTreeNode({regname + name, "Resource", "unknown"}); + } +} + +void ShaderViewer::updateDebugging() +{ + if(!m_Trace || m_CurrentStep < 0 || m_CurrentStep >= m_Trace->states.count) + return; + + const ShaderDebugState &state = m_Trace->states[m_CurrentStep]; + + uint32_t nextInst = state.nextInstruction; + bool done = false; + + if(m_CurrentStep == m_Trace->states.count - 1) + { + nextInst--; + done = true; + } + + // add current instruction marker + m_DisassemblyView->markerDeleteAll(CURRENT_MARKER); + m_DisassemblyView->markerDeleteAll(CURRENT_MARKER + 1); + m_DisassemblyView->markerDeleteAll(FINISHED_MARKER); + m_DisassemblyView->markerDeleteAll(FINISHED_MARKER + 1); + + for(sptr_t i = 0; i < m_DisassemblyView->lineCount(); i++) + { + if(QString::fromUtf8(m_DisassemblyView->getLine(i).trimmed()) + .startsWith(QString("%1:").arg(nextInst))) + { + m_DisassemblyView->markerAdd(i, done ? FINISHED_MARKER : CURRENT_MARKER); + m_DisassemblyView->markerAdd(i, done ? FINISHED_MARKER + 1 : CURRENT_MARKER + 1); + + int pos = m_DisassemblyView->positionFromLine(i); + m_DisassemblyView->setSelection(pos, pos); + + int firstLine = m_DisassemblyView->firstVisibleLine(); + int linesVisible = m_DisassemblyView->linesOnScreen(); + + if(m_DisassemblyView->isVisible() && (i < firstLine || i > (firstLine + linesVisible))) + m_DisassemblyView->scrollCaret(); + + break; + } + } + + // TODO tooltips + // hoverTimer_Tick(hoverTimer, new EventArgs()); + + if(ui->constants->topLevelItemCount() == 0) + { + for(int i = 0; i < m_Trace->cbuffers.count; i++) + { + for(int j = 0; j < m_Trace->cbuffers[i].count; j++) + { + if(m_Trace->cbuffers[i][j].rows > 0 || m_Trace->cbuffers[i][j].columns > 0) + { + QTreeWidgetItem *node = makeTreeNode({ToQStr(m_Trace->cbuffers[i][j].name), "cbuffer", + stringRep(m_Trace->cbuffers[i][j], false)}); + node->setData(0, Qt::UserRole, QVariant::fromValue(CBufferTag(i, j))); + + ui->constants->addTopLevelItem(node); + } + } + } + + for(int i = 0; i < m_Trace->inputs.count; i++) + { + const ShaderVariable &input = m_Trace->inputs[i]; + + if(input.rows > 0 || input.columns > 0) + { + QTreeWidgetItem *node = + makeTreeNode({ToQStr(input.name), ToQStr(input.type) + " input", stringRep(input, true)}); + node->setData(0, Qt::UserRole, QVariant::fromValue(ResourceTag(i))); + + ui->constants->addTopLevelItem(node); + } + } + + QMap> rw = + m_Ctx.CurPipelineState.GetReadWriteResources(m_Stage); + QMap> ro = + m_Ctx.CurPipelineState.GetReadOnlyResources(m_Stage); + + bool tree = false; + + for(int i = 0; + i < m_Mapping->ReadWriteResources.count && m_ShaderDetails->ReadWriteResources.count; i++) + { + BindpointMap bind = m_Mapping->ReadWriteResources[i]; + + if(!bind.used) + continue; + + if(bind.arraySize == 1) + { + QTreeWidgetItem *node = + makeResourceRegister(bind, 0, rw[bind][0], m_ShaderDetails->ReadWriteResources[i]); + if(node) + ui->constants->addTopLevelItem(node); + } + else + { + QTreeWidgetItem *node = makeTreeNode({ToQStr(m_ShaderDetails->ReadWriteResources[i].name), + QString("[%1]").arg(bind.arraySize), ""}); + + for(uint32_t a = 0; a < bind.arraySize; a++) + node->addChild( + makeResourceRegister(bind, a, rw[bind][a], m_ShaderDetails->ReadWriteResources[i])); + + tree = true; + + ui->constants->addTopLevelItem(node); + } + } + + for(int i = 0; + i < m_Mapping->ReadOnlyResources.count && m_ShaderDetails->ReadOnlyResources.count; i++) + { + BindpointMap bind = m_Mapping->ReadOnlyResources[i]; + + if(!bind.used) + continue; + + if(bind.arraySize == 1) + { + QTreeWidgetItem *node = + makeResourceRegister(bind, 0, ro[bind][0], m_ShaderDetails->ReadOnlyResources[i]); + if(node) + ui->constants->addTopLevelItem(node); + } + else + { + QTreeWidgetItem *node = makeTreeNode({ToQStr(m_ShaderDetails->ReadOnlyResources[i].name), + QString("[%1]").arg(bind.arraySize), ""}); + + for(uint32_t a = 0; a < bind.arraySize; a++) + node->addChild( + makeResourceRegister(bind, a, ro[bind][a], m_ShaderDetails->ReadOnlyResources[i])); + + tree = true; + + ui->constants->addTopLevelItem(node); + } + } + + if(tree) + { + ui->constants->setIndentation(20); + ui->constants->setRootIsDecorated(true); + } + } + + if(ui->variables->topLevelItemCount() == 0) + { + for(int i = 0; i < state.registers.count; i++) + ui->variables->addTopLevelItem( + makeTreeNode({ToQStr(state.registers[i].name), "temporary", ""})); + + for(int i = 0; i < state.indexableTemps.count; i++) + { + QTreeWidgetItem *node = makeTreeNode({QString("x%1").arg(i), "indexable", ""}); + for(int t = 0; t < state.indexableTemps[i].count; t++) + node->addChild(makeTreeNode({ToQStr(state.indexableTemps[i][t].name), "indexable", ""})); + ui->variables->addTopLevelItem(node); + } + + for(int i = 0; i < state.outputs.count; i++) + ui->variables->addTopLevelItem(makeTreeNode({ToQStr(state.outputs[i].name), "output", ""})); + } + + ui->variables->setUpdatesEnabled(false); + + int v = 0; + + for(int i = 0; i < state.registers.count; i++) + { + QTreeWidgetItem *node = ui->variables->topLevelItem(v++); + + node->setText(2, stringRep(state.registers[i], false)); + node->setData(0, Qt::UserRole, QVariant::fromValue(state.registers[i])); + } + + for(int i = 0; i < state.indexableTemps.count; i++) + { + QTreeWidgetItem *node = ui->variables->topLevelItem(v++); + + for(int t = 0; t < state.indexableTemps[i].count; t++) + { + QTreeWidgetItem *child = node->child(t); + + child->setText(2, stringRep(state.indexableTemps[i][t], false)); + child->setData(0, Qt::UserRole, QVariant::fromValue(state.indexableTemps[i][t])); + } + } + + for(int i = 0; i < state.outputs.count; i++) + { + QTreeWidgetItem *node = ui->variables->topLevelItem(v++); + + node->setText(2, stringRep(state.outputs[i], false)); + node->setData(0, Qt::UserRole, QVariant::fromValue(state.outputs[i])); + } + + ui->variables->setUpdatesEnabled(true); + + // TODO watch registers +} + +int ShaderViewer::currentStep() +{ + return m_CurrentStep; +} + +void ShaderViewer::setCurrentStep(int step) +{ + if(m_Trace && !m_Trace->states.empty()) + m_CurrentStep = qBound(0, step, m_Trace->states.count - 1); + else + m_CurrentStep = 0; + + updateDebugging(); +} + +void ShaderViewer::toggleBreakpoint(int instruction) +{ + sptr_t instLine = -1; + + if(instruction == -1) + { + // search forward for an instruction + instLine = m_DisassemblyView->lineFromPosition(m_DisassemblyView->currentPos()); + + for(; instLine < m_DisassemblyView->lineCount(); instLine++) + { + instruction = instructionForLine(instLine); + + if(instruction >= 0) + break; + } + } + + if(instruction < 0 || instruction >= m_DisassemblyView->lineCount()) + return; + + if(instLine == -1) + { + // find line for this instruction + for(instLine = 0; instLine < m_DisassemblyView->lineCount(); instLine++) + { + int inst = instructionForLine(instLine); + + if(instruction == inst) + break; + } + + if(instLine >= m_DisassemblyView->lineCount()) + instLine = -1; + } + + if(m_Breakpoints.contains(instruction)) + { + if(instLine >= 0) + { + m_DisassemblyView->markerDelete(instLine, BREAKPOINT_MARKER); + m_DisassemblyView->markerDelete(instLine, BREAKPOINT_MARKER + 1); + } + m_Breakpoints.removeOne(instruction); + } + else + { + if(instLine >= 0) + { + m_DisassemblyView->markerAdd(instLine, BREAKPOINT_MARKER); + m_DisassemblyView->markerAdd(instLine, BREAKPOINT_MARKER + 1); + } + m_Breakpoints.push_back(instruction); + } +} + +void ShaderViewer::showErrors(const QString &errors) +{ + if(m_Errors) + { + m_Errors->setReadOnly(false); + m_Errors->setText(errors.toUtf8().data()); + m_Errors->setReadOnly(true); + } +} + +void ShaderViewer::on_findReplace_clicked() +{ +} + +void ShaderViewer::on_save_clicked() +{ + if(m_Trace) + { + m_Ctx.pipelineViewer()->SaveShaderFile(m_ShaderDetails); + return; + } + + if(m_SaveCallback) + { + QMap files; + for(ScintillaEdit *s : m_Scintillas) + { + QWidget *w = (QWidget *)s; + files[w->property("filename").toString()] = QString::fromUtf8(s->getText(s->textLength())); + } + m_SaveCallback(&m_Ctx, this, files); + } +} + +void ShaderViewer::on_intView_clicked() +{ + ui->intView->setChecked(true); + ui->floatView->setChecked(false); + + updateDebugging(); +} + +void ShaderViewer::on_floatView_clicked() +{ + ui->floatView->setChecked(true); + ui->intView->setChecked(false); + + updateDebugging(); +} diff --git a/qrenderdoc/Windows/ShaderViewer.h b/qrenderdoc/Windows/ShaderViewer.h index 908622feb..49487cb1e 100644 --- a/qrenderdoc/Windows/ShaderViewer.h +++ b/qrenderdoc/Windows/ShaderViewer.h @@ -37,13 +37,45 @@ struct ShaderDebugTrace; struct ShaderReflection; class ScintillaEdit; +// from Scintilla +typedef intptr_t sptr_t; + class ShaderViewer : public QFrame, public ILogViewerForm { Q_OBJECT public: - explicit ShaderViewer(CaptureContext &ctx, ShaderReflection *shader, ShaderStageType stage, - ShaderDebugTrace *trace, const QString &debugContext, QWidget *parent = 0); + typedef std::function SaveMethod; + typedef std::function CloseMethod; + + static ShaderViewer *editShader(CaptureContext &ctx, bool customShader, const QString &entryPoint, + const QStringMap &files, SaveMethod saveCallback, + CloseMethod closeCallback, QWidget *parent) + { + ShaderViewer *ret = new ShaderViewer(ctx, parent); + ret->m_SaveCallback = saveCallback; + ret->m_CloseCallback = closeCallback; + ret->editShader(customShader, entryPoint, files); + return ret; + } + + static ShaderViewer *debugShader(CaptureContext &ctx, const ShaderBindpointMapping *bind, + const ShaderReflection *shader, ShaderStageType stage, + ShaderDebugTrace *trace, const QString &debugContext, + QWidget *parent) + { + ShaderViewer *ret = new ShaderViewer(ctx, parent); + ret->debugShader(bind, shader, stage, trace, debugContext); + return ret; + } + + static ShaderViewer *viewShader(CaptureContext &ctx, const ShaderBindpointMapping *bind, + const ShaderReflection *shader, ShaderStageType stage, + QWidget *parent) + { + return ShaderViewer::debugShader(ctx, bind, shader, stage, NULL, "", parent); + } + ~ShaderViewer(); void OnLogfileLoaded(); @@ -51,22 +83,69 @@ public: void OnSelectedEventChanged(uint32_t eventID) {} void OnEventChanged(uint32_t eventID); + int currentStep(); + void setCurrentStep(int step); + + void toggleBreakpoint(int instruction = -1); + + void showErrors(const QString &errors); + private slots: + // automatic slots + void on_findReplace_clicked(); + void on_save_clicked(); + void on_intView_clicked(); + void on_floatView_clicked(); + // manual slots - void disassembly_keyPressed(QKeyEvent *event); void readonly_keyPressed(QKeyEvent *event); + void editable_keyPressed(QKeyEvent *event); + void disassembly_buttonReleased(QMouseEvent *event); + +public slots: + bool stepBack(); + bool stepNext(); + void runToCursor(); + void runToSample(); + void runToNanOrInf(); + void runBack(); + void run(); private: + explicit ShaderViewer(CaptureContext &ctx, QWidget *parent = 0); + void editShader(bool customShader, const QString &entryPoint, const QStringMap &files); + void debugShader(const ShaderBindpointMapping *bind, const ShaderReflection *shader, + ShaderStageType stage, ShaderDebugTrace *trace, const QString &debugContext); + Ui::ShaderViewer *ui; CaptureContext &m_Ctx; - ShaderDebugTrace *m_Trace; - ShaderReflection *m_ShaderDetails; - ScintillaEdit *m_DisassemblyView; + const ShaderBindpointMapping *m_Mapping = NULL; + const ShaderReflection *m_ShaderDetails = NULL; + ShaderStageType m_Stage; + ScintillaEdit *m_DisassemblyView = NULL; + ScintillaEdit *m_Errors = NULL; QList m_Scintillas; + SaveMethod m_SaveCallback; + CloseMethod m_CloseCallback; + + ShaderDebugTrace *m_Trace = NULL; + int m_CurrentStep; + QList m_Breakpoints; + static const int CURRENT_MARKER = 0; - static const int BREAKPOINT_MARKER = 1; - static const int FINISHED_MARKER = 2; + static const int BREAKPOINT_MARKER = 2; + static const int FINISHED_MARKER = 4; ScintillaEdit *MakeEditor(const QString &name, const QString &text, bool src); + ScintillaEdit *AddFileScintilla(const QString &name, const QString &text); + + int instructionForLine(sptr_t line); + + void updateDebugging(); + void runTo(int runToInstruction, bool forward, ShaderDebugStateFlags condition = eShaderDbg_None); + + QString stringRep(const ShaderVariable &var, bool useType); + QTreeWidgetItem *makeResourceRegister(const BindpointMap &bind, uint32_t idx, + const BoundResource &ro, const ShaderResource &resources); }; diff --git a/qrenderdoc/Windows/ShaderViewer.ui b/qrenderdoc/Windows/ShaderViewer.ui index e93047b46..acfb7c79b 100644 --- a/qrenderdoc/Windows/ShaderViewer.ui +++ b/qrenderdoc/Windows/ShaderViewer.ui @@ -6,8 +6,8 @@ 0 0 - 691 - 427 + 963 + 574 @@ -16,18 +16,18 @@ - 0 - 0 - 100 - 30 + 80 + 90 + 351 + 301 - 60 - 200 + 570 + 160 256 192 @@ -107,8 +107,8 @@ - 360 - 200 + 590 + 360 256 192 @@ -185,14 +185,399 @@ + + + + 20 + 310 + 256 + 192 + + + + Qt::PreventContextMenu + + + true + + + 0 + + + false + + + false + + + true + + + + Name + + + + + Type + + + + + Value + + + + + + + 310 + 310 + 256 + 192 + + + + true + + + 0 + + + false + + + false + + + true + + + + Name + + + + + Type + + + + + Value + + + + + + + 40 + 10 + 291 + 28 + + + + + 0 + 0 + + + + + 0 + 28 + + + + QFrame::Panel + + + QFrame::Raised + + + + 2 + + + 6 + + + 2 + + + 6 + + + 2 + + + + + Find & Replace + + + + + + + :/find.png:/find.png + + + true + + + + + + + Qt::Vertical + + + + + + + Compile & Save changes + + + + + + + :/save.png:/save.png + + + true + + + + + + + Insert built-in snippets + + + + + + + :/plugin_add.png:/plugin_add.png + + + QToolButton::InstantPopup + + + true + + + + + + + Qt::Vertical + + + + + + + Step Back (Shift-F10) + + + + + + + :/stepprev.png:/stepprev.png + + + true + + + + + + + Step Next (F10) + + + + + + + :/stepnext.png:/stepnext.png + + + true + + + + + + + + 23 + 22 + + + + Run to Cursor (Ctrl-F10) + + + + :/runcursor.png:/runcursor.png + + + true + + + + + + + + 23 + 22 + + + + Run to Sample/Load/Gather + + + + :/runsample.png:/runsample.png + + + true + + + + + + + + 23 + 22 + + + + Run to NaN or Inf + + + + :/runnaninf.png:/runnaninf.png + + + true + + + + + + + Qt::Vertical + + + + + + + int + + + true + + + false + + + true + + + + + + + float + + + true + + + true + + + true + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + 710 + 20 + 151 + 131 + + + + true + + + false + + + + Name + + + + + Type + + + + + Value + + + + + RDTreeWidget + QTreeWidget +
Widgets/Extended/RDTreeWidget.h
+
ToolWindowManager QWidget
3rdparty/toolwindowmanager/ToolWindowManager.h
+ + RDTableWidget + QTableWidget +
Widgets/Extended/RDTableWidget.h
+
- + + + diff --git a/qrenderdoc/Windows/TextureViewer.cpp b/qrenderdoc/Windows/TextureViewer.cpp index aedc9ac6e..154fad285 100644 --- a/qrenderdoc/Windows/TextureViewer.cpp +++ b/qrenderdoc/Windows/TextureViewer.cpp @@ -43,6 +43,7 @@ #include "BufferViewer.h" #include "MainWindow.h" #include "PixelHistoryView.h" +#include "ShaderViewer.h" #include "ui_TextureViewer.h" float area(const QSizeF &s) @@ -3267,6 +3268,52 @@ void TextureViewer::on_saveTex_clicked() } } +void TextureViewer::on_debugPixelContext_clicked() +{ + ShaderDebugTrace *trace = new ShaderDebugTrace; + + if(m_PickedPoint.x() < 0 || m_PickedPoint.y() < 0) + return; + + int x = m_PickedPoint.x() >> (int)m_TexDisplay.mip; + int y = m_PickedPoint.y() >> (int)m_TexDisplay.mip; + + bool success = false; + + m_Ctx.Renderer().BlockInvoke([this, x, y, &success, trace](IReplayRenderer *r) { + success = r->DebugPixel((uint32_t)x, (uint32_t)y, m_TexDisplay.sampleIdx, ~0U, trace); + }); + + if(!success || trace->states.count == 0) + { + delete trace; + + // if we couldn't debug the pixel on this event, open up a pixel history + on_pixelHistory_clicked(); + return; + } + + GUIInvoke::call([this, x, y, trace]() { + QString debugContext = tr("Pixel %1,%2").arg(x).arg(y); + + const ShaderReflection *shaderDetails = + m_Ctx.CurPipelineState.GetShaderReflection(eShaderStage_Pixel); + const ShaderBindpointMapping &bindMapping = + m_Ctx.CurPipelineState.GetBindpointMapping(eShaderStage_Pixel); + + // viewer takes ownership of the trace + ShaderViewer *s = ShaderViewer::debugShader(m_Ctx, &bindMapping, shaderDetails, + eShaderStage_Pixel, trace, debugContext, this); + + m_Ctx.setupDockWindow(s); + + ToolWindowManager *manager = ToolWindowManager::managerOf(this); + + ToolWindowManager::AreaReference ref(ToolWindowManager::AddTo, manager->areaOf(this)); + manager->addToolWindow(s, ref); + }); +} + void TextureViewer::on_pixelHistory_clicked() { FetchTexture *texptr = GetCurrentTexture(); diff --git a/qrenderdoc/Windows/TextureViewer.h b/qrenderdoc/Windows/TextureViewer.h index bc69a6e67..194c088c5 100644 --- a/qrenderdoc/Windows/TextureViewer.h +++ b/qrenderdoc/Windows/TextureViewer.h @@ -159,6 +159,7 @@ private slots: void on_viewTexBuffer_clicked(); void on_texListShow_clicked(); void on_saveTex_clicked(); + void on_debugPixelContext_clicked(); void on_pixelHistory_clicked(); void on_cancelTextureListFilter_clicked();