From c00a6ca8efbfd1bf3a8c0a7ff03a3472cdbcacda Mon Sep 17 00:00:00 2001 From: baldurk Date: Wed, 15 Nov 2017 19:28:19 +0000 Subject: [PATCH] Add notion of UI modifications to a capture, saved in .rdc sections --- qrenderdoc/Code/CaptureContext.cpp | 70 +++++++++++++++++ qrenderdoc/Code/CaptureContext.h | 5 ++ qrenderdoc/Code/Interface/QRDInterface.h | 42 +++++++++++ qrenderdoc/Windows/MainWindow.cpp | 95 ++++++++++++++++++++++-- qrenderdoc/Windows/MainWindow.h | 4 +- qrenderdoc/Windows/MainWindow.ui | 10 ++- qrenderdoc/Windows/PythonShell.cpp | 4 + renderdoc/api/replay/basic_types.h | 7 ++ 8 files changed, 227 insertions(+), 10 deletions(-) diff --git a/qrenderdoc/Code/CaptureContext.cpp b/qrenderdoc/Code/CaptureContext.cpp index b24bfeda7..4d6cef034 100644 --- a/qrenderdoc/Code/CaptureContext.cpp +++ b/qrenderdoc/Code/CaptureContext.cpp @@ -147,6 +147,8 @@ void CaptureContext::LoadCapture(const QString &captureFile, const QString &orig { m_CaptureTemporary = temporary; + m_CaptureMods = CaptureModifications::NoModifications; + QVector viewers(m_CaptureViewers); // make sure we're on a consistent event before invoking viewer forms @@ -311,6 +313,18 @@ void CaptureContext::LoadCaptureThreaded(const QString &captureFile, const QStri .arg(origFilename)); } + ICaptureAccess *access = Replay().GetCaptureAccess(); + + if(access) + { + int idx = access->FindSectionByType(SectionType::ResourceRenames); + if(idx >= 0) + { + bytebuf buf = access->GetSectionContents(idx); + LoadRenames(QString::fromUtf8((const char *)buf.data(), buf.count())); + } + } + m_LoadInProgress = false; m_CaptureLoaded = true; } @@ -596,6 +610,17 @@ bool CaptureContext::SaveCaptureTo(const QString &captureFile) Replay().ReopenCaptureFile(captureFile); + if(m_CaptureMods & CaptureModifications::Renames) + { + SectionProperties props; + props.type = SectionType::ResourceRenames; + props.version = 1; + + Replay().GetCaptureAccess()->WriteSection(props, SaveRenames().toUtf8()); + } + + m_CaptureMods = CaptureModifications::NoModifications; + return true; } @@ -697,6 +722,48 @@ void CaptureContext::AddMessages(const rdcarray &msgs) } } +QString CaptureContext::SaveRenames() +{ + QVariantMap resources; + for(ResourceId id : m_CustomNames.keys()) + { + resources[ToQStr(id)] = m_CustomNames[id]; + } + + QVariantMap root; + root[lit("CustomResourceNames")] = resources; + + return VariantToJSON(root); +} + +void CaptureContext::LoadRenames(const QString &data) +{ + QVariantMap root = JSONToVariant(data); + + if(root.contains(lit("CustomResourceNames"))) + { + QVariantMap resources = root[lit("CustomResourceNames")].toMap(); + + for(const QString &str : resources.keys()) + { + ResourceId id; + + if(str.startsWith(lit("resourceid::"))) + { + qulonglong num = str.mid(sizeof("resourceid::") - 1).toULongLong(); + memcpy(&id, &num, sizeof(num)); + } + else + { + qCritical() << "Unrecognised resourceid encoding" << str; + } + + if(id != ResourceId()) + m_CustomNames[id] = resources[str].toString(); + } + } +} + QString CaptureContext::GetResourceName(ResourceId id) { if(id == ResourceId()) @@ -748,6 +815,9 @@ void CaptureContext::SetResourceCustomName(ResourceId id, const QString &name) m_CustomNameCachedID++; + m_CaptureMods |= CaptureModifications::Renames; + m_MainWindow->captureModified(); + RefreshUIStatus({}, true, true); } diff --git a/qrenderdoc/Code/CaptureContext.h b/qrenderdoc/Code/CaptureContext.h index 8af2287e6..67fe0acbf 100644 --- a/qrenderdoc/Code/CaptureContext.h +++ b/qrenderdoc/Code/CaptureContext.h @@ -103,6 +103,7 @@ public: bool IsCaptureTemporary() override { return m_CaptureTemporary; } bool IsCaptureLoading() override { return m_LoadInProgress; } QString GetCaptureFilename() override { return m_CaptureFile; } + CaptureModifications GetCaptureModifications() override { return m_CaptureMods; } const FrameDescription &FrameInfo() override { return m_FrameInfo; } const APIProperties &APIProps() override { return m_APIProps; } uint32_t CurSelectedEvent() override { return m_SelectedEventID; } @@ -233,6 +234,7 @@ private: bool m_CaptureLoaded = false, m_LoadInProgress = false, m_CaptureLocal = false, m_CaptureTemporary = false; QString m_CaptureFile; + CaptureModifications m_CaptureMods = CaptureModifications::NoModifications; QVector m_DebugMessages; int m_UnreadMessageCount = 0; @@ -241,6 +243,9 @@ private: bool ContainsMarker(const rdcarray &m_Drawcalls); void AddFakeProfileMarkers(); + QString SaveRenames(); + void LoadRenames(const QString &data); + float m_LoadProgress = 0.0f; float m_PostloadProgress = 0.0f; float UpdateLoadProgress(); diff --git a/qrenderdoc/Code/Interface/QRDInterface.h b/qrenderdoc/Code/Interface/QRDInterface.h index f075ffb68..a0e741881 100644 --- a/qrenderdoc/Code/Interface/QRDInterface.h +++ b/qrenderdoc/Code/Interface/QRDInterface.h @@ -871,6 +871,40 @@ enum class DockReference : int ConstantBufferArea, }; +DOCUMENT(R"(Details any changes that have been made to a capture in the UI which can be saved to +disk but currently aren't. Note that detection is conservative - e.g. if a change is made, then +cancelled out by reversing the change, this will still count as 'modified' even if the end result is +the same data. In that sense it's analogous to adding and then deleting some characters in a text +editor, since there is no actual undo system. + +This is a bitmask, so several values can be present at once. + +.. data:: NoModifications + + Fixed value of 0 indicating no modifications have been made. + +.. data:: Renames + + One or more resources have been given a custom name which hasn't been saved. + +.. data:: Bookmarks + + Event bookmarks have been added or removed. + +.. data:: Notes + + The general notes field has been changed. +)"); +enum class CaptureModifications : uint32_t +{ + NoModifications = 0x0000, + Renames = 0x0001, + Bookmarks = 0x0002, + Notes = 0x0004, +}; + +BITMASK_OPERATORS(CaptureModifications); + DOCUMENT("The capture context that the python script is running in.") struct ICaptureContext { @@ -998,6 +1032,14 @@ temporary and treated like any other capture. )"); virtual QString GetCaptureFilename() = 0; + DOCUMENT(R"(Get a bitmask indicating which modifications (if any) have been made to the capture in +the UI which aren't reflected in the capture file on disk. + +:return: The modifications (if any) that have been made to the capture. +:rtype: CaptureModifications +)"); + virtual CaptureModifications GetCaptureModifications() = 0; + DOCUMENT(R"(Retrieve the :class:`~renderdoc.FrameDescription` for the currently loaded capture. :return: The frame information. diff --git a/qrenderdoc/Windows/MainWindow.cpp b/qrenderdoc/Windows/MainWindow.cpp index 441d16b16..e89f98160 100644 --- a/qrenderdoc/Windows/MainWindow.cpp +++ b/qrenderdoc/Windows/MainWindow.cpp @@ -220,7 +220,8 @@ MainWindow::MainWindow(ICaptureContext &ctx) : QMainWindow(NULL), ui(new Ui::Mai m_Ctx.AddCaptureViewer(this); - ui->action_Save_Capture->setEnabled(false); + ui->action_Save_Capture_Inplace->setEnabled(false); + ui->action_Save_Capture_As->setEnabled(false); ui->action_Close_Capture->setEnabled(false); QList actions = ui->menuBar->actions(); @@ -282,6 +283,14 @@ void MainWindow::on_action_Open_Capture_triggered() LoadFromFilename(filename, false); } +void MainWindow::captureModified() +{ + // once the capture is modified, enable the save-in-place option. It might already have been + // enabled if this capture was a temporary one + if(m_Ctx.IsCaptureLoaded()) + ui->action_Save_Capture_Inplace->setEnabled(true); +} + void MainWindow::LoadFromFilename(const QString &filename, bool temporary) { QFileInfo path(filename); @@ -652,6 +661,53 @@ bool MainWindow::PromptCloseCapture() deletepath = temppath; m_OwnTempCapture = false; } + else if(m_Ctx.GetCaptureModifications() != CaptureModifications::NoModifications) + { + QMessageBox::StandardButton res = QMessageBox::No; + + QString text = tr("This capture has the following modifications:\n\n"); + + CaptureModifications mods = m_Ctx.GetCaptureModifications(); + + if(mods & CaptureModifications::Renames) + text += tr("Resources have been renamed.\n"); + if(mods & CaptureModifications::Bookmarks) + text += tr("Bookmarks have been changed.\n"); + if(mods & CaptureModifications::Notes) + text += tr("Capture notes have been changed.\n"); + + bool saveas = false; + + if(m_Ctx.IsCaptureLocal()) + { + text += tr("\nWould you like to save those changes to '%1'?").arg(m_Ctx.GetCaptureFilename()); + } + else + { + saveas = true; + text += + tr("\nThe capture is on a remote host, would you like to save these changes locally?"); + } + + res = RDDialog::question(NULL, tr("Save changes to capture?"), text, + QMessageBox::Yes | QMessageBox::No | QMessageBox::Cancel); + + if(res == QMessageBox::Cancel) + return false; + + if(res == QMessageBox::Yes) + { + bool success = false; + + if(saveas) + success = PromptSaveCaptureAs(); + else + success = m_Ctx.SaveCaptureTo(m_Ctx.GetCaptureFilename()); + + if(!success) + return false; + } + } CloseCapture(); @@ -665,7 +721,8 @@ void MainWindow::CloseCapture() { m_Ctx.CloseCapture(); - ui->action_Save_Capture->setEnabled(false); + ui->action_Save_Capture_Inplace->setEnabled(false); + ui->action_Save_Capture_As->setEnabled(false); } void MainWindow::SetTitle(const QString &filename) @@ -1226,7 +1283,11 @@ void MainWindow::statusDoubleClicked(QMouseEvent *event) void MainWindow::OnCaptureLoaded() { - ui->action_Save_Capture->setEnabled(true); + // at first only allow the default save for temporary captures. It should be disabled if we have + // loaded a 'permanent' capture from disk and haven't made any changes. It will be enabled as soon + // as any changes are made. + ui->action_Save_Capture_Inplace->setEnabled(m_Ctx.IsCaptureTemporary()); + ui->action_Save_Capture_As->setEnabled(true); ui->action_Close_Capture->setEnabled(true); // don't allow changing context while capture is open @@ -1247,8 +1308,6 @@ void MainWindow::OnCaptureLoaded() ui->action_Resolve_Symbols->setEnabled(hasResolver); ui->action_Resolve_Symbols->setText(hasResolver ? tr("Resolve Symbols") : tr("Resolve Symbols - None in capture")); - - ui->action_Save_Capture->setEnabled(true); }); }); @@ -1261,7 +1320,8 @@ void MainWindow::OnCaptureLoaded() void MainWindow::OnCaptureClosed() { - ui->action_Save_Capture->setEnabled(false); + ui->action_Save_Capture_Inplace->setEnabled(false); + ui->action_Save_Capture_As->setEnabled(false); ui->action_Close_Capture->setEnabled(false); ui->action_Start_Replay_Loop->setEnabled(false); @@ -1361,7 +1421,28 @@ void MainWindow::on_action_Close_Capture_triggered() PromptCloseCapture(); } -void MainWindow::on_action_Save_Capture_triggered() +void MainWindow::on_action_Save_Capture_Inplace_triggered() +{ + bool saved = false; + + if(m_Ctx.IsCaptureTemporary() || !m_Ctx.IsCaptureLocal()) + { + saved = PromptSaveCaptureAs(); + } + else + { + if(m_Ctx.GetCaptureModifications() != CaptureModifications::NoModifications && + m_Ctx.IsCaptureLocal()) + { + saved = m_Ctx.SaveCaptureTo(m_Ctx.GetCaptureFilename()); + } + } + + if(saved) + ui->action_Save_Capture_Inplace->setEnabled(false); +} + +void MainWindow::on_action_Save_Capture_As_triggered() { PromptSaveCaptureAs(); } diff --git a/qrenderdoc/Windows/MainWindow.h b/qrenderdoc/Windows/MainWindow.h index c9d8cf357..c314f977d 100644 --- a/qrenderdoc/Windows/MainWindow.h +++ b/qrenderdoc/Windows/MainWindow.h @@ -68,6 +68,7 @@ public: void setProgress(float val); void takeCaptureOwnership() { m_OwnTempCapture = true; } + void captureModified(); void LoadFromFilename(const QString &filename, bool temporary); void LoadCapture(const QString &filename, bool temporary, bool local); void CloseCapture(); @@ -101,7 +102,8 @@ private slots: void on_action_Exit_triggered(); void on_action_About_triggered(); void on_action_Open_Capture_triggered(); - void on_action_Save_Capture_triggered(); + void on_action_Save_Capture_Inplace_triggered(); + void on_action_Save_Capture_As_triggered(); void on_action_Close_Capture_triggered(); void on_action_Mesh_Output_triggered(); void on_action_API_Inspector_triggered(); diff --git a/qrenderdoc/Windows/MainWindow.ui b/qrenderdoc/Windows/MainWindow.ui index 94f020db2..f4022b0a8 100644 --- a/qrenderdoc/Windows/MainWindow.ui +++ b/qrenderdoc/Windows/MainWindow.ui @@ -79,7 +79,8 @@ - + + @@ -181,7 +182,7 @@ Ctrl+O - + &Save Capture @@ -409,6 +410,11 @@ &Resource Inspector + + + Sa&ve Capture As + + diff --git a/qrenderdoc/Windows/PythonShell.cpp b/qrenderdoc/Windows/PythonShell.cpp index 34912acd5..b614fa3e8 100644 --- a/qrenderdoc/Windows/PythonShell.cpp +++ b/qrenderdoc/Windows/PythonShell.cpp @@ -60,6 +60,10 @@ struct CaptureContextInvoker : ICaptureContext virtual bool IsCaptureTemporary() override { return m_Ctx.IsCaptureTemporary(); } virtual bool IsCaptureLoading() override { return m_Ctx.IsCaptureLoading(); } virtual QString GetCaptureFilename() override { return m_Ctx.GetCaptureFilename(); } + virtual CaptureModifications GetCaptureModifications() override + { + return m_Ctx.GetCaptureModifications(); + } virtual const FrameDescription &FrameInfo() override { return m_Ctx.FrameInfo(); } virtual const APIProperties &APIProps() override { return m_Ctx.APIProps(); } virtual uint32_t CurSelectedEvent() override { return m_Ctx.CurSelectedEvent(); } diff --git a/renderdoc/api/replay/basic_types.h b/renderdoc/api/replay/basic_types.h index 6ed6d6b86..e81bad20e 100644 --- a/renderdoc/api/replay/basic_types.h +++ b/renderdoc/api/replay/basic_types.h @@ -597,4 +597,11 @@ struct bytebuf : public rdcarray { bytebuf() : rdcarray() {} bytebuf(const std::vector &in) : rdcarray(in) {} +#if defined(RENDERDOC_QT_COMPAT) + bytebuf(const QByteArray &in) + { + resize(in.size()); + memcpy(elems, in.data(), in.size()); + } +#endif };