From b38cbc1b0c8d8362303a8f9b33cad3230627947d Mon Sep 17 00:00:00 2001 From: baldurk Date: Thu, 30 Jan 2020 13:39:00 +0000 Subject: [PATCH] Add explicit functions to initialise and shutdown replay. Closes #1685 * It's required for replay applications to call InitialiseReplay() before doing any work, and to call ShutdownReplay() once they're finished. * This lets us do more heavyweight shutdown work before global destructors are being invoked and the RenderDoc instance is being destroyed. * Anything that needs to be shut down during capture still has to happen in RenderDoc::~RenderDoc since we obviously can't get the application to call a shutdown function in that situation. --- qrenderdoc/Code/qrenderdoc.cpp | 4 +- renderdoc/api/replay/renderdoc_replay.h | 19 +- renderdoc/core/core.cpp | 14 +- renderdoc/core/core.h | 3 +- renderdoc/os/posix/posix_process.cpp | 1 + renderdoc/replay/entry_points.cpp | 11 +- renderdoccmd/3rdparty/cmdline/cmdline.h | 4 + renderdoccmd/renderdoccmd.cpp | 468 ++++++++++++++++-------- renderdoccmd/renderdoccmd.h | 6 +- renderdoccmd/renderdoccmd_win32.cpp | 59 ++- util/test/rdtest/runner.py | 10 +- 11 files changed, 407 insertions(+), 192 deletions(-) diff --git a/qrenderdoc/Code/qrenderdoc.cpp b/qrenderdoc/Code/qrenderdoc.cpp index ed510c76c..28644b64e 100644 --- a/qrenderdoc/Code/qrenderdoc.cpp +++ b/qrenderdoc/Code/qrenderdoc.cpp @@ -378,7 +378,7 @@ int main(int argc, char *argv[]) coreargs.push_back("--crash"); for(const QString &arg : remaining) coreargs.push_back(arg); - RENDERDOC_InitGlobalEnv(env, coreargs); + RENDERDOC_InitialiseReplay(env, coreargs); } if(!crashReportPath.isEmpty()) @@ -490,6 +490,8 @@ int main(int argc, char *argv[]) config.Save(); } + RENDERDOC_ShutdownReplay(); + PythonContext::GlobalShutdown(); Formatter::shutdown(); diff --git a/renderdoc/api/replay/renderdoc_replay.h b/renderdoc/api/replay/renderdoc_replay.h index 8a4672530..5cfca9271 100644 --- a/renderdoc/api/replay/renderdoc_replay.h +++ b/renderdoc/api/replay/renderdoc_replay.h @@ -1889,9 +1889,22 @@ extern "C" RENDERDOC_API void RENDERDOC_CC RENDERDOC_UpdateVulkanLayerRegistrati DOCUMENT("Internal function for updating installed version number in windows registry."); extern "C" RENDERDOC_API void RENDERDOC_CC RENDERDOC_UpdateInstalledVersionNumber(); -DOCUMENT("Internal function for initialising global process environment in a replay program."); -extern "C" RENDERDOC_API void RENDERDOC_CC RENDERDOC_InitGlobalEnv(GlobalEnvironment env, - const rdcarray &args); +DOCUMENT(R"(Initialises RenderDoc for replay. Replay API functions should not be called before this +has been called. It should be called exactly once, and before shutdown you must call +:func:`ShutdownReplay`. + +:param GlobalEnvironment globalEnv: The path to the new log file. +:param ``list`` of ``str`` args: Any extra command-line arguments. +)"); +extern "C" RENDERDOC_API void RENDERDOC_CC RENDERDOC_InitialiseReplay(GlobalEnvironment env, + const rdcarray &args); + +DOCUMENT(R"(Shutdown RenderDoc for replay. Replay API functions should not be called after this +has been called. It is not safe to re-initialise replay after this function has been called so it +should only be called at program shutdown. This function must only be called if +:func:`InitialiseReplay` was previously called. +)"); +extern "C" RENDERDOC_API void RENDERDOC_CC RENDERDOC_ShutdownReplay(); DOCUMENT("Internal function for creating a bug report zip."); extern "C" RENDERDOC_API void RENDERDOC_CC RENDERDOC_CreateBugReport(const char *logfile, diff --git a/renderdoc/core/core.cpp b/renderdoc/core/core.cpp index f3b27fe6e..88571b369 100644 --- a/renderdoc/core/core.cpp +++ b/renderdoc/core/core.cpp @@ -465,6 +465,7 @@ RenderDoc::~RenderDoc() for(auto it = m_ShutdownFunctions.begin(); it != m_ShutdownFunctions.end(); ++it) (*it)(); + m_ShutdownFunctions.clear(); for(size_t i = 0; i < m_Captures.size(); i++) { @@ -520,7 +521,7 @@ void RenderDoc::RemoveHooks() } } -void RenderDoc::ProcessGlobalEnvironment(GlobalEnvironment env, const rdcarray &args) +void RenderDoc::InitialiseReplay(GlobalEnvironment env, const rdcarray &args) { m_GlobalEnv = env; @@ -646,6 +647,17 @@ void RenderDoc::ProcessGlobalEnvironment(GlobalEnvironment env, const rdcarray &args); + void InitialiseReplay(GlobalEnvironment env, const rdcarray &args); + void ShutdownReplay(); int32_t GetForwardedPortSlot() { return Atomic::Inc32(&m_PortSlot); } void RegisterShutdownFunction(ShutdownFunction func); diff --git a/renderdoc/os/posix/posix_process.cpp b/renderdoc/os/posix/posix_process.cpp index c4796a5e9..49d625c14 100644 --- a/renderdoc/os/posix/posix_process.cpp +++ b/renderdoc/os/posix/posix_process.cpp @@ -886,6 +886,7 @@ void Process::Shutdown() delete del; } } + #if ENABLED(ENABLE_UNIT_TESTS) #include "3rdparty/catch/catch.hpp" diff --git a/renderdoc/replay/entry_points.cpp b/renderdoc/replay/entry_points.cpp index 4603b84a5..a018b8c8c 100644 --- a/renderdoc/replay/entry_points.cpp +++ b/renderdoc/replay/entry_points.cpp @@ -270,15 +270,15 @@ extern "C" RENDERDOC_API void RENDERDOC_CC RENDERDOC_GetLogFileContents(rdcstr & logfile = FileIO::logfile_readall(RDCGETLOGFILE()); } -extern "C" RENDERDOC_API void RENDERDOC_CC RENDERDOC_InitGlobalEnv(GlobalEnvironment env, - const rdcarray &args) +extern "C" RENDERDOC_API void RENDERDOC_CC RENDERDOC_InitialiseReplay(GlobalEnvironment env, + const rdcarray &args) { rdcarray argsVec; argsVec.reserve(args.size()); for(const rdcstr &a : args) argsVec.push_back(a.c_str()); - RenderDoc::Inst().ProcessGlobalEnvironment(env, argsVec); + RenderDoc::Inst().InitialiseReplay(env, argsVec); if(RenderDoc::Inst().GetCrashHandler() == NULL) return; @@ -295,6 +295,11 @@ extern "C" RENDERDOC_API void RENDERDOC_CC RENDERDOC_InitGlobalEnv(GlobalEnviron RenderDoc::Inst().RecreateCrashHandler(); } +extern "C" RENDERDOC_API void RENDERDOC_CC RENDERDOC_ShutdownReplay() +{ + RenderDoc::Inst().ShutdownReplay(); +} + extern "C" RENDERDOC_API void RENDERDOC_CC RENDERDOC_CreateBugReport(const char *logfile, const char *dumpfile, rdcstr &report) diff --git a/renderdoccmd/3rdparty/cmdline/cmdline.h b/renderdoccmd/3rdparty/cmdline/cmdline.h index 42481ce84..ef565ac6a 100644 --- a/renderdoccmd/3rdparty/cmdline/cmdline.h +++ b/renderdoccmd/3rdparty/cmdline/cmdline.h @@ -394,6 +394,10 @@ public: return others; } + void set_rest(const std::vector &rest) { + others = rest; + } + bool parse(const std::vector &args, bool processed_arg0 = false){ int argc=static_cast(args.size()); std::vector argv(argc); diff --git a/renderdoccmd/renderdoccmd.cpp b/renderdoccmd/renderdoccmd.cpp index 3fc51ddff..d0c0df0e8 100644 --- a/renderdoccmd/renderdoccmd.cpp +++ b/renderdoccmd/renderdoccmd.cpp @@ -128,12 +128,13 @@ static std::vector version_lines; struct VersionCommand : public Command { - VersionCommand(const GlobalEnvironment &env) : Command(env) {} + VersionCommand() : Command() {} virtual void AddOptions(cmdline::parser &parser) {} virtual const char *Description() { return "Print version information"; } virtual bool IsInternalOnly() { return false; } virtual bool IsCaptureCommand() { return false; } - virtual int Execute(cmdline::parser &parser, const CaptureOptions &) + virtual bool Parse(cmdline::parser &, GlobalEnvironment &) { return true; } + virtual int Execute(const CaptureOptions &) { std::cout << "renderdoccmd " << (sizeof(uintptr_t) == sizeof(uint64_t) ? "x64" : "x86") << " v" MAJOR_MINOR_VERSION_STRING << " built from " << RENDERDOC_GetCommitHash() @@ -160,12 +161,13 @@ void add_version_line(const std::string &str) struct HelpCommand : public Command { - HelpCommand(const GlobalEnvironment &env) : Command(env) {} + HelpCommand() : Command() {} virtual void AddOptions(cmdline::parser &parser) {} virtual const char *Description() { return "Print this help message"; } virtual bool IsInternalOnly() { return false; } virtual bool IsCaptureCommand() { return false; } - virtual int Execute(cmdline::parser &parser, const CaptureOptions &) + virtual bool Parse(cmdline::parser &, GlobalEnvironment &) { return true; } + virtual int Execute(const CaptureOptions &) { command_usage(); return 0; @@ -174,7 +176,14 @@ struct HelpCommand : public Command struct ThumbCommand : public Command { - ThumbCommand(const GlobalEnvironment &env) : Command(env) {} +private: + std::string infile; + std::string outfile; + std::string format; + uint32_t maxsize = 0; + +public: + ThumbCommand() : Command() {} virtual void AddOptions(cmdline::parser &parser) { parser.set_footer(""); @@ -190,7 +199,7 @@ struct ThumbCommand : public Command virtual const char *Description() { return "Saves a capture's embedded thumbnail to disk."; } virtual bool IsInternalOnly() { return false; } virtual bool IsCaptureCommand() { return false; } - virtual int Execute(cmdline::parser &parser, const CaptureOptions &) + virtual bool Parse(cmdline::parser &parser, GlobalEnvironment &) { std::vector rest = parser.rest(); if(rest.empty()) @@ -198,21 +207,24 @@ struct ThumbCommand : public Command std::cerr << "Error: thumb command requires a capture filename." << std::endl << std::endl << parser.usage(); - return 0; + return false; } - std::string filename = rest[0]; + infile = rest[0]; rest.erase(rest.begin()); - RENDERDOC_InitGlobalEnv(m_Env, convertArgs(rest)); + parser.set_rest(rest); - std::string outfile = parser.get("out"); + outfile = parser.get("out"); + format = parser.get("format"); + maxsize = parser.get("max-size"); - std::string format = parser.get("format"); - - uint32_t maxsize = parser.get("max-size"); + return true; + } + virtual int Execute(const CaptureOptions &) + { FileType type = FileType::JPG; if(format == "png") @@ -247,20 +259,20 @@ struct ThumbCommand : public Command bytebuf buf; ICaptureFile *file = RENDERDOC_OpenCaptureFile(); - ReplayStatus st = file->OpenFile(filename.c_str(), "rdc", NULL); + ReplayStatus st = file->OpenFile(infile.c_str(), "rdc", NULL); if(st == ReplayStatus::Succeeded) { buf = file->GetThumbnail(type, maxsize).data; } else { - std::cerr << "Couldn't open '" << filename << "': " << ToStr(st) << std::endl; + std::cerr << "Couldn't open '" << infile << "': " << ToStr(st) << std::endl; } file->Shutdown(); if(buf.empty()) { - std::cerr << "Couldn't fetch the thumbnail in '" << filename << "'" << std::endl; + std::cerr << "Couldn't fetch the thumbnail in '" << infile << "'" << std::endl; } else { @@ -275,7 +287,7 @@ struct ThumbCommand : public Command fwrite(buf.data(), 1, buf.size(), f); fclose(f); - std::cout << "Wrote thumbnail from '" << filename << "' to '" << outfile << "'." << std::endl; + std::cout << "Wrote thumbnail from '" << infile << "' to '" << outfile << "'." << std::endl; } } @@ -285,7 +297,15 @@ struct ThumbCommand : public Command struct CaptureCommand : public Command { - CaptureCommand(const GlobalEnvironment &env) : Command(env) {} +private: + std::string executable; + std::string workingDir; + std::string cmdLine; + std::string logFile; + bool wait_for_exit = false; + +public: + CaptureCommand() : Command() {} virtual void AddOptions(cmdline::parser &parser) { parser.set_footer(" [program arguments]"); @@ -294,9 +314,13 @@ struct CaptureCommand : public Command virtual const char *Description() { return "Launches the given executable to capture."; } virtual bool IsInternalOnly() { return false; } virtual bool IsCaptureCommand() { return true; } - virtual int Execute(cmdline::parser &parser, const CaptureOptions &opts) + virtual bool Parse(cmdline::parser &parser, GlobalEnvironment &) { - if(parser.rest().empty()) + std::vector rest = parser.rest(); + + parser.set_rest({}); + + if(rest.empty()) { std::cerr << "Error: capture command requires an executable to launch." << std::endl << std::endl @@ -304,10 +328,9 @@ struct CaptureCommand : public Command return 0; } - std::string executable = parser.rest()[0]; - std::string workingDir = parser.get("working-dir"); - std::string cmdLine; - std::string logFile = parser.get("capture-file"); + executable = parser.rest()[0]; + workingDir = parser.get("working-dir"); + logFile = parser.get("capture-file"); for(size_t i = 1; i < parser.rest().size(); i++) { @@ -317,8 +340,13 @@ struct CaptureCommand : public Command cmdLine += EscapeArgument(parser.rest()[i]); } - RENDERDOC_InitGlobalEnv(m_Env, rdcarray()); + wait_for_exit = parser.exist("wait-for-exit"); + return true; + } + + virtual int Execute(const CaptureOptions &opts) + { std::cout << "Launching '" << executable << "'"; if(!cmdLine.empty()) @@ -328,10 +356,10 @@ struct CaptureCommand : public Command rdcarray env; - ExecuteResult res = RENDERDOC_ExecuteAndInject( - executable.c_str(), workingDir.empty() ? "" : workingDir.c_str(), - cmdLine.empty() ? "" : cmdLine.c_str(), env, logFile.empty() ? "" : logFile.c_str(), opts, - parser.exist("wait-for-exit")); + ExecuteResult res = + RENDERDOC_ExecuteAndInject(executable.c_str(), workingDir.empty() ? "" : workingDir.c_str(), + cmdLine.empty() ? "" : cmdLine.c_str(), env, + logFile.empty() ? "" : logFile.c_str(), opts, wait_for_exit); if(res.status != ReplayStatus::Succeeded) { @@ -339,7 +367,7 @@ struct CaptureCommand : public Command return (int)res.status; } - if(parser.exist("wait-for-exit")) + if(wait_for_exit) { std::cerr << "'" << executable << "' finished executing." << std::endl; res.ident = 0; @@ -375,7 +403,13 @@ struct CaptureCommand : public Command struct InjectCommand : public Command { - InjectCommand(const GlobalEnvironment &env) : Command(env) {} +private: + uint32_t PID = 0; + std::string captureFile; + bool wait_for_exit = false; + +public: + InjectCommand() : Command() {} virtual void AddOptions(cmdline::parser &parser) { parser.add("PID", 0, "The process ID of the process to inject.", true); @@ -383,19 +417,21 @@ struct InjectCommand : public Command virtual const char *Description() { return "Injects RenderDoc into a given running process."; } virtual bool IsInternalOnly() { return false; } virtual bool IsCaptureCommand() { return true; } - virtual int Execute(cmdline::parser &parser, const CaptureOptions &opts) + virtual bool Parse(cmdline::parser &parser, GlobalEnvironment &) + { + PID = parser.get("PID"); + captureFile = parser.get("capture-file"); + wait_for_exit = parser.exist("wait-for-exit"); + return true; + } + virtual int Execute(const CaptureOptions &opts) { - uint32_t PID = parser.get("PID"); - std::string logFile = parser.get("capture-file"); - std::cout << "Injecting into PID " << PID << std::endl; rdcarray env; - RENDERDOC_InitGlobalEnv(m_Env, convertArgs(parser.rest())); - - ExecuteResult res = RENDERDOC_InjectIntoProcess( - PID, env, logFile.empty() ? "" : logFile.c_str(), opts, parser.exist("wait-for-exit")); + ExecuteResult res = + RENDERDOC_InjectIntoProcess(PID, env, captureFile.c_str(), opts, wait_for_exit); if(res.status != ReplayStatus::Succeeded) { @@ -403,7 +439,7 @@ struct InjectCommand : public Command return (int)res.status; } - if(parser.exist("wait-for-exit")) + if(wait_for_exit) { std::cerr << PID << " finished executing." << std::endl; res.ident = 0; @@ -419,7 +455,13 @@ struct InjectCommand : public Command struct RemoteServerCommand : public Command { - RemoteServerCommand(const GlobalEnvironment &env) : Command(env) {} +private: + std::string host; + bool daemon = false; + bool preview = false; + +public: + RemoteServerCommand() : Command() {} virtual void AddOptions(cmdline::parser &parser) { parser.add("daemon", 'd', "Go into the background."); @@ -433,18 +475,20 @@ struct RemoteServerCommand : public Command } virtual bool IsInternalOnly() { return false; } virtual bool IsCaptureCommand() { return false; } - virtual int Execute(cmdline::parser &parser, const CaptureOptions &) + virtual bool Parse(cmdline::parser &parser, GlobalEnvironment &env) + { + env.enumerateGPUs = true; + host = parser.get("host"); + daemon = parser.exist("daemon"); + preview = parser.exist("preview"); + return true; + } + virtual int Execute(const CaptureOptions &) { - std::string host = parser.get("host"); - - m_Env.enumerateGPUs = true; - - RENDERDOC_InitGlobalEnv(m_Env, convertArgs(parser.rest())); - std::cerr << "Spawning a replay host listening on " << (host.empty() ? "*" : host) << "..." << std::endl; - if(parser.exist("daemon")) + if(daemon) { std::cerr << "Detaching." << std::endl; Daemonise(); @@ -456,7 +500,7 @@ struct RemoteServerCommand : public Command RENDERDOC_PreviewWindowCallback previewWindow; // if the user asked for a preview, then call to the platform-specific preview function - if(parser.exist("preview")) + if(preview) previewWindow = &DisplayRemoteServerPreview; // OR if the platform-specific preview function always has a window, then return it anyway. @@ -474,7 +518,15 @@ struct RemoteServerCommand : public Command struct ReplayCommand : public Command { - ReplayCommand(const GlobalEnvironment &env) : Command(env) {} +private: + std::string filename; + std::string remote_host; + uint32_t width = 0; + uint32_t height = 0; + uint32_t loops = 0; + +public: + ReplayCommand() : Command() {} virtual void AddOptions(cmdline::parser &parser) { parser.set_footer(""); @@ -492,38 +544,47 @@ struct ReplayCommand : public Command } virtual bool IsInternalOnly() { return false; } virtual bool IsCaptureCommand() { return false; } - virtual int Execute(cmdline::parser &parser, const CaptureOptions &) + virtual bool Parse(cmdline::parser &parser, GlobalEnvironment &) { std::vector rest = parser.rest(); if(rest.empty()) { - std::cerr << "Error: capture command requires a filename to load." << std::endl + std::cerr << "Error: replay command requires a filename to load." << std::endl << std::endl << parser.usage(); - return 0; + return false; } - std::string filename = rest[0]; + filename = rest[0]; rest.erase(rest.begin()); - RENDERDOC_InitGlobalEnv(m_Env, convertArgs(rest)); + parser.set_rest(rest); if(parser.exist("remote-host")) + remote_host = parser.get("remote-host"); + + width = parser.get("width"); + height = parser.get("height"); + loops = parser.get("loops"); + + return true; + } + virtual int Execute(const CaptureOptions &) + { + if(!remote_host.empty()) { - std::cout << "Replaying '" << filename << "' on " << parser.get("remote-host") - << "." << std::endl; + std::cout << "Replaying '" << filename << "' on " << remote_host << "." << std::endl; IRemoteServer *remote = NULL; - ReplayStatus status = RENDERDOC_CreateRemoteServerConnection( - parser.get("remote-host").c_str(), &remote); + ReplayStatus status = RENDERDOC_CreateRemoteServerConnection(remote_host.c_str(), &remote); if(remote == NULL || status != ReplayStatus::Succeeded) { - std::cerr << "Error: " << ToStr(status) << " - Couldn't connect to " - << parser.get("remote-host") << "." << std::endl; - std::cerr << " Have you run renderdoccmd remoteserver on '" - << parser.get("remote-host") << "'?" << std::endl; + std::cerr << "Error: " << ToStr(status) << " - Couldn't connect to " << remote_host << "." + << std::endl; + std::cerr << " Have you run renderdoccmd remoteserver on '" << remote_host << "'?" + << std::endl; return 1; } @@ -536,8 +597,7 @@ struct ReplayCommand : public Command if(status == ReplayStatus::Succeeded) { - DisplayRendererPreview(renderer, parser.get("width"), - parser.get("height"), parser.get("loops")); + DisplayRendererPreview(renderer, width, height, loops); remote->CloseCapture(renderer); } @@ -568,8 +628,7 @@ struct ReplayCommand : public Command if(status == ReplayStatus::Succeeded) { - DisplayRendererPreview(renderer, parser.get("width"), - parser.get("height"), parser.get("loops")); + DisplayRendererPreview(renderer, width, height, loops); renderer->Shutdown(); } @@ -621,17 +680,16 @@ private: struct ConvertCommand : public Command { +private: rdcarray m_Formats; + bool list_formats = false; + std::string infile; + std::string outfile; + std::string infmt; + std::string outfmt; - ConvertCommand(const GlobalEnvironment &env) : Command(env) - { - ICaptureFile *tmp = RENDERDOC_OpenCaptureFile(); - - m_Formats = tmp->GetCaptureFileFormats(); - - tmp->Shutdown(); - } - +public: + ConvertCommand() : Command() {} virtual void AddOptions(cmdline::parser &parser) { parser.add("filename", 'f', "The file to convert from.", false); @@ -646,9 +704,45 @@ struct ConvertCommand : public Command virtual const char *Description() { return "Convert between capture formats."; } virtual bool IsInternalOnly() { return false; } virtual bool IsCaptureCommand() { return false; } - virtual int Execute(cmdline::parser &parser, const CaptureOptions &) + virtual bool Parse(cmdline::parser &parser, GlobalEnvironment &) { - if(parser.exist("list-formats")) + list_formats = parser.exist("list-formats"); + + if(list_formats) + return true; + + infile = parser.get("filename"); + outfile = parser.get("output"); + + if(infile.empty()) + { + std::cerr << "Need an input filename (-f)." << std::endl << std::endl; + std::cerr << parser.usage() << std::endl; + return false; + } + + if(outfile.empty()) + { + std::cerr << "Need an output filename (-o)." << std::endl << std::endl; + std::cerr << parser.usage() << std::endl; + return false; + } + + infmt = parser.get("input-format"); + outfmt = parser.get("convert-format"); + + return true; + } + + virtual int Execute(const CaptureOptions &) + { + ICaptureFile *tmp = RENDERDOC_OpenCaptureFile(); + + m_Formats = tmp->GetCaptureFileFormats(); + + tmp->Shutdown(); + + if(list_formats) { std::cout << "Available formats:" << std::endl; for(CaptureFileFormat f : m_Formats) @@ -658,26 +752,6 @@ struct ConvertCommand : public Command return 0; } - std::string infile = parser.get("filename"); - std::string outfile = parser.get("output"); - - if(infile.empty()) - { - std::cerr << "Need an input filename (-f)." << std::endl << std::endl; - std::cerr << parser.usage() << std::endl; - return 1; - } - - if(outfile.empty()) - { - std::cerr << "Need an output filename (-o)." << std::endl << std::endl; - std::cerr << parser.usage() << std::endl; - return 1; - } - - std::string infmt = parser.get("input-format"); - std::string outfmt = parser.get("convert-format"); - // sort the formats by the length of the extension, so we check the longest ones first. This // means that .zip.xml will get chosen before just .xml std::sort(m_Formats.begin(), m_Formats.end(), @@ -700,8 +774,8 @@ struct ConvertCommand : public Command if(infmt.empty()) { - std::cerr << "Couldn't guess input format from filename." << std::endl << std::endl; - std::cerr << parser.usage() << std::endl; + std::cerr << "Couldn't guess input format from filename '" << infile << "'." << std::endl + << std::endl; return 1; } @@ -720,8 +794,8 @@ struct ConvertCommand : public Command if(outfmt.empty()) { - std::cerr << "Couldn't guess output format from filename." << std::endl << std::endl; - std::cerr << parser.usage() << std::endl; + std::cerr << "Couldn't guess output format from filename '" << outfile << "'." << std::endl + << std::endl; return 1; } @@ -753,7 +827,12 @@ struct ConvertCommand : public Command struct TestCommand : public Command { - TestCommand(const GlobalEnvironment &env) : Command(env) {} +private: + std::string mode; + rdcarray args; + +public: + TestCommand() : Command() {} virtual void AddOptions(cmdline::parser &parser) { parser.set_footer( @@ -770,39 +849,66 @@ struct TestCommand : public Command virtual bool HandlesUsageManually() { return true; } virtual bool IsInternalOnly() { return false; } virtual bool IsCaptureCommand() { return false; } - virtual int Execute(cmdline::parser &parser, const CaptureOptions &) + virtual bool Parse(cmdline::parser &parser, GlobalEnvironment &) { std::vector rest = parser.rest(); + parser.set_rest({}); + if(rest.empty()) { std::cerr << "First argument must specify a test framework" << std::endl << std::endl; std::cerr << parser.usage() << std::endl; - return 1; + return false; } std::string mode = rest[0]; rest.erase(rest.begin()); + if(mode != "unit" +#if PYTHON_VERSION_MINOR > 0 + && mode != "functional" +#endif + ) + { + std::cerr << "Unsupported test frame work '" << mode << "'" << std::endl << std::endl; + std::cerr << parser.usage() << std::endl; + return false; + } + if(parser.exist("help")) rest.push_back("--help"); + args = convertArgs(rest); + + return true; + } + + virtual int Execute(const CaptureOptions &) + { if(mode == "unit") - return RENDERDOC_RunUnitTests("renderdoccmd test unit", convertArgs(rest)); + return RENDERDOC_RunUnitTests("renderdoccmd test unit", args); #if PYTHON_VERSION_MINOR > 0 else if(mode == "functional") - return RENDERDOC_RunFunctionalTests(PYTHON_VERSION_MINOR, convertArgs(rest)); + return RENDERDOC_RunFunctionalTests(PYTHON_VERSION_MINOR, args); #endif std::cerr << "Unsupported test frame work '" << mode << "'" << std::endl << std::endl; - std::cerr << parser.usage() << std::endl; return 1; } }; struct CapAltBitCommand : public Command { - CapAltBitCommand(const GlobalEnvironment &env) : Command(env) {} +private: + CaptureOptions cmdopts; + rdcarray env; + std::string debuglog; + uint32_t pid; + std::string capfile; + +public: + CapAltBitCommand() : Command() {} virtual void AddOptions(cmdline::parser &parser) { parser.add("pid", 0, ""); @@ -814,24 +920,22 @@ struct CapAltBitCommand : public Command virtual const char *Description() { return "Internal use only!"; } virtual bool IsInternalOnly() { return true; } virtual bool IsCaptureCommand() { return false; } - virtual int Execute(cmdline::parser &parser, const CaptureOptions &) + virtual bool Parse(cmdline::parser &parser, GlobalEnvironment &) { - CaptureOptions cmdopts; cmdopts.DecodeFromString(conv(parser.get("capopts"))); - RENDERDOC_InitGlobalEnv(m_Env, rdcarray()); - std::vector rest = parser.rest(); + parser.set_rest({}); + if(rest.size() % 3 != 0) { std::cerr << "Invalid generated capaltbit command rest.size() == " << rest.size() << std::endl; - return 0; + return false; } int numEnvs = int(rest.size() / 3); - rdcarray env; env.reserve(numEnvs); for(int i = 0; i < numEnvs; i++) @@ -896,12 +1000,17 @@ struct CapAltBitCommand : public Command EnvironmentModification(type, sep, rest[i * 3 + 1].c_str(), rest[i * 3 + 2].c_str())); } - std::string debuglog = parser.get("debuglog"); + debuglog = parser.get("debuglog"); + pid = parser.get("pid"); + capfile = parser.get("capfile"); + return true; + } + virtual int Execute(const CaptureOptions &) + { RENDERDOC_SetDebugLogFile(debuglog.c_str()); - ExecuteResult result = RENDERDOC_InjectIntoProcess( - parser.get("pid"), env, parser.get("capfile").c_str(), cmdopts, false); + ExecuteResult result = RENDERDOC_InjectIntoProcess(pid, env, capfile.c_str(), cmdopts, false); if(result.status == ReplayStatus::Succeeded) return result.ident; @@ -912,11 +1021,18 @@ struct CapAltBitCommand : public Command struct EmbeddedSectionCommand : public Command { +private: bool m_Extract = false; - EmbeddedSectionCommand(const GlobalEnvironment &env, bool extract) : Command(env) - { - m_Extract = extract; - } + bool list_sections = false; + std::string rdc; + std::string file; + std::string section; + bool noclobber = false; + bool lz4 = false; + bool zstd = false; + +public: + EmbeddedSectionCommand(bool extract) : Command() { m_Extract = extract; } virtual void AddOptions(cmdline::parser &parser) { parser.set_footer(""); @@ -943,15 +1059,12 @@ struct EmbeddedSectionCommand : public Command } virtual bool IsInternalOnly() { return false; } virtual bool IsCaptureCommand() { return false; } - virtual int Execute(cmdline::parser &parser, const CaptureOptions &) + virtual bool Parse(cmdline::parser &parser, GlobalEnvironment &) { - if(parser.exist("list-sections")) - { - std::cout << "Known sections:" << std::endl; - for(SectionType s : values()) - std::cout << ToStr(s) << std::endl; - return 0; - } + list_sections = parser.exist("list-sections"); + + if(list_sections) + return true; std::vector rest = parser.rest(); if(rest.empty()) @@ -959,20 +1072,32 @@ struct EmbeddedSectionCommand : public Command std::cerr << "Error: this command requires a filename to load." << std::endl << std::endl << parser.usage(); - return 0; + return false; } - std::string rdc = rest[0]; + rdc = rest[0]; rest.erase(rest.begin()); - RENDERDOC_InitGlobalEnv(m_Env, convertArgs(rest)); + parser.set_rest(rest); - std::string file = parser.get("file"); - std::string section = parser.get("section"); - bool noclobber = parser.exist("no-clobber"); - bool lz4 = !m_Extract && parser.exist("lz4"); - bool zstd = !m_Extract && parser.exist("zstd"); + file = parser.get("file"); + section = parser.get("section"); + noclobber = parser.exist("no-clobber"); + lz4 = !m_Extract && parser.exist("lz4"); + zstd = !m_Extract && parser.exist("zstd"); + + return true; + } + virtual int Execute(const CaptureOptions &) + { + if(list_sections) + { + std::cout << "Known sections:" << std::endl; + for(SectionType s : values()) + std::cout << ToStr(s) << std::endl; + return 0; + } if(zstd && lz4) { @@ -1132,9 +1257,13 @@ struct VulkanRegisterCommand : public Command private: bool m_LayerNeedUpdate = false; VulkanLayerRegistrationInfo m_Info; + bool explain = false; + bool register_layer = false; + bool user = false; + bool system = false; public: - VulkanRegisterCommand(const GlobalEnvironment &env) : Command(env) + VulkanRegisterCommand() : Command() { m_LayerNeedUpdate = RENDERDOC_NeedVulkanLayerRegistration(&m_Info); } @@ -1155,9 +1284,19 @@ public: return !m_LayerNeedUpdate; } virtual bool IsCaptureCommand() { return false; } - virtual int Execute(cmdline::parser &parser, const CaptureOptions &) + bool Parse(cmdline::parser &parser, GlobalEnvironment &) { - if(parser.exist("explain") || !parser.exist("register")) + explain = parser.exist("explain"); + register_layer = parser.exist("register"); + user = parser.exist("user"); + system = parser.exist("system"); + + return true; + } + + virtual int Execute(const CaptureOptions &) + { + if(explain || !register_layer) { if(m_LayerNeedUpdate) { @@ -1270,8 +1409,6 @@ public: return 0; } - bool user = parser.exist("user"), system = parser.exist("system"); - if(m_Info.flags & VulkanLayerFlags::UserRegisterable) { if(user) @@ -1383,7 +1520,7 @@ int renderdoccmd(GlobalEnvironment &env, std::vector &argv) // we don't need this in renderdoccmd. env.enumerateGPUs = false; - vulkan = new VulkanRegisterCommand(env); + vulkan = new VulkanRegisterCommand(); // if vulkan isn't supported, or the layer is fully registered, this command will not be listed // in help so it will be invisible @@ -1392,7 +1529,7 @@ int renderdoccmd(GlobalEnvironment &env, std::vector &argv) try { // add basic commands, and common aliases - add_command("version", new VersionCommand(env)); + add_command("version", new VersionCommand()); add_alias("--version", "version"); add_alias("-v", "version"); @@ -1400,7 +1537,7 @@ int renderdoccmd(GlobalEnvironment &env, std::vector &argv) add_alias("/version", "version"); add_alias("/v", "version"); - add_command("help", new HelpCommand(env)); + add_command("help", new HelpCommand()); add_alias("--help", "help"); add_alias("-h", "help"); @@ -1412,16 +1549,16 @@ int renderdoccmd(GlobalEnvironment &env, std::vector &argv) add_alias("/?", "help"); // add platform agnostic commands - add_command("thumb", new ThumbCommand(env)); - add_command("capture", new CaptureCommand(env)); - add_command("inject", new InjectCommand(env)); - add_command("remoteserver", new RemoteServerCommand(env)); - add_command("replay", new ReplayCommand(env)); - add_command("capaltbit", new CapAltBitCommand(env)); - add_command("test", new TestCommand(env)); - add_command("convert", new ConvertCommand(env)); - add_command("embed", new EmbeddedSectionCommand(env, false)); - add_command("extract", new EmbeddedSectionCommand(env, true)); + add_command("thumb", new ThumbCommand()); + add_command("capture", new CaptureCommand()); + add_command("inject", new InjectCommand()); + add_command("remoteserver", new RemoteServerCommand()); + add_command("replay", new ReplayCommand()); + add_command("capaltbit", new CapAltBitCommand()); + add_command("test", new TestCommand()); + add_command("convert", new ConvertCommand()); + add_command("embed", new EmbeddedSectionCommand(false)); + add_command("extract", new EmbeddedSectionCommand(true)); if(argv.size() <= 1) { @@ -1536,7 +1673,18 @@ int renderdoccmd(GlobalEnvironment &env, std::vector &argv) return 0; } - int ret = it->second->Execute(cmd, opts); + if(!it->second->Parse(cmd, env)) + { + clean_up(); + return 1; + } + + RENDERDOC_InitialiseReplay(env, convertArgs(cmd.rest())); + + int ret = it->second->Execute(opts); + + RENDERDOC_ShutdownReplay(); + clean_up(); return ret; } @@ -1544,7 +1692,9 @@ int renderdoccmd(GlobalEnvironment &env, std::vector &argv) { fprintf(stderr, "Unexpected exception: %s\n", e.what()); - exit(1); + clean_up(); + + return 1; } } diff --git a/renderdoccmd/renderdoccmd.h b/renderdoccmd/renderdoccmd.h index 8cc3ad08e..31b057123 100644 --- a/renderdoccmd/renderdoccmd.h +++ b/renderdoccmd/renderdoccmd.h @@ -29,17 +29,15 @@ struct Command { - Command(const GlobalEnvironment &env) { m_Env = env; } virtual ~Command() {} virtual void AddOptions(cmdline::parser &parser) = 0; - virtual int Execute(cmdline::parser &parser, const CaptureOptions &opts) = 0; + virtual bool Parse(cmdline::parser &parser, GlobalEnvironment &env) = 0; + virtual int Execute(const CaptureOptions &opts) = 0; virtual const char *Description() = 0; virtual bool HandlesUsageManually() { return false; } virtual bool IsInternalOnly() = 0; virtual bool IsCaptureCommand() = 0; - - GlobalEnvironment m_Env; }; extern bool usingKillSignal; diff --git a/renderdoccmd/renderdoccmd_win32.cpp b/renderdoccmd/renderdoccmd_win32.cpp index 1d0bdb896..715069948 100644 --- a/renderdoccmd/renderdoccmd_win32.cpp +++ b/renderdoccmd/renderdoccmd_win32.cpp @@ -241,15 +241,23 @@ void DisplayRendererPreview(IReplayController *renderer, TextureDisplay &display struct UpgradeCommand : public Command { - UpgradeCommand(const GlobalEnvironment &env) : Command(env) {} +private: + std::wstring wide_path; + +public: + UpgradeCommand() {} virtual void AddOptions(cmdline::parser &parser) { parser.add("path", 0, ""); } virtual const char *Description() { return "Internal use only!"; } virtual bool IsInternalOnly() { return true; } virtual bool IsCaptureCommand() { return false; } - virtual int Execute(cmdline::parser &parser, const CaptureOptions &) + virtual bool Parse(cmdline::parser &parser, GlobalEnvironment &) { - std::wstring wide_path = conv(parser.get("path")); + wide_path = conv(parser.get("path")); + return true; + } + virtual int Execute(const CaptureOptions &) + { if(wide_path.back() != '\\' && wide_path.back() != '/') wide_path += L'\\'; @@ -446,15 +454,22 @@ struct UpgradeCommand : public Command #if CRASH_HANDLER struct CrashHandlerCommand : public Command { - CrashHandlerCommand(const GlobalEnvironment &env) : Command(env) {} +private: + std::wstring pipe; + +public: + CrashHandlerCommand() {} virtual void AddOptions(cmdline::parser &parser) { parser.add("pipe", 0, ""); } virtual const char *Description() { return "Internal use only!"; } virtual bool IsInternalOnly() { return true; } virtual bool IsCaptureCommand() { return false; } - virtual int Execute(cmdline::parser &parser, const CaptureOptions &) + virtual bool Parse(cmdline::parser &parser, GlobalEnvironment &) + { + pipe = conv(parser.get("pipe")); + return true; + } + virtual int Execute(const CaptureOptions &) { - std::wstring pipe = conv(parser.get("pipe")); - CrashGenerationServer *crashServer = NULL; wchar_t tempPath[MAX_PATH] = {0}; @@ -654,7 +669,14 @@ struct CrashHandlerCommand : public Command struct GlobalHookCommand : public Command { - GlobalHookCommand(const GlobalEnvironment &env) : Command(env) {} +private: + std::wstring wpathmatch; + std::string capfile; + std::string debuglog; + std::string opts; + +public: + GlobalHookCommand() {} virtual void AddOptions(cmdline::parser &parser) { parser.add("match", 0, ""); @@ -665,13 +687,16 @@ struct GlobalHookCommand : public Command virtual const char *Description() { return "Internal use only!"; } virtual bool IsInternalOnly() { return true; } virtual bool IsCaptureCommand() { return false; } - virtual int Execute(cmdline::parser &parser, const CaptureOptions &) + virtual bool Parse(cmdline::parser &parser, GlobalEnvironment &) + { + wpathmatch = conv(parser.get("match")); + capfile = parser.get("capfile"); + debuglog = parser.get("debuglog"); + opts = parser.get("capopts"); + return true; + } + virtual int Execute(const CaptureOptions &) { - std::wstring wpathmatch = conv(parser.get("match")); - std::string capfile = parser.get("capfile"); - std::string debuglog = parser.get("debuglog"); - std::string opts = parser.get("capopts"); - CaptureOptions cmdopts; cmdopts.DecodeFromString(rdcstr(opts.c_str(), opts.size())); @@ -808,17 +833,17 @@ int main(int, char *) GlobalEnvironment env; // perform an upgrade of the UI - add_command("upgrade", new UpgradeCommand(env)); + add_command("upgrade", new UpgradeCommand()); #if CRASH_HANDLER // special WIN32 option for launching the crash handler - add_command("crashhandle", new CrashHandlerCommand(env)); + add_command("crashhandle", new CrashHandlerCommand()); #endif // this installs a global windows hook pointing at renderdocshim*.dll that filters all running // processes and loads renderdoc.dll in the target one. In any other process it unloads as soon as // possible - add_command("globalhook", new GlobalHookCommand(env)); + add_command("globalhook", new GlobalHookCommand()); return renderdoccmd(env, argv); } diff --git a/util/test/rdtest/runner.py b/util/test/rdtest/runner.py index 89cf5fc57..157ed8f7b 100644 --- a/util/test/rdtest/runner.py +++ b/util/test/rdtest/runner.py @@ -157,7 +157,7 @@ def fetch_tests(): def run_tests(test_include: str, test_exclude: str, in_process: bool, slow_tests: bool, debugger: bool): start_time = time.time() - rd.InitGlobalEnv(rd.GlobalEnvironment(), []) + rd.InitialiseReplay(rd.GlobalEnvironment(), []) # On windows, disable error reporting if 'windll' in dir(ctypes): @@ -339,6 +339,8 @@ def run_tests(test_include: str, test_exclude: str, in_process: bool, slow_tests # Print a proper footer if we got here log.rawprint('\n\n\n', with_stdout=False) + rd.ShutdownReplay() + if len(failedcases) > 0: sys.exit(1) @@ -369,12 +371,12 @@ def become_remote_server(): def internal_run_test(test_name): testcases = get_tests() - rd.InitGlobalEnv(rd.GlobalEnvironment(), []) - log.add_output(util.get_artifact_path("output.log.html")) for testclass in testcases: if testclass.__name__ == test_name: + rd.InitialiseReplay(rd.GlobalEnvironment(), []) + log.begin_test(test_name, print_header=False) util.set_current_test(test_name) @@ -389,6 +391,8 @@ def internal_run_test(test_name): log.end_test(test_name, print_footer=False) + rd.ShutdownReplay() + if suceeded: sys.exit(0) else: