From 806187f61334a6e5fbe44dafcf32d17b2e80ea2a Mon Sep 17 00:00:00 2001 From: baldurk Date: Wed, 21 Oct 2020 14:14:20 +0100 Subject: [PATCH] Save and load edited shaders as capture modifications * When a shader edit is loaded with a capture, it's loaded as "pending" and not immediately applied. --- qrenderdoc/Code/CaptureContext.cpp | 174 +++++++++++++++- qrenderdoc/Code/CaptureContext.h | 12 ++ qrenderdoc/Code/Interface/QRDInterface.h | 21 +- qrenderdoc/Windows/MainWindow.cpp | 2 + .../PipelineState/PipelineStateViewer.cpp | 56 +----- qrenderdoc/Windows/PythonShell.cpp | 4 + qrenderdoc/Windows/ShaderViewer.cpp | 188 +++++++++++++++++- qrenderdoc/Windows/ShaderViewer.h | 28 ++- qrenderdoc/Windows/TextureViewer.cpp | 7 +- renderdoc/api/replay/renderdoc_tostr.inl | 1 + renderdoc/api/replay/replay_enums.h | 7 + 11 files changed, 429 insertions(+), 71 deletions(-) diff --git a/qrenderdoc/Code/CaptureContext.cpp b/qrenderdoc/Code/CaptureContext.cpp index 09ae304b0..e1cc71dfc 100644 --- a/qrenderdoc/Code/CaptureContext.cpp +++ b/qrenderdoc/Code/CaptureContext.cpp @@ -978,6 +978,16 @@ void CaptureContext::LoadCaptureThreaded(const QString &captureFile, const Repla bytebuf buf = access->GetSectionContents(idx); LoadNotes(QString::fromUtf8((const char *)buf.data(), buf.count())); } + + idx = access->FindSectionByType(SectionType::EditedShaders); + if(idx >= 0) + { + bytebuf buf = access->GetSectionContents(idx); + GUIInvoke::call(m_MainWindow, [this, buf]() { + LoadEdits(QString::fromUtf8((const char *)buf.data(), buf.count())); + }); + } + QString driver = access->DriverName(); if(driver == lit("Image")) { @@ -1480,6 +1490,11 @@ void CaptureContext::SetRemoteHost(int hostIdx) m_MainWindow->setRemoteHost(hostIdx); } +bool CaptureContext::IsResourceReplaced(ResourceId id) +{ + return m_ReplacedResources.contains(id); +} + void CaptureContext::RegisterReplacement(ResourceId id) { if(!m_ReplacedResources.contains(id)) @@ -1596,6 +1611,9 @@ void CaptureContext::SaveChanges() if(m_CaptureMods & CaptureModifications::Notes) success &= SaveNotes(); + if(m_CaptureMods & CaptureModifications::EditedShaders) + success &= SaveEdits(); + if(!success) { RDDialog::critical(m_MainWindow, tr("Can't save file"), @@ -1729,6 +1747,66 @@ void CaptureContext::LoadNotes(const QString &data) } } +bool CaptureContext::SaveEdits() +{ + // make sure this format matches SetCaptureFileComments in app_api.cpp if it changes + QVariantList editors; + + for(ShaderViewer *e : m_ShaderEditors) + { + QVariantMap editor = e->SaveEditor(); + if(!editor.isEmpty()) + editors.push_back(e->SaveEditor()); + } + + QVariantMap root; + root[lit("editors")] = editors; + + QString json = VariantToJSON(root); + + SectionProperties props; + props.type = SectionType::EditedShaders; + props.version = 1; + + return Replay().GetCaptureAccess()->WriteSection(props, json.toUtf8()); +} + +void CaptureContext::LoadEdits(const QString &data) +{ + QVariantMap root = JSONToVariant(data); + + QVariantList editors = root[lit("editors")].toList(); + + auto replaceSaveCallback = [this](ICaptureContext *ctx, IShaderViewer *viewer, ResourceId id, + ShaderStage stage, ShaderEncoding shaderEncoding, + ShaderCompileFlags flags, rdcstr entryFunc, bytebuf shaderBytes) { + + ApplyShaderEdit(viewer, id, stage, shaderEncoding, flags, entryFunc, shaderBytes); + }; + + auto replaceCloseCallback = [this](ICaptureContext *ctx, IShaderViewer *view, ResourceId id) { + RevertShaderEdit(view, id); + }; + + for(QVariant e : editors) + { + ShaderViewer *edit = + ShaderViewer::LoadEditor(*this, e.toMap(), replaceSaveCallback, replaceCloseCallback, + [this](ShaderViewer *view, bool closed) { + m_CaptureMods |= CaptureModifications::EditedShaders; + if(closed) + m_ShaderEditors.removeOne(view); + }, + m_MainWindow->Widget()); + + if(edit) + { + AddDockWindow(edit->Widget(), DockReference::MainToolArea, NULL); + m_ShaderEditors.push_back(edit); + } + } +} + bool CaptureContext::OpenRGPProfile(const rdcstr &filename) { delete m_RGP; @@ -2195,8 +2273,100 @@ IShaderViewer *CaptureContext::EditShader(ResourceId id, ShaderStage stage, IShaderViewer::SaveCallback saveCallback, IShaderViewer::CloseCallback closeCallback) { - return ShaderViewer::EditShader(*this, id, stage, entryPoint, files, shaderEncoding, flags, - saveCallback, closeCallback, m_MainWindow->Widget()); + ShaderViewer *viewer = NULL; + + if(id != ResourceId()) + { + auto replaceSaveCallback = [this, saveCallback]( + ICaptureContext *ctx, IShaderViewer *viewer, ResourceId id, ShaderStage stage, + ShaderEncoding shaderEncoding, ShaderCompileFlags flags, rdcstr entryFunc, + bytebuf shaderBytes) { + + ApplyShaderEdit(viewer, id, stage, shaderEncoding, flags, entryFunc, shaderBytes); + + if(saveCallback) + saveCallback(ctx, viewer, id, stage, shaderEncoding, flags, entryFunc, shaderBytes); + }; + + auto replaceCloseCallback = [this, closeCallback](ICaptureContext *ctx, IShaderViewer *view, + ResourceId id) { + RevertShaderEdit(view, id); + + if(closeCallback) + closeCallback(ctx, view, id); + }; + + viewer = ShaderViewer::EditShader(*this, id, stage, entryPoint, files, shaderEncoding, flags, + replaceSaveCallback, replaceCloseCallback, + [this](ShaderViewer *view, bool closed) { + m_CaptureMods |= CaptureModifications::EditedShaders; + if(closed) + m_ShaderEditors.removeOne(view); + }, + m_MainWindow->Widget()); + + m_ShaderEditors.push_back(viewer); + m_CaptureMods |= CaptureModifications::EditedShaders; + } + else + { + viewer = ShaderViewer::EditShader(*this, id, stage, entryPoint, files, shaderEncoding, flags, + saveCallback, closeCallback, NULL, m_MainWindow->Widget()); + } + + return viewer; +} + +void CaptureContext::ApplyShaderEdit(IShaderViewer *viewer, ResourceId id, ShaderStage stage, + ShaderEncoding shaderEncoding, ShaderCompileFlags flags, + const rdcstr &entryFunc, const bytebuf &shaderBytes) +{ + if(shaderBytes.isEmpty()) + return; + + ANALYTIC_SET(UIFeatures.ShaderEditing, true); + + QPointer ptr(viewer->Widget()); + + // invoke off to the ReplayController to replace the capture's shader + // with our edited one + Replay().AsyncInvoke([this, entryFunc, shaderBytes, shaderEncoding, flags, stage, id, ptr, + viewer](IReplayController *r) { + rdcstr errs; + + ResourceId from = id; + ResourceId to; + + rdctie(to, errs) = + r->BuildTargetShader(entryFunc.c_str(), shaderEncoding, shaderBytes, flags, stage); + + if(to == ResourceId()) + { + r->RemoveReplacement(from); + + // this GUIInvoke call always needs to go through even if the viewer has been closed. + GUIInvoke::call(GetMainWindow()->Widget(), [this, from]() { UnregisterReplacement(from); }); + } + else + { + r->ReplaceResource(from, to); + + GUIInvoke::call(GetMainWindow()->Widget(), [this, from]() { RegisterReplacement(from); }); + } + if(ptr) + GUIInvoke::call(ptr, [viewer, errs]() { viewer->ShowErrors(errs); }); + }); +} + +void CaptureContext::RevertShaderEdit(IShaderViewer *viewer, ResourceId id) +{ + // remove the replacement on close (we could make this more sophisticated if there + // was a place to control replaced resources/shaders). + Replay().AsyncInvoke([this, id](IReplayController *r) { + if(IsCaptureLoaded()) + r->RemoveReplacement(id); + GUIInvoke::call(GetMainWindow()->Widget(), [this, id] { UnregisterReplacement(id); }); + }); } IShaderViewer *CaptureContext::DebugShader(const ShaderBindpointMapping *bind, diff --git a/qrenderdoc/Code/CaptureContext.h b/qrenderdoc/Code/CaptureContext.h index bea3b07fd..965f5d88b 100644 --- a/qrenderdoc/Code/CaptureContext.h +++ b/qrenderdoc/Code/CaptureContext.h @@ -52,6 +52,7 @@ class StatisticsViewer; class TimelineBar; class PythonShell; class ResourceInspector; +class ShaderViewer; class CaptureContext : public ICaptureContext, IExtensionManager { @@ -114,6 +115,7 @@ public: uint32_t eventId, bool force = false) override; void SetRemoteHost(int hostIndex); void RefreshStatus() override { SetEventID({}, m_SelectedEventID, m_EventID, true); } + bool IsResourceReplaced(ResourceId id) override; void RegisterReplacement(ResourceId id) override; void UnregisterReplacement(ResourceId id) override; void RefreshUIStatus(const rdcarray &exclude, bool updateSelectedEvent, @@ -241,6 +243,11 @@ public: ShaderCompileFlags flags, IShaderViewer::SaveCallback saveCallback, IShaderViewer::CloseCallback closeCallback) override; + void ApplyShaderEdit(IShaderViewer *viewer, ResourceId id, ShaderStage stage, + ShaderEncoding shaderEncoding, ShaderCompileFlags flags, + const rdcstr &entryFunc, const bytebuf &shaderBytes); + void RevertShaderEdit(IShaderViewer *viewer, ResourceId id); + IShaderViewer *DebugShader(const ShaderBindpointMapping *bind, const ShaderReflection *shader, ResourceId pipeline, ShaderDebugTrace *trace, const rdcstr &debugContext) override; @@ -303,6 +310,9 @@ private: bool SaveNotes(); void LoadNotes(const QString &data); + bool SaveEdits(); + void LoadEdits(const QString &data); + void CacheResources(); rdcstr GetResourceNameUnsuffixed(const ResourceDescription *desc); @@ -386,6 +396,8 @@ private: QList> m_RegisteredMenuItems; + QList m_ShaderEditors; + // Windows MainWindow *m_MainWindow = NULL; EventBrowser *m_EventBrowser = NULL; diff --git a/qrenderdoc/Code/Interface/QRDInterface.h b/qrenderdoc/Code/Interface/QRDInterface.h index 9364b381e..b7ec38aa8 100644 --- a/qrenderdoc/Code/Interface/QRDInterface.h +++ b/qrenderdoc/Code/Interface/QRDInterface.h @@ -564,6 +564,8 @@ DOCUMENT(R"(A shader window used for viewing, editing, or debugging. :param CaptureContext context: The current capture context. :param ShaderViewer viewer: The open shader viewer. + :param ResourceId id: The id of the shader being replaced. + :param ShaderStage stage: The shader stage of the shader being replaced. :param ShaderEncoding encoding: The encoding of the files being passed. :param ShaderCompileFlags flags: The flags to use during compilation. :param str entryFunc: The name of the entry point. @@ -577,13 +579,15 @@ DOCUMENT(R"(A shader window used for viewing, editing, or debugging. Called whenever a shader viewer that was open for editing is closed. :param CaptureContext context: The current capture context. + :param ShaderViewer viewer: The open shader viewer. + :param ResourceId id: The id of the shader being replaced. )"); struct IShaderViewer { - typedef std::function + typedef std::function SaveCallback; - typedef std::function CloseCallback; + typedef std::function CloseCallback; DOCUMENT( "Retrieves the QWidget for this :class:`ShaderViewer` if PySide2 is available, or otherwise " @@ -1011,6 +1015,10 @@ This is a bitmask, so several values can be present at once. The general notes field has been changed. +.. data:: EditedShaders + + There are shader editing changes (new edits or reverts). + .. data:: All Fixed value with all bits set, indication all modifications have been made. @@ -1021,6 +1029,7 @@ enum class CaptureModifications : uint32_t Renames = 0x0001, Bookmarks = 0x0002, Notes = 0x0004, + EditedShaders = 0x0008, All = 0xffffffff, }; @@ -1162,6 +1171,12 @@ been made. )"); virtual void RefreshStatus() = 0; + DOCUMENT(R"(Determine if a resource has been replaced. See :meth:`RegisterReplacement`. + +:param ResourceId id: The id of the resource to check. +)"); + virtual bool IsResourceReplaced(ResourceId id) = 0; + DOCUMENT(R"(Register that a resource has replaced, so that the UI can be updated to reflect the change. diff --git a/qrenderdoc/Windows/MainWindow.cpp b/qrenderdoc/Windows/MainWindow.cpp index 20a2e6e1a..b399c9653 100644 --- a/qrenderdoc/Windows/MainWindow.cpp +++ b/qrenderdoc/Windows/MainWindow.cpp @@ -1071,6 +1071,8 @@ bool MainWindow::PromptCloseCapture() text += tr("Bookmarks have been changed.\n"); if(mods & CaptureModifications::Notes) text += tr("Capture notes have been changed.\n"); + if(mods & CaptureModifications::EditedShaders) + text += tr("Edited shaders have been changed.\n"); bool saveas = false; diff --git a/qrenderdoc/Windows/PipelineState/PipelineStateViewer.cpp b/qrenderdoc/Windows/PipelineState/PipelineStateViewer.cpp index 26b476e61..d8f0af76d 100644 --- a/qrenderdoc/Windows/PipelineState/PipelineStateViewer.cpp +++ b/qrenderdoc/Windows/PipelineState/PipelineStateViewer.cpp @@ -837,60 +837,8 @@ IShaderViewer *PipelineStateViewer::EditShader(ResourceId id, ShaderStage shader const rdcstr &entry, ShaderCompileFlags compileFlags, ShaderEncoding encoding, const rdcstrpairs &files) { - auto saveCallback = [shaderType, id](ICaptureContext *ctx, IShaderViewer *viewer, - ShaderEncoding shaderEncoding, ShaderCompileFlags flags, - rdcstr entryFunc, bytebuf shaderBytes) { - if(shaderBytes.isEmpty()) - return; - - ANALYTIC_SET(UIFeatures.ShaderEditing, true); - - QPointer ptr(viewer->Widget()); - - // 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, - ptr, viewer](IReplayController *r) { - rdcstr errs; - - ResourceId from = id; - ResourceId to; - - rdctie(to, errs) = - r->BuildTargetShader(entryFunc.c_str(), shaderEncoding, shaderBytes, flags, shaderType); - - if(ptr) - GUIInvoke::call(ptr, [viewer, errs]() { viewer->ShowErrors(errs); }); - if(to == ResourceId()) - { - r->RemoveReplacement(from); - - // this GUIInvoke call always needs to go through even if the viewer has been closed. - GUIInvoke::call(ctx->GetMainWindow()->Widget(), - [ctx, from]() { ctx->UnregisterReplacement(from); }); - } - else - { - r->ReplaceResource(from, to); - - GUIInvoke::call(ctx->GetMainWindow()->Widget(), - [ctx, from]() { ctx->RegisterReplacement(from); }); - } - }); - }; - - auto closeCallback = [id](ICaptureContext *ctx) { - // remove the replacement on close (we could make this more sophisticated if there - // was a place to control replaced resources/shaders). - ctx->Replay().AsyncInvoke([ctx, id](IReplayController *r) { - if(ctx->IsCaptureLoaded()) - r->RemoveReplacement(id); - GUIInvoke::call(ctx->GetMainWindow()->Widget(), [ctx, id] { ctx->UnregisterReplacement(id); }); - }); - }; - - IShaderViewer *sv = m_Ctx.EditShader(id, shaderType, entry, files, encoding, compileFlags, - saveCallback, closeCallback); + IShaderViewer *sv = + m_Ctx.EditShader(id, shaderType, entry, files, encoding, compileFlags, NULL, NULL); m_Ctx.AddDockWindow(sv->Widget(), DockReference::AddTo, this); diff --git a/qrenderdoc/Windows/PythonShell.cpp b/qrenderdoc/Windows/PythonShell.cpp index fa27c55c0..2e2e505c9 100644 --- a/qrenderdoc/Windows/PythonShell.cpp +++ b/qrenderdoc/Windows/PythonShell.cpp @@ -210,6 +210,10 @@ struct CaptureContextInvoker : ICaptureContext InvokeVoidFunction(&ICaptureContext::SetEventID, exclude, selectedEventID, eventId, force); } virtual void RefreshStatus() override { InvokeVoidFunction(&ICaptureContext::RefreshStatus); } + virtual bool IsResourceReplaced(ResourceId id) override + { + return InvokeRetFunction(&ICaptureContext::IsResourceReplaced, id); + } virtual void RegisterReplacement(ResourceId id) override { InvokeVoidFunction(&ICaptureContext::RegisterReplacement, id); diff --git a/qrenderdoc/Windows/ShaderViewer.cpp b/qrenderdoc/Windows/ShaderViewer.cpp index 426af3701..53f52b1b9 100644 --- a/qrenderdoc/Windows/ShaderViewer.cpp +++ b/qrenderdoc/Windows/ShaderViewer.cpp @@ -215,6 +215,7 @@ void ShaderViewer::editShader(ResourceId id, ShaderStage stage, const QString &e m_Flags = flags; m_CustomShader = (id == ResourceId()); + m_EditingShader = id; // set up compilation parameters for(ShaderEncoding i : values()) @@ -230,12 +231,22 @@ void ShaderViewer::editShader(ResourceId id, ShaderStage stage, const QString &e ui->encoding->setCurrentIndex(m_Encodings.indexOf(shaderEncoding)); ui->entryFunc->setText(entryPoint); + QObject::connect(ui->entryFunc, &QLineEdit::textChanged, + [this](const QString &) { MarkModification(); }); + QObject::connect(ui->toolCommandLine, &QTextEdit::textChanged, [this]() { MarkModification(); }); + PopulateCompileTools(); QObject::connect(ui->encoding, OverloadedSlot::of(&QComboBox::currentIndexChanged), - [this](int) { PopulateCompileTools(); }); + [this](int) { + PopulateCompileTools(); + MarkModification(); + }); QObject::connect(ui->compileTool, OverloadedSlot::of(&QComboBox::currentIndexChanged), - [this](int) { PopulateCompileToolParameters(); }); + [this](int) { + PopulateCompileToolParameters(); + MarkModification(); + }); // 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 @@ -289,6 +300,8 @@ void ShaderViewer::editShader(ResourceId id, ShaderStage stage, const QString &e const QByteArray &, int, int, int) { if(type & (SC_MOD_INSERTTEXT | SC_MOD_DELETETEXT | SC_MOD_BEFOREINSERT | SC_MOD_BEFOREDELETE)) m_FindState = FindState(); + + MarkModification(); }); m_Ctx.GetMainWindow()->RegisterShortcut(QKeySequence(QKeySequence::Refresh).toString(), this, @@ -961,6 +974,131 @@ void ShaderViewer::gotoDisassemblyDebugging() m_DisassemblyFrame->setFocus(Qt::MouseFocusReason); } +ShaderViewer *ShaderViewer::LoadEditor(ICaptureContext &ctx, QVariantMap data, + IShaderViewer::SaveCallback saveCallback, + IShaderViewer::CloseCallback closeCallback, + ModifyCallback modifyCallback, QWidget *parent) +{ + if(data.isEmpty()) + return NULL; + + ResourceId id; + + { + QVariant v = data[lit("id")]; + RichResourceTextInitialise(v); + id = v.value(); + } + ShaderStage stage = (ShaderStage)data[lit("stage")].toUInt(); + rdcstr entryPoint = data[lit("entryPoint")].toString(); + ShaderEncoding encoding = (ShaderEncoding)data[lit("encoding")].toUInt(); + QString commandLine = data[lit("commandLine")].toString(); + QString toolName = data[lit("tool")].toString(); + + ShaderCompileFlags flags; + + { + QVariantMap v = data[lit("flags")].toMap(); + + for(const QString &str : v.keys()) + flags.flags.push_back({(rdcstr)str, (rdcstr)v[str].toString()}); + } + + rdcstrpairs files; + + { + QVariantList v = data[lit("files")].toList(); + + for(QVariant f : v) + { + QVariantMap file = f.toMap(); + files.push_back( + {(rdcstr)file[lit("name")].toString(), (rdcstr)file[lit("contents")].toString()}); + } + } + + rdcstr errors; + + if((uint32_t)encoding >= (uint32_t)ShaderEncoding::Count) + { + errors += tr("Unrecognised shader encoding '%1'").arg(ToStr(encoding)); + // with no other information let's guess HLSL as the most likely + encoding = ShaderEncoding::HLSL; + } + + ShaderViewer *view = EditShader(ctx, id, stage, entryPoint, files, encoding, flags, saveCallback, + closeCallback, modifyCallback, parent); + + int toolIndex = -1; + + for(int i = 0; i < view->ui->compileTool->count(); i++) + { + QString tool = view->ui->compileTool->itemText(i); + if(tool == toolName) + { + toolIndex = i; + break; + } + } + + if(toolIndex == -1) + { + errors += tr("Unknown shader tool '%1'").arg(toolName); + // let's pick the highest priority tool for the current encoding + toolIndex = 0; + } + + view->ui->encoding->setCurrentIndex(view->m_Encodings.indexOf(encoding)); + view->ui->compileTool->setCurrentIndex(toolIndex); + view->ui->entryFunc->setText(entryPoint); + view->ui->toolCommandLine->setText(commandLine); + + return view; +} + +QVariantMap ShaderViewer::SaveEditor() +{ + QVariantMap ret; + + if(m_EditingShader != ResourceId()) + { + ret[lit("id")] = m_EditingShader; + ret[lit("stage")] = (uint32_t)m_Stage; + ret[lit("entryPoint")] = ui->entryFunc->text(); + ret[lit("encoding")] = (uint32_t)currentEncoding(); + ret[lit("commandLine")] = ui->toolCommandLine->toPlainText(); + ret[lit("tool")] = ui->compileTool->currentText(); + + { + QVariantMap v; + + for(const ShaderCompileFlag &flag : m_Flags.flags) + v[flag.name] = QString(flag.value); + + ret[lit("flags")] = v; + } + + { + QVariantList v; + + for(ScintillaEdit *edit : m_Scintillas) + { + QWidget *w = (QWidget *)edit; + + QVariantMap f; + f[lit("name")] = w->property("filename").toString(); + f[lit("contents")] = QString::fromUtf8(edit->getText(edit->textLength() + 1)); + + v.push_back(f); + } + + ret[lit("files")] = v; + } + } + + return ret; +} + ShaderViewer::~ShaderViewer() { delete m_FindResults; @@ -975,7 +1113,10 @@ ShaderViewer::~ShaderViewer() m_Ctx.Replay().AsyncInvoke([trace](IReplayController *r) { r->FreeTrace(trace); }); if(m_CloseCallback) - m_CloseCallback(&m_Ctx); + m_CloseCallback(&m_Ctx, this, m_EditingShader); + + if(m_ModifyCallback) + m_ModifyCallback(this, true); m_Ctx.RemoveCaptureViewer(this); delete ui; @@ -2309,6 +2450,30 @@ bool ShaderViewer::getVar(RDTreeWidgetItem *item, ShaderVariable *var, QString * } } +void ShaderViewer::setEditorWindowTitle() +{ + if(m_EditingShader != ResourceId()) + { + if(!m_Ctx.IsResourceReplaced(m_EditingShader)) + m_Modified = true; + + if(m_Modified) + { + QString title = windowTitle(); + if(title[0] != QLatin1Char('*')) + title.prepend(lit("* ")); + setWindowTitle(title); + } + else + { + QString title = windowTitle(); + if(title[0] == QLatin1Char('*')) + title.remove(0, 2); + setWindowTitle(title); + } + } +} + void ShaderViewer::highlightMatchingVars(RDTreeWidgetItem *root, const QString varName, const QColor highlightColor) { @@ -3872,6 +4037,8 @@ void ShaderViewer::ShowErrors(const rdcstr &errors) if(!errors.isEmpty()) ToolWindowManager::raiseToolWindow(m_Errors); } + + setEditorWindowTitle(); } void ShaderViewer::AddWatch(const rdcstr &variable) @@ -4340,6 +4507,16 @@ bool ShaderViewer::eventFilter(QObject *watched, QEvent *event) return QFrame::eventFilter(watched, event); } +void ShaderViewer::MarkModification() +{ + if(m_ModifyCallback) + m_ModifyCallback(this, false); + + m_Modified = true; + + setEditorWindowTitle(); +} + void ShaderViewer::disasm_tooltipShow(int x, int y) { // do nothing if there's no trace @@ -4757,7 +4934,10 @@ void ShaderViewer::on_refresh_clicked() if(!found) flags.flags.push_back({"@cmdline", ui->toolCommandLine->toPlainText()}); - m_SaveCallback(&m_Ctx, this, encoding, flags, ui->entryFunc->text(), shaderBytes); + m_Modified = false; + + m_SaveCallback(&m_Ctx, this, m_EditingShader, m_Stage, encoding, flags, ui->entryFunc->text(), + shaderBytes); } } diff --git a/qrenderdoc/Windows/ShaderViewer.h b/qrenderdoc/Windows/ShaderViewer.h index 309107c6e..ce1743529 100644 --- a/qrenderdoc/Windows/ShaderViewer.h +++ b/qrenderdoc/Windows/ShaderViewer.h @@ -74,15 +74,25 @@ class ShaderViewer : public QFrame, public IShaderViewer, public ICaptureViewer Q_OBJECT public: - static IShaderViewer *EditShader(ICaptureContext &ctx, ResourceId id, ShaderStage stage, - const QString &entryPoint, const rdcstrpairs &files, - ShaderEncoding shaderEncoding, ShaderCompileFlags flags, - IShaderViewer::SaveCallback saveCallback, - IShaderViewer::CloseCallback closeCallback, QWidget *parent) + typedef std::function ModifyCallback; + + static ShaderViewer *LoadEditor(ICaptureContext &ctx, QVariantMap data, + IShaderViewer::SaveCallback saveCallback, + IShaderViewer::CloseCallback closeCallback, + ModifyCallback modifyCallback, QWidget *parent); + QVariantMap SaveEditor(); + + static ShaderViewer *EditShader(ICaptureContext &ctx, ResourceId id, ShaderStage stage, + const QString &entryPoint, const rdcstrpairs &files, + ShaderEncoding shaderEncoding, ShaderCompileFlags flags, + IShaderViewer::SaveCallback saveCallback, + IShaderViewer::CloseCallback closeCallback, + ModifyCallback modifyCallback, QWidget *parent) { ShaderViewer *ret = new ShaderViewer(ctx, parent); ret->m_SaveCallback = saveCallback; ret->m_CloseCallback = closeCallback; + ret->m_ModifyCallback = modifyCallback; ret->editShader(id, stage, entryPoint, files, shaderEncoding, flags); return ret; } @@ -177,6 +187,8 @@ private: ResourceId pipeline, ShaderDebugTrace *trace, const QString &debugContext); bool eventFilter(QObject *watched, QEvent *event) override; + void MarkModification(); + void PopulateCompileTools(); void PopulateCompileToolParameters(); bool ProcessIncludeDirectives(QString &source, const rdcstrpairs &files); @@ -207,6 +219,7 @@ private: ShaderBindpointMapping m_Mapping; const ShaderReflection *m_ShaderDetails = NULL; bool m_CustomShader = false; + ResourceId m_EditingShader; ShaderCompileFlags m_Flags; QList m_Encodings; ShaderStage m_Stage; @@ -250,6 +263,9 @@ private: SaveCallback m_SaveCallback; CloseCallback m_CloseCallback; + ModifyCallback m_ModifyCallback; + + bool m_Modified = true; ShaderDebugTrace *m_Trace = NULL; rdcarray m_States; @@ -315,6 +331,8 @@ private: void find(bool down); + void setEditorWindowTitle(); + void runTo(QVector runToInstructions, bool forward, ShaderEvents condition = ShaderEvents::NoEvent); diff --git a/qrenderdoc/Windows/TextureViewer.cpp b/qrenderdoc/Windows/TextureViewer.cpp index ef60a3742..9d45c3445 100644 --- a/qrenderdoc/Windows/TextureViewer.cpp +++ b/qrenderdoc/Windows/TextureViewer.cpp @@ -4368,8 +4368,9 @@ void TextureViewer::on_customEdit_clicked() ResourceId(), ShaderStage::Fragment, lit("main"), files, encodingExtensions[QFileInfo(filename).completeSuffix()], ShaderCompileFlags(), // Save Callback - [thisPointer, key, filename, path](ICaptureContext *ctx, IShaderViewer *viewer, - ShaderEncoding, ShaderCompileFlags, rdcstr, bytebuf bytes) { + [thisPointer, key, filename, path](ICaptureContext *ctx, IShaderViewer *viewer, ResourceId, + ShaderStage, ShaderEncoding, ShaderCompileFlags, rdcstr, + bytebuf bytes) { { // don't trigger a full refresh if(thisPointer) @@ -4397,7 +4398,7 @@ void TextureViewer::on_customEdit_clicked() } }, - [thisPointer, key](ICaptureContext *ctx) { + [thisPointer, key](ICaptureContext *, IShaderViewer *, ResourceId) { if(thisPointer) thisPointer->m_CustomShaderEditor.remove(key); }); diff --git a/renderdoc/api/replay/renderdoc_tostr.inl b/renderdoc/api/replay/renderdoc_tostr.inl index c04b6cd62..df4a1323c 100644 --- a/renderdoc/api/replay/renderdoc_tostr.inl +++ b/renderdoc/api/replay/renderdoc_tostr.inl @@ -1014,6 +1014,7 @@ rdcstr DoStringise(const SectionType &el) STRINGISE_ENUM_CLASS_NAMED(AMDRGPProfile, "amd/rgp/profile"); STRINGISE_ENUM_CLASS_NAMED(ExtendedThumbnail, "renderdoc/internal/exthumb"); STRINGISE_ENUM_CLASS_NAMED(EmbeddedLogfile, "renderdoc/internal/logfile"); + STRINGISE_ENUM_CLASS_NAMED(EditedShaders, "renderdoc/ui/edits"); } END_ENUM_STRINGISE(); } diff --git a/renderdoc/api/replay/replay_enums.h b/renderdoc/api/replay/replay_enums.h index ace253d75..971793058 100644 --- a/renderdoc/api/replay/replay_enums.h +++ b/renderdoc/api/replay/replay_enums.h @@ -92,6 +92,12 @@ version of RenderDoc that addes a new section type. They should be considered eq This section contains the log file at the time of capture, for debugging. The name for this section will be "renderdoc/internal/logfile". + +.. data:: EditedShaders + + This section contains any edited shaders. + + The name for this section will be "renderdoc/ui/edits". )"); enum class SectionType : uint32_t { @@ -105,6 +111,7 @@ enum class SectionType : uint32_t AMDRGPProfile, ExtendedThumbnail, EmbeddedLogfile, + EditedShaders, Count, };