Add an abstracted interface around android-specific handling

* This makes it easier to use the same kind of interface to manage other kinds
  of devices.
This commit is contained in:
baldurk
2019-07-29 16:54:36 +01:00
parent 06f2e61b8f
commit e2704fa2eb
34 changed files with 2340 additions and 1492 deletions
+2 -1
View File
@@ -31,4 +31,5 @@ In order to help with this, the examples are organised such that most code is wr
fetch_counters
save_texture
decode_mesh
display_window
display_window
remote_capture
@@ -0,0 +1,196 @@
import renderdoc as rd
import threading
import time
# This sample is intended as an example of how to do remote capture and replay
# as well as using device protocols to automatically enumerate remote targets.
#
# It is not complete since it requires filling in with custom logic to select
# the executable and trigger the capture at the desired time
raise RuntimeError("This sample should not be run directly, read the source")
protocols = rd.GetSupportedDeviceProtocols()
print(f"Supported device protocols: {protocols}")
# Protocols are optional - they allow automatic detection and management of
# devices.
if protocol_to_use is not None:
# the protocol must be supported
if protocol_to_use not in protocols:
raise RuntimeError(f"{protocol_to_use} protocol not supported")
protocol = rd.GetDeviceProtocolController(protocol_to_use)
devices = protocol.GetDevices()
if len(devices) == 0:
raise RuntimeError(f"no {protocol_to_use} devices connected")
# Choose the first device
dev = devices[0]
name = protocol.GetFriendlyName(dev)
print(f"Running test on {dev} - named {name}")
URL = protocol.GetProtocolName() + "://" + dev
# Protocols can enumerate devices which are not supported. Capture/replay
# is not guaranteed to work on these devices
if not protocol.IsSupported(URL):
raise RuntimeError(f"{dev} doesn't support capture/replay - too old?")
# Protocol devices may be single-use and not support multiple captured programs
# If so, trying to execute a program for capture is an error
if not protocol.SupportsMultiplePrograms(URL):
# check to see if anything is running. Just use the URL
ident = rd.EnumerateRemoteTargets(URL, 0)
if ident != 0:
raise RuntimeError(f"{name} already has a program running on {ident}")
else:
# If you're not using a protocol then the URL can simply be a hostname.
# The remote server must be running already - how that is done is up
# to you. Everything else will work the same over a normal TCP connection
protocol = None
URL = hostname
# Let's try to connect
status,remote = rd.CreateRemoteServerConnection(URL)
if status == rd.ReplayStatus.NetworkIOFailed and protocol is not None:
# If there's just no I/O, most likely the server is not running. If we have
# a protocol, we can try to start the remote server
print("Couldn't connect to remote server, trying to start it")
status = protocol.StartRemoteServer(URL)
if status != rd.ReplayStatus.Succeeded:
raise RuntimeError(f"Couldn't launch remote server, got error {str(status)}")
# Try to connect again!
status,remote = rd.CreateRemoteServerConnection(URL)
if status != rd.ReplayStatus.Succeeded:
raise RuntimeError(f"Couldn't connect to remote server, got error {str(status)}")
# We now have a remote connection. This works regardless of whether it's a device
# with a protocol or not. In fact we are done with the protocol at this point
protocol = None
print("Got connection to remote server")
# GetHomeFolder() gives you a good default path to start with.
# ListFolder() lists the contents of a folder and can recursively
# browse the remote filesystem.
home = remote.GetHomeFolder()
paths = remote.ListFolder(home)
print(f"Executables in home folder '{home}':")
for p in paths:
print(" - " + p.filename)
# Select your executable, perhaps hardcoded or browsing using the above
# functions
exe,workingDir,cmdLine,env,opts = select_executable()
print(f"Running {exe}")
result = remote.ExecuteAndInject(exe, workingDir, cmdLine, env, opts)
if result.status != rd.ReplayStatus.Succeeded:
remote.ShutdownServerAndConnection()
raise RuntimeError(f"Couldn't launch {exe}, got error {str(result.status)}")
# Spin up a thread to keep the remote server connection alive while we make a capture,
# as it will time out after 5 seconds of inactivity
def ping_remote(remote, kill):
success = True
while success and not kill.is_set():
success = remote.Ping()
time.sleep(1)
kill = threading.Event()
ping_thread = threading.Thread(target=ping_remote, args=(remote,kill))
ping_thread.start()
# Create target control connection
target = rd.CreateTargetControl(URL, result.ident, 'remote_capture.py', True)
if target is None:
kill.set()
ping_thread.join()
remote.ShutdownServerAndConnection()
raise RuntimeError(f"Couldn't connect to target control for {exe}")
print("Connected - waiting for desired capture")
# Wait for the capture condition we want
capture_condition()
print("Triggering capture")
target.TriggerCapture(1)
# Pump messages, keep waiting until we get a capture message. Time out after 30 seconds
msg = None
start = time.clock()
while msg is None or msg.type != rd.TargetControlMessageType.NewCapture:
msg = target.ReceiveMessage(None)
if time.clock() - start > 30:
break
# Close the target connection, we're done either way
target.Shutdown()
target = None
# Stop the background ping thread
kill.set()
ping_thread.join()
# If we didn't get a capture, error now
if msg.type != rd.TargetControlMessageType.NewCapture:
remote.ShutdownServerAndConnection()
raise RuntimeError("Didn't get new capture notification after triggering capture")
cap_path = msg.newCapture.path
cap_id = msg.newCapture.captureId
print(f"Got new capture at {cap_path} which is frame {msg.newCapture.frameNumber} with {msg.newCapture.api}")
# We could save the capture locally
# remote.CopyCaptureFromRemote(cap_path, local_path, None)
# Open a replay. It's recommended to set no proxy preference, but you could
# call remote.LocalProxies and choose an index.
#
# The path must be remote - if the capture isn't freshly created then you need
# to copy it with remote.CopyCaptureToRemote()
status,controller = remote.OpenCapture(rd.RemoteServer.NoPreference, cap_path, None)
if status != rd.ReplayStatus.Succeeded:
remote.ShutdownServerAndConnection()
raise RuntimeError(f"Couldn't open {cap_path}, got error {str(result.status)}")
# We can now use replay as normal.
#
# The replay is tunnelled over the remote connection, so you don't have to keep
# pinging the remote connection while using the controller. Use of the remote
# connection and controller can be interleaved though you should only access
# them from one thread at once. If they are both unused for 5 seconds though,
# the timeout will happen, so if the controller is idle it's advisable to ping
# the remote connection
sampleCode(controller)
print("Shutting down")
controller.Shutdown()
# We can still use remote here - e.g. capture again, replay something else,
# save the capture, etc
remote.ShutdownServerAndConnection()
@@ -0,0 +1,118 @@
Remote Capture and Replay
=========================
This example is a bit different since it's not ready-to-run. It provides a template for how you can capture and replay on a remote machine, instead of the local machine. It also shows how to use device protocols to automatically manage devices.
First we can enumerate which device protocols are currently supported.
.. highlight:: python
.. code:: python
protocols = rd.GetSupportedDeviceProtocols()
Each string in the list corresponds to a protocol that can be used for managing devices. If we're using one we can call :py:func:`~renderdoc.GetDeviceProtocolController` passing the protocol name and retrieve the controller.
The controller provides a few methods for managing devices. First we can call :py:meth:`~renderdoc.DeviceProtocolController.GetDevices` to return a list of device IDs. The format of these device IDs is protocol-dependent but will be equivalent to a normal hostname. Devices may have human-readable names obtainable via :py:meth:`~renderdoc.DeviceProtocolController.GetFriendlyName`.
.. highlight:: python
.. code:: python
protocol = rd.GetDeviceProtocolController(protocol_to_use)
devices = protocol.GetDevices()
if len(devices) == 0:
raise RuntimeError(f"no {protocol_to_use} devices connected")
# Choose the first device
dev = devices[0]
name = protocol.GetFriendlyName(dev)
print(f"Running test on {dev} - named {name}")
URL = protocol.GetProtocolName() + "://" + dev
The URL will be used the same as we would use a hostname, when connecting for target control or remote servers.
Note that protocols may have additional restrictions - be sure to check :py:meth:`~renderdoc.DeviceProtocolController.IsSupported` to check if the device is expected to function at all, and :py:meth:`~renderdoc.DeviceProtocolController.SupportsMultiplePrograms` to see if it supports launching multiple programs. If multiple programs are not supported, you should ensure all running capturable programs are closed before launching a new one.
To begin with we create a remote server connection using :py:func:`~renderdoc.CreateRemoteServerConnection`. The URL is as constructed above for protocol-based connections, or a simple hostname/IP if we're connecting directly to remote machine.
If the connection fails, normally we must fail but if we have a device protocol available we can attempt to launch the remote server automatically using :py:meth:`~renderdoc.DeviceProtocolController.StartRemoteServer`.
.. highlight:: python
.. code:: python
if status == rd.ReplayStatus.NetworkIOFailed and protocol is not None:
# If there's just no I/O, most likely the server is not running. If we have
# a protocol, we can try to start the remote server
print("Couldn't connect to remote server, trying to start it")
status = protocol.StartRemoteServer(URL)
if status != rd.ReplayStatus.Succeeded:
raise RuntimeError(f"Couldn't launch remote server, got error {str(status)}")
# Try to connect again!
status,remote = rd.CreateRemoteServerConnection(URL)
.. note::
The remote server connection has a default timeout of 5 seconds. If the connection is unused for 5 seconds, the other side will disconnect and subsequent use of the interface will fail.
Once we have a remote server connection, we can browse the remote filesystem for the executable we want to launch using :py:meth:`~renderdoc.RemoteServer.GetHomeFolder` and :py:meth:`~renderdoc.RemoteServer.ListFolder`.
Then once we've selected the executable, we can launch the remote program for capturing with :py:meth:`~renderdoc.RemoteServer.ExecuteAndInject`. This function is almost identical to the local :py:func:`~renderdoc.ExecuteAndInject` except that it is not possible to wait for the program to exit.
In our sample, we now place the remote server connection on a background thread that will ping it each second to keep the connection alive while we use a target control connection to trigger a capture in the application.
.. highlight:: python
.. code:: python
def ping_remote(remote, kill):
success = True
while success and not kill.is_set():
success = remote.Ping()
time.sleep(1)
kill = threading.Event()
ping_thread = threading.Thread(target=ping_remote, args=(remote,kill))
ping_thread.start()
To connect to and control an application we use :py:func:`~renderdoc.CreateTargetControl` with the URL as before and the ident returned from :py:meth:`~renderdoc.RemoteServer.ExecuteAndInject`.
.. highlight:: python
.. code:: python
target = rd.CreateTargetControl(URL, result.ident, 'remote_capture.py', True)
# Here we wait for whichever condition you want
target.TriggerCapture(1)
There are a couple of ways to trigger a capture, both :py:meth:`~renderdoc.TargetControl.TriggerCapture` and :py:meth:`~renderdoc.TargetControl.QueueCapture` depending on whether you want a time-based or frame-based trigger. The application itself can also use the in-application API to trigger a capture.
The target control connection can be intermittently polled for messages using :py:meth:`~renderdoc.TargetControl.ReceiveMessage`, which keeps the connection alive and will return any new information such as the data for a new capture that has been created. A message of type :py:data:`~renderdoc.TargetControlMessageType.NewCapture` indicates a new capture has been created, and :py:data:`~renderdoc.TargetControlMessage.newCapture` contains the information including the path.
.. highlight:: python
.. code:: python
msg = target.ReceiveMessage(None)
# Once msg.type == rd.TargetControlMessageType.NewCapture has been retrieved
cap_path = msg.newCapture.path
cap_id = msg.newCapture.captureId
Once the capture has been found we are finished with the target control connection so we can shut it down and stop the background thread that was keeping the remote server connection alive. Using the remote server connection we can copy the capture back to the local machine with :py:meth:`~renderdoc.RemoteServer.CopyCaptureFromRemote`. Similarly if we wanted to load a previously made capture that wasn't on the remote machine :py:meth:`~renderdoc.RemoteServer.CopyCaptureToRemote` would be useful to copy it ready to be opened.
Finally to open the capture we use, and that returns a :py:class:`~renderdoc.ReplayController` which can be used as normal and will tunnel over the remote server connection. It can be useful to intermittently ping the remote server connection to check that it's still valid, and remote server and controller calls can be interleaved as long as they don't overlap on multiple threads.
Example Source
--------------
.. only:: html and not htmlhelp
:download:`Download the example script <remote_capture.py>`.
.. literalinclude:: remote_capture.py
+1 -1
View File
@@ -5,4 +5,4 @@ Enums and Data Structures
:members:
:undoc-members:
:imported-members:
:exclude-members: free_functions__, enum_constants__, name_match__startswith__D3D11, name_match__startswith__D3D12, name_match__startswith__VK, name_match__startswith__GL, name_match__startswith__rdcarray_of, rdcstr, bytebuf, ReplayController, ReplayOutput, TargetControl, RemoteServer, CaptureFile, Viewport, Scissor, BlendEquation, ColorBlend, StencilFace, BoundResource, BoundResourceArray, BoundVBuffer, BoundCBuffer, VertexInputAttribute, PipeState
:exclude-members: free_functions__, enum_constants__, name_match__startswith__D3D11, name_match__startswith__D3D12, name_match__startswith__VK, name_match__startswith__GL, name_match__startswith__rdcarray_of, rdcstr, bytebuf, ReplayController, ReplayOutput, TargetControl, RemoteServer, DeviceProtocolController, CaptureFile, Viewport, Scissor, BlendEquation, ColorBlend, StencilFace, BoundResource, BoundResourceArray, BoundVBuffer, BoundCBuffer, VertexInputAttribute, PipeState
+6 -1
View File
@@ -20,9 +20,14 @@ Remote Servers
--------------
.. autofunction:: renderdoc.CreateRemoteServerConnection
.. autofunction:: renderdoc.GetDefaultRemoteServerPort
.. autofunction:: renderdoc.BecomeRemoteServer
Device Protocols
----------------
.. autofunction:: renderdoc.GetSupportedDeviceProtocols
.. autofunction:: renderdoc.GetDeviceProtocolController
Local Execution & Injection
---------------------------
@@ -33,6 +33,13 @@ RemoteServer
:members:
:undoc-members:
DeviceProtocolController
------------------------
.. autoclass:: renderdoc.DeviceProtocolController
:members:
:undoc-members:
CaptureFile
-----------
+35 -18
View File
@@ -259,15 +259,8 @@ void PersistantConfig::RemoveRemoteHost(RemoteHost host)
}
}
void PersistantConfig::AddAndroidHosts()
void PersistantConfig::UpdateEnumeratedProtocolDevices()
{
QMutexLocker autolock(&RemoteHostLock);
QMap<rdcstr, RemoteHost> oldHosts;
for(int i = RemoteHostList.count() - 1; i >= 0; i--)
if(RemoteHostList[i].IsADB())
oldHosts[RemoteHostList[i].Hostname()] = RemoteHostList.takeAt(i);
QString androidSDKPath = (!Android_SDKPath.isEmpty() && QFile::exists(Android_SDKPath))
? QString(Android_SDKPath)
: QString();
@@ -282,19 +275,39 @@ void PersistantConfig::AddAndroidHosts()
SetConfigSetting("MaxConnectTimeout", QString::number(Android_MaxConnectTimeout));
rdcstr androidHosts;
RENDERDOC_EnumerateAndroidDevices(androidHosts);
for(const QString &hostName :
QString(androidHosts).split(QLatin1Char(','), QString::SkipEmptyParts))
rdcarray<RemoteHost> enumeratedDevices;
rdcarray<rdcstr> protocols;
RENDERDOC_GetSupportedDeviceProtocols(&protocols);
for(const rdcstr &p : protocols)
{
RemoteHost host((rdcstr)hostName);
IDeviceProtocolController *protocol = RENDERDOC_GetDeviceProtocolController(p);
if(oldHosts.contains(hostName))
host = oldHosts.take(hostName);
rdcarray<rdcstr> devices = protocol->GetDevices();
rdcstr friendly;
RENDERDOC_GetAndroidFriendlyName(hostName.toUtf8().data(), friendly);
host.SetFriendlyName(friendly);
for(const rdcstr &d : devices)
{
RemoteHost newhost(protocol->GetProtocolName() + "://" + d);
enumeratedDevices.push_back(newhost);
}
}
QMutexLocker autolock(&RemoteHostLock);
QMap<rdcstr, RemoteHost> oldHosts;
for(int i = RemoteHostList.count() - 1; i >= 0; i--)
if(RemoteHostList[i].Protocol())
oldHosts[RemoteHostList[i].Hostname()] = RemoteHostList.takeAt(i);
for(RemoteHost host : enumeratedDevices)
{
// if we already had this host, use that one.
if(oldHosts.contains(host.Hostname()))
host = oldHosts.take(host.Hostname());
host.SetFriendlyName(host.Protocol()->GetFriendlyName(host.Hostname()));
// Just a command to display in the GUI and allow Launch() to be called.
host.SetRunCommand("Automatically handled");
RemoteHostList.push_back(host);
@@ -363,6 +376,10 @@ bool PersistantConfig::Load(const rdcstr &filename)
if(!host.IsValid())
continue;
// backwards compatibility - skip old adb hosts that were adb:
if(host.Hostname().find("adb:") > 0 && host.Protocol() == NULL)
continue;
RemoteHostList.push_back(host);
if(host.IsLocalhost())
+2 -2
View File
@@ -791,8 +791,8 @@ R)");
:param RemoteHost host: The remote host to remove.
R)");
void RemoveRemoteHost(RemoteHost host);
DOCUMENT("If configured, queries ``adb`` to add android hosts to :data:`RemoteHosts`.");
void AddAndroidHosts();
DOCUMENT("If configured, queries available device protocols to update auto-configured hosts.");
void UpdateEnumeratedProtocolDevices();
DOCUMENT("");
CONFIG_SETTINGS()
+10 -7
View File
@@ -60,6 +60,8 @@ RemoteHost::RemoteHost(const QVariant &var)
m_data->m_runCommand = map[lit("runCommand")].toString();
if(map.contains(lit("lastCapturePath")))
m_data->m_lastCapturePath = map[lit("lastCapturePath")].toString();
m_protocol = RENDERDOC_GetDeviceProtocolController(m_hostname);
}
RemoteHost::RemoteHost()
@@ -72,6 +74,8 @@ RemoteHost::RemoteHost(const rdcstr &host)
// create a new host
m_hostname = host;
m_data = new RemoteHostData();
m_protocol = RENDERDOC_GetDeviceProtocolController(m_hostname);
}
RemoteHost::RemoteHost(const RemoteHost &o)
@@ -82,6 +86,7 @@ RemoteHost::RemoteHost(const RemoteHost &o)
RemoteHost &RemoteHost::operator=(const RemoteHost &o)
{
m_hostname = o.m_hostname;
m_protocol = o.m_protocol;
// deref old data
if(m_data)
@@ -123,7 +128,7 @@ void RemoteHost::CheckStatus()
// to avoid doing complex work while holding the remote host lock, we check the status here then
// call into the internal function that will propagate that data to the proper storage if needed.
IRemoteServer *rend = NULL;
ReplayStatus status = RENDERDOC_CreateRemoteServerConnection(m_hostname.c_str(), 0, &rend);
ReplayStatus status = RENDERDOC_CreateRemoteServerConnection(m_hostname.c_str(), &rend);
if(rend)
rend->ShutdownConnection();
@@ -187,12 +192,10 @@ ReplayStatus RemoteHost::Launch()
{
ReplayStatus status = ReplayStatus::Succeeded;
int WAIT_TIME = 2000;
if(IsADB())
if(m_protocol)
{
status = RENDERDOC_StartAndroidRemoteServer(m_hostname.c_str());
QThread::msleep(WAIT_TIME);
// this is blocking
status = m_protocol->StartRemoteServer(m_hostname);
return status;
}
@@ -205,7 +208,7 @@ ReplayStatus RemoteHost::Launch()
RDProcess process;
process.start(run);
process.waitForFinished(WAIT_TIME);
process.waitForFinished(2000);
process.detach();
return status;
+5 -6
View File
@@ -93,6 +93,9 @@ public:
)");
void SetLastCapturePath(const rdcstr &path);
DOCUMENT(
"The :class:`DeviceProtocolController` for this host, or ``None`` if no protocol is in use");
IDeviceProtocolController *Protocol() const { return m_protocol; }
DOCUMENT(R"(
Returns the name to display for this host in the UI, either :meth:`FriendlyName` if it is valid, or
:meth:`Hostname` if not.
@@ -102,12 +105,6 @@ Returns the name to display for this host in the UI, either :meth:`FriendlyName`
rdcstr friendlyName = FriendlyName();
return !friendlyName.isEmpty() ? friendlyName : m_hostname;
}
DOCUMENT("Returns ``True`` if this host represents a connected ADB (Android) device.");
bool IsADB() const
{
return m_hostname.count() > 4 && m_hostname[0] == 'a' && m_hostname[1] == 'd' &&
m_hostname[2] == 'b' && m_hostname[3] == ':';
}
DOCUMENT("Returns ``True`` if this host represents the special localhost device.");
bool IsLocalhost() const { return m_hostname == "localhost"; }
DOCUMENT("Returns ``True`` if this host represents a valid remote host.");
@@ -117,6 +114,8 @@ private:
// are created with it
rdcstr m_hostname;
IDeviceProtocolController *m_protocol = NULL;
// self-deleting shared and locked data store
RemoteHostData *m_data = NULL;
+2 -2
View File
@@ -307,9 +307,9 @@ void ReplayManager::CloseThread()
ReplayStatus ReplayManager::ConnectToRemoteServer(RemoteHost host)
{
ReplayStatus status = RENDERDOC_CreateRemoteServerConnection(host.Hostname().c_str(), 0, &m_Remote);
ReplayStatus status = RENDERDOC_CreateRemoteServerConnection(host.Hostname().c_str(), &m_Remote);
if(host.IsADB())
if(host.Protocol() && host.Protocol()->GetProtocolName() == "adb")
{
ANALYTIC_SET(UIFeatures.AndroidRemoteReplay, true);
}
@@ -143,7 +143,7 @@ void PersistantConfig::RemoveRemoteHost(RemoteHost host)
{
}
void PersistantConfig::AddAndroidHosts()
void PersistantConfig::UpdateEnumeratedProtocolDevices()
{
}
+23
View File
@@ -85,6 +85,29 @@
$result = SWIG_NewPointerObj($1, $descriptor(struct CaptureOptions*), SWIG_POINTER_OWN);
}
// same for RENDERDOC_GetSupportedDeviceProtocols
%typemap(in, numinputs=0) rdcarray<rdcstr> *supportedProtocols { $1 = new rdcarray<rdcstr>; }
%typemap(argout) rdcarray<rdcstr> *supportedProtocols {
$result = ConvertToPy(*$1);
delete $1;
}
%typemap(freearg) rdcarray<rdcstr> *supportedProtocols { }
// same for RENDERDOC_CreateRemoteServerConnection
%typemap(in, numinputs=0) IRemoteServer **rend (IRemoteServer *outRenderer) {
outRenderer = NULL;
$1 = &outRenderer;
}
%typemap(argout) IRemoteServer **rend {
PyObject *retVal = $result;
$result = PyTuple_New(2);
if($result)
{
PyTuple_SetItem($result, 0, retVal);
PyTuple_SetItem($result, 1, SWIG_NewPointerObj(SWIG_as_voidptr(outRenderer$argnum), SWIGTYPE_p_IRemoteServer, 0));
}
}
// ignore some operators SWIG doesn't have to worry about
%ignore SDType::operator=;
%ignore StructuredObjectList::swap;
-2
View File
@@ -474,8 +474,6 @@ int main(int argc, char *argv[])
config.Save();
}
RENDERDOC_AndroidShutdown();
PythonContext::GlobalShutdown();
Formatter::shutdown();
+4 -2
View File
@@ -678,7 +678,8 @@ void CaptureDialog::on_exePathBrowse_clicked()
{
SetExecutableFilename(filename);
if(m_Ctx.Replay().CurrentRemote().IsADB())
if(m_Ctx.Replay().CurrentRemote().Protocol() &&
m_Ctx.Replay().CurrentRemote().Protocol()->GetProtocolName() == "adb")
{
CheckAndroidSetup(filename);
}
@@ -1122,7 +1123,8 @@ void CaptureDialog::UpdateGlobalHook()
void CaptureDialog::UpdateRemoteHost()
{
if(m_Ctx.Replay().CurrentRemote().IsADB())
if(m_Ctx.Replay().CurrentRemote().Protocol() &&
m_Ctx.Replay().CurrentRemote().Protocol()->GetProtocolName() == "adb")
ui->cmdLineLabel->setText(tr("Intent Arguments"));
else
ui->cmdLineLabel->setText(tr("Command-line Arguments"));
+3 -5
View File
@@ -105,8 +105,6 @@ RemoteManager::RemoteManager(ICaptureContext &ctx, MainWindow *main)
vertical->addWidget(lookupsProgressFlow);
vertical->addWidget(ui->bottomLayout->parentWidget());
m_Ctx.Config().AddAndroidHosts();
for(RemoteHost h : m_Ctx.Config().GetRemoteHosts())
addHost(h);
@@ -446,9 +444,9 @@ void RemoteManager::on_hosts_itemSelectionChanged()
ui->addUpdateHost->setText(tr("Update"));
if(host.IsLocalhost() || host.IsADB())
if(host.IsLocalhost() || host.Protocol())
{
// localhost and android hosts cannot be updated or have their run command changed
// localhost and protocol-configured hosts cannot be updated or have their run command changed
ui->addUpdateHost->setEnabled(false);
ui->runCommand->setEnabled(false);
}
@@ -601,7 +599,7 @@ void RemoteManager::on_connect_clicked()
{
IRemoteServer *server = NULL;
ReplayStatus status =
RENDERDOC_CreateRemoteServerConnection(host.Hostname().c_str(), 0, &server);
RENDERDOC_CreateRemoteServerConnection(host.Hostname().c_str(), &server);
if(server)
server->ShutdownServerAndConnection();
setRemoteServerLive(node, false, false);
+83 -35
View File
@@ -160,11 +160,15 @@ MainWindow::MainWindow(ICaptureContext &ctx) : QMainWindow(NULL), ui(new Ui::Mai
m_RemoteProbeSemaphore.release();
m_RemoteProbe = new LambdaThread([this]() {
RENDERDOC_AndroidInitialise();
// fetch all device protocols to start them processing
rdcarray<rdcstr> protocols;
RENDERDOC_GetSupportedDeviceProtocols(&protocols);
for(const rdcstr &p : protocols)
RENDERDOC_GetDeviceProtocolController(p);
while(m_RemoteProbeSemaphore.available())
{
// do a remoteProbe immediately to populate the android hosts list on startup.
// do a remoteProbe immediately to populate the device list on startup.
remoteProbe();
// do several small sleeps so we can respond quicker when we need to shut down
@@ -510,7 +514,7 @@ void MainWindow::OnCaptureTrigger(const QString &exe, const QString &workingDir,
LambdaThread *th = new LambdaThread([this, exe, workingDir, cmdLine, env, opts, callback]() {
if(isCapturableAppRunningOnAndroid())
if(isUnshareableDeviceInUse())
{
RDDialog::warning(this, tr("RenderDoc is already capturing an app on this device"),
tr("A running app on this device is already being captured with RenderDoc. "
@@ -1583,25 +1587,10 @@ void MainWindow::remoteProbe()
{
if(!m_Ctx.IsCaptureLoaded() && !m_Ctx.IsCaptureLoading())
{
GUIInvoke::call(this, [this] {
m_Ctx.Config().AddAndroidHosts();
// update the latest list by copy. Note this lock only protects m_ProbeRemoteHosts, not the
// actual RemoteHosts list itself - that is only accessed on the UI thread so is not locked.
{
QMutexLocker lock(&m_ProbeRemoteHostsLock);
m_ProbeRemoteHosts.clear();
for(RemoteHost host : m_Ctx.Config().GetRemoteHosts())
m_ProbeRemoteHosts.push_back(host);
}
});
m_Ctx.Config().UpdateEnumeratedProtocolDevices();
// fetch the latest list
rdcarray<RemoteHost> hosts;
{
QMutexLocker lock(&m_ProbeRemoteHostsLock);
hosts = m_ProbeRemoteHosts;
}
rdcarray<RemoteHost> hosts = m_Ctx.Config().GetRemoteHosts();
for(RemoteHost &host : hosts)
{
@@ -1832,23 +1821,64 @@ void MainWindow::setRemoteHost(int hostIdx)
RemoteHost host = h;
host.CheckStatus();
if(host.IsADB() && !RENDERDOC_IsAndroidSupported(host.Hostname().c_str()))
if(host.Protocol() && !host.Protocol()->IsSupported(host.Hostname()))
{
// check to see if we should warn the user about this unsupported android version.
GUIInvoke::call(this, [this]() {
GUIInvoke::call(this, [this, host]() {
QDateTime today = QDateTime::currentDateTimeUtc();
QDateTime compare = today.addDays(-21);
if(compare > m_Ctx.Config().UnsupportedAndroid_LastUpdate)
if(host.Protocol()->GetProtocolName() == "adb")
{
if(compare > m_Ctx.Config().UnsupportedAndroid_LastUpdate)
{
RDDialog::critical(
this, tr("Unsupported Device Android Version"),
tr("This device is older than Android 6.0, the minimum required version for "
"RenderDoc.\n\nThis may break or cause unknown problems - use at your own "
"risk."));
}
m_Ctx.Config().UnsupportedAndroid_LastUpdate = today;
}
else
{
RDDialog::critical(
this, tr("Unsupported Device Android Version"),
tr("This device is older than Android 6.0, the minimum required version for "
"RenderDoc.\n\nThis may break or cause unknown problems - use at your own "
"risk."));
this, tr("Unsupported Device"),
tr("This device is not able to support RenderDoc. Please consult the documentation "
"for this type of device to see what the problem may be."));
}
});
}
m_Ctx.Config().UnsupportedAndroid_LastUpdate = today;
if(host.Protocol() && host.IsVersionMismatch())
{
GUIInvoke::blockcall(this, [this, &host]() {
QMessageBox::StandardButton res =
RDDialog::question(this, tr("Unsupported version"),
tr("Remote server on %1 has an incompatible version.\n"
"Would you like to try to reinstall the version %2?")
.arg(host.Name())
.arg(lit(FULL_VERSION_STRING)),
QMessageBox::Yes | QMessageBox::No | QMessageBox::Cancel);
if(res == QMessageBox::Yes)
{
LambdaThread *launchthread = new LambdaThread([this, &host]() {
// since we have a protocol, try to force-launch which should attempt to reinstall.
host.Launch();
// update status
host.CheckStatus();
});
launchthread->start();
ShowProgressDialog(this, tr("Attempting to update remote server, please wait..."),
[launchthread]() { return !launchthread->isRunning(); });
launchthread->deleteLater();
}
});
}
@@ -2539,11 +2569,25 @@ void MainWindow::on_action_Manage_Extensions_triggered()
void MainWindow::on_action_Manage_Remote_Servers_triggered()
{
RemoteManager *rm = new RemoteManager(m_Ctx, this);
RDDialog::show(rm);
// now that we're done with it, the manager deletes itself when all lookups terminate (or
// immediately if there are no lookups ongoing).
rm->closeWhenFinished();
LambdaThread *th = new LambdaThread([this]() {
m_Ctx.Config().UpdateEnumeratedProtocolDevices();
GUIInvoke::call(this, [this]() {
RemoteManager *rm = new RemoteManager(m_Ctx, this);
RDDialog::show(rm);
// now that we're done with it, the manager deletes itself when all lookups terminate (or
// immediately if there are no lookups ongoing).
rm->closeWhenFinished();
});
});
th->start();
th->wait(500);
if(th->isRunning())
{
ShowProgressDialog(this, tr("Updating available devices, please wait..."),
[th]() { return !th->isRunning(); });
}
th->deleteLater();
}
void MainWindow::on_action_Settings_triggered()
@@ -2895,12 +2939,16 @@ void MainWindow::showLaunchError(ReplayStatus status)
});
}
bool MainWindow::isCapturableAppRunningOnAndroid()
bool MainWindow::isUnshareableDeviceInUse()
{
if(!m_Ctx.Replay().CurrentRemote().IsADB())
if(!m_Ctx.Replay().CurrentRemote().Protocol())
return false;
rdcstr host = m_Ctx.Replay().CurrentRemote().Hostname();
if(m_Ctx.Replay().CurrentRemote().Protocol()->SupportsMultiplePrograms(host))
return false;
uint32_t ident = RENDERDOC_EnumerateRemoteTargets(host.c_str(), 0);
return ident != 0;
}
+1 -1
View File
@@ -256,5 +256,5 @@ private:
void showLaunchError(ReplayStatus status);
bool isCapturableAppRunningOnAndroid();
bool isUnshareableDeviceInUse();
};
+1
View File
@@ -110,6 +110,7 @@ set(sources
core/crash_handler.h
core/target_control.cpp
core/remote_server.cpp
core/remote_server.h
core/replay_proxy.cpp
core/replay_proxy.h
core/intervals.h
File diff suppressed because it is too large Load Diff
+9 -9
View File
@@ -513,12 +513,12 @@ bool IsDebuggable(const std::string &deviceID, const std::string &packageName)
}
};
extern "C" RENDERDOC_API void RENDERDOC_CC RENDERDOC_CheckAndroidPackage(
const char *hostname, const char *packageAndActivity, AndroidFlags *flags)
extern "C" RENDERDOC_API void RENDERDOC_CC
RENDERDOC_CheckAndroidPackage(const char *URL, const char *packageAndActivity, AndroidFlags *flags)
{
int index = 0;
std::string deviceID;
Android::ExtractDeviceIDAndIndex(hostname, index, deviceID);
IDeviceProtocolHandler *adb = RenderDoc::Inst().GetDeviceProtocol("adb");
rdcstr deviceID = adb->GetDeviceID(URL);
// Reset the flags each time we check
*flags = AndroidFlags::NoFlags;
@@ -542,15 +542,15 @@ extern "C" RENDERDOC_API void RENDERDOC_CC RENDERDOC_CheckAndroidPackage(
}
extern "C" RENDERDOC_API AndroidFlags RENDERDOC_CC RENDERDOC_MakeDebuggablePackage(
const char *hostname, const char *packageAndActivity, RENDERDOC_ProgressCallback progress)
const char *URL, const char *packageAndActivity, RENDERDOC_ProgressCallback progress)
{
std::string package = Android::GetPackageName(packageAndActivity);
Process::ProcessResult result = {};
int index = 0;
std::string deviceID;
Android::ExtractDeviceIDAndIndex(hostname, index, deviceID);
IDeviceProtocolHandler *adb = RenderDoc::Inst().GetDeviceProtocol("adb");
rdcstr deviceID = adb->GetDeviceID(URL);
// make sure progress is valid so we don't have to check it everywhere
if(!progress)
+2 -8
View File
@@ -27,8 +27,6 @@
#include "core/core.h"
#include "strings/string_utils.h"
static std::map<std::string, std::string> friendlyNameCache;
namespace Android
{
bool IsHostADB(const char *hostname)
@@ -185,12 +183,8 @@ bool IsSupported(std::string deviceID)
return true;
}
std::string GetFriendlyName(std::string deviceID)
rdcstr GetFriendlyName(const rdcstr &deviceID)
{
auto it = friendlyNameCache.find(deviceID);
if(it != friendlyNameCache.end())
return it->second;
// run adb root now, so we hit any disconnection that we're going to before trying to connect.
// If we can't be root, this is cheap, if we're already root, this is cheap, if we can be root
// and this changes us it will block only the first time - and we expect this function to be
@@ -205,7 +199,7 @@ std::string GetFriendlyName(std::string deviceID)
std::string model =
trim(Android::adbExecCommand(deviceID, "shell getprop ro.product.model").strStdout);
std::string &combined = friendlyNameCache[deviceID];
std::string combined;
if(manuf.empty() && model.empty())
combined = "";
+1 -1
View File
@@ -50,7 +50,7 @@ bool toolExists(const std::string &path);
std::string GetFirstMatchingLine(const std::string &haystack, const std::string &needle);
bool IsSupported(std::string deviceID);
std::string GetFriendlyName(std::string deviceID);
rdcstr GetFriendlyName(const rdcstr &deviceID);
// supported ABIs
enum class ABI
+113 -47
View File
@@ -2054,7 +2054,8 @@ DOCUMENT(R"(Creates a :class:`TargetControl` connection to a given hostname and
This function will block until the control connection is ready, or an error occurs.
:param str host: The hostname to connect to. If blank, the local machine is used.
:param str URL: The URL to connect to. If blank, the local machine is used. If no protocol is
specified then default TCP enumeration happens.
:param int ident: The ident for the particular target to connect to on that machine.
:param str clientName: The client name to use when connecting. See
:meth:`TargetControl.GetBusyClient`.
@@ -2064,7 +2065,7 @@ This function will block until the control connection is ready, or an error occu
:rtype: TargetControl
)");
extern "C" RENDERDOC_API ITargetControl *RENDERDOC_CC RENDERDOC_CreateTargetControl(
const char *host, uint32_t ident, const char *clientName, bool forceConnection);
const char *URL, uint32_t ident, const char *clientName, bool forceConnection);
DOCUMENT(R"(Repeatedly query to enumerate which targets are active on a given machine and their
idents.
@@ -2075,38 +2076,29 @@ more targets.
This function will block for a variable timeout depending on how many targets are scanned.
:param str host: The hostname to connect to. If blank, the local machine is used.
:param str URL: The URL to connect to. If blank, the local machine is used. If no protocol is
specified then default TCP enumeration happens.
:param int nextIdent: The next ident to scan.
:return: The ident of the next active target, or ``0`` if no other targets exist.
:rtype: ``int``
)");
extern "C" RENDERDOC_API uint32_t RENDERDOC_CC RENDERDOC_EnumerateRemoteTargets(const char *host,
extern "C" RENDERDOC_API uint32_t RENDERDOC_CC RENDERDOC_EnumerateRemoteTargets(const char *URL,
uint32_t nextIdent);
//////////////////////////////////////////////////////////////////////////
// Remote server
//////////////////////////////////////////////////////////////////////////
DOCUMENT(R"(Retrieves the default ports where remote servers listen.
:return: The port where remote servers listen by default.
:rtype: ``int``
)");
extern "C" RENDERDOC_API uint32_t RENDERDOC_CC RENDERDOC_GetDefaultRemoteServerPort();
DOCUMENT(R"(Create a connection to a remote server running on given hostname and port.
This function will block until the capture is fully loaded and ready.
:param str host: The hostname to connect to, if blank then localhost is used.
:param int port: The port to connect to, or the default port if 0.
:param RemoteServer rend: A reference to a :class:`RemoteServer` where the connection handle will be
stored.
:return: The status of opening the capture, whether success or failure.
:rtype: ReplayStatus
:param str URL: The hostname to connect to, if blank then localhost is used. If no protocol is
specified then default TCP enumeration happens.
:return: The status of opening the capture, whether success or failure, and a :class:`RemoteServer`
instance if it were successful
:rtype: ``pair`` of ReplayStatus and RemoteServer
)");
extern "C" RENDERDOC_API ReplayStatus RENDERDOC_CC
RENDERDOC_CreateRemoteServerConnection(const char *host, uint32_t port, IRemoteServer **rend);
RENDERDOC_CreateRemoteServerConnection(const char *URL, IRemoteServer **rend);
DOCUMENT(R"(This launches a remote server which will continually run in a loop to server requests
from external sources.
@@ -2115,15 +2107,14 @@ This function will block until a remote connection tells the server to shut down
``killReplay`` callback returns ``True``.
:param str host: The name of the interface to listen on.
:param int port: The port to listen on, or the default port if 0.
:param KillCallback killReplay: A callback that returns a ``bool`` indicating if the server should
be shut down or not.
:param PreviewWindowCallback previewWindow: A callback that returns information for a preview window
when the server wants to display some preview of the ongoing replay.
)");
extern "C" RENDERDOC_API void RENDERDOC_CC RENDERDOC_BecomeRemoteServer(
const char *listenhost, uint32_t port, RENDERDOC_KillCallback killReplay,
RENDERDOC_PreviewWindowCallback previewWindow);
extern "C" RENDERDOC_API void RENDERDOC_CC
RENDERDOC_BecomeRemoteServer(const char *listenhost, RENDERDOC_KillCallback killReplay,
RENDERDOC_PreviewWindowCallback previewWindow);
//////////////////////////////////////////////////////////////////////////
// Injection/execution capture functions.
@@ -2349,33 +2340,108 @@ extern "C" RENDERDOC_API void RENDERDOC_CC RENDERDOC_SetColors(FloatVector darkC
FloatVector lightChecker,
bool darkTheme);
DOCUMENT("Internal function for fetching friendly android names.");
extern "C" RENDERDOC_API void RENDERDOC_CC RENDERDOC_GetAndroidFriendlyName(const rdcstr &device,
rdcstr &friendly);
DOCUMENT("Internal function for enumerating android devices.");
extern "C" RENDERDOC_API void RENDERDOC_CC RENDERDOC_EnumerateAndroidDevices(rdcstr &deviceList);
DOCUMENT("Internal function for initialising android use.");
extern "C" RENDERDOC_API void RENDERDOC_CC RENDERDOC_AndroidInitialise();
DOCUMENT("Internal function for shutting down android use.");
extern "C" RENDERDOC_API void RENDERDOC_CC RENDERDOC_AndroidShutdown();
DOCUMENT("Internal function for checking android support.");
extern "C" RENDERDOC_API bool RENDERDOC_CC RENDERDOC_IsAndroidSupported(const char *device);
DOCUMENT("Internal function for starting an android remote server.");
extern "C" RENDERDOC_API ReplayStatus RENDERDOC_CC
RENDERDOC_StartAndroidRemoteServer(const char *device);
DOCUMENT("Internal function for checking remote Android package for requirements");
extern "C" RENDERDOC_API void RENDERDOC_CC RENDERDOC_CheckAndroidPackage(
const char *hostname, const char *packageAndActivity, AndroidFlags *flags);
extern "C" RENDERDOC_API void RENDERDOC_CC
RENDERDOC_CheckAndroidPackage(const char *URL, const char *packageAndActivity, AndroidFlags *flags);
DOCUMENT("Internal function that attempts to modify APK contents, adding debuggable flag.");
extern "C" RENDERDOC_API AndroidFlags RENDERDOC_CC RENDERDOC_MakeDebuggablePackage(
const char *hostname, const char *packageAndActivity, RENDERDOC_ProgressCallback progress);
const char *URL, const char *packageAndActivity, RENDERDOC_ProgressCallback progress);
DOCUMENT("An interface for enumerating and controlling remote devices.");
struct IDeviceProtocolController
{
DOCUMENT(R"(Retrieves the name of this protocol as passed to :func:`GetDeviceProtocolController`.
:return: A string identifying the protocol.
:rtype: ``str``
)");
virtual rdcstr GetProtocolName() = 0;
DOCUMENT(R"(Returns a list of devices currently available through the given protocol.
Until a device is enumerated through this function it may not be available for connection through
other methods such as target control or remote server access, even if the device is physically
connected, due to initialisation happening only when enumerated.
The returned string is the hostname of the device, which can be connected via
``protocol://hostname`` with interfaces that take a hostname.
:return: A list of the devices currently available.
:rtype: ``list`` of ``str``
)");
virtual rdcarray<rdcstr> GetDevices() = 0;
DOCUMENT(R"(Retrieves the user friendly name of the given device. This may be easier for a user to
correlate to a device than the hostname which may be only a programmatic identifier.
:param str URL: The URL of the device in the form ``protocol://host``, with protocol as returned by
:func:`GetProtocolName` and host as returned by :func:`GetDevices`.
:return: A string identifying the device.
:rtype: ``str``
)");
virtual rdcstr GetFriendlyName(const rdcstr &URL) = 0;
DOCUMENT(R"(Query if the device supports multiple programs running and being captured. If not, the
user can be prompted to close an existing program before a new one is launched.
:param str URL: The URL of the device in the form ``protocol://host``, with protocol as returned by
:func:`GetProtocolName` and host as returned by :func:`GetDevices`.
:return: ``True`` if the device supports multiple programs, ``False`` otherwise.
:rtype: ``bool``
)");
virtual bool SupportsMultiplePrograms(const rdcstr &URL) = 0;
DOCUMENT(R"(Query if the device supports RenderDoc capture and replay.
:param str URL: The URL of the device in the form ``protocol://host``, with protocol as returned by
:func:`GetProtocolName` and host as returned by :func:`GetDevices`.
:return: ``True`` if any the device is supported, ``False`` otherwise.
:rtype: ``bool``
)");
virtual bool IsSupported(const rdcstr &URL) = 0;
DOCUMENT(R"(Start the remote server running on the given device.
:param str URL: The URL of the device in the form ``protocol://host``, with protocol as returned by
:func:`GetProtocolName` and host as returned by :func:`GetDevices`.
:return: The status of starting the server, whether success or failure.
:rtype: ReplayStatus
)");
virtual ReplayStatus StartRemoteServer(const rdcstr &URL) = 0;
protected:
IDeviceProtocolController() = default;
~IDeviceProtocolController() = default;
};
DOCUMENT(R"(Retrieve the set of device protocols supported (see :func:`GetDeviceProtocolController`).
:return: The supported device protocols.
:rtype: ``list`` of ``str``
)");
extern "C" RENDERDOC_API void RENDERDOC_CC
RENDERDOC_GetSupportedDeviceProtocols(rdcarray<rdcstr> *supportedProtocols);
DOCUMENT(R"(Creates a :class:`DeviceProtocolController` that provides device-specific controls.
This interface is intended to allow closer integration with remote devices.
.. note::
Note that the use of scripting with Android is explicitly **not supported** due to the inherent
fragility and unreliability of the Android platform. This interface is designed primarily for
internal use and no support will be provided for Android-specific problems encountered using this.
This function will not block, however the protocol may still be initialising when it is returned so
immediate use of it may block.
:param str protocol: The protocol to fetch a controller for.
:return: A handle to the protocol controller, or ``None`` if something went wrong such as an
unsupported protocol being specified.
:rtype: DeviceProtocolController
)");
extern "C" RENDERDOC_API IDeviceProtocolController *RENDERDOC_CC
RENDERDOC_GetDeviceProtocolController(const rdcstr &protocol);
DOCUMENT("Internal function that runs unit tests.");
extern "C" RENDERDOC_API int RENDERDOC_CC RENDERDOC_RunUnitTests(const rdcstr &command,
+5 -1
View File
@@ -136,7 +136,11 @@ enum
RenderDoc_FirstTargetControlPort = 38920,
RenderDoc_LastTargetControlPort = RenderDoc_FirstTargetControlPort + 7,
RenderDoc_RemoteServerPort = 39920,
RenderDoc_AndroidPortOffset = 50,
RenderDoc_ForwardPortBase = 38950,
RenderDoc_ForwardTargetControlOffset = 0,
RenderDoc_ForwardRemoteServerOffset = 9,
RenderDoc_ForwardPortStride = 10,
};
#define RENDERDOC_VULKAN_LAYER_NAME "VK_LAYER_RENDERDOC_Capture"
+37
View File
@@ -1038,6 +1038,16 @@ void RenderDoc::RegisterCaptureImportExporter(CaptureImporter importer, CaptureE
m_Exporters[filetype] = exporter;
}
void RenderDoc::RegisterDeviceProtocol(const rdcstr &protocol, ProtocolHandler handler)
{
if(m_Protocols[protocol] != NULL)
{
RDCERR("Duplicate protocol registration: %s", protocol.c_str());
return;
}
m_Protocols[protocol] = handler;
}
StructuredProcessor RenderDoc::GetStructuredProcessor(RDCDriver driver)
{
auto it = m_StructProcesssors.find(driver);
@@ -1074,6 +1084,33 @@ CaptureImporter RenderDoc::GetCaptureImporter(const char *filetype)
return it->second;
}
rdcarray<rdcstr> RenderDoc::GetSupportedDeviceProtocols()
{
rdcarray<rdcstr> ret;
for(auto it = m_Protocols.begin(); it != m_Protocols.end(); ++it)
ret.push_back(it->first);
return ret;
}
IDeviceProtocolHandler *RenderDoc::GetDeviceProtocol(const rdcstr &protocol)
{
rdcstr p = protocol;
// allow passing in an URL with ://
int32_t offs = p.find("://");
if(offs >= 0)
p.erase(offs, p.size() - offs);
auto it = m_Protocols.find(p);
if(it != m_Protocols.end())
return it->second();
return NULL;
}
std::vector<CaptureFileFormat> RenderDoc::GetCaptureFileFormats()
{
std::vector<CaptureFileFormat> ret = m_ImportExportFormats;
+37 -1
View File
@@ -58,6 +58,24 @@ struct IFrameCapturer
virtual bool DiscardFrameCapture(void *dev, void *wnd) = 0;
};
// for protocols, we extend the public interface a bit to add callbacks for remapping connection
// ports (typically to some port forwarded on localhost)
struct IDeviceProtocolHandler : public IDeviceProtocolController
{
virtual rdcstr GetDeviceID(const rdcstr &URL)
{
rdcstr ret = URL;
int offs = ret.find("://");
if(offs > 0)
ret.erase(0, offs + 3);
return ret;
}
virtual rdcstr RemapHostname(const rdcstr &deviceID) = 0;
virtual uint16_t RemapPort(const rdcstr &deviceID, uint16_t srcPort) = 0;
virtual IRemoteServer *CreateRemoteServer(Network::Socket *sock, const rdcstr &deviceID) = 0;
};
// In most cases you don't need to check these individually, use the utility functions below
// to determine if you're in a capture or replay state. There are utility functions for each
// state as well.
@@ -155,7 +173,7 @@ enum class SystemChunk : uint32_t
DECLARE_REFLECTION_ENUM(SystemChunk);
enum class RDCDriver
enum class RDCDriver : uint32_t
{
Unknown = 0,
D3D11 = 1,
@@ -328,6 +346,7 @@ typedef ReplayStatus (*CaptureImporter)(const char *filename, StreamReader &read
typedef ReplayStatus (*CaptureExporter)(const char *filename, const RDCFile &rdc,
const SDFile &structData,
RENDERDOC_ProgressCallback progress);
typedef IDeviceProtocolHandler *(*ProtocolHandler)();
typedef bool (*VulkanLayerCheck)(VulkanLayerFlags &flags, std::vector<std::string> &myJSONs,
std::vector<std::string> &otherJSONs);
@@ -406,6 +425,7 @@ public:
const GlobalEnvironment GetGlobalEnvironment() { return m_GlobalEnv; }
void ProcessGlobalEnvironment(GlobalEnvironment env, const std::vector<std::string> &args);
int32_t GetForwardedPortSlot() { return Atomic::Inc32(&m_PortSlot); }
void RegisterShutdownFunction(ShutdownFunction func) { m_ShutdownFunctions.insert(func); }
void SetReplayApp(bool replay) { m_Replay = replay; }
bool IsReplayApp() const { return m_Replay; }
@@ -465,12 +485,16 @@ public:
void RegisterCaptureExporter(CaptureExporter exporter, CaptureFileFormat description);
void RegisterCaptureImportExporter(CaptureImporter importer, CaptureExporter exporter,
CaptureFileFormat description);
void RegisterDeviceProtocol(const rdcstr &protocol, ProtocolHandler handler);
StructuredProcessor GetStructuredProcessor(RDCDriver driver);
CaptureExporter GetCaptureExporter(const char *filetype);
CaptureImporter GetCaptureImporter(const char *filetype);
rdcarray<rdcstr> GetSupportedDeviceProtocols();
IDeviceProtocolHandler *GetDeviceProtocol(const rdcstr &protocol);
std::vector<CaptureFileFormat> GetCaptureFileFormats();
void SetVulkanLayerCheck(VulkanLayerCheck callback) { m_VulkanCheck = callback; }
@@ -590,6 +614,8 @@ private:
GlobalEnvironment m_GlobalEnv;
int32_t m_PortSlot = 0;
FrameTimer m_FrameTimer;
std::string m_LoggingFilename;
@@ -628,6 +654,8 @@ private:
std::map<std::string, CaptureImporter> m_Importers;
std::map<std::string, CaptureExporter> m_Exporters;
std::map<rdcstr, ProtocolHandler> m_Protocols;
VulkanLayerCheck m_VulkanCheck;
VulkanLayerInstall m_VulkanInstall;
@@ -725,4 +753,12 @@ struct ConversionRegistration
{
RenderDoc::Inst().RegisterCaptureExporter(exporter, description);
}
};
struct DeviceProtocolRegistration
{
DeviceProtocolRegistration(const rdcstr &protocol, ProtocolHandler handler)
{
RenderDoc::Inst().RegisterDeviceProtocol(protocol, handler);
}
};
File diff suppressed because it is too large Load Diff
+105
View File
@@ -0,0 +1,105 @@
/******************************************************************************
* The MIT License (MIT)
*
* Copyright (c) 2019 Baldur Karlsson
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
******************************************************************************/
#pragma once
#include "api/replay/renderdoc_replay.h"
namespace Network
{
class Socket;
};
enum class RDCDriver : uint32_t;
class WriteSerialiser;
class ReadSerialiser;
struct RemoteServer : public IRemoteServer
{
public:
RemoteServer(Network::Socket *sock, const rdcstr &deviceID);
virtual ~RemoteServer();
virtual void ShutdownConnection();
virtual void ShutdownServerAndConnection();
virtual bool Connected();
virtual bool Ping();
virtual rdcarray<rdcstr> LocalProxies();
virtual rdcarray<rdcstr> RemoteSupportedReplays();
virtual rdcstr GetHomeFolder();
virtual rdcarray<PathEntry> ListFolder(const char *path);
virtual ExecuteResult ExecuteAndInject(const char *a, const char *w, const char *c,
const rdcarray<EnvironmentModification> &env,
const CaptureOptions &opts);
virtual void CopyCaptureFromRemote(const char *remotepath, const char *localpath,
RENDERDOC_ProgressCallback progress);
virtual rdcstr CopyCaptureToRemote(const char *filename, RENDERDOC_ProgressCallback progress);
virtual void TakeOwnershipCapture(const char *filename);
virtual rdcpair<ReplayStatus, IReplayController *> OpenCapture(uint32_t proxyid,
const char *filename,
RENDERDOC_ProgressCallback progress);
virtual void CloseCapture(IReplayController *rend);
virtual rdcstr DriverName();
virtual int GetSectionCount();
virtual int FindSectionByName(const char *name);
virtual int FindSectionByType(SectionType sectionType);
virtual SectionProperties GetSectionProperties(int index);
virtual bytebuf GetSectionContents(int index);
virtual bool WriteSection(const SectionProperties &props, const bytebuf &contents);
virtual bool HasCallstacks();
virtual bool InitResolver(RENDERDOC_ProgressCallback progress);
virtual rdcarray<rdcstr> GetResolve(const rdcarray<uint64_t> &callstack);
protected:
Network::Socket *m_Socket;
WriteSerialiser *writer;
ReadSerialiser *reader;
rdcstr m_deviceID;
rdcarray<rdcpair<RDCDriver, rdcstr>> m_Proxies;
};
+15 -11
View File
@@ -861,24 +861,28 @@ private:
};
extern "C" RENDERDOC_API ITargetControl *RENDERDOC_CC RENDERDOC_CreateTargetControl(
const char *host, uint32_t ident, const char *clientName, bool forceConnection)
const char *URL, uint32_t ident, const char *clientName, bool forceConnection)
{
std::string s = "localhost";
if(host != NULL && host[0] != '\0')
s = host;
rdcstr host = "localhost";
if(URL != NULL && URL[0] != '\0')
host = URL;
bool android = false;
rdcstr deviceID = host;
uint16_t port = ident & 0xffff;
if(host != NULL && Android::IsHostADB(host))
IDeviceProtocolHandler *protocol = RenderDoc::Inst().GetDeviceProtocol(deviceID);
if(protocol)
{
android = true;
s = "127.0.0.1";
deviceID = protocol->GetDeviceID(deviceID);
host = protocol->RemapHostname(deviceID);
if(host.empty())
return NULL;
// we don't need the index or device ID here, because the port is already the right one
// forwarded to the right device.
port = protocol->RemapPort(deviceID, port);
}
Network::Socket *sock = Network::CreateClientSocket(s.c_str(), ident & 0xffff, 750);
Network::Socket *sock = Network::CreateClientSocket(host.c_str(), port, 750);
if(sock == NULL)
return NULL;
+1
View File
@@ -188,6 +188,7 @@
<ClInclude Include="core\intervals.h" />
<ClInclude Include="core\plugins.h" />
<ClInclude Include="core\precompiled.h" />
<ClInclude Include="core\remote_server.h" />
<ClInclude Include="core\replay_proxy.h" />
<ClInclude Include="core\resource_manager.h" />
<ClInclude Include="data\embedded_files.h" />
+3
View File
@@ -486,6 +486,9 @@
<ClInclude Include="core\bit_flag_iterator.h">
<Filter>Core</Filter>
</ClInclude>
<ClInclude Include="core\remote_server.h">
<Filter>Core\networking</Filter>
</ClInclude>
</ItemGroup>
<ItemGroup>
<ClCompile Include="maths\camera.cpp">
+37 -32
View File
@@ -376,12 +376,14 @@ extern "C" RENDERDOC_API void *RENDERDOC_CC RENDERDOC_AllocArrayMem(uint64_t sz)
return malloc((size_t)sz);
}
extern "C" RENDERDOC_API uint32_t RENDERDOC_CC RENDERDOC_EnumerateRemoteTargets(const char *host,
extern "C" RENDERDOC_API uint32_t RENDERDOC_CC RENDERDOC_EnumerateRemoteTargets(const char *URL,
uint32_t nextIdent)
{
std::string s = "localhost";
if(host != NULL && host[0] != '\0')
s = host;
rdcstr host = "localhost";
if(URL != NULL && URL[0] != '\0')
host = URL;
rdcstr deviceID = host;
// initial case is we're called with 0, start with the first port.
// otherwise we're called with the last successful ident, so increment
@@ -391,32 +393,30 @@ extern "C" RENDERDOC_API uint32_t RENDERDOC_CC RENDERDOC_EnumerateRemoteTargets(
else
nextIdent++;
bool isAndroid = false;
uint32_t lastIdent = RenderDoc_LastTargetControlPort;
if(host != NULL && Android::IsHostADB(host))
IDeviceProtocolHandler *protocol = RenderDoc::Inst().GetDeviceProtocol(deviceID);
if(protocol)
{
int index = 0;
std::string deviceID;
Android::ExtractDeviceIDAndIndex(host, index, deviceID);
isAndroid = true;
// each subsequent device gets a new range of ports. The deviceID isn't needed since we already
// forwarded the ports to the right devices.
if(nextIdent == RenderDoc_FirstTargetControlPort)
nextIdent += RenderDoc_AndroidPortOffset * (index + 1);
lastIdent += RenderDoc_AndroidPortOffset * (index + 1);
s = "127.0.0.1";
deviceID = protocol->GetDeviceID(deviceID);
host = protocol->RemapHostname(deviceID);
if(host.empty())
return 0;
}
for(; nextIdent <= lastIdent; nextIdent++)
for(; nextIdent <= RenderDoc_LastTargetControlPort; nextIdent++)
{
Network::Socket *sock = Network::CreateClientSocket(s.c_str(), (uint16_t)nextIdent, 250);
uint16_t port = (uint16_t)nextIdent;
if(protocol)
port = protocol->RemapPort(deviceID, port);
if(port == 0)
return 0;
Network::Socket *sock = Network::CreateClientSocket(host.c_str(), port, 250);
if(sock)
{
if(isAndroid)
if(protocol)
{
Threading::Sleep(100);
(void)sock->IsRecvDataWaiting();
@@ -436,14 +436,21 @@ extern "C" RENDERDOC_API uint32_t RENDERDOC_CC RENDERDOC_EnumerateRemoteTargets(
return 0;
}
extern "C" RENDERDOC_API uint32_t RENDERDOC_CC RENDERDOC_GetDefaultRemoteServerPort()
extern "C" RENDERDOC_API void RENDERDOC_CC
RENDERDOC_GetSupportedDeviceProtocols(rdcarray<rdcstr> *supportedProtocols)
{
return RenderDoc_RemoteServerPort;
*supportedProtocols = RenderDoc::Inst().GetSupportedDeviceProtocols();
}
extern "C" RENDERDOC_API void RENDERDOC_CC RENDERDOC_BecomeRemoteServer(
const char *listenhost, uint32_t port, RENDERDOC_KillCallback killReplay,
RENDERDOC_PreviewWindowCallback previewWindow)
extern "C" RENDERDOC_API IDeviceProtocolController *RENDERDOC_CC
RENDERDOC_GetDeviceProtocolController(const rdcstr &protocol)
{
return RenderDoc::Inst().GetDeviceProtocol(protocol);
}
extern "C" RENDERDOC_API void RENDERDOC_CC
RENDERDOC_BecomeRemoteServer(const char *listenhost, RENDERDOC_KillCallback killReplay,
RENDERDOC_PreviewWindowCallback previewWindow)
{
if(listenhost == NULL || listenhost[0] == 0)
listenhost = "0.0.0.0";
@@ -459,10 +466,8 @@ extern "C" RENDERDOC_API void RENDERDOC_CC RENDERDOC_BecomeRemoteServer(
return ret;
};
if(port == 0)
port = RENDERDOC_GetDefaultRemoteServerPort();
RenderDoc::Inst().BecomeRemoteServer(listenhost, (uint16_t)port, killReplay, previewWindow);
RenderDoc::Inst().BecomeRemoteServer(listenhost, RenderDoc_RemoteServerPort, killReplay,
previewWindow);
}
extern "C" RENDERDOC_API void RENDERDOC_CC RENDERDOC_StartSelfHostCapture(const char *dllname)
+8 -15
View File
@@ -461,8 +461,6 @@ struct RemoteServerCommand : public Command
parser.add("daemon", 'd', "Go into the background.");
parser.add<std::string>(
"host", 'h', "The interface to listen on. By default listens on all interfaces", false, "");
parser.add<uint32_t>("port", 'p', "The port to listen on.", false,
RENDERDOC_GetDefaultRemoteServerPort());
parser.add("preview", 'v', "Display a preview window when a replay is active.");
}
virtual const char *Description()
@@ -474,12 +472,11 @@ struct RemoteServerCommand : public Command
virtual int Execute(cmdline::parser &parser, const CaptureOptions &)
{
std::string host = parser.get<std::string>("host");
uint32_t port = parser.get<uint32_t>("port");
RENDERDOC_InitGlobalEnv(m_Env, convertArgs(parser.rest()));
std::cerr << "Spawning a replay host listening on " << (host.empty() ? "*" : host) << ":"
<< port << "..." << std::endl;
std::cerr << "Spawning a replay host listening on " << (host.empty() ? "*" : host) << "..."
<< std::endl;
if(parser.exist("daemon"))
{
@@ -500,8 +497,8 @@ struct RemoteServerCommand : public Command
if(DisplayRemoteServerPreview(false, {}).system != WindowingSystem::Unknown)
previewWindow = &DisplayRemoteServerPreview;
RENDERDOC_BecomeRemoteServer(host.empty() ? NULL : host.c_str(), port,
[]() { return killSignal; }, previewWindow);
RENDERDOC_BecomeRemoteServer(host.empty() ? NULL : host.c_str(), []() { return killSignal; },
previewWindow);
std::cerr << std::endl << "Cleaning up from replay hosting." << std::endl;
@@ -522,8 +519,6 @@ struct ReplayCommand : public Command
parser.add<std::string>("remote-host", 0,
"Instead of replaying locally, replay on this host over the network.",
false);
parser.add<uint32_t>("remote-port", 0, "If --remote-host is set, use this port.", false,
RENDERDOC_GetDefaultRemoteServerPort());
}
virtual const char *Description()
{
@@ -551,18 +546,16 @@ struct ReplayCommand : public Command
if(parser.exist("remote-host"))
{
std::cout << "Replaying '" << filename << "' on " << parser.get<std::string>("remote-host")
<< ":" << parser.get<uint32_t>("remote-port") << "." << std::endl;
<< "." << std::endl;
IRemoteServer *remote = NULL;
ReplayStatus status =
RENDERDOC_CreateRemoteServerConnection(parser.get<std::string>("remote-host").c_str(),
parser.get<uint32_t>("remote-port"), &remote);
ReplayStatus status = RENDERDOC_CreateRemoteServerConnection(
parser.get<std::string>("remote-host").c_str(), &remote);
if(remote == NULL || status != ReplayStatus::Succeeded)
{
std::cerr << "Error: " << ToStr(status) << " - Couldn't connect to "
<< parser.get<std::string>("remote-host") << ":"
<< parser.get<uint32_t>("remote-port") << "." << std::endl;
<< parser.get<std::string>("remote-host") << "." << std::endl;
std::cerr << " Have you run renderdoccmd remoteserver on '"
<< parser.get<std::string>("remote-host") << "'?" << std::endl;
return 1;