From 934800793a7294140b3de78a1ff12d5efecde525 Mon Sep 17 00:00:00 2001 From: baldurk Date: Sun, 18 Feb 2018 18:01:54 +0000 Subject: [PATCH] Improve workflow for capture import/export * If the export doesn't need buffers, we export directly from the loaded capture file instead of re-loading it. * Add progress bars for the load step so it shows what's happening instead of looking stalled. * Reduce compression rate on XML+ZIP buffers as it took too long trying to compress when exporting large captures. --- qrenderdoc/Code/CaptureContext.cpp | 145 +++++++++++++++- qrenderdoc/Code/CaptureContext.h | 4 +- qrenderdoc/Code/Interface/QRDInterface.h | 24 +++ qrenderdoc/Code/ReplayManager.cpp | 4 +- qrenderdoc/Windows/Dialogs/CrashDialog.cpp | 2 +- qrenderdoc/Windows/MainWindow.cpp | 156 +++--------------- qrenderdoc/Windows/MainWindow.h | 4 +- qrenderdoc/Windows/PythonShell.cpp | 9 + renderdoc/api/replay/control_types.h | 24 ++- renderdoc/api/replay/renderdoc_replay.h | 16 +- renderdoc/core/core.cpp | 64 ++++--- renderdoc/core/core.h | 21 +-- renderdoc/core/target_control.cpp | 2 +- renderdoc/driver/vulkan/vk_core.cpp | 2 +- renderdoc/replay/capture_file.cpp | 95 ++++++++--- .../serialise/codecs/chrome_json_codec.cpp | 14 +- renderdoc/serialise/codecs/xml_codec.cpp | 61 +++++-- renderdoccmd/renderdoccmd.cpp | 22 +-- 18 files changed, 425 insertions(+), 244 deletions(-) diff --git a/qrenderdoc/Code/CaptureContext.cpp b/qrenderdoc/Code/CaptureContext.cpp index 9f1e842b1..0c032ea23 100644 --- a/qrenderdoc/Code/CaptureContext.cpp +++ b/qrenderdoc/Code/CaptureContext.cpp @@ -135,6 +135,8 @@ rdcstr CaptureContext::TempCaptureFilename(const rdcstr &appname) void CaptureContext::LoadCapture(const rdcstr &captureFile, const rdcstr &origFilename, bool temporary, bool local) { + CloseCapture(); + m_LoadInProgress = true; if(local) @@ -662,7 +664,7 @@ void CaptureContext::RecompressCapture() { // for remote files we open a new short-lived handle on the temporary file tempCap = cap = RENDERDOC_OpenCaptureFile(); - cap->OpenFile(tempFilename.toUtf8().data(), "rdc"); + cap->OpenFile(tempFilename.toUtf8().data(), "rdc", NULL); } if(!cap) @@ -691,7 +693,7 @@ void CaptureContext::RecompressCapture() float progress = 0.0f; LambdaThread *th = new LambdaThread([this, cap, destFilename, &progress]() { - cap->Convert(destFilename.toUtf8().data(), "rdc", [&progress](float p) { progress = p; }); + cap->Convert(destFilename.toUtf8().data(), "rdc", NULL, [&progress](float p) { progress = p; }); }); th->start(); // wait a few ms before popping up a progress bar @@ -709,7 +711,7 @@ void CaptureContext::RecompressCapture() // the original, then re-open. // this releases the hold over the real desired location. - cap->OpenFile("", ""); + cap->OpenFile("", "", NULL); // now remove the old capture QFile::remove(GetCaptureFilename()); @@ -718,7 +720,7 @@ void CaptureContext::RecompressCapture() QFile::rename(destFilename, GetCaptureFilename()); // and re-open - cap->OpenFile(GetCaptureFilename().c_str(), "rdc"); + cap->OpenFile(GetCaptureFilename().c_str(), "rdc", NULL); } else { @@ -869,6 +871,141 @@ void CaptureContext::CloseCapture() } } +bool CaptureContext::ImportCapture(const CaptureFileFormat &fmt, const rdcstr &importfile, + const rdcstr &rdcfile) +{ + CloseCapture(); + + QString ext = fmt.extension; + + ReplayStatus status = ReplayStatus::UnknownError; + QString message; + + // shorten the filename after here for error messages + QString filename = QFileInfo(importfile).fileName(); + + float progress = 0.0f; + + LambdaThread *th = new LambdaThread([rdcfile, importfile, ext, &message, &progress, &status]() { + + ICaptureFile *file = RENDERDOC_OpenCaptureFile(); + + status = file->OpenFile(importfile.c_str(), ext.toUtf8().data(), + [&progress](float p) { progress = p * 0.5f; }); + + if(status != ReplayStatus::Succeeded) + { + message = file->ErrorString(); + file->Shutdown(); + return; + } + + status = file->Convert(rdcfile.c_str(), "rdc", NULL, + [&progress](float p) { progress = 0.5f + p * 0.5f; }); + file->Shutdown(); + }); + th->start(); + // wait a few ms before popping up a progress bar + th->wait(500); + if(th->isRunning()) + { + ShowProgressDialog(m_MainWindow, tr("Importing from %1, please wait...").arg(filename), + [th]() { return !th->isRunning(); }, [&progress]() { return progress; }); + } + th->deleteLater(); + + if(status != ReplayStatus::Succeeded) + { + QString text = tr("Couldn't convert file '%1'\n").arg(filename); + if(message.isEmpty()) + text += tr("%1").arg(ToQStr(status)); + else + text += tr("%1: %2").arg(ToQStr(status)).arg(message); + + RDDialog::critical(m_MainWindow, tr("Error converting capture"), text); + return false; + } + + return true; +} + +void CaptureContext::ExportCapture(const CaptureFileFormat &fmt, const rdcstr &exportfile) +{ + if(!m_CaptureLocal) + return; + + QString ext = fmt.extension; + + ICaptureFile *local = NULL; + ICaptureFile *file = NULL; + ReplayStatus status = ReplayStatus::Succeeded; + + const SDFile *sdfile = NULL; + + // if we don't need buffers, we can export directly from our existing capture file + if(!fmt.requiresBuffers) + { + file = Replay().GetCaptureFile(); + sdfile = m_StructuredFile; + } + + if(!file) + { + local = file = RENDERDOC_OpenCaptureFile(); + status = file->OpenFile(m_CaptureFile.toUtf8().data(), "rdc", NULL); + } + + QString filename = QFileInfo(m_CaptureFile).fileName(); + + if(status != ReplayStatus::Succeeded) + { + QString text = tr("Couldn't open file '%1' for export\n").arg(filename); + QString message = local->ErrorString(); + if(message.isEmpty()) + text += tr("%1").arg(ToQStr(status)); + else + text += tr("%1: %2").arg(ToQStr(status)).arg(message); + + RDDialog::critical(m_MainWindow, tr("Error opening file"), text); + + if(local) + local->Shutdown(); + return; + } + + float progress = 0.0f; + + LambdaThread *th = new LambdaThread([this, file, sdfile, ext, exportfile, &progress, &status]() { + status = file->Convert(exportfile.c_str(), ext.toUtf8().data(), sdfile, + [&progress](float p) { progress = p; }); + }); + th->start(); + // wait a few ms before popping up a progress bar + th->wait(500); + if(th->isRunning()) + { + ShowProgressDialog(m_MainWindow, + tr("Exporting %1 to %2, please wait...").arg(filename).arg(QString(fmt.name)), + [th]() { return !th->isRunning(); }, [&progress]() { return progress; }); + } + th->deleteLater(); + + QString message = file->ErrorString(); + if(local) + local->Shutdown(); + + if(status != ReplayStatus::Succeeded) + { + QString text = tr("Couldn't convert file '%1'\n").arg(filename); + if(message.isEmpty()) + text += tr("%1").arg(ToQStr(status)); + else + text += tr("%1: %2").arg(ToQStr(status)).arg(message); + + RDDialog::critical(m_MainWindow, tr("Error converting capture"), text); + } +} + void CaptureContext::SetEventID(const rdcarray &exclude, uint32_t selectedEventID, uint32_t eventId, bool force) { diff --git a/qrenderdoc/Code/CaptureContext.h b/qrenderdoc/Code/CaptureContext.h index 859628f25..eee127fb3 100644 --- a/qrenderdoc/Code/CaptureContext.h +++ b/qrenderdoc/Code/CaptureContext.h @@ -73,7 +73,9 @@ public: bool SaveCaptureTo(const rdcstr &captureFile) override; void RecompressCapture() override; void CloseCapture() override; - + bool ImportCapture(const CaptureFileFormat &fmt, const rdcstr &importfile, + const rdcstr &rdcfile) override; + void ExportCapture(const CaptureFileFormat &fmt, const rdcstr &exportfile) override; void SetEventID(const rdcarray &exclude, uint32_t selectedEventID, uint32_t eventId, bool force = false) override; diff --git a/qrenderdoc/Code/Interface/QRDInterface.h b/qrenderdoc/Code/Interface/QRDInterface.h index 297b77018..c235899d3 100644 --- a/qrenderdoc/Code/Interface/QRDInterface.h +++ b/qrenderdoc/Code/Interface/QRDInterface.h @@ -1006,6 +1006,30 @@ time. DOCUMENT("Close the currently open capture file."); virtual void CloseCapture() = 0; + DOCUMENT(R"(Imports a capture file from a non-native format, via conversion to temporary rdc. + +This converts the file to a specified temporary .rdc and loads it, closing any existing capture. + +The capture must be available locally, if it's not this function will fail. + +:param CaptureFileFormat fmt: The capture file format to import from. +:param str importfile: The path to import from. +:param str rdcfile: The temporary path to save the rdc file to. +:return: ``True`` if the import operation was successful and the capture was loaded. +:rtype: ``bool`` +)"); + virtual bool ImportCapture(const CaptureFileFormat &fmt, const rdcstr &importfile, + const rdcstr &rdcfile) = 0; + + DOCUMENT(R"(Exports the current capture file to a given path with a specified capture file format. + +The capture must be available locally, if it's not this function will fail. + +:param CaptureFileFormat fmt: The capture file format to export to. +:param str exportfile: The path to export the capture file to. +)"); + virtual void ExportCapture(const CaptureFileFormat &fmt, const rdcstr &exportfile) = 0; + DOCUMENT(R"(Move the current replay to a new event in the capture. :param list exclude: A list of :class:`CaptureViewer` to exclude from being notified of this, to stop diff --git a/qrenderdoc/Code/ReplayManager.cpp b/qrenderdoc/Code/ReplayManager.cpp index f592e3908..604751870 100644 --- a/qrenderdoc/Code/ReplayManager.cpp +++ b/qrenderdoc/Code/ReplayManager.cpp @@ -369,7 +369,7 @@ void ReplayManager::ReopenCaptureFile(const QString &path) { if(!m_CaptureFile) m_CaptureFile = RENDERDOC_OpenCaptureFile(); - m_CaptureFile->OpenFile(path.toUtf8().data(), "rdc"); + m_CaptureFile->OpenFile(path.toUtf8().data(), "rdc", NULL); } uint32_t ReplayManager::ExecuteAndInject(const rdcstr &exe, const rdcstr &workingDir, @@ -423,7 +423,7 @@ void ReplayManager::run(int proxyRenderer, const QString &capturefile, { m_CaptureFile = RENDERDOC_OpenCaptureFile(); - m_CreateStatus = m_CaptureFile->OpenFile(capturefile.toUtf8().data(), "rdc"); + m_CreateStatus = m_CaptureFile->OpenFile(capturefile.toUtf8().data(), "rdc", NULL); if(m_CreateStatus == ReplayStatus::Succeeded) std::tie(m_CreateStatus, m_Renderer) = m_CaptureFile->OpenCapture(progress); diff --git a/qrenderdoc/Windows/Dialogs/CrashDialog.cpp b/qrenderdoc/Windows/Dialogs/CrashDialog.cpp index f49bc90b8..905b9cd0a 100644 --- a/qrenderdoc/Windows/Dialogs/CrashDialog.cpp +++ b/qrenderdoc/Windows/Dialogs/CrashDialog.cpp @@ -76,7 +76,7 @@ CrashDialog::CrashDialog(PersistantConfig &cfg, QVariantMap crashReportJSON, QWi ICaptureFile *cap = RENDERDOC_OpenCaptureFile(); - ReplayStatus status = cap->OpenFile(capInfo.absoluteFilePath().toUtf8().data(), ""); + ReplayStatus status = cap->OpenFile(capInfo.absoluteFilePath().toUtf8().data(), "", NULL); if(status == ReplayStatus::Succeeded) { diff --git a/qrenderdoc/Windows/MainWindow.cpp b/qrenderdoc/Windows/MainWindow.cpp index 847f96d25..2e1408335 100644 --- a/qrenderdoc/Windows/MainWindow.cpp +++ b/qrenderdoc/Windows/MainWindow.cpp @@ -338,39 +338,29 @@ MainWindow::MainWindow(ICaptureContext &ctx) : QMainWindow(NULL), ui(new Ui::Mai for(const CaptureFileFormat &fmt : formats) { - QString name = fmt.name; - if(name == lit("rdc")) + if(fmt.extension == "rdc") continue; - QString desc = fmt.description; - - int idx = desc.indexOf(QLatin1Char('\n')); - - QString title = idx >= 0 ? desc.mid(0, idx).trimmed() : desc.trimmed(); - QString tooltip = idx >= 0 ? desc.mid(idx).trimmed() : QString(); - if(fmt.openSupported) { - QAction *action = new QAction(title, this); + QAction *action = new QAction(fmt.name, this); - QObject::connect(action, &QAction::triggered, - [this, name, title]() { importCapture(name, title); }); + QObject::connect(action, &QAction::triggered, [this, fmt]() { importCapture(fmt); }); - if(!tooltip.isEmpty()) - action->setToolTip(tooltip); + if(!fmt.description.isEmpty()) + action->setToolTip(fmt.description); ui->menu_Import_From->addAction(action); } if(fmt.convertSupported) { - QAction *action = new QAction(title, this); + QAction *action = new QAction(fmt.name, this); - QObject::connect(action, &QAction::triggered, - [this, name, title]() { exportCapture(name, title); }); + QObject::connect(action, &QAction::triggered, [this, fmt]() { exportCapture(fmt); }); - if(!tooltip.isEmpty()) - action->setToolTip(tooltip); + if(!fmt.description.isEmpty()) + action->setToolTip(fmt.description); ui->menu_Export_As->addAction(action); } @@ -444,75 +434,30 @@ void MainWindow::on_action_Open_Capture_triggered() LoadFromFilename(filename, false); } -void MainWindow::importCapture(QString ext, QString title) +void MainWindow::importCapture(const CaptureFileFormat &fmt) { if(!PromptCloseCapture()) return; + QString ext = fmt.extension; + QString title = fmt.name; + QString filename = RDDialog::getOpenFileName(this, tr("Select file to open"), m_Ctx.Config().LastCaptureFilePath, tr("%1 Files (*.%2);;All Files (*)").arg(title).arg(ext)); if(!filename.isEmpty()) { - QString rdcfile = m_Ctx.TempCaptureFilename("imported"); + QString rdcfile = m_Ctx.TempCaptureFilename(lit("imported_") + ext); - ICaptureFile *file = RENDERDOC_OpenCaptureFile(); + bool success = m_Ctx.ImportCapture(fmt, filename, rdcfile); - ReplayStatus status = file->OpenFile(filename.toUtf8().data(), ext.toUtf8().data()); - - filename = QFileInfo(filename).fileName(); - - if(status != ReplayStatus::Succeeded) + if(success) { - QString text = tr("Couldn't open file '%1'\n").arg(filename); - QString message = file->ErrorString(); - if(message.isEmpty()) - text += tr("%1").arg(ToQStr(status)); - else - text += tr("%1: %2").arg(ToQStr(status)).arg(message); - - RDDialog::critical(this, tr("Error opening file"), text); - - file->Shutdown(); - return; + // open file as temporary, in case the user wants to save the imported rdc + LoadFromFilename(rdcfile, true); + takeCaptureOwnership(); } - - float progress = 0.0f; - - LambdaThread *th = new LambdaThread([file, rdcfile, &progress, &status]() { - status = file->Convert(rdcfile.toUtf8().data(), "rdc", [&progress](float p) { progress = p; }); - }); - th->start(); - // wait a few ms before popping up a progress bar - th->wait(500); - if(th->isRunning()) - { - ShowProgressDialog(this, tr("Importing from %1, please wait...").arg(filename), - [th]() { return !th->isRunning(); }, [&progress]() { return progress; }); - } - th->deleteLater(); - - if(status != ReplayStatus::Succeeded) - { - QString text = tr("Couldn't convert file '%1'\n").arg(filename); - QString message = file->ErrorString(); - if(message.isEmpty()) - text += tr("%1").arg(ToQStr(status)); - else - text += tr("%1: %2").arg(ToQStr(status)).arg(message); - - RDDialog::critical(this, tr("Error converting capture"), text); - - file->Shutdown(); - return; - } - - file->Shutdown(); - - // open file as temporary, in case the user wants to save the imported rdc - LoadFromFilename(rdcfile, true); - takeCaptureOwnership(); } } @@ -647,7 +592,7 @@ void MainWindow::LoadCapture(const QString &filename, bool temporary, bool local { ICaptureFile *file = RENDERDOC_OpenCaptureFile(); - ReplayStatus status = file->OpenFile(filename.toUtf8().data(), "rdc"); + ReplayStatus status = file->OpenFile(filename.toUtf8().data(), "rdc", NULL); if(status != ReplayStatus::Succeeded) { @@ -880,9 +825,9 @@ bool MainWindow::PromptSaveCaptureAs() return false; } -void MainWindow::exportCapture(QString ext, QString title) +void MainWindow::exportCapture(const CaptureFileFormat &fmt) { - if(m_Ctx.Replay().CurrentRemote()) + if(!m_Ctx.IsCaptureLocal()) { RDDialog::information( this, tr("Save changes to capture?"), @@ -892,62 +837,11 @@ void MainWindow::exportCapture(QString ext, QString title) } QString saveFilename = - GetSavePath(tr("Export Capture As"), tr("%1 Files (*.%2)").arg(title).arg(ext)); + GetSavePath(tr("Export Capture As"), + tr("%1 Files (*.%2)").arg(QString(fmt.name)).arg(QString(fmt.extension))); if(!saveFilename.isEmpty()) - { - ICaptureFile *file = RENDERDOC_OpenCaptureFile(); - - ReplayStatus status = file->OpenFile(m_Ctx.GetCaptureFilename().c_str(), "rdc"); - - QString filename = QFileInfo(QString(m_Ctx.GetCaptureFilename())).fileName(); - - if(status != ReplayStatus::Succeeded) - { - QString text = tr("Couldn't open file '%1' for export\n").arg(filename); - QString message = file->ErrorString(); - if(message.isEmpty()) - text += tr("%1").arg(ToQStr(status)); - else - text += tr("%1: %2").arg(ToQStr(status)).arg(message); - - RDDialog::critical(this, tr("Error opening file"), text); - - file->Shutdown(); - return; - } - - float progress = 0.0f; - - LambdaThread *th = new LambdaThread([file, ext, saveFilename, &progress, &status]() { - status = file->Convert(saveFilename.toUtf8().data(), ext.toUtf8().data(), - [&progress](float p) { progress = p; }); - }); - th->start(); - // wait a few ms before popping up a progress bar - th->wait(500); - if(th->isRunning()) - { - ShowProgressDialog(this, tr("Exporting %1 to %2, please wait...").arg(filename).arg(title), - [th]() { return !th->isRunning(); }, [&progress]() { return progress; }); - } - th->deleteLater(); - - QString message = file->ErrorString(); - file->Shutdown(); - - if(status != ReplayStatus::Succeeded) - { - QString text = tr("Couldn't convert file '%1'\n").arg(filename); - if(message.isEmpty()) - text += tr("%1").arg(ToQStr(status)); - else - text += tr("%1: %2").arg(ToQStr(status)).arg(message); - - RDDialog::critical(this, tr("Error converting capture"), text); - return; - } - } + m_Ctx.ExportCapture(fmt, saveFilename); } bool MainWindow::PromptCloseCapture() diff --git a/qrenderdoc/Windows/MainWindow.h b/qrenderdoc/Windows/MainWindow.h index fed8fb698..244617453 100644 --- a/qrenderdoc/Windows/MainWindow.h +++ b/qrenderdoc/Windows/MainWindow.h @@ -162,8 +162,8 @@ private: bool eventFilter(QObject *watched, QEvent *event) override; - void importCapture(QString ext, QString title); - void exportCapture(QString ext, QString title); + void importCapture(const CaptureFileFormat &fmt); + void exportCapture(const CaptureFileFormat &fmt); QString dragFilename(const QMimeData *mimeData); diff --git a/qrenderdoc/Windows/PythonShell.cpp b/qrenderdoc/Windows/PythonShell.cpp index 86901d44e..b5ddd1bbe 100644 --- a/qrenderdoc/Windows/PythonShell.cpp +++ b/qrenderdoc/Windows/PythonShell.cpp @@ -183,6 +183,15 @@ struct CaptureContextInvoker : ICaptureContext InvokeVoidFunction(&ICaptureContext::RecompressCapture); } virtual void CloseCapture() override { InvokeVoidFunction(&ICaptureContext::CloseCapture); } + virtual bool ImportCapture(const CaptureFileFormat &fmt, const rdcstr &importfile, + const rdcstr &rdcfile) override + { + return InvokeRetFunction(&ICaptureContext::ImportCapture, fmt, importfile, rdcfile); + } + virtual void ExportCapture(const CaptureFileFormat &fmt, const rdcstr &exportfile) override + { + InvokeVoidFunction(&ICaptureContext::ExportCapture, fmt, exportfile); + } virtual void SetEventID(const rdcarray &exclude, uint32_t selectedEventID, uint32_t eventId, bool force = false) override { diff --git a/renderdoc/api/replay/control_types.h b/renderdoc/api/replay/control_types.h index 6fa2f11bd..319b64569 100644 --- a/renderdoc/api/replay/control_types.h +++ b/renderdoc/api/replay/control_types.h @@ -536,34 +536,48 @@ struct CaptureFileFormat DOCUMENT(""); bool operator==(const CaptureFileFormat &o) const { - return name == o.name && description == o.description && openSupported == o.openSupported && + return extension == o.extension && name == o.name && description == o.description && + requiresBuffers == o.requiresBuffers && openSupported == o.openSupported && convertSupported == o.convertSupported; } bool operator<(const CaptureFileFormat &o) const { + if(!(extension == o.extension)) + return extension < o.extension; if(!(name == o.name)) return name < o.name; if(!(description == o.description)) return description < o.description; + if(!(requiresBuffers == o.requiresBuffers)) + return requiresBuffers < o.requiresBuffers; if(!(openSupported == o.openSupported)) return openSupported < o.openSupported; if(!(convertSupported == o.convertSupported)) return convertSupported < o.convertSupported; return false; } - DOCUMENT("The name of the format as a single minimal string, e.g. ``rdc``."); + DOCUMENT("The file of the format as a single minimal string, e.g. ``rdc``."); + rdcstr extension; + + DOCUMENT("A human readable short phrase naming the file format."); rdcstr name; - DOCUMENT("A human readable description of the file format, e.g. ``RenderDoc native capture``."); + DOCUMENT("A human readable long-form description of the file format."); rdcstr description; + DOCUMENT(R"(Indicates whether exporting to this format requires buffers or just structured data. +If it doesn't require buffers then it can be exported directly from an opened capture, which by +default has structured data but no buffers available. +)"); + bool requiresBuffers; + DOCUMENT(R"(Indicates whether or not files in this format can be opened and processed as structured data. )"); - bool openSupported = false; + bool openSupported; DOCUMENT("Indicates whether captures or structured data can be saved out in this format."); - bool convertSupported = true; + bool convertSupported; }; DECLARE_REFLECTION_STRUCT(CaptureFileFormat); diff --git a/renderdoc/api/replay/renderdoc_replay.h b/renderdoc/api/replay/renderdoc_replay.h index 61c3cf6b4..957edc542 100644 --- a/renderdoc/api/replay/renderdoc_replay.h +++ b/renderdoc/api/replay/renderdoc_replay.h @@ -1486,10 +1486,13 @@ empty or unrecognised. :param str filename: The filename of the file to open. :param str filetype: The format of the given file. +:param ProgressCallback progress: A callback that will be repeatedly called with an updated progress + value if an import step occurs. Can be ``None`` if no progress is desired. :return: The status of the open operation, whether it succeeded or failed (and how it failed). :rtype: ReplayStatus )"); - virtual ReplayStatus OpenFile(const char *filename, const char *filetype) = 0; + virtual ReplayStatus OpenFile(const char *filename, const char *filetype, + RENDERDOC_ProgressCallback progress) = 0; DOCUMENT(R"(Initialises the file handle from a raw memory buffer. @@ -1499,10 +1502,13 @@ For the :paramref:`OpenBuffer.filetype` parameter, see :meth:`OpenFile`. :param bytes buffer: The buffer containing the data to process. :param str filetype: The format of the given file. +:param ProgressCallback progress: A callback that will be repeatedly called with an updated progress + value if an import step occurs. Can be ``None`` if no progress is desired. :return: The status of the open operation, whether it succeeded or failed (and how it failed). :rtype: ReplayStatus )"); - virtual ReplayStatus OpenBuffer(const bytebuf &buffer, const char *filetype) = 0; + virtual ReplayStatus OpenBuffer(const bytebuf &buffer, const char *filetype, + RENDERDOC_ProgressCallback progress) = 0; DOCUMENT(R"(When a capture file is opened, an exclusive lock is held on the file on disk. This makes it impossible to copy the file to another location at the user's request. Calling this @@ -1525,12 +1531,16 @@ representation back to native RDC. :param str filename: The filename to save to. :param str filetype: The format to convert to. +:param SDFile file: An optional :class:`SDFile` with the structured data to source from. This is + useful in case the format specifies that it doesn't need buffers, and you already have a + :class:`ReplayController` open with the structured data. This saves the need to load the file + again. If ``None`` then structured data will be fetched if not already present and used. :param ProgressCallback progress: A callback that will be repeatedly called with an updated progress value for the conversion. Can be ``None`` if no progress is desired. :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, + virtual ReplayStatus Convert(const char *filename, const char *filetype, const SDFile *file, RENDERDOC_ProgressCallback progress) = 0; DOCUMENT(R"(Returns the human-readable error string for the last error received. diff --git a/renderdoc/core/core.cpp b/renderdoc/core/core.cpp index 32c3d1599..c9f1cab81 100644 --- a/renderdoc/core/core.cpp +++ b/renderdoc/core/core.cpp @@ -788,22 +788,45 @@ void RenderDoc::RegisterStructuredProcessor(RDCDriver driver, StructuredProcesso m_StructProcesssors[driver] = provider; } -void RenderDoc::RegisterCaptureExporter(const char *filetype, const char *description, - CaptureExporter exporter) +void RenderDoc::RegisterCaptureExporter(CaptureExporter exporter, CaptureFileFormat description) { - RDCASSERT(m_ImportExportFormats.find(filetype) == m_ImportExportFormats.end()); + std::string filetype = description.extension; - m_ImportExportFormats[filetype] = description; + for(const CaptureFileFormat &fmt : m_ImportExportFormats) + { + if(fmt.extension == filetype) + { + RDCERR("Duplicate exporter for '%s' found", filetype.c_str()); + return; + } + } + + description.openSupported = false; + description.convertSupported = true; + + m_ImportExportFormats.push_back(description); m_Exporters[filetype] = exporter; } -void RenderDoc::RegisterCaptureImportExporter(const char *filetype, const char *description, - CaptureImporter importer, CaptureExporter exporter) +void RenderDoc::RegisterCaptureImportExporter(CaptureImporter importer, CaptureExporter exporter, + CaptureFileFormat description) { - RDCASSERT(m_ImportExportFormats.find(filetype) == m_ImportExportFormats.end()); + std::string filetype = description.extension; - m_ImportExportFormats[filetype] = description; + for(const CaptureFileFormat &fmt : m_ImportExportFormats) + { + if(fmt.extension == filetype) + { + RDCERR("Duplicate import/exporter for '%s' found", filetype.c_str()); + return; + } + } + + description.openSupported = true; + description.convertSupported = true; + + m_ImportExportFormats.push_back(description); m_Importers[filetype] = importer; m_Exporters[filetype] = exporter; @@ -847,30 +870,19 @@ CaptureImporter RenderDoc::GetCaptureImporter(const char *filetype) std::vector RenderDoc::GetCaptureFileFormats() { - std::vector ret; + std::vector ret = m_ImportExportFormats; + + std::sort(ret.begin(), ret.end()); { CaptureFileFormat rdc; - rdc.name = "rdc"; - rdc.description = "Native RDC capture file format."; + rdc.extension = "rdc"; + rdc.name = "Native RDC capture file format."; + rdc.description = "The format produced by frame-captures from applications directly."; rdc.openSupported = true; rdc.convertSupported = true; - ret.push_back(rdc); - } - - for(auto it = m_ImportExportFormats.begin(); it != m_ImportExportFormats.end(); ++it) - { - CaptureFileFormat fmt; - fmt.name = it->first; - fmt.description = it->second; - - fmt.openSupported = m_Importers.find(it->first) != m_Importers.end(); - fmt.convertSupported = m_Exporters.find(it->first) != m_Exporters.end(); - - RDCASSERT(fmt.openSupported || fmt.convertSupported); - - ret.push_back(fmt); + ret.insert(ret.begin(), rdc); } return ret; diff --git a/renderdoc/core/core.h b/renderdoc/core/core.h index 34ae8eae6..6d1c66f74 100644 --- a/renderdoc/core/core.h +++ b/renderdoc/core/core.h @@ -312,8 +312,6 @@ class IReplayDriver; class StreamReader; class RDCFile; -class RDCFile; - typedef ReplayStatus (*RemoteDriverProvider)(RDCFile *rdc, IRemoteDriver **driver); typedef ReplayStatus (*ReplayDriverProvider)(RDCFile *rdc, IReplayDriver **driver); @@ -430,10 +428,9 @@ public: void RegisterStructuredProcessor(RDCDriver driver, StructuredProcessor provider); - void RegisterCaptureExporter(const char *filetype, const char *description, - CaptureExporter exporter); - void RegisterCaptureImportExporter(const char *filetype, const char *description, - CaptureImporter importer, CaptureExporter exporter); + void RegisterCaptureExporter(CaptureExporter exporter, CaptureFileFormat description); + void RegisterCaptureImportExporter(CaptureImporter importer, CaptureExporter exporter, + CaptureFileFormat description); StructuredProcessor GetStructuredProcessor(RDCDriver driver); @@ -584,7 +581,7 @@ private: std::map m_StructProcesssors; - std::map m_ImportExportFormats; + std::vector m_ImportExportFormats; std::map m_Importers; std::map m_Exporters; @@ -674,13 +671,13 @@ struct StructuredProcessRegistration struct ConversionRegistration { - ConversionRegistration(const char *filetype, const char *description, CaptureImporter importer, - CaptureExporter exporter) + ConversionRegistration(CaptureImporter importer, CaptureExporter exporter, + CaptureFileFormat description) { - RenderDoc::Inst().RegisterCaptureImportExporter(filetype, description, importer, exporter); + RenderDoc::Inst().RegisterCaptureImportExporter(importer, exporter, description); } - ConversionRegistration(const char *filetype, const char *description, CaptureExporter exporter) + ConversionRegistration(CaptureExporter exporter, CaptureFileFormat description) { - RenderDoc::Inst().RegisterCaptureExporter(filetype, description, exporter); + RenderDoc::Inst().RegisterCaptureExporter(exporter, description); } }; \ No newline at end of file diff --git a/renderdoc/core/target_control.cpp b/renderdoc/core/target_control.cpp index 56268bd77..76f6a062f 100644 --- a/renderdoc/core/target_control.cpp +++ b/renderdoc/core/target_control.cpp @@ -179,7 +179,7 @@ void RenderDoc::TargetControlClientThread(uint32_t version, Network::Socket *cli bytebuf buf; ICaptureFile *file = RENDERDOC_OpenCaptureFile(); - if(file->OpenFile(captures.back().path.c_str(), "rdc") == ReplayStatus::Succeeded) + if(file->OpenFile(captures.back().path.c_str(), "rdc", NULL) == ReplayStatus::Succeeded) { buf = file->GetThumbnail(FileType::JPG, 0).data; } diff --git a/renderdoc/driver/vulkan/vk_core.cpp b/renderdoc/driver/vulkan/vk_core.cpp index 9b695944c..310da5fc4 100644 --- a/renderdoc/driver/vulkan/vk_core.cpp +++ b/renderdoc/driver/vulkan/vk_core.cpp @@ -1552,7 +1552,7 @@ ReplayStatus WrappedVulkan::ReadLogInitialisation(RDCFile *rdc, bool storeStruct // only set progress after we've initialised the debug manager, to prevent progress jumping // backwards. - if(m_DebugManager) + if(m_DebugManager || IsStructuredExporting(m_State)) { RenderDoc::Inst().SetProgress(LoadProgress::FileInitialRead, float(offsetEnd) / float(reader->GetSize())); diff --git a/renderdoc/replay/capture_file.cpp b/renderdoc/replay/capture_file.cpp index b7f108329..49f64ec1b 100644 --- a/renderdoc/replay/capture_file.cpp +++ b/renderdoc/replay/capture_file.cpp @@ -112,8 +112,10 @@ public: CaptureFile(); virtual ~CaptureFile(); - ReplayStatus OpenFile(const char *filename, const char *filetype); - ReplayStatus OpenBuffer(const bytebuf &buffer, const char *filetype); + ReplayStatus OpenFile(const char *filename, const char *filetype, + RENDERDOC_ProgressCallback progress); + ReplayStatus OpenBuffer(const bytebuf &buffer, const char *filetype, + RENDERDOC_ProgressCallback progress); bool CopyFileTo(const char *filename); rdcstr ErrorString() { return m_ErrorString; } void Shutdown() { delete this; } @@ -125,7 +127,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, const SDFile *file, RENDERDOC_ProgressCallback progress); rdcarray GetCaptureFileFormats() @@ -135,16 +137,8 @@ public: const SDFile &GetStructuredData() { - if(m_StructuredData.chunks.empty() && m_RDC && m_RDC->SectionIndex(SectionType::FrameCapture) >= 0) - { - // decompile to structured data on demand. - StructuredProcessor proc = RenderDoc::Inst().GetStructuredProcessor(m_RDC->GetDriver()); - - if(proc) - proc(m_RDC, m_StructuredData); - else - RDCERR("Can't get structured data for driver %s", m_RDC->GetDriverName().c_str()); - } + // decompile to structured data on demand. + InitStructuredData(); return m_StructuredData; } @@ -182,6 +176,8 @@ public: private: ReplayStatus Init(); + void InitStructuredData(RENDERDOC_ProgressCallback progress = RENDERDOC_ProgressCallback()); + RDCFile *m_RDC = NULL; Callstack::StackResolver *m_Resolver = NULL; @@ -201,7 +197,8 @@ CaptureFile::~CaptureFile() SAFE_DELETE(m_Resolver); } -ReplayStatus CaptureFile::OpenFile(const char *filename, const char *filetype) +ReplayStatus CaptureFile::OpenFile(const char *filename, const char *filetype, + RENDERDOC_ProgressCallback progress) { CaptureImporter importer = RenderDoc::Inst().GetCaptureImporter(filetype); @@ -213,7 +210,7 @@ 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, NULL); + ret = importer(filename, reader, m_RDC, m_StructuredData, progress); } if(ret != ReplayStatus::Succeeded) @@ -228,15 +225,22 @@ ReplayStatus CaptureFile::OpenFile(const char *filename, const char *filetype) if(filetype != NULL && strcmp(filetype, "") && strcmp(filetype, "rdc")) RDCWARN("Opening file with unrecognised filetype '%s' - treating as 'rdc'", filetype); + if(progress) + progress(0.0f); + delete m_RDC; m_RDC = new RDCFile; m_RDC->Open(filename); + + if(progress) + progress(1.0f); } return Init(); } -ReplayStatus CaptureFile::OpenBuffer(const bytebuf &buffer, const char *filetype) +ReplayStatus CaptureFile::OpenBuffer(const bytebuf &buffer, const char *filetype, + RENDERDOC_ProgressCallback progress) { CaptureImporter importer = RenderDoc::Inst().GetCaptureImporter(filetype); @@ -249,7 +253,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, NULL); + ret = importer(NULL, reader, m_RDC, m_StructuredData, progress); } if(ret != ReplayStatus::Succeeded) @@ -264,8 +268,14 @@ ReplayStatus CaptureFile::OpenBuffer(const bytebuf &buffer, const char *filetype if(filetype != NULL && strcmp(filetype, "") && strcmp(filetype, "rdc")) RDCWARN("Opening file with unrecognised filetype '%s' - treating as 'rdc'", filetype); + if(progress) + progress(0.0f); + m_RDC = new RDCFile; m_RDC->Open(vec); + + if(progress) + progress(1.0f); } return Init(); @@ -325,6 +335,23 @@ ReplayStatus CaptureFile::Init() return ReplayStatus::InternalError; } +void CaptureFile::InitStructuredData(RENDERDOC_ProgressCallback progress /*= RENDERDOC_ProgressCallback()*/) +{ + if(m_StructuredData.chunks.empty() && m_RDC && m_RDC->SectionIndex(SectionType::FrameCapture) >= 0) + { + StructuredProcessor proc = RenderDoc::Inst().GetStructuredProcessor(m_RDC->GetDriver()); + + RenderDoc::Inst().SetProgressCallback(progress); + + if(proc) + proc(m_RDC, m_StructuredData); + else + RDCERR("Can't get structured data for driver %s", m_RDC->GetDriverName().c_str()); + + RenderDoc::Inst().SetProgressCallback(RENDERDOC_ProgressCallback()); + } +} + rdcpair CaptureFile::OpenCapture(RENDERDOC_ProgressCallback progress) { if(!m_RDC || m_RDC->ErrorCode() != ContainerError::NoError) @@ -377,7 +404,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, const SDFile *file, RENDERDOC_ProgressCallback progress) { if(!m_RDC) @@ -386,10 +413,30 @@ ReplayStatus CaptureFile::Convert(const char *filename, const char *filetype, return ReplayStatus::FileCorrupted; } + // make sure progress is valid so we don't have to check it everywhere + if(!progress) + progress = [](float) {}; + + // we have two separate steps that can take time - fetching the structured data, and then + // exporting or writing to RDC + RENDERDOC_ProgressCallback fetchProgress = [progress](float p) { progress(p * 0.5f); }; + RENDERDOC_ProgressCallback exportProgress = [progress](float p) { progress(0.5f + p * 0.5f); }; + CaptureExporter exporter = RenderDoc::Inst().GetCaptureExporter(filetype); if(exporter) - return exporter(filename, *m_RDC, GetStructuredData(), progress); + { + if(file) + { + return exporter(filename, *m_RDC, *file, exportProgress); + } + else + { + InitStructuredData(fetchProgress); + + return exporter(filename, *m_RDC, GetStructuredData(), exportProgress); + } + } if(filetype != NULL && strcmp(filetype, "") && strcmp(filetype, "rdc")) RDCWARN("Converting file to unrecognised filetype '%s' - treating as 'rdc'", filetype); @@ -419,17 +466,23 @@ ReplayStatus CaptureFile::Convert(const char *filename, const char *filetype, if(frameCaptureIndex == -1) { + if(file == NULL) + { + InitStructuredData(fetchProgress); + file = &m_StructuredData; + } + SectionProperties frameCapture; frameCapture.flags = SectionFlags::ZstdCompressed; frameCapture.type = SectionType::FrameCapture; frameCapture.name = ToStr(frameCapture.type); - frameCapture.version = GetStructuredData().version; + frameCapture.version = file->version; StreamWriter *writer = output.WriteSection(frameCapture); WriteSerialiser ser(writer, Ownership::Nothing); - ser.WriteStructuredFile(GetStructuredData(), progress); + ser.WriteStructuredFile(*file, exportProgress); writer->Finish(); diff --git a/renderdoc/serialise/codecs/chrome_json_codec.cpp b/renderdoc/serialise/codecs/chrome_json_codec.cpp index ac05b3f94..b5ca62d4a 100644 --- a/renderdoc/serialise/codecs/chrome_json_codec.cpp +++ b/renderdoc/serialise/codecs/chrome_json_codec.cpp @@ -92,9 +92,11 @@ ReplayStatus exportChrome(const char *filename, const RDCFile &rdc, const SDFile return ReplayStatus::Succeeded; } -static ConversionRegistration XMLConversionRegistration("chrome.json", R"(Chrome profiler JSON - -Exports the chunk threadID, timestamp and duration data to a JSON format that can be loaded by -chrome's profiler at chrome://tracing -)", - &exportChrome); \ No newline at end of file +static ConversionRegistration XMLConversionRegistration( + &exportChrome, + { + "chrome.json", "Chrome profiler JSON", + R"(Exports the chunk threadID, timestamp and duration data to a JSON format that can be loaded +by chrome's profiler at chrome://tracing)", + false, + }); \ No newline at end of file diff --git a/renderdoc/serialise/codecs/xml_codec.cpp b/renderdoc/serialise/codecs/xml_codec.cpp index cc1e3f542..5b0b99a2f 100644 --- a/renderdoc/serialise/codecs/xml_codec.cpp +++ b/renderdoc/serialise/codecs/xml_codec.cpp @@ -289,6 +289,9 @@ static ReplayStatus Structured2XML(const char *filename, const RDCFile &file, ui } } + if(progress) + progress(StructuredProgress(0.1f)); + // write all other sections for(int i = 0; i < file.NumSections(); i++) { @@ -340,6 +343,9 @@ static ReplayStatus Structured2XML(const char *filename, const RDCFile &file, ui delete reader; } + if(progress) + progress(StructuredProgress(0.2f)); + pugi::xml_node xChunks = xRoot.append_child("chunks"); xChunks.append_attribute("version") = version; @@ -382,6 +388,9 @@ static ReplayStatus Structured2XML(const char *filename, const RDCFile &file, ui for(size_t o = 0; o < chunk->data.children.size(); o++) Obj2XML(xChunk, *chunk->data.children[o]); } + + if(progress) + progress(StructuredProgress(0.2f + 0.8f * (float(c) / float(chunks.size())))); } xml_file_writer writer(filename); @@ -712,8 +721,7 @@ 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); + mz_zip_writer_add_mem(&zip, GetBufferName(i).c_str(), buffers[i]->data(), buffers[i]->size(), 2); if(progress) progress(BufferProgress(float(i) / float(buffers.size()))); @@ -729,13 +737,17 @@ static ReplayStatus Buffers2ZIP(const std::string &filename, const RDCFile &file return ReplayStatus::Succeeded; } -static void ZIP2Buffers(const std::string &filename, StructuredBufferList &buffers, +static bool ZIP2Buffers(const std::string &filename, StructuredBufferList &buffers, RENDERDOC_ProgressCallback progress) { - std::string zipFile = filename + ".zip"; + std::string zipFile = filename; + zipFile.erase(zipFile.size() - 4); // remove the .xml, leave only the .zip if(!FileIO::exists(zipFile.c_str())) - return; + { + RDCERR("Expected to file zip for %s at %s", filename.c_str(), zipFile.c_str()); + return false; + } mz_zip_archive zip; memset(&zip, 0, sizeof(zip)); @@ -780,13 +792,22 @@ static void ZIP2Buffers(const std::string &filename, StructuredBufferList &buffe } mz_zip_reader_end(&zip); + + return true; } ReplayStatus importXMLZ(const char *filename, StreamReader &reader, RDCFile *rdc, SDFile &structData, RENDERDOC_ProgressCallback progress) { if(filename) - ZIP2Buffers(filename, structData.buffers, progress); + { + bool success = ZIP2Buffers(filename, structData.buffers, progress); + if(!success) + { + RDCERR("Couldn't load zip to go with %s", filename); + return ReplayStatus::FileCorrupted; + } + } uint64_t len = reader.GetSize(); char *buf = new char[(size_t)len + 1]; @@ -814,16 +835,20 @@ ReplayStatus exportXMLOnly(const char *filename, const RDCFile &rdc, const SDFil return Structured2XML(filename, rdc, structData.version, structData.chunks, progress); } -static ConversionRegistration XMLZIPConversionRegistration("zip.xml", R"(XML+ZIP capture +static ConversionRegistration XMLZIPConversionRegistration( + &importXMLZ, &exportXMLZ, + { + "zip.xml", "XML+ZIP capture", + R"(Stores the structured data in an xml tree, with large buffer data stored in indexed blobs in +similarly named zip file.)", + true, + }); -Stores the structured data in an xml tree, with large buffer data stored in indexed blobs in -similarly named zip file. -)", - &importXMLZ, &exportXMLZ); - -static ConversionRegistration XMLOnlyConversionRegistration("xml", R"(XML capture - -Stores the structured data in an xml tree, with large buffer data omitted - that makes it easier to -work with but it cannot then be imported. -)", - &exportXMLOnly); \ No newline at end of file +static ConversionRegistration XMLOnlyConversionRegistration( + &exportXMLOnly, + { + "xml", "XML capture", + R"(Stores the structured data in an xml tree, with large buffer data omitted - that makes it +easier to work with but it cannot then be imported.)", + false, + }); \ No newline at end of file diff --git a/renderdoccmd/renderdoccmd.cpp b/renderdoccmd/renderdoccmd.cpp index e6fa7b8a1..374d85637 100644 --- a/renderdoccmd/renderdoccmd.cpp +++ b/renderdoccmd/renderdoccmd.cpp @@ -282,7 +282,7 @@ struct ThumbCommand : public Command bytebuf buf; ICaptureFile *file = RENDERDOC_OpenCaptureFile(); - ReplayStatus st = file->OpenFile(filename.c_str(), "rdc"); + ReplayStatus st = file->OpenFile(filename.c_str(), "rdc", NULL); if(st == ReplayStatus::Succeeded) { buf = file->GetThumbnail(type, maxsize).data; @@ -591,7 +591,7 @@ struct ReplayCommand : public Command ICaptureFile *file = RENDERDOC_OpenCaptureFile(); - if(file->OpenFile(filename.c_str(), "rdc") != ReplayStatus::Succeeded) + if(file->OpenFile(filename.c_str(), "rdc", NULL) != ReplayStatus::Succeeded) { std::cerr << "Couldn't load '" << filename << "'." << std::endl; return 1; @@ -657,7 +657,9 @@ struct ConvertCommand : public Command { std::cout << "Available formats:" << std::endl; for(CaptureFileFormat f : m_Formats) - std::cout << "'" << (std::string)f.name << "': " << (std::string)f.description << std::endl; + std::cout << "'" << (std::string)f.name << "': " << (std::string)f.name << std::endl + << std::endl + << (std::string)f.description << std::endl; return 0; } @@ -687,11 +689,11 @@ struct ConvertCommand : public Command for(CaptureFileFormat f : m_Formats) { string extension = "."; - extension += f.name; + extension += f.extension; if(infile.find(extension.c_str()) != string::npos) { - infmt = f.name; + infmt = f.extension; break; } } @@ -710,11 +712,11 @@ struct ConvertCommand : public Command for(CaptureFileFormat f : m_Formats) { string extension = "."; - extension += f.name; + extension += f.extension; if(outfile.find(extension.c_str()) != string::npos) { - outfmt = f.name; + outfmt = f.extension; break; } } @@ -729,7 +731,7 @@ struct ConvertCommand : public Command ICaptureFile *file = RENDERDOC_OpenCaptureFile(); - ReplayStatus st = file->OpenFile(infile.c_str(), infmt.c_str()); + ReplayStatus st = file->OpenFile(infile.c_str(), infmt.c_str(), NULL); if(st != ReplayStatus::Succeeded) { @@ -738,7 +740,7 @@ struct ConvertCommand : public Command return 1; } - st = file->Convert(outfile.c_str(), outfmt.c_str(), NULL); + st = file->Convert(outfile.c_str(), outfmt.c_str(), NULL, NULL); if(st != ReplayStatus::Succeeded) { @@ -956,7 +958,7 @@ struct EmbeddedSectionCommand : public Command ICaptureFile *capfile = RENDERDOC_OpenCaptureFile(); - ReplayStatus status = capfile->OpenFile(rdc.c_str(), ""); + ReplayStatus status = capfile->OpenFile(rdc.c_str(), "", NULL); if(status != ReplayStatus::Succeeded) {