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;