diff --git a/renderdoc/api/app/renderdoc_app.h b/renderdoc/api/app/renderdoc_app.h index 49c19e530..81ffe9e70 100644 --- a/renderdoc/api/app/renderdoc_app.h +++ b/renderdoc/api/app/renderdoc_app.h @@ -199,6 +199,17 @@ enum InAppOverlay eOverlay_None = 0, }; +//////////////////////////////////////////////// +// !!!! IMPORTANT NOTE !!!! // +// // +// This API is pretty much experimental and // +// still in flux. The only thing guaranteed // +// to remain compatible is a call to // +// RENDERDOC_GetAPIVersion which must exactly // +// match the version you expect. // +// It will be bumped on breaking changes. // +//////////////////////////////////////////////// + // API breaking change history: // Version 1 -> 2 - strings changed from wchar_t* to char* (UTF-8) // Version 2 -> 3 - StartFrameCapture, EndFrameCapture and SetActiveWindow take @@ -260,5 +271,11 @@ typedef void (RENDERDOC_CC *pRENDERDOC_SetCaptureKeys)(KeyButton *keys, int num) extern "C" RENDERDOC_API void RENDERDOC_CC RENDERDOC_InitRemoteAccess(uint32_t *ident); typedef void (RENDERDOC_CC *pRENDERDOC_InitRemoteAccess)(uint32_t *ident); +extern "C" RENDERDOC_API uint32_t RENDERDOC_CC RENDERDOC_IsRemoteAccessConnected(); +typedef uint32_t (RENDERDOC_CC *pRENDERDOC_IsRemoteAccessConnected)(); + +extern "C" RENDERDOC_API uint32_t RENDERDOC_CC RENDERDOC_LaunchReplayUI(uint32_t connectRemoteAccess, const char *cmdline); +typedef uint32_t (RENDERDOC_CC *pRENDERDOC_LaunchReplayUI)(uint32_t connectRemoteAccess, const char *cmdline); + extern "C" RENDERDOC_API void RENDERDOC_CC RENDERDOC_UnloadCrashHandler(); typedef void (RENDERDOC_CC *pRENDERDOC_UnloadCrashHandler)(); diff --git a/renderdoc/core/core.cpp b/renderdoc/core/core.cpp index a49e9df41..a5b4f3675 100644 --- a/renderdoc/core/core.cpp +++ b/renderdoc/core/core.cpp @@ -394,6 +394,12 @@ bool RenderDoc::EndFrameCapture(void *dev, void *wnd) return it->second.FrameCapturer->EndFrameCapture(dev, wnd); } +bool RenderDoc::IsRemoteAccessConnected() +{ + SCOPED_LOCK(RenderDoc::Inst().m_SingleClientLock); + return !RenderDoc::Inst().m_SingleClientName.empty(); +} + void RenderDoc::Tick() { static bool prev_focus = false; diff --git a/renderdoc/core/core.h b/renderdoc/core/core.h index 3a13f8b11..e85d7d25d 100644 --- a/renderdoc/core/core.h +++ b/renderdoc/core/core.h @@ -232,6 +232,7 @@ class RenderDoc void GetCurrentDriver(RDCDriver &driver, string &name); uint32_t GetRemoteAccessIdent() const { return m_RemoteIdent; } + bool IsRemoteAccessConnected(); void Tick(); diff --git a/renderdoc/os/linux/linux_process.cpp b/renderdoc/os/linux/linux_process.cpp index dc4b2c6da..7295fadb7 100644 --- a/renderdoc/os/linux/linux_process.cpp +++ b/renderdoc/os/linux/linux_process.cpp @@ -34,13 +34,176 @@ #include "serialise/string_utils.h" +pid_t RunProcess(const char *app, const char *workingDir, const char *cmdLine, char *const *envp) +{ + if(!app) return (pid_t)0; + + int argc = 0; + char *emptyargv[] = { NULL }; + char **argv = emptyargv; + + const char *c = cmdLine; + + // parse command line into argv[], similar to how bash would + if(cmdLine) + { + argc = 1; + + // get a rough upper bound on the number of arguments + while(*c) + { + if(*c == ' ' || *c == '\t') argc++; + c++; + } + + argv = new char*[argc+2]; + + c = cmdLine; + + string a; + + argc = 0; // current argument we're fetching + + // argv[0] is the application name, by convention + size_t len = strlen(app)+1; + argv[argc] = new char[len]; + strcpy(argv[argc], app); + + argc++; + + bool dquot = false, squot = false; // are we inside ''s or ""s + while(*c) + { + if(!dquot && !squot && (*c == ' ' || *c == '\t')) + { + if(!a.empty()) + { + // if we've fetched some number of non-space characters + argv[argc] = new char[a.length()+1]; + memcpy(argv[argc], a.c_str(), a.length()+1); + argc++; + } + + a = ""; + } + else if(!dquot && *c == '"') + { + dquot = true; + } + else if(!squot && *c == '\'') + { + squot = true; + } + else if(dquot && *c == '"') + { + dquot = false; + } + else if(squot && *c == '\'') + { + squot = false; + } + else if(squot) + { + // single quotes don't escape, just copy literally until we leave single quote mode + a.push_back(*c); + } + else if(dquot) + { + // handle escaping + if(*c == '\\') + { + c++; + if(*c) + { + a.push_back(*c); + } + else + { + RDCERR("Malformed command line:\n%s", cmdLine); + return 0; + } + } + else + { + a.push_back(*c); + } + } + else + { + a.push_back(*c); + } + + c++; + } + + if(!a.empty()) + { + // if we've fetched some number of non-space characters + argv[argc] = new char[a.length()+1]; + memcpy(argv[argc], a.c_str(), a.length()+1); + argc++; + } + + argv[argc] = NULL; + + if(squot || dquot) + { + RDCERR("Malformed command line\n%s", cmdLine); + return 0; + } + } + + pid_t childPid = fork(); + if(childPid == 0) + { + if(workingDir) + { + chdir(workingDir); + } + else + { + string exedir = app; + chdir(dirname((char *)exedir.c_str())); + } + + execve(app, argv, envp); + exit(0); + } + + char **argv_delete = argv; + + if(argv != emptyargv) + { + while(*argv) + { + delete[] *argv; + argv++; + } + + delete[] argv_delete; + } + + return childPid; +} + uint32_t Process::InjectIntoProcess(uint32_t pid, const char *logfile, const CaptureOptions *opts, bool waitForExit) { RDCUNIMPLEMENTED("Injecting into already running processes on linux"); return 0; } -uint32_t Process::CreateAndInjectIntoProcess(const char *app, const char *workingDir, const char *cmdLine, +uint32_t Process::LaunchProcess(const char *app, const char *workingDir, const char *cmdLine) +{ + if(app == NULL || app[0] == 0) + { + RDCERR("Invalid empty 'app'"); + return 0; + } + + return (uint32_t)RunProcess(app, workingDir, cmdLine, environ); +} + +uint32_t Process::LaunchAndInjectIntoProcess(const char *app, const char *workingDir, const char *cmdLine, const char *logfile, const CaptureOptions *opts, bool waitForExit) { if(app == NULL || app[0] == 0) @@ -160,140 +323,11 @@ uint32_t Process::CreateAndInjectIntoProcess(const char *app, const char *workin envp[i] = NULL; } - int argc = 0; - char *emptyargv[] = { NULL }; - char **argv = emptyargv; - - const char *c = cmdLine; - - // parse command line into argv[], similar to how bash would - if(cmdLine) - { - argc = 1; - - // get a rough upper bound on the number of arguments - while(*c) - { - if(*c == ' ' || *c == '\t') argc++; - c++; - } - - argv = new char*[argc+2]; - - c = cmdLine; - - string a; - - argc = 0; // current argument we're fetching - - // argv[0] is the application name, by convention - size_t len = strlen(app)+1; - argv[argc] = new char[len]; - strcpy(argv[argc], app); - - argc++; - - bool dquot = false, squot = false; // are we inside ''s or ""s - while(*c) - { - if(!dquot && !squot && (*c == ' ' || *c == '\t')) - { - if(!a.empty()) - { - // if we've fetched some number of non-space characters - argv[argc] = new char[a.length()+1]; - memcpy(argv[argc], a.c_str(), a.length()+1); - argc++; - } - - a = ""; - } - else if(!dquot && *c == '"') - { - dquot = true; - } - else if(!squot && *c == '\'') - { - squot = true; - } - else if(dquot && *c == '"') - { - dquot = false; - } - else if(squot && *c == '\'') - { - squot = false; - } - else if(squot) - { - // single quotes don't escape, just copy literally until we leave single quote mode - a.push_back(*c); - } - else if(dquot) - { - // handle escaping - if(*c == '\\') - { - c++; - if(*c) - { - a.push_back(*c); - } - else - { - RDCERR("Malformed command line:\n%s", cmdLine); - return 0; - } - } - else - { - a.push_back(*c); - } - } - else - { - a.push_back(*c); - } - - c++; - } - - if(!a.empty()) - { - // if we've fetched some number of non-space characters - argv[argc] = new char[a.length()+1]; - memcpy(argv[argc], a.c_str(), a.length()+1); - argc++; - } - - argv[argc] = NULL; - - if(squot || dquot) - { - RDCERR("Malformed command line\n%s", cmdLine); - return 0; - } - } + pid_t childPid = RunProcess(app, workingDir, cmdLine, envp); int ret = 0; - pid_t childPid = fork(); - if(childPid == 0) - { - if(workingDir) - { - chdir(workingDir); - } - else - { - string exedir = app; - chdir(dirname((char *)exedir.c_str())); - } - - execve(app, argv, envp); - exit(0); - } - else if(childPid > 0) + if(childPid != (pid_t)0) { // wait for child to have /proc/ and read out tcp socket usleep(1000); @@ -343,20 +377,8 @@ uint32_t Process::CreateAndInjectIntoProcess(const char *app, const char *workin } } - char **argv_delete = argv; char **envp_delete = envp; - if(argv != emptyargv) - { - while(*argv) - { - delete[] *argv; - argv++; - } - - delete[] argv_delete; - } - while(*envp) { delete[] *envp; diff --git a/renderdoc/os/linux/linux_stringio.cpp b/renderdoc/os/linux/linux_stringio.cpp index 66471a41c..2d0dabed8 100644 --- a/renderdoc/os/linux/linux_stringio.cpp +++ b/renderdoc/os/linux/linux_stringio.cpp @@ -39,10 +39,16 @@ #include +// for dladdr +#include + #include "common/string_utils.h" #include "common/threading.h" using std::string; +// gives us an address to identify this so with +static int soLocator=0; + namespace Keyboard { void Init() @@ -147,6 +153,55 @@ namespace FileIO selfName = string(path); } + string GetReplayAppFilename() + { + // look up the shared object's path via dladdr + Dl_info info; + dladdr((void *)&soLocator, &info); + string path = info.dli_fname ? info.dli_fname : ""; + path = dirname(path); + string replay = path + "/qrenderdoc"; + + FILE *f = FileIO::fopen(replay.c_str(), "r"); + if(f) + { + FileIO::fclose(f); + return replay; + } + + // if it's not in the same directory, try in a sibling /bin + // e.g. /foo/bar/lib/librenderdoc.so -> /foo/bar/bin/qrenderdoc + replay = path + "/../bin/qrenderdoc"; + + f = FileIO::fopen(replay.c_str(), "r"); + if(f) + { + FileIO::fclose(f); + return replay; + } + + // random guesses! + const char *guess[] = { + "/opt/renderdoc/qrenderdoc", + "/opt/renderdoc/bin/qrenderdoc", + "/usr/local/bin/qrenderdoc", + "/usr/bin/qrenderdoc" + }; + + for(size_t i=0; i < ARRAY_COUNT(guess); i++) + { + f = FileIO::fopen(guess[i], "r"); + if(f) + { + FileIO::fclose(f); + return guess[i]; + } + } + + // out of ideas + return ""; + } + void GetDefaultFiles(const char *logBaseName, string &capture_filename, string &logging_filename, string &target) { char path[2048] = {0}; diff --git a/renderdoc/os/os_specific.h b/renderdoc/os/os_specific.h index 761bf3472..754da391a 100644 --- a/renderdoc/os/os_specific.h +++ b/renderdoc/os/os_specific.h @@ -47,7 +47,8 @@ namespace Process { void StartGlobalHook(const char *pathmatch, const char *logfile, const CaptureOptions *opts); uint32_t InjectIntoProcess(uint32_t pid, const char *logfile, const CaptureOptions *opts, bool waitForExit); - uint32_t CreateAndInjectIntoProcess(const char *app, const char *workingDir, const char *cmdLine, + uint32_t LaunchProcess(const char *app, const char *workingDir, const char *cmdLine); + uint32_t LaunchAndInjectIntoProcess(const char *app, const char *workingDir, const char *cmdLine, const char *logfile, const CaptureOptions *opts, bool waitForExit); void *GetFunctionAddress(const char *module, const char *function); uint32_t GetCurrentPID(); @@ -174,6 +175,7 @@ namespace FileIO { void GetDefaultFiles(const char *logBaseName, string &capture_filename, string &logging_filename, string &target); string GetAppFolderFilename(string filename); + string GetReplayAppFilename(); void GetExecutableFilename(string &selfName); diff --git a/renderdoc/os/win32/win32_process.cpp b/renderdoc/os/win32/win32_process.cpp index 04f3ca9e2..71ff4a4a2 100644 --- a/renderdoc/os/win32/win32_process.cpp +++ b/renderdoc/os/win32/win32_process.cpp @@ -181,6 +181,74 @@ void InjectFunctionCall(HANDLE hProcess, uintptr_t renderdoc_remote, const char VirtualFreeEx(hProcess, remoteMem, dataLen, MEM_RELEASE); } +static PROCESS_INFORMATION RunProcess(const char *app, const char *workingDir, const char *cmdLine) +{ + PROCESS_INFORMATION pi; + STARTUPINFO si; + SECURITY_ATTRIBUTES pSec; + SECURITY_ATTRIBUTES tSec; + + RDCEraseEl(pi); + RDCEraseEl(si); + RDCEraseEl(pSec); + RDCEraseEl(tSec); + + pSec.nLength = sizeof(pSec); + tSec.nLength = sizeof(tSec); + + wstring workdir = L""; + + if(workingDir != NULL && workingDir[0] != 0) + workdir = StringFormat::UTF82Wide(string(workingDir)); + else + workdir = StringFormat::UTF82Wide(dirname(string(app))); + + wchar_t *paramsAlloc = NULL; + + wstring wapp = StringFormat::UTF82Wide(string(app)); + + // CreateProcessW can modify the params, need space. + size_t len = wapp.length()+10; + + wstring wcmd = L""; + + if(cmdLine != NULL && cmdLine[0] != 0) + { + wcmd = StringFormat::UTF82Wide(string(cmdLine)); + len += wcmd.length(); + } + + paramsAlloc = new wchar_t[len]; + + RDCEraseMem(paramsAlloc, len*sizeof(wchar_t)); + + wcscpy_s(paramsAlloc, len, L"\""); + wcscat_s(paramsAlloc, len, wapp.c_str()); + wcscat_s(paramsAlloc, len, L"\""); + + if(cmdLine != NULL && cmdLine[0] != 0) + { + wcscat_s(paramsAlloc, len, L" "); + wcscat_s(paramsAlloc, len, wcmd.c_str()); + } + + BOOL retValue = CreateProcessW(NULL, paramsAlloc, + &pSec, &tSec, false, CREATE_SUSPENDED, + NULL, workdir.c_str(), &si, &pi); + + SAFE_DELETE_ARRAY(paramsAlloc); + + if (!retValue) + { + RDCERR("Process %s could not be loaded.", app); + CloseHandle(pi.hProcess); + CloseHandle(pi.hThread); + RDCEraseEl(pi); + } + + return pi; +} + uint32_t Process::InjectIntoProcess(uint32_t pid, const char *logfile, const CaptureOptions *opts, bool waitForExit) { CaptureOptions options; @@ -353,7 +421,21 @@ uint32_t Process::InjectIntoProcess(uint32_t pid, const char *logfile, const Cap return remoteident; } -uint32_t Process::CreateAndInjectIntoProcess(const char *app, const char *workingDir, const char *cmdLine, +uint32_t Process::LaunchProcess(const char *app, const char *workingDir, const char *cmdLine) +{ + PROCESS_INFORMATION pi = RunProcess(app, workingDir, cmdLine); + + if(pi.dwProcessId == 0) + return 0; + + ResumeThread(pi.hThread); + CloseHandle(pi.hThread); + CloseHandle(pi.hProcess); + + return pi.dwProcessId; +} + +uint32_t Process::LaunchAndInjectIntoProcess(const char *app, const char *workingDir, const char *cmdLine, const char *logfile, const CaptureOptions *opts, bool waitForExit) { void *func = GetProcAddress(GetModuleHandleA("renderdoc.dll"), "RENDERDOC_SetLogFile"); @@ -364,70 +446,14 @@ uint32_t Process::CreateAndInjectIntoProcess(const char *app, const char *workin return 0; } - PROCESS_INFORMATION pi; - STARTUPINFO si; - SECURITY_ATTRIBUTES pSec; - SECURITY_ATTRIBUTES tSec; + PROCESS_INFORMATION pi = RunProcess(app, workingDir, cmdLine); - RDCEraseEl(pi); - RDCEraseEl(si); - RDCEraseEl(pSec); - RDCEraseEl(tSec); - - pSec.nLength = sizeof(pSec); - tSec.nLength = sizeof(tSec); - - wstring workdir = L""; - - if(workingDir != NULL && workingDir[0] != 0) - workdir = StringFormat::UTF82Wide(string(workingDir)); - else - workdir = StringFormat::UTF82Wide(dirname(string(app))); - - wchar_t *paramsAlloc = NULL; - - wstring wapp = StringFormat::UTF82Wide(string(app)); - - // CreateProcessW can modify the params, need space. - size_t len = wapp.length()+10; - - wstring wcmd = L""; - - if(cmdLine != NULL && cmdLine[0] != 0) - { - wcmd = StringFormat::UTF82Wide(string(cmdLine)); - len += wcmd.length(); - } - - paramsAlloc = new wchar_t[len]; - - RDCEraseMem(paramsAlloc, len*sizeof(wchar_t)); - - wcscpy_s(paramsAlloc, len, L"\""); - wcscat_s(paramsAlloc, len, wapp.c_str()); - wcscat_s(paramsAlloc, len, L"\""); - - if(cmdLine != NULL && cmdLine[0] != 0) - { - wcscat_s(paramsAlloc, len, L" "); - wcscat_s(paramsAlloc, len, wcmd.c_str()); - } - - BOOL retValue = CreateProcessW(NULL, paramsAlloc, - &pSec, &tSec, false, CREATE_SUSPENDED, - NULL, workdir.c_str(), &si, &pi); - - SAFE_DELETE_ARRAY(paramsAlloc); + if(pi.dwProcessId == 0) + return 0; // don't need it CloseHandle(pi.hProcess); - if (!retValue) - { - RDCERR("Process %s could not be loaded.", app); - return 0; - } - uint32_t ret = InjectIntoProcess(pi.dwProcessId, logfile, opts, false); if(ret == 0) diff --git a/renderdoc/os/win32/win32_stringio.cpp b/renderdoc/os/win32/win32_stringio.cpp index 3852cbe9f..f0f809a75 100644 --- a/renderdoc/os/win32/win32_stringio.cpp +++ b/renderdoc/os/win32/win32_stringio.cpp @@ -25,6 +25,7 @@ #include "os/os_specific.h" #include "api/app/renderdoc_app.h" +#include "serialise/string_utils.h" #include #include @@ -159,6 +160,56 @@ namespace FileIO selfName = StringFormat::Wide2UTF8(wstring(curFile)); } + string GetReplayAppFilename() + { + HMODULE hModule = NULL; + GetModuleHandleEx( + GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS|GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT, + (LPCTSTR)&dllLocator, + &hModule); + wchar_t curFile[512] = {0}; + GetModuleFileNameW(hModule, curFile, 511); + + string path = StringFormat::Wide2UTF8(wstring(curFile)); + path = dirname(path); + string exe = path + "/renderdocui.exe"; + + FILE *f = FileIO::fopen(exe.c_str(), "rb"); + if(f) + { + FileIO::fclose(f); + return exe; + } + + // if renderdocui.exe doesn't live in the same dir, we must be in x86/ + // so look one up the tree. + exe = path + "/../renderdocui.exe"; + + f = FileIO::fopen(exe.c_str(), "rb"); + if(f) + { + FileIO::fclose(f); + return exe; + } + + // if we didn't find the exe at all, we must not be in a standard + // distributed renderdoc package. On windows we can check in the registry + // to try and find the installed path. + + DWORD type = 0; + DWORD dataSize = sizeof(curFile); + RDCEraseEl(curFile); + RegGetValueW(HKEY_CLASSES_ROOT, L"RenderDoc.RDCCapture.1\\DefaultIcon", NULL, RRF_RT_ANY, + &type, (void *)curFile, &dataSize); + + if(type == REG_EXPAND_SZ || type == REG_SZ) + { + return StringFormat::Wide2UTF8(wstring(curFile)); + } + + return ""; + } + void GetDefaultFiles(const char *logBaseName, string &capture_filename, string &logging_filename, string &target) { wchar_t temp_filename[MAX_PATH]; diff --git a/renderdoc/replay/entry_points.cpp b/renderdoc/replay/entry_points.cpp index 82c1fba79..0a3f969bc 100644 --- a/renderdoc/replay/entry_points.cpp +++ b/renderdoc/replay/entry_points.cpp @@ -236,7 +236,7 @@ extern "C" RENDERDOC_API uint32_t RENDERDOC_CC RENDERDOC_ExecuteAndInject(const char *app, const char *workingDir, const char *cmdLine, const char *logfile, const CaptureOptions *opts, bool32 waitForExit) { - return Process::CreateAndInjectIntoProcess(app, workingDir, cmdLine, logfile, opts, waitForExit != 0); + return Process::LaunchAndInjectIntoProcess(app, workingDir, cmdLine, logfile, opts, waitForExit != 0); } extern "C" RENDERDOC_API @@ -369,6 +369,27 @@ void RENDERDOC_CC RENDERDOC_InitRemoteAccess(uint32_t *ident) if(ident) *ident = RenderDoc::Inst().GetRemoteAccessIdent(); } +extern "C" RENDERDOC_API +uint32_t RENDERDOC_CC RENDERDOC_IsRemoteAccessConnected() +{ + return RenderDoc::Inst().IsRemoteAccessConnected(); +} + +extern "C" RENDERDOC_API +uint32_t RENDERDOC_CC RENDERDOC_LaunchReplayUI(uint32_t connectRemoteAccess, const char *cmdline) +{ + string replayapp = FileIO::GetReplayAppFilename(); + + if(replayapp.empty()) + return 0; + + string cmd = cmdline ? cmdline : ""; + if(connectRemoteAccess) + cmd += StringFormat::Fmt(" --remoteaccess localhost:%u", RenderDoc::Inst().GetRemoteAccessIdent()); + + return Process::LaunchProcess(replayapp.c_str(), "", cmd.c_str()); +} + extern "C" RENDERDOC_API uint32_t RENDERDOC_CC RENDERDOC_EnumerateRemoteConnections(const char *host, uint32_t *idents) {