Add new API calls for embedding renderdoc in apps

* This will easily let an application tell if an instance of the replay
  UI is currently connected, and if not launch it and connect. Good for
  integrating renderdoc somewhere where you can trigger a capture, and
  then have the replay UI pop up to either connect live, or open the
  most recently log.
This commit is contained in:
baldurk
2015-06-19 22:15:51 +02:00
parent de61af5921
commit b8226fc97c
9 changed files with 407 additions and 206 deletions
+17
View File
@@ -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)();
+6
View File
@@ -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;
+1
View File
@@ -232,6 +232,7 @@ class RenderDoc
void GetCurrentDriver(RDCDriver &driver, string &name);
uint32_t GetRemoteAccessIdent() const { return m_RemoteIdent; }
bool IsRemoteAccessConnected();
void Tick();
+166 -144
View File
@@ -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/<pid> 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;
+55
View File
@@ -39,10 +39,16 @@
#include <iconv.h>
// for dladdr
#include <dlfcn.h>
#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};
+3 -1
View File
@@ -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);
+86 -60
View File
@@ -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)
+51
View File
@@ -25,6 +25,7 @@
#include "os/os_specific.h"
#include "api/app/renderdoc_app.h"
#include "serialise/string_utils.h"
#include <time.h>
#include <stdio.h>
@@ -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];
+22 -1
View File
@@ -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)
{