From 9388d2b71ba192aab8ab2f871a883101521a4920 Mon Sep 17 00:00:00 2001 From: baldurk Date: Wed, 27 Sep 2017 16:44:15 +0100 Subject: [PATCH] Change core capture code to be more centred around container handle * When opening a capture file, a format is now available to allow easy import from another format without a completely different interface. Only rdc files can be replayed, but any other file can load and access structured data through the same interface. * The replay initialisation and capture writing interfaces also use the RDCFile instead of passing filenames or Serialisers around directly. Driver initialisation parameters are now entirely private, and don't need to be exposed - any agnostic metadata like thumbnail, driver, etc are all accessed via the RDCFile container itself. * Callstack resolution is now part of the container file, not the back-end via way of its Serialiser. * Importers/Exporters to other non-RDC formats are registered in a similar way to replay/remote drivers. * It is also then possible to construct an RDC file from thin air, by creating an empty RDCFile container and filling it with data, then requesting it to be written to disk. --- qrenderdoc/Code/Interface/QRDInterface.h | 7 + qrenderdoc/Code/ReplayManager.cpp | 14 +- qrenderdoc/Code/ReplayManager.h | 9 + qrenderdoc/Windows/APIInspector.cpp | 18 +- qrenderdoc/Windows/MainWindow.cpp | 45 +- renderdoc/api/replay/control_types.h | 20 + renderdoc/api/replay/renderdoc_replay.h | 191 +++++-- renderdoc/core/core.cpp | 381 +++++++------ renderdoc/core/core.h | 89 ++-- renderdoc/core/image_viewer.cpp | 20 +- renderdoc/core/resource_manager.h | 8 +- renderdoc/core/target_control.cpp | 12 +- renderdoc/driver/d3d11/d3d11_replay.cpp | 15 - renderdoc/driver/d3d11/d3d11_replay.h | 4 - renderdoc/os/os_specific.h | 4 +- .../os/posix/android/android_callstack.cpp | 24 +- renderdoc/os/posix/apple/apple_callstack.cpp | 24 +- renderdoc/os/posix/linux/linux_callstack.cpp | 45 +- renderdoc/os/win32/win32_callstack.cpp | 49 +- renderdoc/replay/capture_file.cpp | 502 ++++++++++++++++-- renderdoc/replay/entry_points.cpp | 54 -- renderdoc/replay/replay_controller.cpp | 63 +-- renderdoc/replay/replay_controller.h | 8 +- renderdoc/replay/replay_driver.h | 8 +- renderdoc/serialise/codecs/xml_codec.cpp | 7 + renderdoccmd/renderdoccmd.cpp | 8 +- 26 files changed, 1036 insertions(+), 593 deletions(-) diff --git a/qrenderdoc/Code/Interface/QRDInterface.h b/qrenderdoc/Code/Interface/QRDInterface.h index acc71e7e1..a365e3f9c 100644 --- a/qrenderdoc/Code/Interface/QRDInterface.h +++ b/qrenderdoc/Code/Interface/QRDInterface.h @@ -634,6 +634,13 @@ struct IReplayManager )"); virtual const RemoteHost *CurrentRemote() = 0; + DOCUMENT(R"(Retrieves the capture file handle for the currently open file. + +:return: The file handle active, or ``None`` if no capture is open. +:rtype: StackResolver +)"); + virtual IStackResolver *GetResolver() = 0; + DOCUMENT(R"(Launch an application and inject into it to allow capturing. This happens either locally, or on the remote server, depending on whether a connection is active. diff --git a/qrenderdoc/Code/ReplayManager.cpp b/qrenderdoc/Code/ReplayManager.cpp index 93dd88361..e01218c81 100644 --- a/qrenderdoc/Code/ReplayManager.cpp +++ b/qrenderdoc/Code/ReplayManager.cpp @@ -407,14 +407,12 @@ void ReplayManager::run() } else { - ICaptureFile *file = RENDERDOC_OpenCaptureFile(m_Logfile.toUtf8().data()); + m_CaptureFile = RENDERDOC_OpenCaptureFile(); - m_CreateStatus = file->OpenStatus(); + m_CreateStatus = m_CaptureFile->OpenFile(m_Logfile.toUtf8().data(), "rdc"); if(m_CreateStatus == ReplayStatus::Succeeded) - std::tie(m_CreateStatus, m_Renderer) = file->OpenCapture(m_Progress); - - file->Shutdown(); + std::tie(m_CreateStatus, m_Renderer) = m_CaptureFile->OpenCapture(m_Progress); } if(m_Renderer == NULL) @@ -479,4 +477,10 @@ void ReplayManager::run() m_Remote->CloseCapture(m_Renderer); else m_Renderer->Shutdown(); + + m_Renderer = NULL; + + if(m_CaptureFile) + m_CaptureFile->Shutdown(); + m_CaptureFile = NULL; } diff --git a/qrenderdoc/Code/ReplayManager.h b/qrenderdoc/Code/ReplayManager.h index 3eb681de7..3c0daaee4 100644 --- a/qrenderdoc/Code/ReplayManager.h +++ b/qrenderdoc/Code/ReplayManager.h @@ -75,6 +75,14 @@ public: void ShutdownServer(); void PingRemote(); + IStackResolver *GetResolver() + { + if(m_CaptureFile) + return m_CaptureFile; + if(m_Remote) + return m_Remote; + return NULL; + } const RemoteHost *CurrentRemote() { return m_RemoteHost; } uint32_t ExecuteAndInject(const QString &exe, const QString &workingDir, const QString &cmdLine, const QList &env, const QString &logfile, @@ -108,6 +116,7 @@ private: QQueue m_RenderQueue; QWaitCondition m_RenderCondition; + ICaptureFile *m_CaptureFile = NULL; IReplayController *m_Renderer = NULL; void PushInvoke(InvokeHandle *cmd); diff --git a/qrenderdoc/Windows/APIInspector.cpp b/qrenderdoc/Windows/APIInspector.cpp index c0f365ea4..1855774b9 100644 --- a/qrenderdoc/Windows/APIInspector.cpp +++ b/qrenderdoc/Windows/APIInspector.cpp @@ -101,11 +101,21 @@ void APIInspector::on_apiEvents_itemSelectionChanged() if(!ev.callstack.isEmpty()) { - m_Ctx.Replay().AsyncInvoke([this, ev](IReplayController *r) { - rdcarray trace = r->GetResolve(ev.callstack); + if(m_Ctx.Replay().GetResolver()) + { + m_Ctx.Replay().AsyncInvoke([this, ev](IReplayController *) { + rdcarray stack = m_Ctx.Replay().GetResolver()->GetResolve(ev.callstack); - GUIInvoke::call([this, trace]() { addCallstack(trace); }); - }); + GUIInvoke::call([this, stack]() { addCallstack(stack); }); + }); + } + else + { + ui->callstack->setUpdatesEnabled(false); + ui->callstack->clear(); + ui->callstack->addItem(tr("Callstack resolution not available.")); + ui->callstack->setUpdatesEnabled(true); + } } else { diff --git a/qrenderdoc/Windows/MainWindow.cpp b/qrenderdoc/Windows/MainWindow.cpp index e6f2b1aa4..3d386d697 100644 --- a/qrenderdoc/Windows/MainWindow.cpp +++ b/qrenderdoc/Windows/MainWindow.cpp @@ -402,9 +402,9 @@ void MainWindow::LoadLogfile(const QString &filename, bool temporary, bool local if(local) { - ICaptureFile *file = RENDERDOC_OpenCaptureFile(filename.toUtf8().data()); + ICaptureFile *file = RENDERDOC_OpenCaptureFile(); - if(file->OpenStatus() != ReplayStatus::Succeeded) + if(file->OpenFile(filename.toUtf8().data(), "rdc") != ReplayStatus::Succeeded) { RDDialog::critical(NULL, tr("Error opening capture"), tr("Couldn't open file '%1'").arg(filename)); @@ -1275,18 +1275,20 @@ void MainWindow::OnLogfileLoaded() setLogHasErrors(!m_Ctx.DebugMessages().empty()); - m_Ctx.Replay().AsyncInvoke([this](IReplayController *r) { - bool hasResolver = r->HasCallstacks(); + ui->action_Resolve_Symbols->setEnabled(false); + + m_Ctx.Replay().AsyncInvoke([this](IReplayController *) { + bool hasResolver = m_Ctx.Replay().GetResolver()->HasCallstacks(); GUIInvoke::call([this, hasResolver]() { ui->action_Resolve_Symbols->setEnabled(hasResolver); ui->action_Resolve_Symbols->setText(hasResolver ? tr("Resolve Symbols") : tr("Resolve Symbols - None in log")); + + ui->action_Save_Log->setEnabled(true); }); }); - ui->action_Save_Log->setEnabled(true); - SetTitle(); PopulateRecentFiles(); @@ -1540,15 +1542,34 @@ void MainWindow::on_action_Python_Shell_triggered() void MainWindow::on_action_Resolve_Symbols_triggered() { - m_Ctx.Replay().AsyncInvoke([this](IReplayController *r) { r->InitResolver(); }); + if(!m_Ctx.Replay().GetResolver()) + { + RDDialog::critical( + this, tr("Not Available"), + tr("Callstack resolution is not available.\n\nCheck remote server connection.")); + return; + } - ShowProgressDialog(this, tr("Please Wait - Resolving Symbols"), [this]() { - bool running = true; - m_Ctx.Replay().BlockInvoke( - [&running](IReplayController *r) { running = r->HasCallstacks() && !r->InitResolver(); }); - return !running; + float progress = 0.0f; + bool finished = false; + + m_Ctx.Replay().AsyncInvoke([this, &progress, &finished](IReplayController *) { + bool success = m_Ctx.Replay().GetResolver()->InitResolver(&progress, NULL); + + if(!success) + { + RDDialog::critical( + this, tr("Error loading symbols"), + tr("Couldn't load symbols for callstack resolution.\n\nCheck diagnostic log in " + "Help menu for more details.")); + } + + finished = true; }); + ShowProgressDialog(this, tr("Resolving symbols, please wait..."), + [&finished]() { return finished; }, [&progress]() { return progress; }); + if(m_Ctx.HasAPIInspector()) m_Ctx.GetAPIInspector()->Refresh(); } diff --git a/renderdoc/api/replay/control_types.h b/renderdoc/api/replay/control_types.h index 9b68a2820..67ea1ade3 100644 --- a/renderdoc/api/replay/control_types.h +++ b/renderdoc/api/replay/control_types.h @@ -494,3 +494,23 @@ struct EnvironmentModification }; DECLARE_REFLECTION_STRUCT(EnvironmentModification); + +DOCUMENT("The format for a capture file either supported to read from, or export to"); +struct CaptureFileFormat +{ + DOCUMENT("The name of the format as a single minimal string, e.g. ``rdc``."); + rdcstr name; + + DOCUMENT("A human readable description of the file format, e.g. ``RenderDoc native capture``."); + rdcstr description; + + DOCUMENT(R"(Indicates whether or not files in this format can be opened and processed as +structured data. +)"); + bool openSupported = false; + + DOCUMENT("Indicates whether captures or structured data can be saved out in this format."); + bool convertSupported = true; +}; + +DECLARE_REFLECTION_STRUCT(CaptureFileFormat); diff --git a/renderdoc/api/replay/renderdoc_replay.h b/renderdoc/api/replay/renderdoc_replay.h index 4f935c270..2cffdd68c 100644 --- a/renderdoc/api/replay/renderdoc_replay.h +++ b/renderdoc/api/replay/renderdoc_replay.h @@ -612,25 +612,6 @@ function must be called from another thread. DOCUMENT("Notify the interface that the file it has open has been changed on disk."); virtual void FileChanged() = 0; - DOCUMENT(R"(Query if per-event or per-draw callstacks are available in this capture. - -:return: ``True`` if any callstacks are available, ``False`` otherwise. -:rtype: ``bool`` -)"); - virtual bool HasCallstacks() = 0; - - DOCUMENT(R"(Begin initialising a callstack resolver, looking up symbol files and caching as -necessary. - -This function will eventually return true if either the resolver successfully initialises, or if it -comes to a point where a problem is encountered that the user cannot solve. That means this can be -used to present a progress dialog and repeatedly queried to see when to allow the user to continue. - -:return: ``True`` if any callstacks are available, ``False`` otherwise. -:rtype: ``bool`` -)"); - virtual bool InitResolver() = 0; - DOCUMENT(R"(Move the replay to reflect the state immediately *after* the given :data:`EID `. @@ -825,16 +806,6 @@ understanding as well as the type and unit of the resulting information. )"); virtual rdcarray GetBuffers() = 0; - DOCUMENT(R"(Retrieve the list of buffers alive in the capture. - -Must only be called after :meth:`InitResolver` has returned ``True``. - -:param list callstack: The integer addresses in the original callstack. -:return: The list of resolved callstack entries as strings. -:rtype: ``list`` of ``str`` -)"); - virtual rdcarray GetResolve(const rdcarray &callstack) = 0; - DOCUMENT(R"(Retrieve a list of any newly generated diagnostic messages. Every time this function is called, any debug messages returned will not be returned again. Only @@ -1077,6 +1048,48 @@ protected: ~ITargetControl() = default; }; +DOCUMENT(R"(An interface for resolving callstacks. This is separate since it can be either +implemented locally by a file handle, or remotely to an open capture. +)"); +struct IStackResolver +{ + DOCUMENT(R"(Query if callstacks are available. + +:return: ``True`` if any callstacks are available, ``False`` otherwise. +:rtype: ``bool`` +)"); + virtual bool HasCallstacks() = 0; + + DOCUMENT(R"(Begin initialising a callstack resolver, looking up symbol files and caching as +necessary. + +This function blocks while trying to initialise callstack resolving, so it should be called on a +separate thread. + +:param float progress: A reference to a ``float`` value that will be updated as the init happens + from ``0.0`` to ``1.0``. The parameter can be ``None`` if no progress update is desired. +:param bool killSignal: A reference to a ``bool`` that can be set to ``True`` to stop the lookup + process. +:return: ``True`` if the resolver successfully initialised, ``False`` if something went wrong. +:rtype: ``bool`` +)"); + virtual bool InitResolver(float *progress, volatile bool *killSignal) = 0; + + DOCUMENT(R"(Retrieve the details of each stackframe in the provided callstack. + +Must only be called after :meth:`InitResolver` has returned ``True``. + +:param list callstack: The integer addresses in the original callstack. +:return: The list of resolved callstack entries as strings. +:rtype: ``list`` of ``str`` +)"); + virtual rdcarray GetResolve(const rdcarray &callstack) = 0; + +protected: + IStackResolver() = default; + ~IStackResolver() = default; +}; + DOCUMENT(R"(A connection to a running remote RenderDoc server on another machine. This allows the transfer of captures to and from the local machine, as well as remotely replaying a capture with a local proxy renderer, so that captures that are not supported locally can still be debugged with as @@ -1086,7 +1099,7 @@ much work as possible happening on the local machine. No preference for a particular value, see :meth:`DebugPixel`. )"); -struct IRemoteServer +struct IRemoteServer : public IStackResolver { DOCUMENT("Closes the connection without affecting the running server."); virtual void ShutdownConnection() = 0; @@ -1237,32 +1250,64 @@ protected: DOCUMENT(R"(A handle to a capture file. Used for simple cheap processing and meta-data fetching without opening the capture for analysis. )") -struct ICaptureFile +struct ICaptureFile : public IStackResolver { DOCUMENT("Closes the file handle."); virtual void Shutdown() = 0; - DOCUMENT(R"(Retrieves the status of the handle. + DOCUMENT(R"(Initialises the capture handle from a file. -This returns an error if the capture file used to create the handle wasn't found, or was corrupted, -or something else went wrong while processing it. +This method supports converting from non-native representations via structured data, by specifying +the input format in the :param:`filetype` parameter. The list of supported formats can be retrieved +by calling :meth:`GetCaptureFileFormats`. -:return: The status of the handle to the file. +``rdc`` is guaranteed to always be a supported filetype, and will be assumed if the filetype is +empty or unrecognised. + +:param str filename: The filename of the file to open. +:param str filetype: The format of the given file. +:return: The status of the open operation, whether it succeeded or failed (and how it failed). :rtype: ReplayStatus )"); - virtual ReplayStatus OpenStatus() = 0; + virtual ReplayStatus OpenFile(const char *filename, const char *filetype) = 0; - DOCUMENT(R"(Retrieves the filename used to open this handle. + DOCUMENT(R"(Initialises the file handle from a raw memory buffer. -This filename is exactly as specified without any modificaton to make it an absolute path. +This may be useful if you don't want to parse the whole file or already have the file in memory. -:return: The filename used to create this handle. -:rtype: ``str`` +For the :param:`filetype` parameter, see :meth:`OpenFile`. + +:param bytes buffer: The buffer containing the data to process. +:param str filetype: The format of the given file. +:return: The status of the open operation, whether it succeeded or failed (and how it failed). +:rtype: ReplayStatus )"); - virtual const char *Filename() = 0; + virtual ReplayStatus OpenBuffer(const bytebuf &buffer, const char *filetype) = 0; + + DOCUMENT(R"(Converts the currently loaded file to a given format and saves it to disk. + +This allows converting a native RDC to another representation, or vice-versa converting another +representation back to native RDC. + +:param str filename: The filename to save to. +:param str filetype: The format to convert to. +: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; + + DOCUMENT(R"(Returns the list of capture file formats. + +:return: The list of capture file formats available. +:rtype: ``list`` of :class:`CaptureFileFormat` +)"); + virtual rdcarray GetCaptureFileFormats() = 0; DOCUMENT(R"(Queries for how well a particular capture is supported on the local machine. +If the file was opened with a format other than native ``rdc`` this will always return no +replay support. + :return: How much support for replay exists locally. :rtype: ReplaySupport )"); @@ -1282,7 +1327,29 @@ This filename is exactly as specified without any modificaton to make it an abso )"); virtual const char *RecordedMachineIdent() = 0; - DOCUMENT(R"(Opens a capture for replay locally and returns a handle to the capture. + DOCUMENT(R"(Sets the matadata for this capture handle. + +This function may only be called if the handle is 'empty' - i.e. no file has been opened with +:meth:`OpenFile` or :meth:`OpenBuffer`. + +.. note:: The only supported values for :param:`thumbType` are :data:`FileType.JPG`, + :data:`FileType.PNG`, :data:`FileType.TGA`, and :data:`FileType.BMP`. + +:param str driverName: The name of the driver. Must be a recognised driver name (even if replay + support for that driver is not compiled in locally. +:param int machineIdent: The encoded machine identity value. Optional value and can be left to 0, as + the bits to set are internally defined, so only generally useful if copying a machine ident from + an existing capture. +:param FileType thumbType: The file type of the thumbnail. Ignored if :param:`thumbData` is empty. +:param int thumbWidth: The width of the thumbnail. Ignored if :param:`thumbData` is empty. +:param int thumbHeight: The height of the thumbnail. Ignored if :param:`thumbData` is empty. +:param bytes thumbData: The raw data of the thumbnail. If empty, no thumbnail is set. +)"); + virtual void SetMetadata(const char *driverName, uint64_t machineIdent, FileType thumbType, + uint32_t thumbWidth, uint32_t thumbHeight, const bytebuf &thumbData) = 0; + + DOCUMENT(R"(Opens a capture for replay locally and returns a handle to the capture. Only supported +for handles opened with a native ``rdc`` capture, otherwise this will fail. This function will block until the capture is fully loaded and ready. @@ -1297,8 +1364,33 @@ by the :class:`ReplayController`. )"); virtual rdcpair OpenCapture(float *progress) = 0; + DOCUMENT(R"(Returns the structured data for this capture. + +The lifetime of this data is scoped to the lifetime of the capture handle, so it cannot be used +after the handle is destroyed. + +:return: The structured data representing the file. +:rtype: SDFile +)"); + virtual const SDFile &GetStructuredData() = 0; + + DOCUMENT(R"(Sets the structured data for this capture. + +This allows calling code to populate a capture out of generated structured data. In combination with +:meth:`SetMetadata` this allows a purely in-memory creation of a file to be saved out with +:meth:`Convert`. + +The data is copied internally so it can be destroyed after calling this function. + +:param SDFile file: The structured data representing the file. +)"); + virtual void SetStructuredData(const SDFile &file) = 0; + DOCUMENT(R"(Retrieves the embedded thumbnail from the capture. +.. note:: The only supported values for :param:`type` are :data:`FileType.JPG`, + :data:`FileType.PNG`, :data:`FileType.TGA`, and :data:`FileType.BMP`. + :param FileType type: The image format to convert the thumbnail to. :param int maxsize: The largest width or height allowed. If the thumbnail is larger, it's resized. :return: The raw contents of the thumbnail, converted to the desired type at the desired max @@ -1454,21 +1546,18 @@ extern "C" RENDERDOC_API uint32_t RENDERDOC_CC RENDERDOC_VertexOffset(Topology t uint32_t primitive); ////////////////////////////////////////////////////////////////////////// -// Create a capture handle. -// -// Takes the filename of the log. Always returns a valid handle that must be shut down. -// If any errors happen this can be queried with :meth:`CaptureFile.Status`. +// Create a capture file handle. ////////////////////////////////////////////////////////////////////////// -DOCUMENT(R"(Create a capture handle. +DOCUMENT(R"(Create a handle for a capture file. -Takes the filename of the log. This function *always* returns a valid handle that must be shut down. -If any errors happen this can be queried with :meth:`CaptureFile.Status`. +This function returns a new handle to a capture file. Once initialised by opening a file the handle +can only be shut-down, it is not re-usable. :return: A handle to the specified path. -:rtype: ReplaySupport +:rtype: ICaptureFile )"); -extern "C" RENDERDOC_API ICaptureFile *RENDERDOC_CC RENDERDOC_OpenCaptureFile(const char *logfile); +extern "C" RENDERDOC_API ICaptureFile *RENDERDOC_CC RENDERDOC_OpenCaptureFile(); ////////////////////////////////////////////////////////////////////////// // Target Control diff --git a/renderdoc/core/core.cpp b/renderdoc/core/core.cpp index 5d8e0f815..b962ff0c2 100644 --- a/renderdoc/core/core.cpp +++ b/renderdoc/core/core.cpp @@ -28,11 +28,10 @@ #include #include "api/replay/version.h" #include "common/common.h" -#include "common/dds_readwrite.h" #include "hooks/hooks.h" #include "replay/replay_driver.h" +#include "serialise/rdcfile.h" #include "serialise/serialiser.h" -#include "stb/stb_image.h" #include "strings/string_utils.h" #include "crash_handler.h" @@ -40,27 +39,25 @@ #include "replay/renderdoc_serialise.inl" +// this one is done by hand as we format it template <> std::string DoStringise(const ResourceId &el) { - char tostrBuf[256] = {0}; - RDCCOMPILE_ASSERT(sizeof(el) == sizeof(uint64_t), "ResourceId is no longer 1:1 with uint64_t"); - StringFormat::snprintf(tostrBuf, 255, "ResID_%llu", (uint64_t &)el); - - return tostrBuf; + return StringFormat::Fmt("ResourceId(%llu)", el); } +BASIC_TYPE_SERIALISE_STRINGIFY(ResourceId, (uint64_t &)el, SDBasic::UnsignedInteger, 8); + +INSTANTIATE_SERIALISE_TYPE(ResourceId); + #if ENABLED(RDOC_LINUX) && ENABLED(RDOC_XLIB) #include #endif // from image_viewer.cpp -ReplayStatus IMG_CreateReplayDevice(const char *logfile, IReplayDriver **driver); - -// not provided by tinyexr, just do by hand -bool is_exr_file(FILE *f); +ReplayStatus IMG_CreateReplayDevice(RDCFile *rdc, IReplayDriver **driver); template <> std::string DoStringise(const RDCDriver &el) @@ -82,6 +79,18 @@ std::string DoStringise(const RDCDriver &el) END_ENUM_STRINGISE(); } +template <> +std::string DoStringise(const ReplayLogType &el) +{ + BEGIN_ENUM_STRINGISE(ReplayLogType); + { + STRINGISE_ENUM_CLASS_NAMED(eReplay_Full, "Full replay including draw"); + STRINGISE_ENUM_CLASS_NAMED(eReplay_WithoutDraw, "Replay without draw"); + STRINGISE_ENUM_CLASS_NAMED(eReplay_OnlyDraw, "Replay only draw"); + } + END_ENUM_STRINGISE(); +} + template <> std::string DoStringise(const WindowingSystem &el) { @@ -144,6 +153,18 @@ std::string DoStringise(const RENDERDOC_InputButton &el) END_ENUM_STRINGISE(); } +template <> +std::string DoStringise(const SystemChunk &el) +{ + BEGIN_ENUM_STRINGISE(SystemChunk); + { + STRINGISE_ENUM_CLASS_NAMED(DriverInit, "Driver Initialisation Parameters"); + STRINGISE_ENUM_CLASS_NAMED(InitialContentsList, "List of Initial Contents Resources"); + STRINGISE_ENUM_CLASS_NAMED(InitialContents, "Initial Contents"); + } + END_ENUM_STRINGISE(); +} + RenderDoc *RenderDoc::m_Inst = NULL; RenderDoc &RenderDoc::Inst() @@ -675,16 +696,10 @@ bool RenderDoc::ShouldTriggerCapture(uint32_t frameNumber) return ret; } -Serialiser *RenderDoc::OpenWriteSerialiser(uint32_t frameNum, RDCInitParams *params, void *thpixels, - size_t thlen, uint32_t thwidth, uint32_t thheight) +RDCFile *RenderDoc::CreateRDC(uint32_t frameNum, void *thpixels, size_t thlen, uint16_t thwidth, + uint16_t thheight) { - RDCASSERT(m_CurrentDriver != RDC_Unknown); - -#if ENABLED(RDOC_RELEASE) - const bool debugSerialiser = false; -#else - const bool debugSerialiser = true; -#endif + RDCFile *ret = new RDCFile; m_CurrentLogFile = StringFormat::Fmt("%s_frame%u.rdc", m_LogFile.c_str(), frameNum); @@ -701,143 +716,29 @@ Serialiser *RenderDoc::OpenWriteSerialiser(uint32_t frameNum, RDCInitParams *par } } - Serialiser *fileSerialiser = - new Serialiser(m_CurrentLogFile.c_str(), Serialiser::WRITING, debugSerialiser); - - Serialiser *chunkSerialiser = new Serialiser(NULL, Serialiser::WRITING, debugSerialiser); + RDCThumb th; + RDCThumb *thumb = NULL; + if(thpixels) { - ScopedContext scope(chunkSerialiser, "Thumbnail", THUMBNAIL_DATA, false); - - bool HasThumbnail = (thpixels != NULL && thwidth > 0 && thheight > 0); - chunkSerialiser->Serialise("HasThumbnail", HasThumbnail); - - if(HasThumbnail) - { - byte *buf = (byte *)thpixels; - chunkSerialiser->Serialise("ThumbWidth", thwidth); - chunkSerialiser->Serialise("ThumbHeight", thheight); - chunkSerialiser->SerialiseBuffer("ThumbnailPixels", buf, thlen); - } - - fileSerialiser->Insert(scope.Get(true)); + th.len = (uint32_t)thlen; + th.pixels = (const byte *)thpixels; + th.width = thwidth; + th.height = thheight; + thumb = &th; } + ret->SetData(m_CurrentDriver, m_CurrentDriverName.c_str(), OSUtility::GetMachineIdent(), thumb); + + ret->Create(m_CurrentLogFile.c_str()); + + if(ret->ErrorCode() != ContainerError::NoError) { - ScopedContext scope(chunkSerialiser, "Capture Create Parameters", CREATE_PARAMS, false); - - chunkSerialiser->Serialise("DriverType", m_CurrentDriver); - chunkSerialiser->SerialiseString("DriverName", m_CurrentDriverName); - - { - ScopedContext driverparams(chunkSerialiser, "Driver Specific", DRIVER_INIT_PARAMS, false); - - params->m_pSerialiser = chunkSerialiser; - params->m_State = WRITING; - params->Serialise(); - } - - fileSerialiser->Insert(scope.Get(true)); + RDCERR("Error creating RDC at '%s'", m_CurrentLogFile.c_str()); + SAFE_DELETE(ret); } - SAFE_DELETE(chunkSerialiser); - - return fileSerialiser; -} - -ReplayStatus RenderDoc::FillInitParams(const char *logFile, RDCDriver &driverType, string &driverName, - uint64_t &fileMachineIdent, RDCInitParams *params) -{ - Serialiser ser(logFile, Serialiser::READING, true); - - if(ser.HasError()) - { - FILE *f = FileIO::fopen(logFile, "rb"); - if(f) - { - int x = 0, y = 0, comp = 0; - int ret = stbi_info_from_file(f, &x, &y, &comp); - - FileIO::fseek64(f, 0, SEEK_SET); - - if(is_dds_file(f)) - ret = x = y = comp = 1; - - if(is_exr_file(f)) - ret = x = y = comp = 1; - - FileIO::fclose(f); - - if(ret == 1 && x > 0 && y > 0 && comp > 0) - { - driverType = RDC_Image; - driverName = "Image"; - fileMachineIdent = 0; - return ReplayStatus::Succeeded; - } - } - - RDCERR("Couldn't open '%s'", logFile); - - switch(ser.ErrorCode()) - { - case Serialiser::eSerError_FileIO: return ReplayStatus::FileIOFailed; - case Serialiser::eSerError_Corrupt: return ReplayStatus::FileCorrupted; - case Serialiser::eSerError_UnsupportedVersion: return ReplayStatus::FileIncompatibleVersion; - default: break; - } - - return ReplayStatus::InternalError; - } - - ser.Rewind(); - - fileMachineIdent = ser.GetSavedMachineIdent(); - - { - int chunkType = ser.PushContext(NULL, NULL, 1, false); - - if(chunkType != THUMBNAIL_DATA) - { - RDCERR("Malformed logfile '%s', first chunk isn't thumbnail data", logFile); - return ReplayStatus::FileCorrupted; - } - - ser.SkipCurrentChunk(); - - ser.PopContext(1); - } - - { - int chunkType = ser.PushContext(NULL, NULL, 1, false); - - if(chunkType != CREATE_PARAMS) - { - RDCERR("Malformed logfile '%s', second chunk isn't create params", logFile); - return ReplayStatus::FileCorrupted; - } - - ser.Serialise("DriverType", driverType); - ser.SerialiseString("DriverName", driverName); - - chunkType = ser.PushContext(NULL, NULL, 1, false); - - if(chunkType != DRIVER_INIT_PARAMS) - { - RDCERR("Malformed logfile '%s', chunk doesn't contain driver init params", logFile); - return ReplayStatus::FileCorrupted; - } - - if(params) - { - params->m_State = READING; - params->m_pSerialiser = &ser; - return params->Serialise(); - } - } - - // we can just throw away the serialiser, don't need to care about closing/popping contexts - return ReplayStatus::Succeeded; + return ret; } bool RenderDoc::HasReplayDriver(RDCDriver driver) const @@ -883,42 +784,146 @@ void RenderDoc::RegisterRemoteProvider(RDCDriver driver, const char *name, m_RemoteDriverProviders[driver] = provider; } -ReplayStatus RenderDoc::CreateReplayDriver(RDCDriver driverType, const char *logfile, - IReplayDriver **driver) +void RenderDoc::RegisterCaptureExporter(const char *filetype, const char *description, + CaptureExporter exporter) { - if(driver == NULL) - return ReplayStatus::InternalError; + RDCASSERT(m_ImportExportFormats.find(filetype) == m_ImportExportFormats.end()); - // allows passing RDC_Unknown as 'I don't care, give me a proxy driver of any type' - // only valid if logfile is NULL and it will be used as a proxy, not to process a log - if(driverType == RDC_Unknown && logfile == NULL && !m_ReplayDriverProviders.empty()) - return m_ReplayDriverProviders.begin()->second(logfile, driver); + m_ImportExportFormats[filetype] = description; - // image support is special, handle it here - if(driverType == RDC_Image && logfile != NULL) - return IMG_CreateReplayDevice(logfile, driver); + m_Exporters[filetype] = exporter; +} - if(m_ReplayDriverProviders.find(driverType) != m_ReplayDriverProviders.end()) - return m_ReplayDriverProviders[driverType](logfile, driver); +void RenderDoc::RegisterCaptureImportExporter(const char *filetype, const char *description, + CaptureImporter importer, CaptureExporter exporter) +{ + RDCASSERT(m_ImportExportFormats.find(filetype) == m_ImportExportFormats.end()); - RDCERR("Unsupported replay driver requested: %d", driverType); + m_ImportExportFormats[filetype] = description; + + m_Importers[filetype] = importer; + m_Exporters[filetype] = exporter; +} + +CaptureExporter RenderDoc::GetCaptureExporter(const char *filetype) +{ + auto it = m_Exporters.find(filetype); + + if(it == m_Exporters.end()) + return NULL; + + return it->second; +} + +CaptureImporter RenderDoc::GetCaptureImporter(const char *filetype) +{ + auto it = m_Importers.find(filetype); + + if(it == m_Importers.end()) + return NULL; + + return it->second; +} + +std::vector RenderDoc::GetCaptureFileFormats() +{ + std::vector ret; + + CaptureFileFormat rdc; + rdc.name = "rdc"; + rdc.description = "Native RDC capture file format."; + 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; + + rdc.openSupported = m_Importers.find(it->first) != m_Importers.end(); + rdc.convertSupported = m_Exporters.find(it->first) != m_Exporters.end(); + + RDCASSERT(rdc.openSupported || rdc.convertSupported); + + ret.push_back(fmt); + } + + return ret; +} + +bool RenderDoc::HasReplaySupport(RDCDriver driverType) +{ + if(driverType == RDC_Image) + return true; + + if(driverType == RDC_Unknown && !m_ReplayDriverProviders.empty()) + return true; + + return m_ReplayDriverProviders.find(driverType) != m_ReplayDriverProviders.end(); +} + +ReplayStatus RenderDoc::CreateProxyReplayDriver(RDCDriver proxyDriver, IReplayDriver **driver) +{ + // passing RDC_Unknown means 'I don't care, give me a proxy driver of any type' + if(proxyDriver == RDC_Unknown) + { + if(!m_ReplayDriverProviders.empty()) + return m_ReplayDriverProviders.begin()->second(NULL, driver); + } + + if(m_ReplayDriverProviders.find(proxyDriver) != m_ReplayDriverProviders.end()) + return m_ReplayDriverProviders[proxyDriver](NULL, driver); + + RDCERR("Unsupported replay driver requested: %s", ToStr(proxyDriver).c_str()); return ReplayStatus::APIUnsupported; } -ReplayStatus RenderDoc::CreateRemoteDriver(RDCDriver driverType, const char *logfile, - IRemoteDriver **driver) +ReplayStatus RenderDoc::CreateReplayDriver(RDCFile *rdc, IReplayDriver **driver) { if(driver == NULL) return ReplayStatus::InternalError; + // allows passing NULL rdcfile as 'I don't care, give me a proxy driver of any type' + if(rdc == NULL) + { + if(!m_ReplayDriverProviders.empty()) + return m_ReplayDriverProviders.begin()->second(NULL, driver); + + RDCERR("Request for proxy replay device, but no replay providers are available."); + return ReplayStatus::InternalError; + } + + RDCDriver driverType = rdc->GetDriver(); + + // image support is special, handle it here + if(driverType == RDC_Image) + return IMG_CreateReplayDevice(rdc, driver); + + if(m_ReplayDriverProviders.find(driverType) != m_ReplayDriverProviders.end()) + return m_ReplayDriverProviders[driverType](rdc, driver); + + RDCERR("Unsupported replay driver requested: %s", ToStr(driverType).c_str()); + return ReplayStatus::APIUnsupported; +} + +ReplayStatus RenderDoc::CreateRemoteDriver(RDCFile *rdc, IRemoteDriver **driver) +{ + if(rdc == NULL || driver == NULL) + return ReplayStatus::InternalError; + + RDCDriver driverType = rdc->GetDriver(); + if(m_RemoteDriverProviders.find(driverType) != m_RemoteDriverProviders.end()) - return m_RemoteDriverProviders[driverType](logfile, driver); + return m_RemoteDriverProviders[driverType](rdc, driver); // replay drivers are remote drivers, fall back and try them if(m_ReplayDriverProviders.find(driverType) != m_ReplayDriverProviders.end()) { IReplayDriver *dr = NULL; - auto status = m_ReplayDriverProviders[driverType](logfile, &dr); + ReplayStatus status = m_ReplayDriverProviders[driverType](rdc, &dr); if(status == ReplayStatus::Succeeded) *driver = (IRemoteDriver *)dr; @@ -928,7 +933,7 @@ ReplayStatus RenderDoc::CreateRemoteDriver(RDCDriver driverType, const char *log return status; } - RDCERR("Unsupported replay driver requested: %d", driverType); + RDCERR("Unsupported replay driver requested: %s", ToStr(driverType).c_str()); return ReplayStatus::APIUnsupported; } @@ -1013,14 +1018,40 @@ void RenderDoc::SetProgress(LoadProgressSection section, float delta) *m_ProgressPtr = progress; } -void RenderDoc::SuccessfullyWrittenLog(uint32_t frameNumber) +void RenderDoc::FinishCaptureWriting(RDCFile *rdc, uint32_t frameNumber) { - RDCLOG("Written to disk: %s", m_CurrentLogFile.c_str()); - - CaptureData cap(m_CurrentLogFile, Timing::GetUnixTimestamp(), frameNumber); + if(rdc) { - SCOPED_LOCK(m_CaptureLock); - m_Captures.push_back(cap); + // add the resolve database if we were capturing callstacks. + if(m_Options.CaptureCallstacks) + { + SectionProperties props = {}; + props.type = SectionType::ResolveDatabase; + props.version = 1; + StreamWriter *w = rdc->WriteSection(props); + + size_t sz = 0; + Callstack::GetLoadedModules(NULL, sz); + + byte *buf = new byte[sz]; + Callstack::GetLoadedModules(buf, sz); + + w->Write(buf, sz); + + w->Finish(); + + delete w; + } + + delete rdc; + + RDCLOG("Written to disk: %s", m_CurrentLogFile.c_str()); + + CaptureData cap(m_CurrentLogFile, Timing::GetUnixTimestamp(), frameNumber); + { + SCOPED_LOCK(m_CaptureLock); + m_Captures.push_back(cap); + } } } diff --git a/renderdoc/core/core.h b/renderdoc/core/core.h index 4fc101998..b7fd7fde7 100644 --- a/renderdoc/core/core.h +++ b/renderdoc/core/core.h @@ -44,7 +44,6 @@ using std::map; using std::pair; using std::set; -class Serialiser; class Chunk; // not provided by tinyexr, just do by hand @@ -85,18 +84,18 @@ enum LogState WRITING_CAPFRAME, }; -enum SystemChunks +enum class SystemChunk : uint32_t { // 0 is reserved as a 'null' chunk that is only for debug - CREATE_PARAMS = 1, - THUMBNAIL_DATA, - DRIVER_INIT_PARAMS, - INITIAL_CONTENTS_LIST, - INITIAL_CONTENTS, + DriverInit = 1, + InitialContentsList, + InitialContents, - FIRST_CHUNK_ID, + FirstDriverChunk = 1000, }; +DECLARE_REFLECTION_ENUM(SystemChunk); + enum RDCDriver { RDC_Unknown = 0, @@ -110,6 +109,7 @@ enum RDCDriver RDC_Vulkan = 8, RDC_OpenGLES = 9, RDC_D3D8 = 10, + RDC_MaxBuiltin, RDC_Custom = 100000, RDC_Custom0 = RDC_Custom, RDC_Custom1, @@ -141,21 +141,7 @@ enum ReplayLogType eReplay_OnlyDraw, }; -struct RDCInitParams -{ - RDCInitParams() - { - m_State = WRITING; - m_pSerialiser = NULL; - } - virtual ~RDCInitParams() {} - virtual ReplayStatus Serialise() = 0; - - LogState m_State; - Serialiser *m_pSerialiser; - - Serialiser *GetSerialiser() { return m_pSerialiser; } -}; +DECLARE_REFLECTION_ENUM(ReplayLogType); struct CaptureData { @@ -180,8 +166,18 @@ enum LoadProgressSection class IRemoteDriver; class IReplayDriver; -typedef ReplayStatus (*RemoteDriverProvider)(const char *logfile, IRemoteDriver **driver); -typedef ReplayStatus (*ReplayDriverProvider)(const char *logfile, IReplayDriver **driver); +class StreamReader; +class RDCFile; + +class RDCFile; + +typedef ReplayStatus (*RemoteDriverProvider)(RDCFile *rdc, IRemoteDriver **driver); +typedef ReplayStatus (*ReplayDriverProvider)(RDCFile *rdc, IReplayDriver **driver); + +typedef ReplayStatus (*CaptureImporter)(const char *filename, StreamReader &reader, RDCFile *rdc, + SDFile &structData); +typedef ReplayStatus (*CaptureExporter)(const char *filename, const RDCFile &rdc, + const SDFile &structData); typedef bool (*VulkanLayerCheck)(VulkanLayerFlags &flags, std::vector &myJSONs, std::vector &otherJSONs); @@ -224,9 +220,9 @@ public: void RecreateCrashHandler(); void UnloadCrashHandler(); ICrashHandler *GetCrashHandler() const { return m_ExHandler; } - Serialiser *OpenWriteSerialiser(uint32_t frameNum, RDCInitParams *params, void *thpixels, - size_t thlen, uint32_t thwidth, uint32_t thheight); - void SuccessfullyWrittenLog(uint32_t frameNumber); + RDCFile *CreateRDC(uint32_t frameNum, void *thpixels, size_t thlen, uint16_t thwidth, + uint16_t thheight); + void FinishCaptureWriting(RDCFile *rdc, uint32_t frameNumber); void AddChildProcess(uint32_t pid, uint32_t ident) { @@ -254,12 +250,19 @@ public: } } - ReplayStatus FillInitParams(const char *logfile, RDCDriver &driverType, string &driverName, - uint64_t &fileMachineIdent, RDCInitParams *params); - void RegisterReplayProvider(RDCDriver driver, const char *name, ReplayDriverProvider provider); void RegisterRemoteProvider(RDCDriver driver, const char *name, RemoteDriverProvider provider); + void RegisterCaptureExporter(const char *filetype, const char *description, + CaptureExporter exporter); + void RegisterCaptureImportExporter(const char *filetype, const char *description, + CaptureImporter importer, CaptureExporter exporter); + + CaptureExporter GetCaptureExporter(const char *filetype); + CaptureImporter GetCaptureImporter(const char *filetype); + + std::vector GetCaptureFileFormats(); + void SetVulkanLayerCheck(VulkanLayerCheck callback) { m_VulkanCheck = callback; } void SetVulkanLayerInstall(VulkanLayerInstall callback) { m_VulkanInstall = callback; } bool NeedVulkanLayerRegistration(VulkanLayerFlags &flags, std::vector &myJSONs, @@ -283,8 +286,11 @@ public: void SetDarkCheckerboardColor(const Vec4f &col) { m_DarkChecker = col; } bool IsDarkTheme() { return m_DarkTheme; } void SetDarkTheme(bool dark) { m_DarkTheme = dark; } - ReplayStatus CreateReplayDriver(RDCDriver driverType, const char *logfile, IReplayDriver **driver); - ReplayStatus CreateRemoteDriver(RDCDriver driverType, const char *logfile, IRemoteDriver **driver); + ReplayStatus CreateProxyReplayDriver(RDCDriver proxyDriver, IReplayDriver **driver); + ReplayStatus CreateReplayDriver(RDCFile *rdc, IReplayDriver **driver); + ReplayStatus CreateRemoteDriver(RDCFile *rdc, IRemoteDriver **driver); + + bool HasReplaySupport(RDCDriver driverType); map GetReplayDrivers(); map GetRemoteDrivers(); @@ -398,6 +404,10 @@ private: map m_ReplayDriverProviders; map m_RemoteDriverProviders; + std::map m_ImportExportFormats; + std::map m_Importers; + std::map m_Exporters; + VulkanLayerCheck m_VulkanCheck; VulkanLayerInstall m_VulkanInstall; @@ -473,3 +483,16 @@ struct DriverRegistration RenderDoc::Inst().RegisterRemoteProvider(driver, name, provider); } }; + +struct ConversionRegistration +{ + ConversionRegistration(const char *filetype, const char *description, CaptureImporter importer, + CaptureExporter exporter) + { + RenderDoc::Inst().RegisterCaptureImportExporter(filetype, description, importer, exporter); + } + ConversionRegistration(const char *filetype, const char *description, CaptureExporter exporter) + { + RenderDoc::Inst().RegisterCaptureExporter(filetype, description, exporter); + } +}; \ No newline at end of file diff --git a/renderdoc/core/image_viewer.cpp b/renderdoc/core/image_viewer.cpp index c128ec351..d39412c96 100644 --- a/renderdoc/core/image_viewer.cpp +++ b/renderdoc/core/image_viewer.cpp @@ -25,6 +25,7 @@ #include "common/dds_readwrite.h" #include "core/core.h" #include "replay/replay_driver.h" +#include "serialise/rdcfile.h" #include "stb/stb_image.h" #include "tinyexr/tinyexr.h" @@ -147,7 +148,7 @@ public: FrameRecord GetFrameRecord() { return m_FrameRecord; } const D3D11Pipe::State &GetD3D11PipelineState() { return m_PipelineState; } // other operations are dropped/ignored, to avoid confusion - void ReadLogInitialisation() {} + void ReadLogInitialisation(RDCFile *rdc) {} void RenderMesh(uint32_t eventID, const vector &secondaryDraws, const MeshDisplay &cfg) { } @@ -202,9 +203,6 @@ public: { return ""; } - bool HasCallstacks() { return false; } - void InitCallstackResolver() {} - Callstack::StackResolver *GetCallstackResolver() { return NULL; } void FreeTargetResource(ResourceId id) {} vector PixelHistory(vector events, ResourceId target, uint32_t x, uint32_t y, uint32_t slice, uint32_t mip, @@ -280,9 +278,13 @@ private: TextureDescription m_TexDetails; }; -ReplayStatus IMG_CreateReplayDevice(const char *logfile, IReplayDriver **driver) +ReplayStatus IMG_CreateReplayDevice(RDCFile *rdc, IReplayDriver **driver) { - FILE *f = FileIO::fopen(logfile, "rb"); + if(!rdc) + return ReplayStatus::InternalError; + + std::string filename; + FILE *f = rdc->StealImageFileHandle(filename); if(!f) return ReplayStatus::FileIOFailed; @@ -382,16 +384,18 @@ ReplayStatus IMG_CreateReplayDevice(const char *logfile, IReplayDriver **driver) FileIO::fclose(f); IReplayDriver *proxy = NULL; - auto status = RenderDoc::Inst().CreateReplayDriver(RDC_Unknown, NULL, &proxy); + ReplayStatus status = RenderDoc::Inst().CreateProxyReplayDriver(RDC_Unknown, &proxy); if(status != ReplayStatus::Succeeded || !proxy) { + RDCERR("Couldn't create replay driver to proxy-render images"); + if(proxy) proxy->Shutdown(); return status; } - *driver = new ImageViewer(proxy, logfile); + *driver = new ImageViewer(proxy, filename.c_str()); return ReplayStatus::Succeeded; } diff --git a/renderdoc/core/resource_manager.h b/renderdoc/core/resource_manager.h index defe8daee..ce37dbf13 100644 --- a/renderdoc/core/resource_manager.h +++ b/renderdoc/core/resource_manager.h @@ -704,7 +704,7 @@ void ResourceManagerType::SetInitialChunk(ResourceId id, Chunk *chunk) auto it = m_InitialChunks.find(id); - RDCASSERT(chunk->GetChunkType() == INITIAL_CONTENTS); + RDCASSERT(chunk->GetChunkType() == SystemChunk::InitialContents); if(it != m_InitialChunks.end()) { @@ -787,7 +787,7 @@ void ResourceManagerType::Serialise_InitialContentsNeeded(WriteSerialiser &ser) uint32_t chunkSize = uint32_t(WrittenRecords.size() * sizeof(WrittenRecord) + 16); - SCOPED_SERIALISE_CHUNK(INITIAL_CONTENTS_LIST, chunkSize); + SCOPED_SERIALISE_CHUNK(SystemChunk::InitialContentsList, chunkSize); SERIALISE_ELEMENT(WrittenRecords); } @@ -1039,7 +1039,7 @@ void ResourceManagerType::InsertInitialContentsChunks(WriteSerialiser &ser) { uint32_t size = GetSize_InitialState(id, res); - SCOPED_SERIALISE_CHUNK(INITIAL_CONTENTS, size); + SCOPED_SERIALISE_CHUNK(SystemChunk::InitialContents, size); Serialise_InitialState(ser, id, res); } @@ -1068,7 +1068,7 @@ void ResourceManagerType::InsertInitialContentsChunks(WriteSerialiser &ser) { uint32_t size = GetSize_InitialState(it->first, it->second); - SCOPED_SERIALISE_CHUNK(INITIAL_CONTENTS, size); + SCOPED_SERIALISE_CHUNK(SystemChunk::InitialContents, size); Serialise_InitialState(ser, it->first, it->second); } diff --git a/renderdoc/core/target_control.cpp b/renderdoc/core/target_control.cpp index c83e6d097..25fe81872 100644 --- a/renderdoc/core/target_control.cpp +++ b/renderdoc/core/target_control.cpp @@ -149,8 +149,8 @@ void RenderDoc::TargetControlClientThread(Network::Socket *client) bytebuf buf; - ICaptureFile *file = RENDERDOC_OpenCaptureFile(captures.back().path.c_str()); - if(file->OpenStatus() == ReplayStatus::Succeeded) + ICaptureFile *file = RENDERDOC_OpenCaptureFile(); + if(file->OpenFile(captures.back().path.c_str(), "rdc") == ReplayStatus::Succeeded) { buf = file->GetThumbnail(FileType::JPG, 0); } @@ -196,7 +196,7 @@ void RenderDoc::TargetControlClientThread(Network::Socket *client) if(client->IsRecvDataWaiting()) { - PacketType type = (PacketType)reader.BeginChunk(0); + PacketType type = reader.ReadChunk(); if(type == ePacket_TriggerCapture) { @@ -309,7 +309,7 @@ void RenderDoc::TargetControlServerThread(Network::Socket *sock) { ReadSerialiser ser(new StreamReader(client, Ownership::Nothing), Ownership::Stream); - PacketType type = (PacketType)ser.BeginChunk(0); + PacketType type = ser.ReadChunk(); if(type != ePacket_Handshake) { @@ -423,7 +423,7 @@ public: } } - PacketType type = (PacketType)reader.BeginChunk(0); + PacketType type = reader.ReadChunk(); if(reader.IsErrored()) { @@ -547,7 +547,7 @@ public: return msg; } - PacketType type = (PacketType)reader.BeginChunk(0); + PacketType type = reader.ReadChunk(); if(reader.IsErrored()) { diff --git a/renderdoc/driver/d3d11/d3d11_replay.cpp b/renderdoc/driver/d3d11/d3d11_replay.cpp index 3b9ed2f4d..3517c8c7d 100644 --- a/renderdoc/driver/d3d11/d3d11_replay.cpp +++ b/renderdoc/driver/d3d11/d3d11_replay.cpp @@ -1588,21 +1588,6 @@ bool D3D11Replay::IsRenderOutput(ResourceId id) return false; } -void D3D11Replay::InitCallstackResolver() -{ - m_pDevice->GetSerialiser()->InitCallstackResolver(); -} - -bool D3D11Replay::HasCallstacks() -{ - return m_pDevice->GetSerialiser()->HasCallstacks(); -} - -Callstack::StackResolver *D3D11Replay::GetCallstackResolver() -{ - return m_pDevice->GetSerialiser()->GetCallstackResolver(); -} - ResourceId D3D11Replay::CreateProxyTexture(const TextureDescription &templateTex) { ResourceId ret; diff --git a/renderdoc/driver/d3d11/d3d11_replay.h b/renderdoc/driver/d3d11/d3d11_replay.h index 9b430bf85..431e7467f 100644 --- a/renderdoc/driver/d3d11/d3d11_replay.h +++ b/renderdoc/driver/d3d11/d3d11_replay.h @@ -165,10 +165,6 @@ public: bool IsRenderOutput(ResourceId id); void FileChanged() {} - void InitCallstackResolver(); - bool HasCallstacks(); - Callstack::StackResolver *GetCallstackResolver(); - private: bool m_WARP; bool m_Proxy; diff --git a/renderdoc/os/os_specific.h b/renderdoc/os/os_specific.h index 035fb08d1..5e28d76ae 100644 --- a/renderdoc/os/os_specific.h +++ b/renderdoc/os/os_specific.h @@ -228,10 +228,10 @@ void Init(); Stackwalk *Collect(); Stackwalk *Create(); -StackResolver *MakeResolver(char *moduleDB, size_t DBSize, string pdbSearchPaths, +StackResolver *MakeResolver(byte *moduleDB, size_t DBSize, float *progress, volatile bool *killSignal); -bool GetLoadedModules(char *&buf, size_t &size); +bool GetLoadedModules(byte *buf, size_t &size); }; // namespace Callstack namespace FileIO diff --git a/renderdoc/os/posix/android/android_callstack.cpp b/renderdoc/os/posix/android/android_callstack.cpp index 1733557f4..647f30f9a 100644 --- a/renderdoc/os/posix/android/android_callstack.cpp +++ b/renderdoc/os/posix/android/android_callstack.cpp @@ -66,35 +66,19 @@ Stackwalk *Create() return new AndroidCallstack(NULL, 0); } -bool GetLoadedModules(char *&buf, size_t &size) +bool GetLoadedModules(byte *buf, size_t &size) { if(buf) - { - buf[0] = 'A'; - buf[1] = 'N'; - buf[2] = 'R'; - buf[3] = 'D'; - buf[4] = 'C'; - buf[5] = 'A'; - buf[6] = 'L'; - buf[7] = 'L'; - } + memcpy(buf, "ANRDCALL", 8); size += 8; return true; } -class AndroidResolver : public Callstack::StackResolver -{ -public: - Callstack::AddressDetails GetAddr(uint64_t addr) { return Callstack::AddressDetails(); } -}; - -StackResolver *MakeResolver(char *moduleDB, size_t DBSize, string pdbSearchPaths, - volatile bool *killSignal) +StackResolver *MakeResolver(byte *moduleDB, size_t DBSize, float *progress, volatile bool *killSignal) { RDCERR("Callstack resolving not supported on Android."); - return new AndroidResolver(); + return NULL; } }; diff --git a/renderdoc/os/posix/apple/apple_callstack.cpp b/renderdoc/os/posix/apple/apple_callstack.cpp index c5aa6c8ea..06b91092c 100644 --- a/renderdoc/os/posix/apple/apple_callstack.cpp +++ b/renderdoc/os/posix/apple/apple_callstack.cpp @@ -66,35 +66,19 @@ Stackwalk *Create() return new AndroidCallstack(NULL, 0); } -bool GetLoadedModules(char *&buf, size_t &size) +bool GetLoadedModules(byte *buf, size_t &size) { if(buf) - { - buf[0] = 'A'; - buf[1] = 'P'; - buf[2] = 'P'; - buf[3] = 'L'; - buf[4] = 'C'; - buf[5] = 'A'; - buf[6] = 'L'; - buf[7] = 'L'; - } + memcpy(buf, "APPLCALL", 8); size += 8; return true; } -class AndroidResolver : public Callstack::StackResolver -{ -public: - Callstack::AddressDetails GetAddr(uint64_t addr) { return Callstack::AddressDetails(); } -}; - -StackResolver *MakeResolver(char *moduleDB, size_t DBSize, string pdbSearchPaths, - volatile bool *killSignal) +StackResolver *MakeResolver(byte *moduleDB, size_t DBSize, float *progress, volatile bool *killSignal) { RDCERR("Callstack resolving not supported on Apple."); - return new AndroidResolver(); + return NULL; } }; diff --git a/renderdoc/os/posix/linux/linux_callstack.cpp b/renderdoc/os/posix/linux/linux_callstack.cpp index c1c0d228a..63dc5a4d8 100644 --- a/renderdoc/os/posix/linux/linux_callstack.cpp +++ b/renderdoc/os/posix/linux/linux_callstack.cpp @@ -114,31 +114,22 @@ Stackwalk *Create() return new LinuxCallstack(NULL, 0); } -bool GetLoadedModules(char *&buf, size_t &size) +bool GetLoadedModules(byte *buf, size_t &size) { // we just dump the whole file rather than pre-parsing, that way we can improve // parsing without needing to recapture FILE *f = FileIO::fopen("/proc/self/maps", "r"); if(buf) - { - buf[0] = 'L'; - buf[1] = 'N'; - buf[2] = 'U'; - buf[3] = 'X'; - buf[4] = 'C'; - buf[5] = 'A'; - buf[6] = 'L'; - buf[7] = 'L'; - } + memcpy(buf, "LNUXCALL", 8); size += 8; - char dummy[512]; + byte dummy[512]; while(!feof(f)) { - char *readbuf = buf ? buf + size : dummy; + byte *readbuf = buf ? buf + size : dummy; size += FileIO::fread(readbuf, 1, 512, f); } @@ -230,30 +221,33 @@ private: std::map m_Cache; }; -StackResolver *MakeResolver(char *moduleDB, size_t DBSize, string pdbSearchPaths, - volatile bool *killSignal) +StackResolver *MakeResolver(byte *moduleDB, size_t DBSize, float *progress, volatile bool *killSignal) { // we look in the original locations for the files, we don't prompt if we can't // find the file, or the file doesn't have symbols (and we don't validate that // the file is the right version). A good option for doing this would be // http://github.com/mlabbe/nativefiledialog - bool valid = true; - if(memcmp(moduleDB, "LNUXCALL", 8)) + if(DBSize < 8 || memcmp(moduleDB, "LNUXCALL", 8)) { RDCWARN("Can't load callstack resolve for this log. Possibly from another platform?"); - valid = false; + return NULL; } - char *search = moduleDB + 8; + char *start = (char *)(moduleDB + 8); + char *search = start; + char *dbend = (char *)(moduleDB + DBSize); vector modules; - while(valid && search && size_t(search - moduleDB) < DBSize) + while(search && search < dbend) { if(killSignal && *killSignal) break; + if(progress) + *progress = float(search - start) / float(DBSize); + // find .text segments { long unsigned int base = 0, end = 0; @@ -273,10 +267,10 @@ StackResolver *MakeResolver(char *moduleDB, size_t DBSize, string pdbSearchPaths mod.end = (uint64_t)end; search += offs; - while(size_t(search - moduleDB) < DBSize && (*search == ' ' || *search == '\t')) + while(search < dbend && (*search == ' ' || *search == '\t')) search++; - if(size_t(search - moduleDB) < DBSize && *search != '[' && *search != 0 && *search != '\n') + if(search < dbend && *search != '[' && *search != 0 && *search != '\n') { size_t n = ARRAY_COUNT(mod.path) - 1; mod.path[n] = 0; @@ -287,7 +281,7 @@ StackResolver *MakeResolver(char *moduleDB, size_t DBSize, string pdbSearchPaths mod.path[i] = 0; break; } - if(search + i >= moduleDB + DBSize) + if(search + i >= dbend) { mod.path[i] = 0; break; @@ -330,7 +324,10 @@ StackResolver *MakeResolver(char *moduleDB, size_t DBSize, string pdbSearchPaths } } - if(search >= (char *)(moduleDB + DBSize)) + if(progress) + *progress = RDCMIN(1.0f, float(search - start) / float(DBSize)); + + if(search >= dbend) break; search = strchr(search, '\n'); diff --git a/renderdoc/os/win32/win32_callstack.cpp b/renderdoc/os/win32/win32_callstack.cpp index 87dc289f8..6f2242eb5 100644 --- a/renderdoc/os/win32/win32_callstack.cpp +++ b/renderdoc/os/win32/win32_callstack.cpp @@ -369,8 +369,7 @@ private: class Win32CallstackResolver : public Callstack::StackResolver { public: - Win32CallstackResolver(char *moduleDB, size_t DBSize, string pdbSearchPaths, - volatile bool *killSignal); + Win32CallstackResolver(byte *moduleDB, size_t DBSize, float *progress, volatile bool *killSignal); ~Win32CallstackResolver(); Callstack::AddressDetails GetAddr(uint64_t addr); @@ -519,7 +518,7 @@ struct CV_INFO_PDB70 struct EnumBuf { - char *bufPtr; + byte *bufPtr; size_t size; }; @@ -676,7 +675,7 @@ wstring Win32CallstackResolver::pdbBrowse(wstring startingPoint) return outBuf; } -Win32CallstackResolver::Win32CallstackResolver(char *moduleDB, size_t DBSize, string pdbSearchPaths, +Win32CallstackResolver::Win32CallstackResolver(byte *moduleDB, size_t DBSize, float *progress, volatile bool *killSignal) { wstring configPath = StringFormat::UTF82Wide(FileIO::GetAppFolderFilename("config.ini")); @@ -711,18 +710,8 @@ Win32CallstackResolver::Win32CallstackResolver(char *moduleDB, size_t DBSize, st split(ignores, pdbIgnores, L';'); - wstring widepdbsearch = StringFormat::UTF82Wide(pdbSearchPaths); - - split(widepdbsearch, pdbRememberedPaths, L';'); - - if(memcmp(moduleDB, "WN32CALL", 8)) - { - RDCWARN("Can't load callstack resolve for this log. Possibly from another platform?"); - return; - } - - char *chunks = moduleDB + 8; - char *end = chunks + DBSize - 8; + byte *chunks = moduleDB + 8; + byte *end = chunks + DBSize - 8; EnumModChunk *chunk = (EnumModChunk *)(chunks); WCHAR *modName = (WCHAR *)(chunks + sizeof(EnumModChunk)); @@ -733,6 +722,9 @@ Win32CallstackResolver::Win32CallstackResolver(char *moduleDB, size_t DBSize, st chunk = (EnumModChunk *)chunks; modName = (WCHAR *)(chunks + sizeof(EnumModChunk)); + if(progress) + *progress = float(chunks - moduleDB) / float(end - moduleDB); + if(killSignal && *killSignal) break; @@ -862,6 +854,9 @@ Win32CallstackResolver::Win32CallstackResolver(char *moduleDB, size_t DBSize, st continue; } + if(progress) + *progress = RDCMIN(1.0f, float(chunks - moduleDB) / float(end - moduleDB)); + DIA2::SetBaseAddress(m.moduleId, chunk->base); RDCLOG("Loaded Symbols for %ls", m.name.c_str()); @@ -971,13 +966,18 @@ Stackwalk *Create() return new Win32Callstack(NULL, 0); } -StackResolver *MakeResolver(char *moduleDB, size_t DBSize, string pdbSearchPaths, - volatile bool *killSignal) +StackResolver *MakeResolver(byte *moduleDB, size_t DBSize, float *progress, volatile bool *killSignal) { - return new Win32CallstackResolver(moduleDB, DBSize, pdbSearchPaths, killSignal); + if(DBSize < 8 || memcmp(moduleDB, "WN32CALL", 8)) + { + RDCWARN("Can't load callstack resolve for this log. Possibly from another platform?"); + return NULL; + } + + return new Win32CallstackResolver(moduleDB, DBSize, progress, killSignal); } -bool GetLoadedModules(char *&buf, size_t &size) +bool GetLoadedModules(byte *buf, size_t &size) { EnumBuf e; e.bufPtr = buf; @@ -985,14 +985,7 @@ bool GetLoadedModules(char *&buf, size_t &size) if(buf) { - buf[0] = 'W'; - buf[1] = 'N'; - buf[2] = '3'; - buf[3] = '2'; - buf[4] = 'C'; - buf[5] = 'A'; - buf[6] = 'L'; - buf[7] = 'L'; + memcpy(buf, "WN32CALL", 8); e.bufPtr += 8; } diff --git a/renderdoc/replay/capture_file.cpp b/renderdoc/replay/capture_file.cpp index 8ad00f9a8..365980b90 100644 --- a/renderdoc/replay/capture_file.cpp +++ b/renderdoc/replay/capture_file.cpp @@ -27,7 +27,9 @@ #include "jpeg-compressor/jpgd.h" #include "jpeg-compressor/jpge.h" #include "replay/replay_controller.h" +#include "serialise/rdcfile.h" #include "serialise/serialiser.h" +#include "stb/stb_image.h" #include "stb/stb_image_resize.h" #include "stb/stb_image_write.h" @@ -39,70 +41,263 @@ static void writeToByteVector(void *context, void *data, int size) vec->insert(vec->end(), start, end); } +static RDCDriver driverFromName(const char *driverName) +{ + for(int d = RDC_Unknown; d < RDC_MaxBuiltin; d++) + { + if(driverName == ToStr((RDCDriver)d)) + return (RDCDriver)d; + } + + return RDC_Unknown; +} + +static RDCThumb convertThumb(FileType thumbType, uint32_t thumbWidth, uint32_t thumbHeight, + const bytebuf &thumbData) +{ + RDCThumb ret; + + if(thumbWidth > 0xffff || thumbHeight > 0xffff) + return ret; + + ret.width = thumbWidth & 0xffff; + ret.height = thumbHeight & 0xffff; + + byte *decoded = NULL; + + if(thumbType == FileType::JPG) + { + // just need to copy + byte *pixels = (byte *)malloc(thumbData.size()); + memcpy(pixels, thumbData.data(), thumbData.size()); + + ret.pixels = pixels; + ret.len = (uint32_t)thumbData.size(); + } + else + { + int ignore = 0; + decoded = + stbi_load_from_memory(thumbData.data(), thumbData.count(), &ignore, &ignore, &ignore, 3); + + if(decoded == NULL) + { + RDCERR("Couldn't decode provided thumbnail"); + return ret; + } + } + + if(decoded) + { + int len = ret.width * ret.height * 3; + byte *pixels = (byte *)malloc(len); + + jpge::params p; + p.m_quality = 90; + jpge::compress_image_to_jpeg_file_in_memory(pixels, len, (int)ret.width, (int)ret.height, 3, + decoded, p); + + ret.pixels = pixels; + ret.len = (uint32_t)len; + } + + return ret; +} + class CaptureFile : public ICaptureFile { public: - CaptureFile(const char *f); - virtual ~CaptureFile() {} + CaptureFile(); + virtual ~CaptureFile(); + + ReplayStatus OpenFile(const char *filename, const char *filetype); + ReplayStatus OpenBuffer(const bytebuf &buffer, const char *filetype); + void Shutdown() { delete this; } - ReplayStatus OpenStatus() { return m_Status; } - const char *Filename() { return m_Filename.c_str(); } ReplaySupport LocalReplaySupport() { return m_Support; } const char *DriverName() { return m_DriverName.c_str(); } const char *RecordedMachineIdent() { return m_Ident.c_str(); } rdcpair OpenCapture(float *progress); + 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); + + rdcarray GetCaptureFileFormats() + { + return RenderDoc::Inst().GetCaptureFileFormats(); + } + + const SDFile &GetStructuredData() + { + // TODO - fetch structured data from capture on demand? + return m_StructuredData; + } + + void SetStructuredData(const SDFile &file) + { + m_StructuredData.version = file.version; + + m_StructuredData.chunks.reserve(file.chunks.size()); + + for(SDChunk *obj : file.chunks) + m_StructuredData.chunks.push_back(obj->Duplicate()); + + m_StructuredData.buffers.reserve(file.buffers.size()); + + for(bytebuf *buf : file.buffers) + m_StructuredData.buffers.push_back(new bytebuf(*buf)); + } + bytebuf GetThumbnail(FileType type, uint32_t maxsize); + bool HasCallstacks(); + bool InitResolver(float *progress, volatile bool *killSignal); + rdcarray GetResolve(const rdcarray &callstack); private: - std::string m_Filename, m_DriverName, m_Ident; - RDCDriver m_DriverType; - ReplayStatus m_Status; - ReplaySupport m_Support; + ReplayStatus Init(); + + RDCFile *m_RDC = NULL; + Callstack::StackResolver *m_Resolver = NULL; + + SDFile m_StructuredData; + + std::string m_DriverName, m_Ident; + ReplaySupport m_Support = ReplaySupport::Unsupported; }; -CaptureFile::CaptureFile(const char *f) +CaptureFile::CaptureFile() { - m_Filename = f; +} - m_DriverType = RDC_Unknown; - uint64_t fileMachineIdent = 0; - m_Status = RenderDoc::Inst().FillInitParams(Filename(), m_DriverType, m_DriverName, - fileMachineIdent, NULL); +CaptureFile::~CaptureFile() +{ + SAFE_DELETE(m_RDC); + SAFE_DELETE(m_Resolver); +} - if(m_Status != ReplayStatus::Succeeded) +ReplayStatus CaptureFile::OpenFile(const char *filename, const char *filetype) +{ + CaptureImporter importer = RenderDoc::Inst().GetCaptureImporter(filetype); + + if(importer) { - m_Support = ReplaySupport::Unsupported; + ReplayStatus ret; + + { + StreamReader reader(FileIO::fopen(filename, "rb")); + m_RDC = new RDCFile; + ret = importer(filename, reader, m_RDC, m_StructuredData); + } + + if(ret != ReplayStatus::Succeeded) + { + delete m_RDC; + return ret; + } } else { - m_Support = RenderDoc::Inst().HasReplayDriver(m_DriverType) ? ReplaySupport::Supported - : ReplaySupport::Unsupported; + if(filetype != NULL && strcmp(filetype, "") && strcmp(filetype, "rdc")) + RDCWARN("Opening file with unrecognised filetype '%s' - treating as 'rdc'", filetype); + + m_RDC = new RDCFile; + m_RDC->Open(filename); + } + + return Init(); +} + +ReplayStatus CaptureFile::OpenBuffer(const bytebuf &buffer, const char *filetype) +{ + CaptureImporter importer = RenderDoc::Inst().GetCaptureImporter(filetype); + + std::vector vec(buffer.begin(), buffer.end()); + + if(importer) + { + ReplayStatus ret; - if(fileMachineIdent != 0) { - uint64_t machineIdent = OSUtility::GetMachineIdent(); + StreamReader reader(vec); + m_RDC = new RDCFile; + ret = importer(NULL, reader, m_RDC, m_StructuredData); + } - m_Ident = OSUtility::MakeMachineIdentString(fileMachineIdent); - - if((machineIdent & OSUtility::MachineIdent_OS_Mask) != - (fileMachineIdent & OSUtility::MachineIdent_OS_Mask)) - m_Support = ReplaySupport::SuggestRemote; + if(ret != ReplayStatus::Succeeded) + { + delete m_RDC; + return ret; } } + else + { + if(filetype != NULL && strcmp(filetype, "") && strcmp(filetype, "rdc")) + RDCWARN("Opening file with unrecognised filetype '%s' - treating as 'rdc'", filetype); + + m_RDC = new RDCFile; + m_RDC->Open(vec); + } + + return Init(); +} + +ReplayStatus CaptureFile::Init() +{ + if(!m_RDC) + return ReplayStatus::InternalError; + + switch(m_RDC->ErrorCode()) + { + case ContainerError::FileNotFound: return ReplayStatus::FileNotFound; break; + case ContainerError::FileIO: return ReplayStatus::FileIOFailed; break; + case ContainerError::Corrupt: return ReplayStatus::FileCorrupted; break; + case ContainerError::UnsupportedVersion: return ReplayStatus::FileIncompatibleVersion; break; + case ContainerError::NoError: + { + RDCDriver driverType = m_RDC->GetDriver(); + m_DriverName = m_RDC->GetDriverName(); + + uint64_t fileMachineIdent = m_RDC->GetMachineIdent(); + + m_Support = RenderDoc::Inst().HasReplayDriver(driverType) ? ReplaySupport::Supported + : ReplaySupport::Unsupported; + + if(fileMachineIdent != 0) + { + uint64_t machineIdent = OSUtility::GetMachineIdent(); + + m_Ident = OSUtility::MakeMachineIdentString(fileMachineIdent); + + if((machineIdent & OSUtility::MachineIdent_OS_Mask) != + (fileMachineIdent & OSUtility::MachineIdent_OS_Mask)) + m_Support = ReplaySupport::SuggestRemote; + } + + // can't open files without a capture in them (except images, which are special) + if(driverType != RDC_Image && m_RDC->SectionIndex(SectionType::FrameCapture) == -1) + m_Support = ReplaySupport::Unsupported; + + return ReplayStatus::Succeeded; + } + } + + // all container errors should be handled and returned above + return ReplayStatus::InternalError; } rdcpair CaptureFile::OpenCapture(float *progress) { - if(m_Status != ReplayStatus::Succeeded) - return make_rdcpair(m_Status, NULL); + if(!m_RDC || m_RDC->ErrorCode() != ContainerError::NoError) + return make_rdcpair(ReplayStatus::InternalError, NULL); ReplayController *render = new ReplayController(); ReplayStatus ret; RenderDoc::Inst().SetProgressPtr(progress); - ret = render->CreateDevice(Filename()); + ret = render->CreateDevice(m_RDC); RenderDoc::Inst().SetProgressPtr(NULL); @@ -112,36 +307,164 @@ rdcpair CaptureFile::OpenCapture(float *progr return make_rdcpair(ret, render); } +void CaptureFile::SetMetadata(const char *driverName, uint64_t machineIdent, FileType thumbType, + uint32_t thumbWidth, uint32_t thumbHeight, const bytebuf &thumbData) +{ + if(m_RDC) + { + RDCERR("Cannot set metadata on file that's already opened."); + return; + } + + RDCThumb *thumb = NULL; + RDCThumb th; + + if(!thumbData.empty()) + { + th = convertThumb(thumbType, thumbWidth, thumbHeight, thumbData); + thumb = &th; + } + + RDCDriver driver = driverFromName(driverName); + + if(driver == RDC_Unknown) + { + RDCERR("Unrecognised driver name '%s'.", driverName); + return; + } + + m_RDC = new RDCFile; + m_RDC->SetData(driver, driverName, machineIdent, thumb); + + free((void *)th.pixels); +} + +ReplayStatus CaptureFile::Convert(const char *filename, const char *filetype) +{ + if(!m_RDC) + { + RDCERR("Data missing for creation of file, set metadata first."); + return ReplayStatus::FileCorrupted; + } + + CaptureExporter exporter = RenderDoc::Inst().GetCaptureExporter(filetype); + + if(exporter) + return exporter(filename, *m_RDC, GetStructuredData()); + + if(filetype != NULL && strcmp(filetype, "") && strcmp(filetype, "rdc")) + RDCWARN("Converting file to unrecognised filetype '%s' - treating as 'rdc'", filetype); + + RDCFile output; + + output.SetData(m_RDC->GetDriver(), m_RDC->GetDriverName().c_str(), m_RDC->GetMachineIdent(), + &m_RDC->GetThumbnail()); + + output.Create(filename); + + if(output.ErrorCode() != ContainerError::NoError) + { + switch(output.ErrorCode()) + { + case ContainerError::FileNotFound: return ReplayStatus::FileNotFound; break; + case ContainerError::FileIO: return ReplayStatus::FileIOFailed; break; + default: break; + } + return ReplayStatus::InternalError; + } + + bool success = true; + + // when we don't have a frame capture section, write it from the structured data. + int frameCaptureIndex = m_RDC->SectionIndex(SectionType::FrameCapture); + + if(frameCaptureIndex == -1) + { + SectionProperties frameCapture; + frameCapture.flags = SectionFlags::ZstdCompressed; + frameCapture.type = SectionType::FrameCapture; + frameCapture.name = SectionTypeNames[uint32_t(frameCapture.type)]; + frameCapture.version = GetStructuredData().version; + + StreamWriter *writer = output.WriteSection(frameCapture); + + WriteSerialiser ser(writer, Ownership::Nothing); + + ser.WriteStructuredFile(GetStructuredData()); + + writer->Finish(); + + success = success && !writer->IsErrored(); + + delete writer; + } + else + { + // otherwise write it straight, but compress it to zstd + SectionProperties props = m_RDC->GetSectionProperties(frameCaptureIndex); + props.flags = SectionFlags::ZstdCompressed; + + StreamWriter *writer = output.WriteSection(props); + StreamReader *reader = m_RDC->ReadSection(frameCaptureIndex); + + StreamTransfer(writer, reader, NULL); + + writer->Finish(); + + success = success && !writer->IsErrored() && !reader->IsErrored(); + + delete reader; + delete writer; + + if(!success) + return ReplayStatus::FileIOFailed; + } + + if(!success) + return ReplayStatus::FileIOFailed; + + // write all other sections + for(int i = 0; i < m_RDC->NumSections(); i++) + { + const SectionProperties &props = m_RDC->GetSectionProperties(i); + + if(props.type == SectionType::FrameCapture) + continue; + + StreamWriter *writer = output.WriteSection(props); + StreamReader *reader = m_RDC->ReadSection(i); + + StreamTransfer(writer, reader, NULL); + + writer->Finish(); + + success = success && !writer->IsErrored() && !reader->IsErrored(); + + delete reader; + delete writer; + + if(!success) + return ReplayStatus::FileIOFailed; + } + + if(!success) + return ReplayStatus::FileIOFailed; + + return ReplayStatus::Succeeded; +} + bytebuf CaptureFile::GetThumbnail(FileType type, uint32_t maxsize) { bytebuf buf; - Serialiser ser(Filename(), Serialiser::READING, false); - - if(ser.HasError()) + if(m_RDC == NULL) return buf; - ser.Rewind(); + const RDCThumb &thumb = m_RDC->GetThumbnail(); - int chunkType = ser.PushContext(NULL, NULL, 1, false); - - if(chunkType != THUMBNAIL_DATA) - return buf; - - bool HasThumbnail = false; - ser.Serialise(NULL, HasThumbnail); - - if(!HasThumbnail) - return buf; - - byte *jpgbuf = NULL; - size_t thumblen = 0; - uint32_t thumbwidth = 0, thumbheight = 0; - { - ser.Serialise("ThumbWidth", thumbwidth); - ser.Serialise("ThumbHeight", thumbheight); - ser.SerialiseBuffer("ThumbnailPixels", jpgbuf, thumblen); - } + const byte *jpgbuf = thumb.pixels; + size_t thumblen = thumb.len; + uint32_t thumbwidth = thumb.width, thumbheight = thumb.height; if(jpgbuf == NULL) return buf; @@ -228,7 +551,6 @@ bytebuf CaptureFile::GetThumbnail(FileType type, uint32_t maxsize) { RDCERR("Unsupported file type %d in thumbnail fetch", type); free(thumbpixels); - delete[] jpgbuf; return buf; } } @@ -238,12 +560,78 @@ bytebuf CaptureFile::GetThumbnail(FileType type, uint32_t maxsize) free(thumbpixels); } - delete[] jpgbuf; - return buf; } -extern "C" RENDERDOC_API ICaptureFile *RENDERDOC_CC RENDERDOC_OpenCaptureFile(const char *logfile) +bool CaptureFile::HasCallstacks() { - return new CaptureFile(logfile); -} \ No newline at end of file + return m_RDC && m_RDC->SectionIndex(SectionType::ResolveDatabase) >= 0; +} + +bool CaptureFile::InitResolver(float *progress, volatile bool *killSignal) +{ + if(!HasCallstacks()) + { + RDCERR("Capture has no callstacks - can't initialise resolver."); + return false; + } + + if(progress) + *progress = 0.001f; + + int idx = m_RDC->SectionIndex(SectionType::ResolveDatabase); + StreamReader *reader = m_RDC->ReadSection(idx); + + std::vector buf; + buf.resize((size_t)reader->GetSize()); + bool success = reader->Read(buf.data(), reader->GetSize()); + + delete reader; + + if(!success) + { + RDCERR("Failed to read resolve database."); + return false; + } + + if(progress) + *progress = 0.002f; + + m_Resolver = Callstack::MakeResolver(buf.data(), buf.size(), progress, killSignal); + + if(!m_Resolver) + { + RDCERR("Couldn't create callstack resolver - capture possibly from another platform."); + return false; + } + + return true; +} + +rdcarray CaptureFile::GetResolve(const rdcarray &callstack) +{ + rdcarray ret; + + if(callstack.empty()) + return ret; + + if(!m_Resolver) + { + ret = {""}; + return ret; + } + + ret.reserve(callstack.size()); + for(uint64_t frame : callstack) + { + Callstack::AddressDetails info = m_Resolver->GetAddr(frame); + ret.push_back(info.formattedString()); + } + + return ret; +} + +extern "C" RENDERDOC_API ICaptureFile *RENDERDOC_CC RENDERDOC_OpenCaptureFile() +{ + return new CaptureFile(); +} diff --git a/renderdoc/replay/entry_points.cpp b/renderdoc/replay/entry_points.cpp index 860a3bf37..b9a8d0f78 100644 --- a/renderdoc/replay/entry_points.cpp +++ b/renderdoc/replay/entry_points.cpp @@ -265,44 +265,6 @@ extern "C" RENDERDOC_API void RENDERDOC_CC RENDERDOC_TriggerExceptionHandler(voi } } -extern "C" RENDERDOC_API ReplaySupport RENDERDOC_CC -RENDERDOC_SupportLocalReplay(const char *logfile, rdcstr *driver, rdcstr *recordMachineIdent) -{ - ICaptureFile *file = RENDERDOC_OpenCaptureFile(logfile); - - if(driver) - *driver = file->DriverName(); - - if(recordMachineIdent) - *recordMachineIdent = file->RecordedMachineIdent(); - - ReplaySupport support = file->LocalReplaySupport(); - - file->Shutdown(); - - return support; -} - -extern "C" RENDERDOC_API ReplayStatus RENDERDOC_CC -RENDERDOC_CreateReplayRenderer(const char *logfile, float *progress, IReplayController **rend) -{ - ICaptureFile *file = RENDERDOC_OpenCaptureFile(logfile); - - ReplayStatus ret = file->OpenStatus(); - - if(ret != ReplayStatus::Succeeded) - { - file->Shutdown(); - return ret; - } - - std::tie(ret, *rend) = file->OpenCapture(progress); - - file->Shutdown(); - - return ret; -} - extern "C" RENDERDOC_API uint32_t RENDERDOC_CC RENDERDOC_ExecuteAndInject(const char *app, const char *workingDir, const char *cmdLine, const rdcarray &env, const char *logfile, @@ -346,22 +308,6 @@ RENDERDOC_InjectIntoProcess(uint32_t pid, const rdcarrayOpenStatus() != ReplayStatus::Succeeded) - { - file->Shutdown(); - return false; - } - - *buf = file->GetThumbnail(type, maxsize); - file->Shutdown(); - return true; -} - extern "C" RENDERDOC_API void RENDERDOC_CC RENDERDOC_FreeArrayMem(const void *mem) { free((void *)mem); diff --git a/renderdoc/replay/replay_controller.cpp b/renderdoc/replay/replay_controller.cpp index 0ca0af37f..1884e0247 100644 --- a/renderdoc/replay/replay_controller.cpp +++ b/renderdoc/replay/replay_controller.cpp @@ -31,6 +31,7 @@ #include "jpeg-compressor/jpge.h" #include "maths/formatpacking.h" #include "os/os_specific.h" +#include "serialise/rdcfile.h" #include "serialise/serialiser.h" #include "stb/stb_image.h" #include "stb/stb_image_write.h" @@ -321,31 +322,6 @@ rdcarray ReplayController::GetTextures() return m_Textures; } -rdcarray ReplayController::GetResolve(const rdcarray &callstack) -{ - rdcarray ret; - - if(callstack.empty()) - return ret; - - Callstack::StackResolver *resolv = m_pDevice->GetCallstackResolver(); - - if(resolv == NULL) - { - ret = {""}; - return ret; - } - - ret.reserve(callstack.size()); - for(uint64_t frame : callstack) - { - Callstack::AddressDetails info = resolv->GetAddr(frame); - ret.push_back(info.formattedString()); - } - - return ret; -} - rdcarray ReplayController::GetDebugMessages() { return m_pDevice->GetDebugMessages(); @@ -1549,29 +1525,15 @@ void ReplayController::RemoveReplacement(ResourceId id) m_Outputs[i]->Display(); } -ReplayStatus ReplayController::CreateDevice(const char *logfile) +ReplayStatus ReplayController::CreateDevice(RDCFile *rdc) { - RDCLOG("Creating replay device for %s", logfile); - - RDCDriver driverType = RDC_Unknown; - string driverName = ""; - uint64_t fileMachineIdent = 0; - auto status = - RenderDoc::Inst().FillInitParams(logfile, driverType, driverName, fileMachineIdent, NULL); - - if(driverType == RDC_Unknown || driverName == "" || status != ReplayStatus::Succeeded) - { - RDCERR("Couldn't get device type from log"); - return status; - } - IReplayDriver *driver = NULL; - status = RenderDoc::Inst().CreateReplayDriver(driverType, logfile, &driver); + ReplayStatus status = RenderDoc::Inst().CreateReplayDriver(rdc, &driver); if(driver && status == ReplayStatus::Succeeded) { RDCLOG("Created replay driver."); - return PostCreateInit(driver); + return PostCreateInit(driver, rdc); } RDCERR("Couldn't create a replay device :(."); @@ -1583,18 +1545,18 @@ ReplayStatus ReplayController::SetDevice(IReplayDriver *device) if(device) { RDCLOG("Got replay driver."); - return PostCreateInit(device); + return PostCreateInit(device, NULL); } RDCERR("Given invalid replay driver."); return ReplayStatus::InternalError; } -ReplayStatus ReplayController::PostCreateInit(IReplayDriver *device) +ReplayStatus ReplayController::PostCreateInit(IReplayDriver *device, RDCFile *rdc) { m_pDevice = device; - m_pDevice->ReadLogInitialisation(); + m_pDevice->ReadLogInitialisation(rdc); FetchPipelineState(); @@ -1611,22 +1573,11 @@ void ReplayController::FileChanged() m_pDevice->FileChanged(); } -bool ReplayController::HasCallstacks() -{ - return m_pDevice->HasCallstacks(); -} - APIProperties ReplayController::GetAPIProperties() { return m_pDevice->GetAPIProperties(); } -bool ReplayController::InitResolver() -{ - m_pDevice->InitCallstackResolver(); - return m_pDevice->GetCallstackResolver() != NULL; -} - void ReplayController::FetchPipelineState() { m_pDevice->SavePipelineState(); diff --git a/renderdoc/replay/replay_controller.h b/renderdoc/replay/replay_controller.h index 0f1128491..6173c8b72 100644 --- a/renderdoc/replay/replay_controller.h +++ b/renderdoc/replay/replay_controller.h @@ -126,14 +126,11 @@ public: APIProperties GetAPIProperties(); - ReplayStatus CreateDevice(const char *logfile); + ReplayStatus CreateDevice(RDCFile *rdc); ReplayStatus SetDevice(IReplayDriver *device); void FileChanged(); - bool HasCallstacks(); - bool InitResolver(); - void SetFrameEvent(uint32_t eventID, bool force); void FetchPipelineState(); @@ -165,7 +162,6 @@ public: CounterDescription DescribeCounter(GPUCounter counterID); rdcarray GetTextures(); rdcarray GetBuffers(); - rdcarray GetResolve(const rdcarray &callstack); rdcarray GetDebugMessages(); rdcarray PixelHistory(ResourceId target, uint32_t x, uint32_t y, uint32_t slice, @@ -200,7 +196,7 @@ public: void Shutdown(); private: - ReplayStatus PostCreateInit(IReplayDriver *device); + ReplayStatus PostCreateInit(IReplayDriver *device, RDCFile *rdc); DrawcallDescription *GetDrawcallByEID(uint32_t eventID); diff --git a/renderdoc/replay/replay_driver.h b/renderdoc/replay/replay_driver.h index afe04939e..706fdc1bb 100644 --- a/renderdoc/replay/replay_driver.h +++ b/renderdoc/replay/replay_driver.h @@ -65,6 +65,8 @@ struct GetTextureDataParams } }; +class RDCFile; + // these two interfaces define what an API driver implementation must provide // to the replay. At minimum it must implement IRemoteDriver which contains // all of the functionality that cannot be achieved elsewhere. An IReplayDriver @@ -106,7 +108,7 @@ public: virtual FrameRecord GetFrameRecord() = 0; - virtual void ReadLogInitialisation() = 0; + virtual void ReadLogInitialisation(RDCFile *rdc) = 0; virtual void ReplayLog(uint32_t endEventID, ReplayLogType replayType) = 0; virtual vector GetPassEvents(uint32_t eventID) = 0; @@ -153,10 +155,6 @@ public: virtual void FileChanged() = 0; - virtual void InitCallstackResolver() = 0; - virtual bool HasCallstacks() = 0; - virtual Callstack::StackResolver *GetCallstackResolver() = 0; - virtual bool NeedRemapForFetch(const ResourceFormat &format) = 0; }; diff --git a/renderdoc/serialise/codecs/xml_codec.cpp b/renderdoc/serialise/codecs/xml_codec.cpp index d518bcec0..fc75fddeb 100644 --- a/renderdoc/serialise/codecs/xml_codec.cpp +++ b/renderdoc/serialise/codecs/xml_codec.cpp @@ -763,3 +763,10 @@ ReplayStatus exportXMLZ(const char *filename, const RDCFile &rdc, const SDFile & return Structured2XML(filename, rdc, structData.version, structData.chunks); } + +static ConversionRegistration XMLConversionRegistration("xml", R"(XML+ZIP format. + +Stores the structured data in an xml tree, with large buffer data stored in indexed blobs in +similarly named zip file. +)", + &importXMLZ, &exportXMLZ); \ No newline at end of file diff --git a/renderdoccmd/renderdoccmd.cpp b/renderdoccmd/renderdoccmd.cpp index b6796db34..305d7e56e 100644 --- a/renderdoccmd/renderdoccmd.cpp +++ b/renderdoccmd/renderdoccmd.cpp @@ -292,8 +292,8 @@ struct ThumbCommand : public Command bytebuf buf; - ICaptureFile *file = RENDERDOC_OpenCaptureFile(filename.c_str()); - ReplayStatus st = file->OpenStatus(); + ICaptureFile *file = RENDERDOC_OpenCaptureFile(); + ReplayStatus st = file->OpenFile(filename.c_str(), "rdc"); if(st == ReplayStatus::Succeeded) { buf = file->GetThumbnail(type, maxsize); @@ -587,9 +587,9 @@ struct ReplayCommand : public Command { std::cout << "Replaying '" << filename << "' locally.." << std::endl; - ICaptureFile *file = RENDERDOC_OpenCaptureFile(filename.c_str()); + ICaptureFile *file = RENDERDOC_OpenCaptureFile(); - if(file->OpenStatus() != ReplayStatus::Succeeded) + if(file->OpenFile(filename.c_str(), "rdc") != ReplayStatus::Succeeded) { std::cerr << "Couldn't load '" << filename << "'." << std::endl; return 1;