From 016bc29609baa4b7b88e4bc340b543286e77afc8 Mon Sep 17 00:00:00 2001 From: baldurk Date: Thu, 16 Nov 2017 19:06:45 +0000 Subject: [PATCH] Add a tool menu item that will recompress a capture file --- qrenderdoc/Code/CaptureContext.cpp | 214 +++++++++++++++++++---- qrenderdoc/Code/CaptureContext.h | 10 +- qrenderdoc/Code/Interface/QRDInterface.h | 4 + qrenderdoc/Windows/MainWindow.cpp | 11 ++ qrenderdoc/Windows/MainWindow.h | 1 + qrenderdoc/Windows/MainWindow.ui | 6 + qrenderdoc/Windows/PythonShell.cpp | 4 + renderdoc/api/replay/renderdoc_replay.h | 3 +- renderdoc/core/core.h | 4 +- renderdoc/replay/capture_file.cpp | 15 +- renderdoc/serialise/codecs/xml_codec.cpp | 58 ++++-- renderdoc/serialise/rdcfile.cpp | 8 + renderdoc/serialise/serialiser.cpp | 5 +- renderdoc/serialise/serialiser.h | 2 +- renderdoc/serialise/serialiser_tests.cpp | 6 +- renderdoccmd/renderdoccmd.cpp | 2 +- 16 files changed, 289 insertions(+), 64 deletions(-) diff --git a/qrenderdoc/Code/CaptureContext.cpp b/qrenderdoc/Code/CaptureContext.cpp index 5867aff08..cfbe6d1b0 100644 --- a/qrenderdoc/Code/CaptureContext.cpp +++ b/qrenderdoc/Code/CaptureContext.cpp @@ -567,6 +567,148 @@ void CaptureContext::AddFakeProfileMarkers() m_Drawcalls = ret; } +void CaptureContext::RecompressCapture() +{ + QString destFilename = GetCaptureFilename(); + QString tempFilename; + + ICaptureFile *cap = NULL; + ICaptureFile *tempCap = NULL; + + bool inplace = false; + + if(IsCaptureTemporary() || !IsCaptureLocal()) + { + QMessageBox::StandardButton res = + RDDialog::question(m_MainWindow, tr("Unsaved capture"), + tr("To recompress a capture you must save it first. Save this capture?"), + QMessageBox::Yes | QMessageBox::No | QMessageBox::Cancel); + + if(res == QMessageBox::Cancel || res == QMessageBox::No) + return; + + destFilename = m_MainWindow->GetSavePath(); + + // if it's already local, we'll do the save as part of the recompression convert. If it's + // remote, we need to copy it first, but we copy it to a temporary so we can do the conversion + // to the target location + + if(IsCaptureLocal()) + { + tempFilename = GetCaptureFilename(); + } + else + { + tempFilename = TempCaptureFilename(lit("recompress")); + Replay().CopyCaptureFromRemote(GetCaptureFilename(), tempFilename, m_MainWindow); + + if(!QFile::exists(tempFilename)) + { + RDDialog::critical(m_MainWindow, tr("Failed to save capture"), + tr("Capture couldn't be saved from remote.")); + return; + } + } + } + else + { + // if we're doing this inplace on an already saved capture, then we need to recompress to a + // temporary and close/move it afterwards. + inplace = true; + destFilename = TempCaptureFilename(lit("recompress")); + } + + if(IsCaptureLocal()) + { + // for local files we already have a handle. We'll reuse it, then re-open + cap = Replay().GetCaptureFile(); + } + else + { + // for remote files we open a new short-lived handle on the temporary file + tempCap = cap = RENDERDOC_OpenCaptureFile(); + cap->OpenFile(tempFilename.toUtf8().data(), "rdc"); + } + + if(!cap) + { + RDDialog::critical(m_MainWindow, tr("Unexpected missing handle"), + tr("Couldn't get open handle to file for recompression.")); + return; + } + + int index = cap->FindSectionByType(SectionType::FrameCapture); + SectionProperties props = cap->GetSectionProperties(index); + + if(props.flags & SectionFlags::ZstdCompressed) + { + RDDialog::information(m_MainWindow, tr("Capture already compressed"), + tr("This capture is already compressed as much as is possible.")); + + if(tempCap) + tempCap->Shutdown(); + if(!tempFilename.isEmpty()) + QFile::remove(tempFilename); + return; + } + + // convert from the currently open cap to the destination + float progress = 0.0f; + + LambdaThread *th = new LambdaThread([this, cap, destFilename, &progress]() { + cap->Convert(destFilename.toUtf8().data(), "rdc", &progress); + }); + th->start(); + // wait a few ms before popping up a progress bar + th->wait(500); + if(th->isRunning()) + { + ShowProgressDialog(m_MainWindow, tr("Recompressing file."), [th]() { return !th->isRunning(); }, + [&progress]() { return progress; }); + } + th->deleteLater(); + + if(inplace) + { + // if we're recompressing "in place", we need to close our capture, move the temporary over + // the original, then re-open. + + // this releases the hold over the real desired location. + cap->OpenFile("", ""); + + // now remove the old capture + QFile::remove(GetCaptureFilename()); + + // move the recompressed one over + QFile::rename(destFilename, GetCaptureFilename()); + + // and re-open + cap->OpenFile(GetCaptureFilename().toUtf8().data(), "rdc"); + } + else + { + // we've converted into the desired location. We don't have to do anything else but mark our + // new locally saved non-temporary status. + + m_CaptureFile = destFilename; + m_CaptureLocal = true; + m_CaptureTemporary = false; + + // open the saved capture file. This will let us remove the old file too + Replay().ReopenCaptureFile(m_CaptureFile); + + m_CaptureMods = CaptureModifications::All; + + SaveChanges(); + } + + // close any temporary resources + if(tempCap) + tempCap->Shutdown(); + if(!tempFilename.isEmpty()) + QFile::remove(tempFilename); +} + bool CaptureContext::SaveCaptureTo(const QString &captureFile) { bool success = false; @@ -633,33 +775,7 @@ bool CaptureContext::SaveCaptureTo(const QString &captureFile) m_CaptureTemporary = false; Replay().ReopenCaptureFile(captureFile); - - if(m_CaptureMods & CaptureModifications::Renames) - { - SectionProperties props; - props.type = SectionType::ResourceRenames; - props.version = 1; - - Replay().GetCaptureAccess()->WriteSection(props, SaveRenames().toUtf8()); - } - if(m_CaptureMods & CaptureModifications::Bookmarks) - { - SectionProperties props; - props.type = SectionType::Bookmarks; - props.version = 1; - - Replay().GetCaptureAccess()->WriteSection(props, SaveBookmarks().toUtf8()); - } - if(m_CaptureMods & CaptureModifications::Notes) - { - SectionProperties props; - props.type = SectionType::Notes; - props.version = 1; - - Replay().GetCaptureAccess()->WriteSection(props, SaveNotes().toUtf8()); - } - - m_CaptureMods = CaptureModifications::NoModifications; + SaveChanges(); return true; } @@ -810,7 +926,21 @@ void CaptureContext::RemoveBookmark(uint32_t EID) RefreshUIStatus({}, true, true); } -QString CaptureContext::SaveRenames() +void CaptureContext::SaveChanges() +{ + if(m_CaptureMods & CaptureModifications::Renames) + SaveRenames(); + + if(m_CaptureMods & CaptureModifications::Bookmarks) + SaveBookmarks(); + + if(m_CaptureMods & CaptureModifications::Notes) + SaveNotes(); + + m_CaptureMods = CaptureModifications::NoModifications; +} + +void CaptureContext::SaveRenames() { QVariantMap resources; for(ResourceId id : m_CustomNames.keys()) @@ -821,7 +951,13 @@ QString CaptureContext::SaveRenames() QVariantMap root; root[lit("CustomResourceNames")] = resources; - return VariantToJSON(root); + QString json = VariantToJSON(root); + + SectionProperties props; + props.type = SectionType::ResourceRenames; + props.version = 1; + + Replay().GetCaptureAccess()->WriteSection(props, json.toUtf8()); } void CaptureContext::LoadRenames(const QString &data) @@ -852,7 +988,7 @@ void CaptureContext::LoadRenames(const QString &data) } } -QString CaptureContext::SaveBookmarks() +void CaptureContext::SaveBookmarks() { QVariantList bookmarks; for(const EventBookmark &mark : m_Bookmarks) @@ -867,7 +1003,13 @@ QString CaptureContext::SaveBookmarks() QVariantMap root; root[lit("Bookmarks")] = bookmarks; - return VariantToJSON(root); + QString json = VariantToJSON(root); + + SectionProperties props; + props.type = SectionType::Bookmarks; + props.version = 1; + + Replay().GetCaptureAccess()->WriteSection(props, json.toUtf8()); } void CaptureContext::LoadBookmarks(const QString &data) @@ -892,13 +1034,19 @@ void CaptureContext::LoadBookmarks(const QString &data) } } -QString CaptureContext::SaveNotes() +void CaptureContext::SaveNotes() { QVariantMap root; for(const QString &key : m_Notes.keys()) root[key] = m_Notes[key]; - return VariantToJSON(root); + QString json = VariantToJSON(root); + + SectionProperties props; + props.type = SectionType::Notes; + props.version = 1; + + Replay().GetCaptureAccess()->WriteSection(props, json.toUtf8()); } void CaptureContext::LoadNotes(const QString &data) diff --git a/qrenderdoc/Code/CaptureContext.h b/qrenderdoc/Code/CaptureContext.h index be09940e0..439da52e5 100644 --- a/qrenderdoc/Code/CaptureContext.h +++ b/qrenderdoc/Code/CaptureContext.h @@ -74,6 +74,7 @@ public: void LoadCapture(const QString &captureFile, const QString &origFilename, bool temporary, bool local) override; bool SaveCaptureTo(const QString &captureFile) override; + void RecompressCapture() override; void CloseCapture() override; void SetEventID(const QVector &exclude, uint32_t selectedEventID, @@ -253,13 +254,15 @@ private: bool ContainsMarker(const rdcarray &m_Drawcalls); void AddFakeProfileMarkers(); - QString SaveRenames(); + void SaveChanges(); + + void SaveRenames(); void LoadRenames(const QString &data); - QString SaveBookmarks(); + void SaveBookmarks(); void LoadBookmarks(const QString &data); - QString SaveNotes(); + void SaveNotes(); void LoadNotes(const QString &data); float m_LoadProgress = 0.0f; @@ -291,7 +294,6 @@ private: } void setupDockWindow(QWidget *shad); - rdcarray m_Drawcalls; APIProperties m_APIProps; diff --git a/qrenderdoc/Code/Interface/QRDInterface.h b/qrenderdoc/Code/Interface/QRDInterface.h index 34ba285da..6a0fe1363 100644 --- a/qrenderdoc/Code/Interface/QRDInterface.h +++ b/qrenderdoc/Code/Interface/QRDInterface.h @@ -917,6 +917,7 @@ enum class CaptureModifications : uint32_t Renames = 0x0001, Bookmarks = 0x0002, Notes = 0x0004, + All = 0xffffffff, }; BITMASK_OPERATORS(CaptureModifications); @@ -984,6 +985,9 @@ time. )"); virtual bool SaveCaptureTo(const QString &captureFile) = 0; + DOCUMENT("Recompress the current capture as much as possible."); + virtual void RecompressCapture() = 0; + DOCUMENT("Close the currently open capture file."); virtual void CloseCapture() = 0; diff --git a/qrenderdoc/Windows/MainWindow.cpp b/qrenderdoc/Windows/MainWindow.cpp index c4423477b..62ec55a21 100644 --- a/qrenderdoc/Windows/MainWindow.cpp +++ b/qrenderdoc/Windows/MainWindow.cpp @@ -183,6 +183,8 @@ MainWindow::MainWindow(ICaptureContext &ctx) : QMainWindow(NULL), ui(new Ui::Mai ui->action_Resolve_Symbols->setEnabled(false); ui->action_Resolve_Symbols->setText(tr("Resolve Symbols")); + ui->action_Recompress_Capture->setEnabled(false); + LambdaThread *th = new LambdaThread([this]() { m_Ctx.Config().AddAndroidHosts(); for(RemoteHost *host : m_Ctx.Config().RemoteHosts) @@ -1295,6 +1297,8 @@ void MainWindow::OnCaptureLoaded() statusProgress->setVisible(false); + ui->action_Recompress_Capture->setEnabled(true); + ui->action_Start_Replay_Loop->setEnabled(true); setCaptureHasErrors(!m_Ctx.DebugMessages().empty()); @@ -1335,6 +1339,8 @@ void MainWindow::OnCaptureClosed() ui->action_Resolve_Symbols->setEnabled(false); ui->action_Resolve_Symbols->setText(tr("Resolve Symbols")); + ui->action_Recompress_Capture->setEnabled(false); + SetTitle(); // if the remote sever disconnected during capture replay, resort back to a 'disconnected' state @@ -1628,6 +1634,11 @@ void MainWindow::on_action_Resolve_Symbols_triggered() m_Ctx.GetAPIInspector()->Refresh(); } +void MainWindow::on_action_Recompress_Capture_triggered() +{ + m_Ctx.RecompressCapture(); +} + void MainWindow::on_action_Start_Replay_Loop_triggered() { if(!m_Ctx.IsCaptureLoaded()) diff --git a/qrenderdoc/Windows/MainWindow.h b/qrenderdoc/Windows/MainWindow.h index c1682d505..0ee08b56a 100644 --- a/qrenderdoc/Windows/MainWindow.h +++ b/qrenderdoc/Windows/MainWindow.h @@ -119,6 +119,7 @@ private slots: void on_action_Python_Shell_triggered(); void on_action_Inject_into_Process_triggered(); void on_action_Resolve_Symbols_triggered(); + void on_action_Recompress_Capture_triggered(); void on_action_Start_Replay_Loop_triggered(); void on_action_Attach_to_Running_Instance_triggered(); void on_action_Manage_Remote_Servers_triggered(); diff --git a/qrenderdoc/Windows/MainWindow.ui b/qrenderdoc/Windows/MainWindow.ui index 2c9f71196..feba1eb29 100644 --- a/qrenderdoc/Windows/MainWindow.ui +++ b/qrenderdoc/Windows/MainWindow.ui @@ -50,6 +50,7 @@ &Tools + @@ -424,6 +425,11 @@ Capture C&omments + + + Re&compress Capture + + diff --git a/qrenderdoc/Windows/PythonShell.cpp b/qrenderdoc/Windows/PythonShell.cpp index 17d16d8a1..655c90a2f 100644 --- a/qrenderdoc/Windows/PythonShell.cpp +++ b/qrenderdoc/Windows/PythonShell.cpp @@ -169,6 +169,10 @@ struct CaptureContextInvoker : ICaptureContext { return InvokeRetFunction(&ICaptureContext::SaveCaptureTo, capture); } + virtual void RecompressCapture() override + { + InvokeVoidFunction(&ICaptureContext::RecompressCapture); + } virtual void CloseCapture() override { InvokeVoidFunction(&ICaptureContext::CloseCapture); } virtual void SetEventID(const QVector &exclude, uint32_t selectedEventID, uint32_t eventID, bool force = false) override diff --git a/renderdoc/api/replay/renderdoc_replay.h b/renderdoc/api/replay/renderdoc_replay.h index f9a4a845c..6b7770467 100644 --- a/renderdoc/api/replay/renderdoc_replay.h +++ b/renderdoc/api/replay/renderdoc_replay.h @@ -1377,10 +1377,11 @@ representation back to native RDC. :param str filename: The filename to save to. :param str filetype: The format to convert to. +:param float progress: A reference to a ``float`` value that will be updated as the copy happens :return: The status of the conversion operation, whether it succeeded or failed (and how it failed). :rtype: ReplayStatus )"); - virtual ReplayStatus Convert(const char *filename, const char *filetype) = 0; + virtual ReplayStatus Convert(const char *filename, const char *filetype, float *progress) = 0; DOCUMENT(R"(Returns the list of capture file formats. diff --git a/renderdoc/core/core.h b/renderdoc/core/core.h index 7254a00e7..ceb5ad375 100644 --- a/renderdoc/core/core.h +++ b/renderdoc/core/core.h @@ -240,9 +240,9 @@ typedef ReplayStatus (*ReplayDriverProvider)(RDCFile *rdc, IReplayDriver **drive typedef void (*StructuredProcessor)(RDCFile *rdc, SDFile &structData); typedef ReplayStatus (*CaptureImporter)(const char *filename, StreamReader &reader, RDCFile *rdc, - SDFile &structData); + SDFile &structData, float *progress); typedef ReplayStatus (*CaptureExporter)(const char *filename, const RDCFile &rdc, - const SDFile &structData); + const SDFile &structData, float *progress); typedef bool (*VulkanLayerCheck)(VulkanLayerFlags &flags, std::vector &myJSONs, std::vector &otherJSONs); diff --git a/renderdoc/replay/capture_file.cpp b/renderdoc/replay/capture_file.cpp index afc887f9e..e9749e700 100644 --- a/renderdoc/replay/capture_file.cpp +++ b/renderdoc/replay/capture_file.cpp @@ -123,7 +123,7 @@ public: void SetMetadata(const char *driverName, uint64_t machineIdent, FileType thumbType, uint32_t thumbWidth, uint32_t thumbHeight, const bytebuf &thumbData); - ReplayStatus Convert(const char *filename, const char *filetype); + ReplayStatus Convert(const char *filename, const char *filetype, float *progress); rdcarray GetCaptureFileFormats() { @@ -207,8 +207,9 @@ ReplayStatus CaptureFile::OpenFile(const char *filename, const char *filetype) { StreamReader reader(FileIO::fopen(filename, "rb")); + delete m_RDC; m_RDC = new RDCFile; - ret = importer(filename, reader, m_RDC, m_StructuredData); + ret = importer(filename, reader, m_RDC, m_StructuredData, NULL); } if(ret != ReplayStatus::Succeeded) @@ -243,7 +244,7 @@ ReplayStatus CaptureFile::OpenBuffer(const bytebuf &buffer, const char *filetype { StreamReader reader(vec); m_RDC = new RDCFile; - ret = importer(NULL, reader, m_RDC, m_StructuredData); + ret = importer(NULL, reader, m_RDC, m_StructuredData, NULL); } if(ret != ReplayStatus::Succeeded) @@ -368,7 +369,7 @@ void CaptureFile::SetMetadata(const char *driverName, uint64_t machineIdent, Fil free((void *)th.pixels); } -ReplayStatus CaptureFile::Convert(const char *filename, const char *filetype) +ReplayStatus CaptureFile::Convert(const char *filename, const char *filetype, float *progress) { if(!m_RDC) { @@ -379,7 +380,7 @@ ReplayStatus CaptureFile::Convert(const char *filename, const char *filetype) CaptureExporter exporter = RenderDoc::Inst().GetCaptureExporter(filetype); if(exporter) - return exporter(filename, *m_RDC, GetStructuredData()); + return exporter(filename, *m_RDC, GetStructuredData(), progress); if(filetype != NULL && strcmp(filetype, "") && strcmp(filetype, "rdc")) RDCWARN("Converting file to unrecognised filetype '%s' - treating as 'rdc'", filetype); @@ -419,7 +420,7 @@ ReplayStatus CaptureFile::Convert(const char *filename, const char *filetype) WriteSerialiser ser(writer, Ownership::Nothing); - ser.WriteStructuredFile(GetStructuredData()); + ser.WriteStructuredFile(GetStructuredData(), progress); writer->Finish(); @@ -436,7 +437,7 @@ ReplayStatus CaptureFile::Convert(const char *filename, const char *filetype) StreamWriter *writer = output.WriteSection(props); StreamReader *reader = m_RDC->ReadSection(frameCaptureIndex); - StreamTransfer(writer, reader, NULL); + StreamTransfer(writer, reader, progress); writer->Finish(); diff --git a/renderdoc/serialise/codecs/xml_codec.cpp b/renderdoc/serialise/codecs/xml_codec.cpp index fc75fddeb..b0879a834 100644 --- a/renderdoc/serialise/codecs/xml_codec.cpp +++ b/renderdoc/serialise/codecs/xml_codec.cpp @@ -22,6 +22,7 @@ * THE SOFTWARE. ******************************************************************************/ +#include #include "common/common.h" #include "serialise/rdcfile.h" @@ -39,6 +40,16 @@ std::string GetBufferName(inttype i) return StringFormat::Fmt("%06u", (uint32_t)i); } +inline float BufferProgress(float progress) +{ + return 0.2f * progress; +} + +inline float StructuredProgress(float progress) +{ + return 0.2f + 0.8f * progress; +} + struct xml_file_writer : pugi::xml_writer { StreamWriter stream; @@ -247,7 +258,7 @@ static void Obj2XML(pugi::xml_node &parent, SDObject &child) } static ReplayStatus Structured2XML(const char *filename, const RDCFile &file, uint64_t version, - const StructuredChunkList &chunks) + const StructuredChunkList &chunks, float *progress) { pugi::xml_document doc; @@ -462,8 +473,8 @@ static SDObject *XML2Obj(pugi::xml_node &obj) return ret; } -static ReplayStatus XML2Structured(const char *xml, const StructuredBufferList &buffers, - RDCFile *rdc, uint64_t &version, StructuredChunkList &chunks) +static ReplayStatus XML2Structured(const char *xml, const StructuredBufferList &buffers, RDCFile *rdc, + uint64_t &version, StructuredChunkList &chunks, float *progress) { pugi::xml_document doc; doc.load_string(xml); @@ -528,6 +539,9 @@ static ReplayStatus XML2Structured(const char *xml, const StructuredBufferList & // push in other sections pugi::xml_node xSection = xHeader.next_sibling(); + if(progress) + *progress = StructuredProgress(0.1f); + while(!strcmp(xSection.name(), "section")) { SectionProperties props; @@ -596,6 +610,9 @@ static ReplayStatus XML2Structured(const char *xml, const StructuredBufferList & xSection = xSection.next_sibling(); } + if(progress) + *progress = StructuredProgress(0.2f); + pugi::xml_node xChunks = xSection; if(strcmp(xSection.name(), "chunks")) @@ -612,6 +629,9 @@ static ReplayStatus XML2Structured(const char *xml, const StructuredBufferList & version = xChunks.attribute("version").as_ullong(); + size_t chunkIdx = 0; + size_t numChunks = std::distance(xChunks.begin(), xChunks.end()); + for(pugi::xml_node xChunk = xChunks.first_child(); xChunk; xChunk = xChunk.next_sibling()) { if(strcmp(xChunk.name(), "chunk")) @@ -657,13 +677,18 @@ static ReplayStatus XML2Structured(const char *xml, const StructuredBufferList & } chunks.push_back(chunk); + + if(progress) + *progress = StructuredProgress(0.2f + 0.8f * (float(chunkIdx) / float(numChunks))); + + chunkIdx++; } return ReplayStatus::Succeeded; } static ReplayStatus Buffers2ZIP(const std::string &filename, const RDCFile &file, - const StructuredBufferList &buffers) + const StructuredBufferList &buffers, float *progress) { std::string zipFile = filename + ".zip"; @@ -679,9 +704,14 @@ static ReplayStatus Buffers2ZIP(const std::string &filename, const RDCFile &file } for(size_t i = 0; i < buffers.size(); i++) + { mz_zip_writer_add_mem(&zip, GetBufferName(i).c_str(), buffers[i]->data(), buffers[i]->size(), MZ_BEST_COMPRESSION); + if(progress) + *progress = BufferProgress(float(i) / float(buffers.size())); + } + const RDCThumb &th = file.GetThumbnail(); if(th.pixels && th.len > 0 && th.width > 0 && th.height > 0) mz_zip_writer_add_mem(&zip, "thumb.jpg", th.pixels, th.len, MZ_BEST_COMPRESSION); @@ -692,7 +722,7 @@ static ReplayStatus Buffers2ZIP(const std::string &filename, const RDCFile &file return ReplayStatus::Succeeded; } -static void ZIP2Buffers(const std::string &filename, StructuredBufferList &buffers) +static void ZIP2Buffers(const std::string &filename, StructuredBufferList &buffers, float *progress) { std::string zipFile = filename + ".zip"; @@ -735,33 +765,39 @@ static void ZIP2Buffers(const std::string &filename, StructuredBufferList &buffe buffers.back() = new bytebuf; buffers.back()->assign(buf, sz); } + + if(progress) + *progress = BufferProgress(float(i) / float(numfiles)); } } mz_zip_reader_end(&zip); } -ReplayStatus importXMLZ(const char *filename, StreamReader &reader, RDCFile *rdc, SDFile &structData) +ReplayStatus importXMLZ(const char *filename, StreamReader &reader, RDCFile *rdc, + SDFile &structData, float *progress) { if(filename) - ZIP2Buffers(filename, structData.buffers); + ZIP2Buffers(filename, structData.buffers, progress); uint64_t len = reader.GetSize(); char *buf = new char[(size_t)len + 1]; reader.Read(buf, (size_t)len); buf[len] = 0; - return XML2Structured(buf, structData.buffers, rdc, structData.version, structData.chunks); + return XML2Structured(buf, structData.buffers, rdc, structData.version, structData.chunks, + progress); } -ReplayStatus exportXMLZ(const char *filename, const RDCFile &rdc, const SDFile &structData) +ReplayStatus exportXMLZ(const char *filename, const RDCFile &rdc, const SDFile &structData, + float *progress) { - ReplayStatus ret = Buffers2ZIP(filename, rdc, structData.buffers); + ReplayStatus ret = Buffers2ZIP(filename, rdc, structData.buffers, progress); if(ret != ReplayStatus::Succeeded) return ret; - return Structured2XML(filename, rdc, structData.version, structData.chunks); + return Structured2XML(filename, rdc, structData.version, structData.chunks, progress); } static ConversionRegistration XMLConversionRegistration("xml", R"(XML+ZIP format. diff --git a/renderdoc/serialise/rdcfile.cpp b/renderdoc/serialise/rdcfile.cpp index fb3cc9f2b..e2e89c20c 100644 --- a/renderdoc/serialise/rdcfile.cpp +++ b/renderdoc/serialise/rdcfile.cpp @@ -241,6 +241,14 @@ RDCFile::~RDCFile() void RDCFile::Open(const char *path) { + // silently fail when opening the empty string, to allow 'releasing' a capture file by opening an + // empty path. + if(path == NULL || path[0] == 0) + { + m_Error = ContainerError::FileNotFound; + return; + } + RDCLOG("Opening RDCFile %s", path); // ensure section header is compiled correctly diff --git a/renderdoc/serialise/serialiser.cpp b/renderdoc/serialise/serialiser.cpp index 80a756075..59063c7a1 100644 --- a/renderdoc/serialise/serialiser.cpp +++ b/renderdoc/serialise/serialiser.cpp @@ -407,7 +407,7 @@ void Serialiser::EndChunk() } template <> -void Serialiser::WriteStructuredFile(const SDFile &file) +void Serialiser::WriteStructuredFile(const SDFile &file, float *progress) { Serialiser scratchWriter( new StreamWriter(StreamWriter::DefaultScratchSize), Ownership::Stream); @@ -472,6 +472,9 @@ void Serialiser::WriteStructuredFile(const SDFile &file m_Write->Write(scratchWriter.GetWriter()->GetData(), scratchWriter.GetWriter()->GetOffset()); scratchWriter.GetWriter()->Rewind(); } + + if(progress) + *progress = float(i) / float(file.chunks.size()); } m_StructuredFile = &m_StructData; diff --git a/renderdoc/serialise/serialiser.h b/renderdoc/serialise/serialiser.h index 932c33a17..e57deff1d 100644 --- a/renderdoc/serialise/serialiser.h +++ b/renderdoc/serialise/serialiser.h @@ -131,7 +131,7 @@ public: // up-front void SetStreamingMode(bool stream) { m_DataStreaming = stream; } SDFile &GetStructuredFile() { return *m_StructuredFile; } - void WriteStructuredFile(const SDFile &file); + void WriteStructuredFile(const SDFile &file, float *progress); void SetDrawChunk() { m_DrawChunk = true; } ////////////////////////////////////////// // Public serialisation interface diff --git a/renderdoc/serialise/serialiser_tests.cpp b/renderdoc/serialise/serialiser_tests.cpp index 576689d43..6b24e18be 100644 --- a/renderdoc/serialise/serialiser_tests.cpp +++ b/renderdoc/serialise/serialiser_tests.cpp @@ -462,7 +462,7 @@ TEST_CASE("Read/write via structured of basic types", "[serialiser]") { WriteSerialiser rewrite(rewriteBuf, Ownership::Nothing); - rewrite.WriteStructuredFile(structFile); + rewrite.WriteStructuredFile(structFile, NULL); } // must be bitwise identical to the original serialised data. @@ -934,7 +934,7 @@ TEST_CASE("Read/write container types", "[serialiser][structured]") { WriteSerialiser rewrite(rewriteBuf, Ownership::Nothing); - rewrite.WriteStructuredFile(structData); + rewrite.WriteStructuredFile(structData, NULL); } // must be bitwise identical to the original serialised data. @@ -1304,7 +1304,7 @@ TEST_CASE("Read/write complex types", "[serialiser][structured]") { WriteSerialiser rewrite(rewriteBuf, Ownership::Nothing); - rewrite.WriteStructuredFile(structData); + rewrite.WriteStructuredFile(structData, NULL); } // must be bitwise identical to the original serialised data. diff --git a/renderdoccmd/renderdoccmd.cpp b/renderdoccmd/renderdoccmd.cpp index 779e6985e..f6403bfc3 100644 --- a/renderdoccmd/renderdoccmd.cpp +++ b/renderdoccmd/renderdoccmd.cpp @@ -722,7 +722,7 @@ struct ConvertCommand : public Command return 1; } - st = file->Convert(outfile.c_str(), outfmt.c_str()); + st = file->Convert(outfile.c_str(), outfmt.c_str(), NULL); if(st != ReplayStatus::Succeeded) {