diff --git a/docs/python_api/examples/renderdoc/index.rst b/docs/python_api/examples/renderdoc/index.rst index f64e0fbf7..9560d6d8b 100644 --- a/docs/python_api/examples/renderdoc/index.rst +++ b/docs/python_api/examples/renderdoc/index.rst @@ -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 \ No newline at end of file + display_window + remote_capture diff --git a/docs/python_api/examples/renderdoc/remote_capture.py b/docs/python_api/examples/renderdoc/remote_capture.py new file mode 100644 index 000000000..eb7f75069 --- /dev/null +++ b/docs/python_api/examples/renderdoc/remote_capture.py @@ -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() diff --git a/docs/python_api/examples/renderdoc/remote_capture.rst b/docs/python_api/examples/renderdoc/remote_capture.rst new file mode 100644 index 000000000..69e846d0b --- /dev/null +++ b/docs/python_api/examples/renderdoc/remote_capture.rst @@ -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 `. + +.. literalinclude:: remote_capture.py + diff --git a/docs/python_api/renderdoc/enums_data.rst b/docs/python_api/renderdoc/enums_data.rst index 44a7b6554..7b846e7c8 100644 --- a/docs/python_api/renderdoc/enums_data.rst +++ b/docs/python_api/renderdoc/enums_data.rst @@ -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 diff --git a/docs/python_api/renderdoc/funcs.rst b/docs/python_api/renderdoc/funcs.rst index db52f8343..6985aa25a 100644 --- a/docs/python_api/renderdoc/funcs.rst +++ b/docs/python_api/renderdoc/funcs.rst @@ -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 --------------------------- diff --git a/docs/python_api/renderdoc/main_ifaces.rst b/docs/python_api/renderdoc/main_ifaces.rst index a09bec7e8..27ceaa96a 100644 --- a/docs/python_api/renderdoc/main_ifaces.rst +++ b/docs/python_api/renderdoc/main_ifaces.rst @@ -33,6 +33,13 @@ RemoteServer :members: :undoc-members: +DeviceProtocolController +------------------------ + +.. autoclass:: renderdoc.DeviceProtocolController + :members: + :undoc-members: + CaptureFile ----------- diff --git a/qrenderdoc/Code/Interface/PersistantConfig.cpp b/qrenderdoc/Code/Interface/PersistantConfig.cpp index 9d9947cca..7bee7649a 100644 --- a/qrenderdoc/Code/Interface/PersistantConfig.cpp +++ b/qrenderdoc/Code/Interface/PersistantConfig.cpp @@ -259,15 +259,8 @@ void PersistantConfig::RemoveRemoteHost(RemoteHost host) } } -void PersistantConfig::AddAndroidHosts() +void PersistantConfig::UpdateEnumeratedProtocolDevices() { - QMutexLocker autolock(&RemoteHostLock); - - QMap 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 enumeratedDevices; + + rdcarray 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 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 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()) diff --git a/qrenderdoc/Code/Interface/PersistantConfig.h b/qrenderdoc/Code/Interface/PersistantConfig.h index 7f8a2e693..9c0330457 100644 --- a/qrenderdoc/Code/Interface/PersistantConfig.h +++ b/qrenderdoc/Code/Interface/PersistantConfig.h @@ -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() diff --git a/qrenderdoc/Code/Interface/RemoteHost.cpp b/qrenderdoc/Code/Interface/RemoteHost.cpp index 1df93398d..6b6c56d71 100644 --- a/qrenderdoc/Code/Interface/RemoteHost.cpp +++ b/qrenderdoc/Code/Interface/RemoteHost.cpp @@ -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; diff --git a/qrenderdoc/Code/Interface/RemoteHost.h b/qrenderdoc/Code/Interface/RemoteHost.h index bddb6b87a..ea2579928 100644 --- a/qrenderdoc/Code/Interface/RemoteHost.h +++ b/qrenderdoc/Code/Interface/RemoteHost.h @@ -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; diff --git a/qrenderdoc/Code/ReplayManager.cpp b/qrenderdoc/Code/ReplayManager.cpp index 45a52d3e8..59ee959a3 100644 --- a/qrenderdoc/Code/ReplayManager.cpp +++ b/qrenderdoc/Code/ReplayManager.cpp @@ -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); } diff --git a/qrenderdoc/Code/pyrenderdoc/qrenderdoc_stub.cpp b/qrenderdoc/Code/pyrenderdoc/qrenderdoc_stub.cpp index e29995611..e5faba74c 100644 --- a/qrenderdoc/Code/pyrenderdoc/qrenderdoc_stub.cpp +++ b/qrenderdoc/Code/pyrenderdoc/qrenderdoc_stub.cpp @@ -143,7 +143,7 @@ void PersistantConfig::RemoveRemoteHost(RemoteHost host) { } -void PersistantConfig::AddAndroidHosts() +void PersistantConfig::UpdateEnumeratedProtocolDevices() { } diff --git a/qrenderdoc/Code/pyrenderdoc/renderdoc.i b/qrenderdoc/Code/pyrenderdoc/renderdoc.i index 190fb9dfd..330be7c1b 100644 --- a/qrenderdoc/Code/pyrenderdoc/renderdoc.i +++ b/qrenderdoc/Code/pyrenderdoc/renderdoc.i @@ -85,6 +85,29 @@ $result = SWIG_NewPointerObj($1, $descriptor(struct CaptureOptions*), SWIG_POINTER_OWN); } +// same for RENDERDOC_GetSupportedDeviceProtocols +%typemap(in, numinputs=0) rdcarray *supportedProtocols { $1 = new rdcarray; } +%typemap(argout) rdcarray *supportedProtocols { + $result = ConvertToPy(*$1); + delete $1; +} +%typemap(freearg) rdcarray *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; diff --git a/qrenderdoc/Code/qrenderdoc.cpp b/qrenderdoc/Code/qrenderdoc.cpp index e5d201478..f632e0711 100644 --- a/qrenderdoc/Code/qrenderdoc.cpp +++ b/qrenderdoc/Code/qrenderdoc.cpp @@ -474,8 +474,6 @@ int main(int argc, char *argv[]) config.Save(); } - RENDERDOC_AndroidShutdown(); - PythonContext::GlobalShutdown(); Formatter::shutdown(); diff --git a/qrenderdoc/Windows/Dialogs/CaptureDialog.cpp b/qrenderdoc/Windows/Dialogs/CaptureDialog.cpp index def1faa46..6ab92ee58 100644 --- a/qrenderdoc/Windows/Dialogs/CaptureDialog.cpp +++ b/qrenderdoc/Windows/Dialogs/CaptureDialog.cpp @@ -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")); diff --git a/qrenderdoc/Windows/Dialogs/RemoteManager.cpp b/qrenderdoc/Windows/Dialogs/RemoteManager.cpp index bfe286f6b..577889a8c 100644 --- a/qrenderdoc/Windows/Dialogs/RemoteManager.cpp +++ b/qrenderdoc/Windows/Dialogs/RemoteManager.cpp @@ -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); diff --git a/qrenderdoc/Windows/MainWindow.cpp b/qrenderdoc/Windows/MainWindow.cpp index 3740653fd..415910dca 100644 --- a/qrenderdoc/Windows/MainWindow.cpp +++ b/qrenderdoc/Windows/MainWindow.cpp @@ -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 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 hosts; - { - QMutexLocker lock(&m_ProbeRemoteHostsLock); - hosts = m_ProbeRemoteHosts; - } + rdcarray 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; } diff --git a/qrenderdoc/Windows/MainWindow.h b/qrenderdoc/Windows/MainWindow.h index 6860e7e54..8e7965a2d 100644 --- a/qrenderdoc/Windows/MainWindow.h +++ b/qrenderdoc/Windows/MainWindow.h @@ -256,5 +256,5 @@ private: void showLaunchError(ReplayStatus status); - bool isCapturableAppRunningOnAndroid(); + bool isUnshareableDeviceInUse(); }; diff --git a/renderdoc/CMakeLists.txt b/renderdoc/CMakeLists.txt index 5942ed756..c976cfd94 100644 --- a/renderdoc/CMakeLists.txt +++ b/renderdoc/CMakeLists.txt @@ -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 diff --git a/renderdoc/android/android.cpp b/renderdoc/android/android.cpp index ddfc0fa7b..d80baf2f8 100644 --- a/renderdoc/android/android.cpp +++ b/renderdoc/android/android.cpp @@ -26,21 +26,24 @@ #include #include "api/replay/version.h" #include "core/core.h" +#include "core/remote_server.h" #include "strings/string_utils.h" #include "android_utils.h" namespace Android { -void adbForwardPorts(int index, const std::string &deviceID, uint16_t jdwpPort, int pid, bool silent) +void adbForwardPorts(uint16_t portbase, const std::string &deviceID, uint16_t jdwpPort, int pid, + bool silent) { const char *forwardCommand = "forward tcp:%i localabstract:renderdoc_%i"; - int offs = RenderDoc_AndroidPortOffset * (index + 1); - adbExecCommand(deviceID, StringFormat::Fmt(forwardCommand, RenderDoc_RemoteServerPort + offs, - RenderDoc_RemoteServerPort), + adbExecCommand(deviceID, + StringFormat::Fmt(forwardCommand, portbase + RenderDoc_ForwardRemoteServerOffset, + RenderDoc_RemoteServerPort), ".", silent); - adbExecCommand(deviceID, StringFormat::Fmt(forwardCommand, RenderDoc_FirstTargetControlPort + offs, - RenderDoc_FirstTargetControlPort), + adbExecCommand(deviceID, + StringFormat::Fmt(forwardCommand, portbase + RenderDoc_ForwardTargetControlOffset, + RenderDoc_FirstTargetControlPort), ".", silent); if(jdwpPort && pid) @@ -51,12 +54,12 @@ uint16_t GetJdwpPort() { // we loop over a number of ports to try and avoid previous failed attempts from leaving sockets // open and messing with subsequent attempts - const uint16_t portBase = RenderDoc_FirstTargetControlPort + RenderDoc_AndroidPortOffset * 2; + const uint16_t portBase = 39500; static uint16_t portIndex = 0; portIndex++; - portIndex %= RenderDoc_AndroidPortOffset; + portIndex %= 100; return portBase + portIndex; } @@ -176,159 +179,6 @@ int GetCurrentPID(const std::string &deviceID, const std::string &packageName) return 0; } -ExecuteResult StartAndroidPackageForCapture(const char *host, const char *packageAndActivity, - const char *intentArgs, const CaptureOptions &opts) -{ - int index = 0; - std::string deviceID; - Android::ExtractDeviceIDAndIndex(host, index, deviceID); - - ExecuteResult ret; - ret.status = ReplayStatus::UnknownError; - ret.ident = RenderDoc_FirstTargetControlPort + RenderDoc_AndroidPortOffset * (index + 1); - - std::string packageName = GetPackageName(packageAndActivity); // Remove leading '/' if any - - // adb shell cmd package resolve-activity -c android.intent.category.LAUNCHER com.jake.cube1 - std::string activityName = GetActivityName(packageAndActivity); - - // if the activity name isn't specified, get the default one - if(activityName.empty() || activityName == "#DefaultActivity") - activityName = GetDefaultActivityForPackage(deviceID, packageName); - - uint16_t jdwpPort = GetJdwpPort(); - - // remove any previous jdwp port forward on this port - adbExecCommand(deviceID, StringFormat::Fmt("forward --remove tcp:%i", jdwpPort)); - // force stop the package if it was running before - adbExecCommand(deviceID, "shell am force-stop " + packageName); - // enable the vulkan layer (will only be used by vulkan programs) - adbExecCommand(deviceID, "shell setprop debug.vulkan.layers " RENDERDOC_VULKAN_LAYER_NAME); - // if in VR mode, enable frame delimiter markers - adbExecCommand(deviceID, "shell setprop debug.vr.profiler 1"); - // create the data directory we will use for storing, in case the application doesn't - adbExecCommand(deviceID, "shell mkdir -p /sdcard/Android/data/" + packageName); - // set our property with the capture options encoded, to be picked up by the library on the device - adbExecCommand(deviceID, StringFormat::Fmt("shell setprop debug.rdoc.RENDERDOC_CAPOPTS %s", - opts.EncodeAsString().c_str())); - - std::string installedPath = GetPathForPackage(deviceID, packageName); - - std::string RDCLib = trim( - adbExecCommand(deviceID, "shell ls " + installedPath + "/lib/*/" RENDERDOC_ANDROID_LIBRARY) - .strStdout); - - // some versions of adb/android return the error message on stdout, so try to detect those and - // clear the output. - if(RDCLib.size() < installedPath.size() || RDCLib.substr(0, installedPath.size()) != installedPath) - RDCLib.clear(); - - // some versions of adb/android also don't print any error message at all! Look to see if the - // wildcard glob is still present. - if(RDCLib.find("/lib/*/" RENDERDOC_ANDROID_LIBRARY) != std::string::npos) - RDCLib.clear(); - - bool injectLibraries = true; - - if(RDCLib.empty()) - { - RDCLOG("No library found in %s/lib/*/" RENDERDOC_ANDROID_LIBRARY - " for %s - assuming injection is required.", - installedPath.c_str(), packageName.c_str()); - } - else - { - injectLibraries = false; - RDCLOG("Library found, no injection required: %s", RDCLib.c_str()); - } - - int pid = 0; - - RDCLOG("Launching package '%s' with activity '%s'", packageName.c_str(), activityName.c_str()); - - if(injectLibraries) - { - RDCLOG("Setting up to launch the application as a debugger to inject."); - - // start the activity in this package with debugging enabled and force-stop after starting - adbExecCommand(deviceID, - StringFormat::Fmt("shell am start -S -D -n %s/%s %s", packageName.c_str(), - activityName.c_str(), intentArgs)); - - // adb shell ps | grep $PACKAGE | awk '{print $2}') - pid = GetCurrentPID(deviceID, packageName); - - if(pid == 0) - RDCERR("Couldn't get PID when launching %s with activity %s", packageName.c_str(), - activityName.c_str()); - } - else - { - RDCLOG("Not doing any injection - assuming APK is pre-loaded with RenderDoc capture library."); - - // start the activity in this package with debugging enabled and force-stop after starting - adbExecCommand(deviceID, StringFormat::Fmt("shell am start -n %s/%s %s", packageName.c_str(), - activityName.c_str(), intentArgs)); - - // don't connect JDWP - jdwpPort = 0; - } - - adbForwardPorts(index, deviceID, jdwpPort, pid, false); - - // sleep a little to let the ports initialise - Threading::Sleep(500); - - if(jdwpPort) - { - // use a JDWP connection to inject our libraries - bool injected = InjectWithJDWP(deviceID, jdwpPort); - if(!injected) - { - RDCERR("Failed to inject using JDWP"); - ret.status = ReplayStatus::JDWPFailure; - ret.ident = 0; - return ret; - } - } - - ret.status = ReplayStatus::InjectionFailed; - - uint32_t elapsed = 0, - timeout = 1000 * - RDCMAX(5, atoi(RenderDoc::Inst().GetConfigSetting("MaxConnectTimeout").c_str())); - while(elapsed < timeout) - { - // Check if the target app has started yet and we can connect to it. - ITargetControl *control = RENDERDOC_CreateTargetControl(host, ret.ident, "testConnection", false); - if(control) - { - control->Shutdown(); - ret.status = ReplayStatus::Succeeded; - break; - } - - // check to see if the PID is still there. If it was before and isn't now, the APK has exited - // without ever opening a connection. - int curpid = GetCurrentPID(deviceID, packageName); - - if(pid != 0 && curpid == 0) - { - RDCERR("APK has crashed or never opened target control connection before closing."); - break; - } - - Threading::Sleep(1000); - elapsed += 1000; - } - - // we leave the setprop in case the application later initialises a vulkan device. It's impossible - // to tell if it will or not, since many applications will init and present from GLES and then - // later use vulkan. - - return ret; -} - bool CheckAndroidServerVersion(const std::string &deviceID, ABI abi) { // assume all servers are updated at the same rate. Only check first ABI's version @@ -554,38 +404,13 @@ void ResetCaptureSettings(const std::string &deviceID) { Android::adbExecCommand(deviceID, "shell setprop debug.vulkan.layers :", ".", true); } -}; // namespace Android -extern "C" RENDERDOC_API void RENDERDOC_CC RENDERDOC_GetAndroidFriendlyName(const rdcstr &device, - rdcstr &friendly) +rdcarray EnumerateDevices() { - if(!Android::IsHostADB(device.c_str())) - { - RDCERR("Calling RENDERDOC_GetAndroidFriendlyName with non-android device: %s", device.c_str()); - return; - } + rdcarray ret; - int index = 0; - std::string deviceID; - Android::ExtractDeviceIDAndIndex(device.c_str(), index, deviceID); - - if(deviceID.empty()) - { - RDCERR("Failed to get android device and index from: %s", device.c_str()); - return; - } - - friendly = Android::GetFriendlyName(deviceID); -} - -extern "C" RENDERDOC_API void RENDERDOC_CC RENDERDOC_EnumerateAndroidDevices(rdcstr &deviceList) -{ std::string adbStdout = Android::adbExecCommand("", "devices", ".", true).strStdout; - int idx = 0; - - std::string ret; - std::vector lines; split(adbStdout, lines, '\n'); for(const std::string &line : lines) @@ -593,102 +418,690 @@ extern "C" RENDERDOC_API void RENDERDOC_CC RENDERDOC_EnumerateAndroidDevices(rdc std::vector tokens; split(line, tokens, '\t'); if(tokens.size() == 2 && trim(tokens[1]) == "device") - { - if(ret.length()) - ret += ","; - - ret += StringFormat::Fmt("adb:%d:%s", idx, tokens[0].c_str()); - - // Forward the ports so we can see if a remoteserver/captured app is already running. - Android::adbForwardPorts(idx, tokens[0], 0, 0, true); - - idx++; - } + ret.push_back(tokens[0]); } - deviceList = ret; + return ret; } +}; // namespace Android -extern "C" RENDERDOC_API void RENDERDOC_CC RENDERDOC_AndroidInitialise() +struct AndroidRemoteServer : public RemoteServer { - Android::initAdb(); -} - -extern "C" RENDERDOC_API void RENDERDOC_CC RENDERDOC_AndroidShutdown() -{ - Android::shutdownAdb(); -} - -extern "C" RENDERDOC_API bool RENDERDOC_CC RENDERDOC_IsAndroidSupported(const char *device) -{ - int index = 0; - std::string deviceID; - - Android::ExtractDeviceIDAndIndex(device, index, deviceID); - - return Android::IsSupported(deviceID); -} - -extern "C" RENDERDOC_API ReplayStatus RENDERDOC_CC RENDERDOC_StartAndroidRemoteServer(const char *device) -{ - ReplayStatus status = ReplayStatus::Succeeded; - int index = 0; - std::string deviceID; - - Android::ExtractDeviceIDAndIndex(device, index, deviceID); - - std::string packagesOutput = trim( - Android::adbExecCommand(deviceID, "shell pm list packages " RENDERDOC_ANDROID_PACKAGE_BASE) - .strStdout); - - std::vector packages; - split(packagesOutput, packages, '\n'); - - std::vector abis = Android::GetSupportedABIs(deviceID); - - RDCLOG("Starting RenderDoc server, supported ABIs:"); - for(Android::ABI abi : abis) - RDCLOG(" - %s", ToStr(abi).c_str()); - - if(abis.empty()) - return ReplayStatus::UnknownError; - - // assume all servers are updated at the same rate. Only check first ABI's version - if(packages.size() != abis.size() || !Android::CheckAndroidServerVersion(deviceID, abis[0])) + AndroidRemoteServer(Network::Socket *sock, const rdcstr &deviceID, uint16_t portbase) + : RemoteServer(sock, deviceID), m_portbase(portbase) { - // if there was any existing package, remove it - if(!packages.empty()) - { - if(Android::RemoveRenderDocAndroidServer(deviceID)) - RDCLOG("Uninstall of old server succeeded"); - else - RDCERR("Uninstall of old server failed"); - } + } - // If server is not detected or has been removed due to incompatibility, install it - status = Android::InstallRenderDocServer(deviceID); - if(status != ReplayStatus::Succeeded && status != ReplayStatus::AndroidGrantPermissionsFailed && - status != ReplayStatus::AndroidAPKVerifyFailed) + virtual ~AndroidRemoteServer() + { + if(m_LogcatThread) + m_LogcatThread->Finish(); + } + + virtual void ShutdownConnection() override + { + ResetAndroidSettings(); + RemoteServer::ShutdownConnection(); + } + + virtual void ShutdownServerAndConnection() override + { + ResetAndroidSettings(); + RemoteServer::ShutdownServerAndConnection(); + } + + virtual bool Ping() override + { + if(!Connected()) + return false; + + LazilyStartLogcatThread(); + + return RemoteServer::Ping(); + } + + virtual rdcpair OpenCapture( + uint32_t proxyid, const char *filename, RENDERDOC_ProgressCallback progress) override + { + ResetAndroidSettings(); + + LazilyStartLogcatThread(); + + return RemoteServer::OpenCapture(proxyid, filename, progress); + } + + virtual rdcstr GetHomeFolder() override { return ""; } + virtual rdcarray ListFolder(const char *path) override + { + if(path[0] == 0 || (path[0] == '/' && path[1] == 0)) { - RDCERR("Failed to install RenderDoc server app"); - return status; + SCOPED_TIMER("Fetching android packages and activities"); + + std::string adbStdout = + Android::adbExecCommand(m_deviceID, "shell pm list packages -3").strStdout; + + std::vector lines; + split(adbStdout, lines, '\n'); + + std::vector packages; + for(const std::string &line : lines) + { + // hide our own internal packages + if(strstr(line.c_str(), "package:org.renderdoc.")) + continue; + + if(!strncmp(line.c_str(), "package:", 8)) + { + PathEntry pkg; + pkg.filename = trim(line.substr(8)); + pkg.size = 0; + pkg.lastmod = 0; + pkg.flags = PathProperty::Directory; + + packages.push_back(pkg); + } + } + + adbStdout = Android::adbExecCommand(m_deviceID, "shell dumpsys package").strStdout; + + split(adbStdout, lines, '\n'); + + for(const std::string &line : lines) + { + // quick check, look for a / + if(line.find('/') == std::string::npos) + continue; + + // line should be something like: ' 78f9sba com.package.name/.NameOfActivity .....' + + const char *c = line.c_str(); + + // expect whitespace + while(*c && isspace(*c)) + c++; + + // expect hex + while(*c && ((*c >= '0' && *c <= '9') || (*c >= 'a' && *c <= 'f'))) + c++; + + // expect space + if(*c != ' ') + continue; + + c++; + + // expect the package now. Search to see if it's one of the ones we listed above + std::string package; + + for(const PathEntry &p : packages) + if(!strncmp(c, p.filename.c_str(), p.filename.size())) + package = p.filename; + + // didn't find a matching package + if(package.empty()) + continue; + + c += package.size(); + + // expect a / + if(*c != '/') + continue; + + c++; + + const char *end = strchr(c, ' '); + + if(end == NULL) + end = c + strlen(c); + + while(isspace(*(end - 1))) + end--; + + m_AndroidActivities.insert({package, rdcstr(c, end - c)}); + } + + return packages; + } + else + { + rdcstr package = path; + + if(!package.empty() && package[0] == '/') + package.erase(0); + + std::vector activities; + + for(const Activity &act : m_AndroidActivities) + { + if(act.package == package) + { + PathEntry activity; + if(act.activity[0] == '.') + activity.filename = package + act.activity; + else + activity.filename = act.activity; + activity.size = 0; + activity.lastmod = 0; + activity.flags = PathProperty::Executable; + activities.push_back(activity); + } + } + + PathEntry defaultActivity; + defaultActivity.filename = "#DefaultActivity"; + defaultActivity.size = 0; + defaultActivity.lastmod = 0; + defaultActivity.flags = PathProperty::Executable; + + // if there's only one activity listed, assume it's the default and don't add a virtual + // entry + if(activities.size() != 1) + activities.push_back(defaultActivity); + + return activities; } } - // stop all servers of any ABI - for(Android::ABI abi : abis) - Android::adbExecCommand(deviceID, "shell am force-stop " + GetRenderDocPackageForABI(abi)); + virtual ExecuteResult ExecuteAndInject(const char *a, const char *w, const char *c, + const rdcarray &env, + const CaptureOptions &opts) override; - if(abis.empty()) - return ReplayStatus::AndroidABINotFound; +private: + void ResetAndroidSettings() { Android::ResetCaptureSettings(m_deviceID); } + void LazilyStartLogcatThread() + { + if(m_LogcatThread) + return; - Android::adbForwardPorts(index, deviceID, 0, 0, false); - Android::ResetCaptureSettings(deviceID); + m_LogcatThread = Android::ProcessLogcat(m_deviceID); + } - // launch the last ABI, as the 64-bit version where possible, or 32-bit version where not. - // Captures are portable across bitness and in some cases a 64-bit capture can't replay on a - // 32-bit remote server. - Android::adbExecCommand(deviceID, "shell am start -n " + GetRenderDocPackageForABI(abis.back()) + - "/.Loader -e renderdoccmd remoteserver"); - return status; + uint16_t m_portbase = 0; + Android::LogcatThread *m_LogcatThread = NULL; + + struct Activity + { + rdcstr package; + rdcstr activity; + + bool operator<(const Activity &o) const + { + if(package != o.package) + return package < o.package; + return activity < o.activity; + } + }; + + std::set m_AndroidActivities; +}; + +struct AndroidController : public IDeviceProtocolHandler +{ + void Start() + { + SCOPED_LOCK(lock); + if(running == 0) + { + Atomic::Inc32(&running); + + Android::initAdb(); + + thread = Threading::CreateThread([]() { m_Inst.ThreadEntry(); }); + RenderDoc::Inst().RegisterShutdownFunction([]() { m_Inst.Shutdown(); }); + } + } + + void Shutdown() + { + SCOPED_LOCK(lock); + Atomic::Dec32(&running); + Threading::JoinThread(thread); + Threading::CloseThread(thread); + thread = 0; + + Android::shutdownAdb(); + } + + struct Command + { + std::function meth; + int32_t done = 0; + }; + + rdcarray cmdqueue; + + void ThreadEntry() + { + while(Atomic::CmpExch32(&running, 1, 1) == 1) + { + Threading::Sleep(5); + Command *cmd = NULL; + + { + SCOPED_LOCK(lock); + if(cmdqueue.empty()) + continue; + + cmd = cmdqueue[0]; + cmdqueue.erase(0); + } + + cmd->meth(); + + Atomic::Inc32(&cmd->done); + } + } + + void Invoke(std::function method) + { + Command cmd; + cmd.meth = method; + + { + SCOPED_LOCK(lock); + cmdqueue.push_back(&cmd); + } + + while(Atomic::CmpExch32(&cmd.done, 0, 0) == 0) + Threading::Sleep(5); + } + + rdcstr GetProtocolName() override { return "adb"; } + rdcarray GetDevices() override + { + rdcarray ret; + + Invoke([this, &ret]() { + rdcarray activedevices = Android::EnumerateDevices(); + + // reset all devices to inactive + for(auto it = devices.begin(); it != devices.end(); ++it) + it->second.active = false; + + // process the list of active devices, find matches and activate them, or add a new entry + for(const rdcstr &d : activedevices) + { + auto it = devices.find(d); + if(it != devices.end()) + { + it->second.active = true; + + // silently forward the ports now. These may be refreshed but this will allow us to + // connect + Android::adbForwardPorts(it->second.portbase, d, 0, 0, true); + continue; + } + + // not found - add a new device + Device dev; + dev.active = true; + dev.name = Android::GetFriendlyName(d); + dev.portbase = + uint16_t(RenderDoc_ForwardPortBase + + RenderDoc::Inst().GetForwardedPortSlot() * RenderDoc_ForwardPortStride); + + // silently forward the ports now. These may be refreshed but this will allow us to connect + Android::adbForwardPorts(dev.portbase, d, 0, 0, true); + + devices[d] = dev; + } + + for(auto it = devices.begin(); it != devices.end(); ++it) + { + if(it->second.active) + ret.push_back(it->first); + } + }); + + return ret; + } + + rdcstr GetFriendlyName(const rdcstr &URL) override + { + rdcstr ret; + + { + SCOPED_LOCK(lock); + ret = devices[GetDeviceID(URL)].name; + } + + return ret; + } + + bool SupportsMultiplePrograms(const rdcstr &URL) override { return false; } + bool IsSupported(const rdcstr &URL) override + { + bool ret = false; + + { + SCOPED_LOCK(lock); + ret = Android::IsSupported(GetDeviceID(URL)); + } + + return ret; + } + + ReplayStatus StartRemoteServer(const rdcstr &URL) override + { + ReplayStatus status = ReplayStatus::Succeeded; + + Invoke([this, &status, URL]() { + + rdcstr deviceID = GetDeviceID(URL); + + Device &dev = devices[deviceID]; + + if(!dev.active) + { + status = ReplayStatus::InternalError; + return; + } + + std::string packagesOutput = + trim(Android::adbExecCommand(deviceID, + "shell pm list packages " RENDERDOC_ANDROID_PACKAGE_BASE) + .strStdout); + + std::vector packages; + split(packagesOutput, packages, '\n'); + + std::vector abis = Android::GetSupportedABIs(deviceID); + + RDCLOG("Starting RenderDoc server, supported ABIs:"); + for(Android::ABI abi : abis) + RDCLOG(" - %s", ToStr(abi).c_str()); + + if(abis.empty()) + { + status = ReplayStatus::AndroidABINotFound; + return; + } + + // assume all servers are updated at the same rate. Only check first ABI's version + if(packages.size() != abis.size() || !Android::CheckAndroidServerVersion(deviceID, abis[0])) + { + // if there was any existing package, remove it + if(!packages.empty()) + { + if(Android::RemoveRenderDocAndroidServer(deviceID)) + RDCLOG("Uninstall of old server succeeded"); + else + RDCERR("Uninstall of old server failed"); + } + + // If server is not detected or has been removed due to incompatibility, install it + status = Android::InstallRenderDocServer(deviceID); + if(status != ReplayStatus::Succeeded && + status != ReplayStatus::AndroidGrantPermissionsFailed && + status != ReplayStatus::AndroidAPKVerifyFailed) + { + RDCERR("Failed to install RenderDoc server app"); + return; + } + } + + // stop all servers of any ABI + for(Android::ABI abi : abis) + Android::adbExecCommand(deviceID, "shell am force-stop " + GetRenderDocPackageForABI(abi)); + + Android::adbForwardPorts(dev.portbase, deviceID, 0, 0, false); + Android::ResetCaptureSettings(deviceID); + + // launch the last ABI, as the 64-bit version where possible, or 32-bit version where not. + // Captures are portable across bitness and in some cases a 64-bit capture can't replay on a + // 32-bit remote server. + Android::adbExecCommand(deviceID, "shell am start -n " + GetRenderDocPackageForABI(abis.back()) + + "/.Loader -e renderdoccmd remoteserver"); + }); + + // allow the package to start and begin listening before we return + Threading::Sleep(500); + + return status; + } + + rdcstr RemapHostname(const rdcstr &deviceID) override + { + // we always connect to localhost + return "127.0.0.1"; + } + + uint16_t RemapPort(const rdcstr &deviceID, uint16_t srcPort) override + { + uint16_t portbase = 0; + + { + SCOPED_LOCK(lock); + portbase = devices[deviceID].portbase; + } + + if(portbase == 0) + return 0; + + if(srcPort == RenderDoc_RemoteServerPort) + return portbase + RenderDoc_ForwardRemoteServerOffset; + // we only support a single target control connection on android + else if(srcPort == RenderDoc_FirstTargetControlPort) + return portbase + RenderDoc_ForwardTargetControlOffset; + + return 0; + } + + IRemoteServer *CreateRemoteServer(Network::Socket *sock, const rdcstr &deviceID) override + { + uint16_t portbase = 0; + + { + SCOPED_LOCK(lock); + portbase = devices[deviceID].portbase; + } + + return new AndroidRemoteServer(sock, deviceID, portbase); + } + + int32_t running = 0; + struct Device + { + rdcstr name; + uint16_t portbase; + bool active; + }; + std::map devices; + Threading::CriticalSection lock; + Threading::ThreadHandle thread; + static AndroidController m_Inst; + + static IDeviceProtocolHandler *Get() + { + m_Inst.Start(); + return &m_Inst; + }; +}; +ExecuteResult AndroidRemoteServer::ExecuteAndInject(const char *a, const char *w, const char *c, + const rdcarray &env, + const CaptureOptions &opts) +{ + LazilyStartLogcatThread(); + + std::string packageAndActivity = a && a[0] ? a : ""; + std::string intentArgs = c && c[0] ? c : ""; + + // we spin up a thread to Ping() every second, since StartAndroidPackageForCapture can block + // for a long time. + volatile int32_t done = 0; + Threading::ThreadHandle pingThread = Threading::CreateThread([&done, this]() { + bool ok = true; + while(ok && Atomic::CmpExch32(&done, 0, 0) == 0) + ok = Ping(); + }); + + ExecuteResult ret; + + AndroidController::m_Inst.Invoke([this, &ret, packageAndActivity, intentArgs, opts]() { + ret.status = ReplayStatus::UnknownError; + ret.ident = RenderDoc_FirstTargetControlPort; + + std::string packageName = + Android::GetPackageName(packageAndActivity); // Remove leading '/' if any + + // adb shell cmd package resolve-activity -c android.intent.category.LAUNCHER com.jake.cube1 + std::string activityName = Android::GetActivityName(packageAndActivity); + + // if the activity name isn't specified, get the default one + if(activityName.empty() || activityName == "#DefaultActivity") + activityName = Android::GetDefaultActivityForPackage(m_deviceID, packageName); + + uint16_t jdwpPort = Android::GetJdwpPort(); + + // remove any previous jdwp port forward on this port + Android::adbExecCommand(m_deviceID, StringFormat::Fmt("forward --remove tcp:%i", jdwpPort)); + // force stop the package if it was running before + Android::adbExecCommand(m_deviceID, "shell am force-stop " + packageName); + // enable the vulkan layer (will only be used by vulkan programs) + Android::adbExecCommand(m_deviceID, + "shell setprop debug.vulkan.layers " RENDERDOC_VULKAN_LAYER_NAME); + // if in VR mode, enable frame delimiter markers + Android::adbExecCommand(m_deviceID, "shell setprop debug.vr.profiler 1"); + // create the data directory we will use for storing, in case the application doesn't + Android::adbExecCommand(m_deviceID, "shell mkdir -p /sdcard/Android/data/" + packageName); + // set our property with the capture options encoded, to be picked up by the library on the + // device + Android::adbExecCommand(m_deviceID, + StringFormat::Fmt("shell setprop debug.rdoc.RENDERDOC_CAPOPTS %s", + opts.EncodeAsString().c_str())); + + std::string installedPath = Android::GetPathForPackage(m_deviceID, packageName); + + std::string RDCLib = + trim(Android::adbExecCommand( + m_deviceID, "shell ls " + installedPath + "/lib/*/" RENDERDOC_ANDROID_LIBRARY) + .strStdout); + + // some versions of adb/android return the error message on stdout, so try to detect those and + // clear the output. + if(RDCLib.size() < installedPath.size() || RDCLib.substr(0, installedPath.size()) != installedPath) + RDCLib.clear(); + + // some versions of adb/android also don't print any error message at all! Look to see if the + // wildcard glob is still present. + if(RDCLib.find("/lib/*/" RENDERDOC_ANDROID_LIBRARY) != std::string::npos) + RDCLib.clear(); + + bool injectLibraries = true; + + if(RDCLib.empty()) + { + RDCLOG("No library found in %s/lib/*/" RENDERDOC_ANDROID_LIBRARY + " for %s - assuming injection is required.", + installedPath.c_str(), packageName.c_str()); + } + else + { + injectLibraries = false; + RDCLOG("Library found, no injection required: %s", RDCLib.c_str()); + } + + int pid = 0; + + RDCLOG("Launching package '%s' with activity '%s'", packageName.c_str(), activityName.c_str()); + + if(injectLibraries) + { + RDCLOG("Setting up to launch the application as a debugger to inject."); + + // start the activity in this package with debugging enabled and force-stop after starting + Android::adbExecCommand( + m_deviceID, StringFormat::Fmt("shell am start -S -D -n %s/%s %s", packageName.c_str(), + activityName.c_str(), intentArgs.c_str())); + + // adb shell ps | grep $PACKAGE | awk '{print $2}') + pid = Android::GetCurrentPID(m_deviceID, packageName); + + if(pid == 0) + RDCERR("Couldn't get PID when launching %s with activity %s", packageName.c_str(), + activityName.c_str()); + } + else + { + RDCLOG( + "Not doing any injection - assuming APK is pre-loaded with RenderDoc capture library."); + + // start the activity in this package with debugging enabled and force-stop after starting + Android::adbExecCommand(m_deviceID, + StringFormat::Fmt("shell am start -n %s/%s %s", packageName.c_str(), + activityName.c_str(), intentArgs.c_str())); + + // don't connect JDWP + jdwpPort = 0; + } + + Android::adbForwardPorts(m_portbase, m_deviceID, jdwpPort, pid, false); + + // sleep a little to let the ports initialise + Threading::Sleep(500); + + if(jdwpPort) + { + // use a JDWP connection to inject our libraries + bool injected = Android::InjectWithJDWP(m_deviceID, jdwpPort); + if(!injected) + { + RDCERR("Failed to inject using JDWP"); + ret.status = ReplayStatus::JDWPFailure; + ret.ident = 0; + return; + } + } + + ret.status = ReplayStatus::InjectionFailed; + + uint32_t elapsed = 0, + timeout = + 1000 * + RDCMAX(5, atoi(RenderDoc::Inst().GetConfigSetting("MaxConnectTimeout").c_str())); + while(elapsed < timeout) + { + // Check if the target app has started yet and we can connect to it. + ITargetControl *control = RENDERDOC_CreateTargetControl( + (AndroidController::m_Inst.GetProtocolName() + "://" + m_deviceID).c_str(), ret.ident, + "testConnection", false); + if(control) + { + control->Shutdown(); + ret.status = ReplayStatus::Succeeded; + break; + } + + // check to see if the PID is still there. If it was before and isn't now, the APK has + // exited + // without ever opening a connection. + int curpid = Android::GetCurrentPID(m_deviceID, packageName); + + if(pid != 0 && curpid == 0) + { + RDCERR("APK has crashed or never opened target control connection before closing."); + break; + } + + Threading::Sleep(1000); + elapsed += 1000; + } + + // we leave the setprop in case the application later initialises a vulkan device. It's + // impossible to tell if it will or not, since many applications will init and present from GLES + // and then later use vulkan. + + return; + }); + + Atomic::Inc32(&done); + + Threading::JoinThread(pingThread); + Threading::CloseThread(pingThread); + + return ret; } + +AndroidController AndroidController::m_Inst; + +DeviceProtocolRegistration androidProtocol("adb", &AndroidController::Get); \ No newline at end of file diff --git a/renderdoc/android/android_patch.cpp b/renderdoc/android/android_patch.cpp index 9863bc0b7..dba197f01 100644 --- a/renderdoc/android/android_patch.cpp +++ b/renderdoc/android/android_patch.cpp @@ -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) diff --git a/renderdoc/android/android_utils.cpp b/renderdoc/android/android_utils.cpp index bece1e294..516e4bf77 100644 --- a/renderdoc/android/android_utils.cpp +++ b/renderdoc/android/android_utils.cpp @@ -27,8 +27,6 @@ #include "core/core.h" #include "strings/string_utils.h" -static std::map 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 = ""; diff --git a/renderdoc/android/android_utils.h b/renderdoc/android/android_utils.h index 78a64959e..d508cb816 100644 --- a/renderdoc/android/android_utils.h +++ b/renderdoc/android/android_utils.h @@ -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 diff --git a/renderdoc/api/replay/renderdoc_replay.h b/renderdoc/api/replay/renderdoc_replay.h index 6949b5ad1..3489c56b2 100644 --- a/renderdoc/api/replay/renderdoc_replay.h +++ b/renderdoc/api/replay/renderdoc_replay.h @@ -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 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 *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, diff --git a/renderdoc/common/globalconfig.h b/renderdoc/common/globalconfig.h index 45b78c638..e1fa7431b 100644 --- a/renderdoc/common/globalconfig.h +++ b/renderdoc/common/globalconfig.h @@ -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" diff --git a/renderdoc/core/core.cpp b/renderdoc/core/core.cpp index 8761e0537..d0d83a6bd 100644 --- a/renderdoc/core/core.cpp +++ b/renderdoc/core/core.cpp @@ -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 RenderDoc::GetSupportedDeviceProtocols() +{ + rdcarray 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 RenderDoc::GetCaptureFileFormats() { std::vector ret = m_ImportExportFormats; diff --git a/renderdoc/core/core.h b/renderdoc/core/core.h index d52a8ffef..8249a037e 100644 --- a/renderdoc/core/core.h +++ b/renderdoc/core/core.h @@ -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 &myJSONs, std::vector &otherJSONs); @@ -406,6 +425,7 @@ public: const GlobalEnvironment GetGlobalEnvironment() { return m_GlobalEnv; } void ProcessGlobalEnvironment(GlobalEnvironment env, const std::vector &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 GetSupportedDeviceProtocols(); + IDeviceProtocolHandler *GetDeviceProtocol(const rdcstr &protocol); + std::vector 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 m_Importers; std::map m_Exporters; + std::map 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); + } }; \ No newline at end of file diff --git a/renderdoc/core/remote_server.cpp b/renderdoc/core/remote_server.cpp index 388b4eabc..22ddd5ba6 100644 --- a/renderdoc/core/remote_server.cpp +++ b/renderdoc/core/remote_server.cpp @@ -23,6 +23,7 @@ * THE SOFTWARE. ******************************************************************************/ +#include "remote_server.h" #include #include #include "android/android.h" @@ -1027,1027 +1028,33 @@ void RenderDoc::BecomeRemoteServer(const char *listenhost, uint16_t port, SAFE_DELETE(sock); } -struct RemoteServer : public IRemoteServer -{ -public: - RemoteServer(Network::Socket *sock, const char *hostname) - : m_Socket(sock), - m_hostname(hostname), - reader(new StreamReader(sock, Ownership::Nothing), Ownership::Stream), - writer(new StreamWriter(sock, Ownership::Nothing), Ownership::Stream) - { - writer.SetStreamingMode(true); - reader.SetStreamingMode(true); - - std::map m = RenderDoc::Inst().GetReplayDrivers(); - - m_Proxies.reserve(m.size()); - for(auto it = m.begin(); it != m.end(); ++it) - m_Proxies.push_back({it->first, it->second}); - - m_LogcatThread = NULL; - } - - const std::string &hostname() const { return m_hostname; } - virtual ~RemoteServer() - { - SAFE_DELETE(m_Socket); - if(m_LogcatThread) - m_LogcatThread->Finish(); - } - - void ShutdownConnection() - { - ResetAndroidSettings(); - delete this; - } - - void ShutdownServerAndConnection() - { - ResetAndroidSettings(); - { - WRITE_DATA_SCOPE(); - SCOPED_SERIALISE_CHUNK(eRemoteServer_ShutdownServer); - } - - { - READ_DATA_SCOPE(); - RemoteServerPacket type = ser.ReadChunk(); - ser.EndChunk(); - - RDCASSERT(type == eRemoteServer_ShutdownServer); - } - - delete this; - } - - bool Connected() { return m_Socket != NULL && m_Socket->Connected(); } - bool Ping() - { - if(!Connected()) - return false; - - LazilyStartLogcatThread(); - - const char *host = hostname().c_str(); - if(Android::IsHostADB(host)) - { - int index = 0; - std::string deviceID; - Android::ExtractDeviceIDAndIndex(m_hostname, index, deviceID); - } - - { - WRITE_DATA_SCOPE(); - SCOPED_SERIALISE_CHUNK(eRemoteServer_Ping); - } - - RemoteServerPacket type; - - { - READ_DATA_SCOPE(); - type = ser.ReadChunk(); - ser.EndChunk(); - } - - return type == eRemoteServer_Ping; - } - - rdcarray LocalProxies() - { - rdcarray out; - - m_Proxies.reserve(m_Proxies.size()); - - size_t i = 0; - for(auto it = m_Proxies.begin(); it != m_Proxies.end(); ++it, ++i) - out.push_back(it->second); - - return out; - } - - rdcarray RemoteSupportedReplays() - { - rdcarray out; - - { - WRITE_DATA_SCOPE(); - SCOPED_SERIALISE_CHUNK(eRemoteServer_RemoteDriverList); - } - - { - READ_DATA_SCOPE(); - - RemoteServerPacket type = ser.ReadChunk(); - - if(type == eRemoteServer_RemoteDriverList) - { - uint32_t count = 0; - SERIALISE_ELEMENT(count); - - out.reserve(count); - - for(uint32_t i = 0; i < count; i++) - { - RDCDriver driverType = RDCDriver::Unknown; - std::string driverName = ""; - - SERIALISE_ELEMENT(driverType); - SERIALISE_ELEMENT(driverName); - - out.push_back(driverName); - } - } - else - { - RDCERR("Unexpected response to remote driver list request"); - } - - ser.EndChunk(); - } - - return out; - } - - rdcstr GetHomeFolder() - { - if(Android::IsHostADB(m_hostname.c_str())) - return ""; - - { - WRITE_DATA_SCOPE(); - SCOPED_SERIALISE_CHUNK(eRemoteServer_HomeDir); - } - - rdcstr home; - - { - READ_DATA_SCOPE(); - - RemoteServerPacket type = ser.ReadChunk(); - - if(type == eRemoteServer_HomeDir) - { - SERIALISE_ELEMENT(home); - } - else - { - RDCERR("Unexpected response to home folder request"); - } - - ser.EndChunk(); - } - - return home; - } - - rdcarray ListFolder(const char *path) - { - if(Android::IsHostADB(m_hostname.c_str())) - { - int index = 0; - std::string deviceID; - Android::ExtractDeviceIDAndIndex(m_hostname, index, deviceID); - - if(path[0] == 0 || (path[0] == '/' && path[1] == 0)) - { - SCOPED_TIMER("Fetching android packages and activities"); - - std::string adbStdout = - Android::adbExecCommand(deviceID, "shell pm list packages -3").strStdout; - - std::vector lines; - split(adbStdout, lines, '\n'); - - std::vector packages; - for(const std::string &line : lines) - { - // hide our own internal packages - if(strstr(line.c_str(), "package:org.renderdoc.")) - continue; - - if(!strncmp(line.c_str(), "package:", 8)) - { - PathEntry pkg; - pkg.filename = trim(line.substr(8)); - pkg.size = 0; - pkg.lastmod = 0; - pkg.flags = PathProperty::Directory; - - packages.push_back(pkg); - } - } - - adbStdout = Android::adbExecCommand(deviceID, "shell dumpsys package").strStdout; - - split(adbStdout, lines, '\n'); - - for(const std::string &line : lines) - { - // quick check, look for a / - if(line.find('/') == std::string::npos) - continue; - - // line should be something like: ' 78f9sba com.package.name/.NameOfActivity .....' - - const char *c = line.c_str(); - - // expect whitespace - while(*c && isspace(*c)) - c++; - - // expect hex - while(*c && ((*c >= '0' && *c <= '9') || (*c >= 'a' && *c <= 'f'))) - c++; - - // expect space - if(*c != ' ') - continue; - - c++; - - // expect the package now. Search to see if it's one of the ones we listed above - std::string package; - - for(const PathEntry &p : packages) - if(!strncmp(c, p.filename.c_str(), p.filename.size())) - package = p.filename; - - // didn't find a matching package - if(package.empty()) - continue; - - c += package.size(); - - // expect a / - if(*c != '/') - continue; - - c++; - - const char *end = strchr(c, ' '); - - if(end == NULL) - end = c + strlen(c); - - while(isspace(*(end - 1))) - end--; - - m_AndroidActivities.insert({package, rdcstr(c, end - c)}); - } - - return packages; - } - else - { - rdcstr package = path; - - if(!package.empty() && package[0] == '/') - package.erase(0); - - std::vector activities; - - for(const Activity &act : m_AndroidActivities) - { - if(act.package == package) - { - PathEntry activity; - if(act.activity[0] == '.') - activity.filename = package + act.activity; - else - activity.filename = act.activity; - activity.size = 0; - activity.lastmod = 0; - activity.flags = PathProperty::Executable; - activities.push_back(activity); - } - } - - PathEntry defaultActivity; - defaultActivity.filename = "#DefaultActivity"; - defaultActivity.size = 0; - defaultActivity.lastmod = 0; - defaultActivity.flags = PathProperty::Executable; - - // if there's only one activity listed, assume it's the default and don't add a virtual - // entry - if(activities.size() != 1) - activities.push_back(defaultActivity); - - return activities; - } - } - - { - WRITE_DATA_SCOPE(); - SCOPED_SERIALISE_CHUNK(eRemoteServer_ListDir); - SERIALISE_ELEMENT(path); - } - - rdcarray files; - - { - READ_DATA_SCOPE(); - - RemoteServerPacket type = ser.ReadChunk(); - - if(type == eRemoteServer_ListDir) - { - SERIALISE_ELEMENT(files); - } - else - { - RDCERR("Unexpected response to list directory request"); - files.resize(1); - files[0].filename = path; - files[0].flags = PathProperty::ErrorUnknown; - } - - ser.EndChunk(); - } - - return files; - } - - ExecuteResult ExecuteAndInject(const char *a, const char *w, const char *c, - const rdcarray &env, - const CaptureOptions &opts) - { - std::string app = a && a[0] ? a : ""; - std::string workingDir = w && w[0] ? w : ""; - std::string cmdline = c && c[0] ? c : ""; - - LazilyStartLogcatThread(); - - const char *host = hostname().c_str(); - if(Android::IsHostADB(host)) - { - // we spin up a thread to Ping() every second, since StartAndroidPackageForCapture can block - // for a long time. - volatile int32_t done = 0; - Threading::ThreadHandle pingThread = Threading::CreateThread([&done, this]() { - bool ok = true; - while(ok && Atomic::CmpExch32(&done, 0, 0) == 0) - ok = Ping(); - }); - - ExecuteResult ret = - Android::StartAndroidPackageForCapture(host, app.c_str(), cmdline.c_str(), opts); - - Atomic::Inc32(&done); - - Threading::JoinThread(pingThread); - Threading::CloseThread(pingThread); - - return ret; - } - - { - WRITE_DATA_SCOPE(); - SCOPED_SERIALISE_CHUNK(eRemoteServer_ExecuteAndInject); - SERIALISE_ELEMENT(app); - SERIALISE_ELEMENT(workingDir); - SERIALISE_ELEMENT(cmdline); - SERIALISE_ELEMENT(opts); - SERIALISE_ELEMENT(env); - } - - ExecuteResult ret = {}; - - { - READ_DATA_SCOPE(); - RemoteServerPacket type = ser.ReadChunk(); - - if(type == eRemoteServer_ExecuteAndInject) - { - SERIALISE_ELEMENT(ret); - } - else - { - RDCERR("Unexpected response to execute and inject request"); - } - - ser.EndChunk(); - } - - return ret; - } - - void CopyCaptureFromRemote(const char *remotepath, const char *localpath, - RENDERDOC_ProgressCallback progress) - { - std::string path = remotepath; - - { - WRITE_DATA_SCOPE(); - SCOPED_SERIALISE_CHUNK(eRemoteServer_CopyCaptureFromRemote); - SERIALISE_ELEMENT(path); - } - - { - READ_DATA_SCOPE(); - RemoteServerPacket type = ser.ReadChunk(); - - if(type == eRemoteServer_CopyCaptureFromRemote) - { - StreamWriter streamWriter(FileIO::fopen(localpath, "wb"), Ownership::Stream); - - ser.SerialiseStream(localpath, streamWriter, progress); - - if(ser.IsErrored()) - { - RDCERR("Network error receiving file"); - return; - } - } - else - { - RDCERR("Unexpected response to capture copy request"); - } - - ser.EndChunk(); - } - } - - rdcstr CopyCaptureToRemote(const char *filename, RENDERDOC_ProgressCallback progress) - { - { - WRITE_DATA_SCOPE(); - SCOPED_SERIALISE_CHUNK(eRemoteServer_CopyCaptureToRemote); - - StreamReader fileStream(FileIO::fopen(filename, "rb")); - ser.SerialiseStream(filename, fileStream, progress); - } - - std::string path; - - { - READ_DATA_SCOPE(); - RemoteServerPacket type = ser.ReadChunk(); - - if(type == eRemoteServer_CopyCaptureToRemote) - { - SERIALISE_ELEMENT(path); - } - else - { - RDCERR("Unexpected response to capture copy request"); - } - - ser.EndChunk(); - } - - return path; - } - - void TakeOwnershipCapture(const char *filename) - { - std::string path = filename; - - { - WRITE_DATA_SCOPE(); - SCOPED_SERIALISE_CHUNK(eRemoteServer_TakeOwnershipCapture); - SERIALISE_ELEMENT(path); - } - } - - rdcpair OpenCapture(uint32_t proxyid, const char *filename, - RENDERDOC_ProgressCallback progress) - { - rdcpair ret; - ret.first = ReplayStatus::InternalError; - ret.second = NULL; - - ResetAndroidSettings(); - - LazilyStartLogcatThread(); - - if(proxyid != ~0U && proxyid >= m_Proxies.size()) - { - RDCERR("Invalid proxy driver id %d specified for remote renderer", proxyid); - ret.first = ReplayStatus::InternalError; - return ret; - } - - RDCLOG("Opening capture remotely"); - - // if the proxy id is ~0U, then we just don't care so let RenderDoc pick the most - // appropriate supported proxy for the current platform. - RDCDriver proxydrivertype = proxyid == ~0U ? RDCDriver::Unknown : m_Proxies[proxyid].first; - - { - WRITE_DATA_SCOPE(); - SCOPED_SERIALISE_CHUNK(eRemoteServer_OpenLog); - SERIALISE_ELEMENT(filename); - } - - RemoteServerPacket type = eRemoteServer_Noop; - while(!reader.IsErrored()) - { - READ_DATA_SCOPE(); - type = ser.ReadChunk(); - - if(reader.IsErrored() || type != eRemoteServer_LogOpenProgress) - break; - - float progressValue = 0.0f; - - SERIALISE_ELEMENT(progressValue); - - ser.EndChunk(); - - if(progress) - progress(progressValue); - } - - RDCLOG("Capture open complete"); - - if(reader.IsErrored() || type != eRemoteServer_LogOpened) - { - RDCERR("Error opening capture"); - ret.first = ReplayStatus::NetworkIOFailed; - return ret; - } - - ReplayStatus status = ReplayStatus::Succeeded; - { - READ_DATA_SCOPE(); - SERIALISE_ELEMENT(status); - ser.EndChunk(); - } - - if(progress) - progress(1.0f); - - if(status != ReplayStatus::Succeeded) - { - RDCERR("Capture open failed: %s", ToStr(status).c_str()); - ret.first = status; - return ret; - } - - RDCLOG("Capture ready on replay host"); - - IReplayDriver *proxyDriver = NULL; - status = RenderDoc::Inst().CreateProxyReplayDriver(proxydrivertype, &proxyDriver); - - if(status != ReplayStatus::Succeeded || !proxyDriver) - { - RDCERR("Creating proxy driver failed: %s", ToStr(status).c_str()); - if(proxyDriver) - proxyDriver->Shutdown(); - ret.first = status; - return ret; - } - - ReplayController *rend = new ReplayController(); - - ReplayProxy *proxy = new ReplayProxy(reader, writer, proxyDriver); - status = rend->SetDevice(proxy); - - if(status != ReplayStatus::Succeeded) - { - SAFE_DELETE(rend); - ret.first = status; - return ret; - } - - // ReplayController takes ownership of the ProxySerialiser (as IReplayDriver) - // and it cleans itself up in Shutdown. - - RDCLOG("Remote capture open complete & proxy ready"); - - ret.first = ReplayStatus::Succeeded; - ret.second = rend; - return ret; - } - - void ResetAndroidSettings() - { - if(Android::IsHostADB(m_hostname.c_str())) - { - int index = 0; - std::string deviceID; - Android::ExtractDeviceIDAndIndex(m_hostname.c_str(), index, deviceID); - Android::ResetCaptureSettings(deviceID); - } - } - - void CloseCapture(IReplayController *rend) - { - { - WRITE_DATA_SCOPE(); - SCOPED_SERIALISE_CHUNK(eRemoteServer_CloseLog); - } - - rend->Shutdown(); - } - - rdcstr DriverName() - { - if(!Connected()) - return ""; - - { - WRITE_DATA_SCOPE(); - SCOPED_SERIALISE_CHUNK(eRemoteServer_GetDriverName); - } - - std::string driverName = ""; - - { - READ_DATA_SCOPE(); - RemoteServerPacket type = ser.ReadChunk(); - - if(type == eRemoteServer_GetDriverName) - { - SERIALISE_ELEMENT(driverName); - } - else - { - RDCERR("Unexpected response to GetDriverName"); - } - - ser.EndChunk(); - } - - return driverName; - } - - int GetSectionCount() - { - if(!Connected()) - return 0; - - { - WRITE_DATA_SCOPE(); - SCOPED_SERIALISE_CHUNK(eRemoteServer_GetSectionCount); - } - - int count = 0; - - { - READ_DATA_SCOPE(); - RemoteServerPacket type = ser.ReadChunk(); - - if(type == eRemoteServer_GetSectionCount) - { - SERIALISE_ELEMENT(count); - } - else - { - RDCERR("Unexpected response to GetSectionCount"); - } - - ser.EndChunk(); - } - - return count; - } - - int FindSectionByName(const char *name) - { - if(!Connected()) - return -1; - - { - WRITE_DATA_SCOPE(); - SCOPED_SERIALISE_CHUNK(eRemoteServer_FindSectionByName); - SERIALISE_ELEMENT(name); - } - - int index = -1; - - { - READ_DATA_SCOPE(); - RemoteServerPacket type = ser.ReadChunk(); - - if(type == eRemoteServer_FindSectionByName) - { - SERIALISE_ELEMENT(index); - } - else - { - RDCERR("Unexpected response to FindSectionByName"); - } - - ser.EndChunk(); - } - - return index; - } - - int FindSectionByType(SectionType sectionType) - { - if(!Connected()) - return -1; - - { - WRITE_DATA_SCOPE(); - SCOPED_SERIALISE_CHUNK(eRemoteServer_FindSectionByType); - SERIALISE_ELEMENT(sectionType); - } - - int index = -1; - - { - READ_DATA_SCOPE(); - RemoteServerPacket type = ser.ReadChunk(); - - if(type == eRemoteServer_FindSectionByType) - { - SERIALISE_ELEMENT(index); - } - else - { - RDCERR("Unexpected response to FindSectionByType"); - } - - ser.EndChunk(); - } - - return index; - } - - SectionProperties GetSectionProperties(int index) - { - if(!Connected()) - return SectionProperties(); - - { - WRITE_DATA_SCOPE(); - SCOPED_SERIALISE_CHUNK(eRemoteServer_GetSectionProperties); - SERIALISE_ELEMENT(index); - } - - SectionProperties props; - - { - READ_DATA_SCOPE(); - RemoteServerPacket type = ser.ReadChunk(); - - if(type == eRemoteServer_GetSectionProperties) - { - SERIALISE_ELEMENT(props); - } - else - { - RDCERR("Unexpected response to GetSectionProperties"); - } - - ser.EndChunk(); - } - - return props; - } - - bytebuf GetSectionContents(int index) - { - if(!Connected()) - return bytebuf(); - - { - WRITE_DATA_SCOPE(); - SCOPED_SERIALISE_CHUNK(eRemoteServer_GetSectionContents); - SERIALISE_ELEMENT(index); - } - - bytebuf contents; - - { - READ_DATA_SCOPE(); - RemoteServerPacket type = ser.ReadChunk(); - - if(type == eRemoteServer_GetSectionContents) - { - SERIALISE_ELEMENT(contents); - } - else - { - RDCERR("Unexpected response to GetSectionContents"); - } - - ser.EndChunk(); - } - - return contents; - } - - bool WriteSection(const SectionProperties &props, const bytebuf &contents) - { - if(!Connected()) - return false; - - { - WRITE_DATA_SCOPE(); - SCOPED_SERIALISE_CHUNK(eRemoteServer_WriteSection); - SERIALISE_ELEMENT(props); - SERIALISE_ELEMENT(contents); - } - - bool success = false; - - { - READ_DATA_SCOPE(); - RemoteServerPacket type = ser.ReadChunk(); - - if(type == eRemoteServer_WriteSection) - { - SERIALISE_ELEMENT(success); - } - else - { - RDCERR("Unexpected response to has write section request"); - } - - ser.EndChunk(); - } - - return success; - } - - bool HasCallstacks() - { - if(!Connected()) - return false; - - { - WRITE_DATA_SCOPE(); - SCOPED_SERIALISE_CHUNK(eRemoteServer_HasCallstacks); - } - - bool hasCallstacks = false; - - { - READ_DATA_SCOPE(); - RemoteServerPacket type = ser.ReadChunk(); - - if(type == eRemoteServer_HasCallstacks) - { - SERIALISE_ELEMENT(hasCallstacks); - } - else - { - RDCERR("Unexpected response to has callstacks request"); - } - - ser.EndChunk(); - } - - return hasCallstacks; - } - - bool InitResolver(RENDERDOC_ProgressCallback progress) - { - { - WRITE_DATA_SCOPE(); - SCOPED_SERIALISE_CHUNK(eRemoteServer_InitResolver); - } - - RemoteServerPacket type = eRemoteServer_Noop; - while(!reader.IsErrored()) - { - READ_DATA_SCOPE(); - type = ser.ReadChunk(); - - if(reader.IsErrored() || type != eRemoteServer_ResolverProgress) - break; - - float progressValue = 0.0f; - - SERIALISE_ELEMENT(progressValue); - - ser.EndChunk(); - - if(progress) - progress(progressValue); - - RDCLOG("% 3.0f%%...", progressValue * 100.0f); - } - - if(reader.IsErrored() || type != eRemoteServer_InitResolver) - { - return false; - } - - bool success = false; - { - READ_DATA_SCOPE(); - SERIALISE_ELEMENT(success); - ser.EndChunk(); - } - - if(progress) - progress(1.0f); - - return success; - } - - rdcarray GetResolve(const rdcarray &callstack) - { - if(!Connected()) - return {""}; - - { - WRITE_DATA_SCOPE(); - SCOPED_SERIALISE_CHUNK(eRemoteServer_GetResolve); - SERIALISE_ELEMENT(callstack); - } - - rdcarray StackFrames; - - { - READ_DATA_SCOPE(); - RemoteServerPacket type = ser.ReadChunk(); - - if(type == eRemoteServer_GetResolve) - { - SERIALISE_ELEMENT(StackFrames); - } - else - { - RDCERR("Unexpected response to resolve request"); - } - - ser.EndChunk(); - } - - return StackFrames; - } - -private: - void LazilyStartLogcatThread() - { - if(m_LogcatThread) - return; - - if(Android::IsHostADB(m_hostname.c_str())) - { - int index = 0; - std::string deviceID; - Android::ExtractDeviceIDAndIndex(m_hostname, index, deviceID); - - m_LogcatThread = Android::ProcessLogcat(deviceID); - } - else - { - m_LogcatThread = NULL; - } - } - - Network::Socket *m_Socket; - WriteSerialiser writer; - ReadSerialiser reader; - std::string m_hostname; - Android::LogcatThread *m_LogcatThread; - - std::vector > m_Proxies; - - struct Activity - { - rdcstr package; - rdcstr activity; - - bool operator<(const Activity &o) const - { - if(package != o.package) - return package < o.package; - return activity < o.activity; - } - }; - - std::set m_AndroidActivities; -}; - extern "C" RENDERDOC_API ReplayStatus RENDERDOC_CC -RENDERDOC_CreateRemoteServerConnection(const char *host, uint32_t port, IRemoteServer **rend) +RENDERDOC_CreateRemoteServerConnection(const char *URL, IRemoteServer **rend) { if(rend == NULL) return ReplayStatus::InternalError; - std::string s = "localhost"; - if(host != NULL && host[0] != '\0') - s = host; + rdcstr host = "localhost"; + if(URL != NULL && URL[0] != '\0') + host = URL; - if(port == 0) - port = RENDERDOC_GetDefaultRemoteServerPort(); + rdcstr deviceID = host; - if(host != NULL && Android::IsHostADB(host)) + IDeviceProtocolHandler *protocol = RenderDoc::Inst().GetDeviceProtocol(deviceID); + + uint16_t port = RenderDoc_RemoteServerPort; + + if(protocol) { - s = "127.0.0.1"; + deviceID = protocol->GetDeviceID(deviceID); + host = protocol->RemapHostname(deviceID); + if(host.empty()) + return ReplayStatus::NetworkIOFailed; - int index = 0; - std::string deviceID; - Android::ExtractDeviceIDAndIndex(host, index, deviceID); - - // 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(port == RENDERDOC_GetDefaultRemoteServerPort()) - port += RenderDoc_AndroidPortOffset * (index + 1); + port = protocol->RemapPort(deviceID, port); } - Network::Socket *sock = Network::CreateClientSocket(s.c_str(), (uint16_t)port, 750); + Network::Socket *sock = Network::CreateClientSocket(host.c_str(), port, 750); if(sock == NULL) return ReplayStatus::NetworkIOFailed; @@ -2093,7 +1100,771 @@ RENDERDOC_CreateRemoteServerConnection(const char *host, uint32_t port, IRemoteS } } - *rend = new RemoteServer(sock, host); + if(protocol) + *rend = protocol->CreateRemoteServer(sock, deviceID); + else + *rend = new RemoteServer(sock, deviceID); return ReplayStatus::Succeeded; } + +#undef WRITE_DATA_SCOPE +#undef READ_DATA_SCOPE +#define WRITE_DATA_SCOPE() WriteSerialiser &ser = *writer; +#define READ_DATA_SCOPE() ReadSerialiser &ser = *reader; + +RemoteServer::RemoteServer(Network::Socket *sock, const rdcstr &deviceID) + : m_Socket(sock), m_deviceID(deviceID) +{ + reader = new ReadSerialiser(new StreamReader(sock, Ownership::Nothing), Ownership::Stream); + writer = new WriteSerialiser(new StreamWriter(sock, Ownership::Nothing), Ownership::Stream); + + writer->SetStreamingMode(true); + reader->SetStreamingMode(true); + + std::map m = RenderDoc::Inst().GetReplayDrivers(); + + m_Proxies.reserve(m.size()); + for(auto it = m.begin(); it != m.end(); ++it) + m_Proxies.push_back({it->first, it->second}); +} + +RemoteServer::~RemoteServer() +{ + SAFE_DELETE(writer); + SAFE_DELETE(reader); + SAFE_DELETE(m_Socket); +} + +void RemoteServer::ShutdownConnection() +{ + delete this; +} + +void RemoteServer::ShutdownServerAndConnection() +{ + { + WRITE_DATA_SCOPE(); + SCOPED_SERIALISE_CHUNK(eRemoteServer_ShutdownServer); + } + + { + READ_DATA_SCOPE(); + RemoteServerPacket type = ser.ReadChunk(); + ser.EndChunk(); + + RDCASSERT(type == eRemoteServer_ShutdownServer); + } + + delete this; +} + +bool RemoteServer::Connected() +{ + return m_Socket != NULL && m_Socket->Connected(); +} + +bool RemoteServer::Ping() +{ + if(!Connected()) + return false; + + { + WRITE_DATA_SCOPE(); + SCOPED_SERIALISE_CHUNK(eRemoteServer_Ping); + } + + RemoteServerPacket type; + + { + READ_DATA_SCOPE(); + type = ser.ReadChunk(); + ser.EndChunk(); + } + + return type == eRemoteServer_Ping; +} + +rdcarray RemoteServer::LocalProxies() +{ + rdcarray out; + + m_Proxies.reserve(m_Proxies.size()); + + size_t i = 0; + for(auto it = m_Proxies.begin(); it != m_Proxies.end(); ++it, ++i) + out.push_back(it->second); + + return out; +} + +rdcarray RemoteServer::RemoteSupportedReplays() +{ + rdcarray out; + + { + WRITE_DATA_SCOPE(); + SCOPED_SERIALISE_CHUNK(eRemoteServer_RemoteDriverList); + } + + { + READ_DATA_SCOPE(); + + RemoteServerPacket type = ser.ReadChunk(); + + if(type == eRemoteServer_RemoteDriverList) + { + uint32_t count = 0; + SERIALISE_ELEMENT(count); + + out.reserve(count); + + for(uint32_t i = 0; i < count; i++) + { + RDCDriver driverType = RDCDriver::Unknown; + std::string driverName = ""; + + SERIALISE_ELEMENT(driverType); + SERIALISE_ELEMENT(driverName); + + out.push_back(driverName); + } + } + else + { + RDCERR("Unexpected response to remote driver list request"); + } + + ser.EndChunk(); + } + + return out; +} + +rdcstr RemoteServer::GetHomeFolder() +{ + { + WRITE_DATA_SCOPE(); + SCOPED_SERIALISE_CHUNK(eRemoteServer_HomeDir); + } + + rdcstr home; + + { + READ_DATA_SCOPE(); + + RemoteServerPacket type = ser.ReadChunk(); + + if(type == eRemoteServer_HomeDir) + { + SERIALISE_ELEMENT(home); + } + else + { + RDCERR("Unexpected response to home folder request"); + } + + ser.EndChunk(); + } + + return home; +} + +rdcarray RemoteServer::ListFolder(const char *path) +{ + { + WRITE_DATA_SCOPE(); + SCOPED_SERIALISE_CHUNK(eRemoteServer_ListDir); + SERIALISE_ELEMENT(path); + } + + rdcarray files; + + { + READ_DATA_SCOPE(); + + RemoteServerPacket type = ser.ReadChunk(); + + if(type == eRemoteServer_ListDir) + { + SERIALISE_ELEMENT(files); + } + else + { + RDCERR("Unexpected response to list directory request"); + files.resize(1); + files[0].filename = path; + files[0].flags = PathProperty::ErrorUnknown; + } + + ser.EndChunk(); + } + + return files; +} + +ExecuteResult RemoteServer::ExecuteAndInject(const char *a, const char *w, const char *c, + const rdcarray &env, + const CaptureOptions &opts) +{ + std::string app = a && a[0] ? a : ""; + std::string workingDir = w && w[0] ? w : ""; + std::string cmdline = c && c[0] ? c : ""; + + { + WRITE_DATA_SCOPE(); + SCOPED_SERIALISE_CHUNK(eRemoteServer_ExecuteAndInject); + SERIALISE_ELEMENT(app); + SERIALISE_ELEMENT(workingDir); + SERIALISE_ELEMENT(cmdline); + SERIALISE_ELEMENT(opts); + SERIALISE_ELEMENT(env); + } + + ExecuteResult ret = {}; + + { + READ_DATA_SCOPE(); + RemoteServerPacket type = ser.ReadChunk(); + + if(type == eRemoteServer_ExecuteAndInject) + { + SERIALISE_ELEMENT(ret); + } + else + { + RDCERR("Unexpected response to execute and inject request"); + } + + ser.EndChunk(); + } + + return ret; +} + +void RemoteServer::CopyCaptureFromRemote(const char *remotepath, const char *localpath, + RENDERDOC_ProgressCallback progress) +{ + std::string path = remotepath; + + { + WRITE_DATA_SCOPE(); + SCOPED_SERIALISE_CHUNK(eRemoteServer_CopyCaptureFromRemote); + SERIALISE_ELEMENT(path); + } + + { + READ_DATA_SCOPE(); + RemoteServerPacket type = ser.ReadChunk(); + + if(type == eRemoteServer_CopyCaptureFromRemote) + { + StreamWriter streamWriter(FileIO::fopen(localpath, "wb"), Ownership::Stream); + + ser.SerialiseStream(localpath, streamWriter, progress); + + if(ser.IsErrored()) + { + RDCERR("Network error receiving file"); + return; + } + } + else + { + RDCERR("Unexpected response to capture copy request"); + } + + ser.EndChunk(); + } +} + +rdcstr RemoteServer::CopyCaptureToRemote(const char *filename, RENDERDOC_ProgressCallback progress) +{ + { + WRITE_DATA_SCOPE(); + SCOPED_SERIALISE_CHUNK(eRemoteServer_CopyCaptureToRemote); + + StreamReader fileStream(FileIO::fopen(filename, "rb")); + ser.SerialiseStream(filename, fileStream, progress); + } + + std::string path; + + { + READ_DATA_SCOPE(); + RemoteServerPacket type = ser.ReadChunk(); + + if(type == eRemoteServer_CopyCaptureToRemote) + { + SERIALISE_ELEMENT(path); + } + else + { + RDCERR("Unexpected response to capture copy request"); + } + + ser.EndChunk(); + } + + return path; +} + +void RemoteServer::TakeOwnershipCapture(const char *filename) +{ + std::string path = filename; + + { + WRITE_DATA_SCOPE(); + SCOPED_SERIALISE_CHUNK(eRemoteServer_TakeOwnershipCapture); + SERIALISE_ELEMENT(path); + } +} + +rdcpair RemoteServer::OpenCapture( + uint32_t proxyid, const char *filename, RENDERDOC_ProgressCallback progress) +{ + rdcpair ret; + ret.first = ReplayStatus::InternalError; + ret.second = NULL; + + if(proxyid != ~0U && proxyid >= m_Proxies.size()) + { + RDCERR("Invalid proxy driver id %d specified for remote renderer", proxyid); + ret.first = ReplayStatus::InternalError; + return ret; + } + + RDCLOG("Opening capture remotely"); + + // if the proxy id is ~0U, then we just don't care so let RenderDoc pick the most + // appropriate supported proxy for the current platform. + RDCDriver proxydrivertype = proxyid == ~0U ? RDCDriver::Unknown : m_Proxies[proxyid].first; + + { + WRITE_DATA_SCOPE(); + SCOPED_SERIALISE_CHUNK(eRemoteServer_OpenLog); + SERIALISE_ELEMENT(filename); + } + + RemoteServerPacket type = eRemoteServer_Noop; + while(!reader->IsErrored()) + { + READ_DATA_SCOPE(); + type = ser.ReadChunk(); + + if(reader->IsErrored() || type != eRemoteServer_LogOpenProgress) + break; + + float progressValue = 0.0f; + + SERIALISE_ELEMENT(progressValue); + + ser.EndChunk(); + + if(progress) + progress(progressValue); + } + + RDCLOG("Capture open complete"); + + if(reader->IsErrored() || type != eRemoteServer_LogOpened) + { + RDCERR("Error opening capture"); + ret.first = ReplayStatus::NetworkIOFailed; + return ret; + } + + ReplayStatus status = ReplayStatus::Succeeded; + { + READ_DATA_SCOPE(); + SERIALISE_ELEMENT(status); + ser.EndChunk(); + } + + if(progress) + progress(1.0f); + + if(status != ReplayStatus::Succeeded) + { + RDCERR("Capture open failed: %s", ToStr(status).c_str()); + ret.first = status; + return ret; + } + + RDCLOG("Capture ready on replay host"); + + IReplayDriver *proxyDriver = NULL; + status = RenderDoc::Inst().CreateProxyReplayDriver(proxydrivertype, &proxyDriver); + + if(status != ReplayStatus::Succeeded || !proxyDriver) + { + RDCERR("Creating proxy driver failed: %s", ToStr(status).c_str()); + if(proxyDriver) + proxyDriver->Shutdown(); + ret.first = status; + return ret; + } + + ReplayController *rend = new ReplayController(); + + ReplayProxy *proxy = new ReplayProxy(*reader, *writer, proxyDriver); + status = rend->SetDevice(proxy); + + if(status != ReplayStatus::Succeeded) + { + SAFE_DELETE(rend); + ret.first = status; + return ret; + } + + // ReplayController takes ownership of the ProxySerialiser (as IReplayDriver) + // and it cleans itself up in Shutdown. + + RDCLOG("Remote capture open complete & proxy ready"); + + ret.first = ReplayStatus::Succeeded; + ret.second = rend; + return ret; +} + +void RemoteServer::CloseCapture(IReplayController *rend) +{ + { + WRITE_DATA_SCOPE(); + SCOPED_SERIALISE_CHUNK(eRemoteServer_CloseLog); + } + + rend->Shutdown(); +} + +rdcstr RemoteServer::DriverName() +{ + if(!Connected()) + return ""; + + { + WRITE_DATA_SCOPE(); + SCOPED_SERIALISE_CHUNK(eRemoteServer_GetDriverName); + } + + std::string driverName = ""; + + { + READ_DATA_SCOPE(); + RemoteServerPacket type = ser.ReadChunk(); + + if(type == eRemoteServer_GetDriverName) + { + SERIALISE_ELEMENT(driverName); + } + else + { + RDCERR("Unexpected response to GetDriverName"); + } + + ser.EndChunk(); + } + + return driverName; +} + +int RemoteServer::GetSectionCount() +{ + if(!Connected()) + return 0; + + { + WRITE_DATA_SCOPE(); + SCOPED_SERIALISE_CHUNK(eRemoteServer_GetSectionCount); + } + + int count = 0; + + { + READ_DATA_SCOPE(); + RemoteServerPacket type = ser.ReadChunk(); + + if(type == eRemoteServer_GetSectionCount) + { + SERIALISE_ELEMENT(count); + } + else + { + RDCERR("Unexpected response to GetSectionCount"); + } + + ser.EndChunk(); + } + + return count; +} + +int RemoteServer::FindSectionByName(const char *name) +{ + if(!Connected()) + return -1; + + { + WRITE_DATA_SCOPE(); + SCOPED_SERIALISE_CHUNK(eRemoteServer_FindSectionByName); + SERIALISE_ELEMENT(name); + } + + int index = -1; + + { + READ_DATA_SCOPE(); + RemoteServerPacket type = ser.ReadChunk(); + + if(type == eRemoteServer_FindSectionByName) + { + SERIALISE_ELEMENT(index); + } + else + { + RDCERR("Unexpected response to FindSectionByName"); + } + + ser.EndChunk(); + } + + return index; +} + +int RemoteServer::FindSectionByType(SectionType sectionType) +{ + if(!Connected()) + return -1; + + { + WRITE_DATA_SCOPE(); + SCOPED_SERIALISE_CHUNK(eRemoteServer_FindSectionByType); + SERIALISE_ELEMENT(sectionType); + } + + int index = -1; + + { + READ_DATA_SCOPE(); + RemoteServerPacket type = ser.ReadChunk(); + + if(type == eRemoteServer_FindSectionByType) + { + SERIALISE_ELEMENT(index); + } + else + { + RDCERR("Unexpected response to FindSectionByType"); + } + + ser.EndChunk(); + } + + return index; +} + +SectionProperties RemoteServer::GetSectionProperties(int index) +{ + if(!Connected()) + return SectionProperties(); + + { + WRITE_DATA_SCOPE(); + SCOPED_SERIALISE_CHUNK(eRemoteServer_GetSectionProperties); + SERIALISE_ELEMENT(index); + } + + SectionProperties props; + + { + READ_DATA_SCOPE(); + RemoteServerPacket type = ser.ReadChunk(); + + if(type == eRemoteServer_GetSectionProperties) + { + SERIALISE_ELEMENT(props); + } + else + { + RDCERR("Unexpected response to GetSectionProperties"); + } + + ser.EndChunk(); + } + + return props; +} + +bytebuf RemoteServer::GetSectionContents(int index) +{ + if(!Connected()) + return bytebuf(); + + { + WRITE_DATA_SCOPE(); + SCOPED_SERIALISE_CHUNK(eRemoteServer_GetSectionContents); + SERIALISE_ELEMENT(index); + } + + bytebuf contents; + + { + READ_DATA_SCOPE(); + RemoteServerPacket type = ser.ReadChunk(); + + if(type == eRemoteServer_GetSectionContents) + { + SERIALISE_ELEMENT(contents); + } + else + { + RDCERR("Unexpected response to GetSectionContents"); + } + + ser.EndChunk(); + } + + return contents; +} + +bool RemoteServer::WriteSection(const SectionProperties &props, const bytebuf &contents) +{ + if(!Connected()) + return false; + + { + WRITE_DATA_SCOPE(); + SCOPED_SERIALISE_CHUNK(eRemoteServer_WriteSection); + SERIALISE_ELEMENT(props); + SERIALISE_ELEMENT(contents); + } + + bool success = false; + + { + READ_DATA_SCOPE(); + RemoteServerPacket type = ser.ReadChunk(); + + if(type == eRemoteServer_WriteSection) + { + SERIALISE_ELEMENT(success); + } + else + { + RDCERR("Unexpected response to has write section request"); + } + + ser.EndChunk(); + } + + return success; +} + +bool RemoteServer::HasCallstacks() +{ + if(!Connected()) + return false; + + { + WRITE_DATA_SCOPE(); + SCOPED_SERIALISE_CHUNK(eRemoteServer_HasCallstacks); + } + + bool hasCallstacks = false; + + { + READ_DATA_SCOPE(); + RemoteServerPacket type = ser.ReadChunk(); + + if(type == eRemoteServer_HasCallstacks) + { + SERIALISE_ELEMENT(hasCallstacks); + } + else + { + RDCERR("Unexpected response to has callstacks request"); + } + + ser.EndChunk(); + } + + return hasCallstacks; +} + +bool RemoteServer::InitResolver(RENDERDOC_ProgressCallback progress) +{ + { + WRITE_DATA_SCOPE(); + SCOPED_SERIALISE_CHUNK(eRemoteServer_InitResolver); + } + + RemoteServerPacket type = eRemoteServer_Noop; + while(!reader->IsErrored()) + { + READ_DATA_SCOPE(); + type = ser.ReadChunk(); + + if(reader->IsErrored() || type != eRemoteServer_ResolverProgress) + break; + + float progressValue = 0.0f; + + SERIALISE_ELEMENT(progressValue); + + ser.EndChunk(); + + if(progress) + progress(progressValue); + + RDCLOG("% 3.0f%%...", progressValue * 100.0f); + } + + if(reader->IsErrored() || type != eRemoteServer_InitResolver) + { + return false; + } + + bool success = false; + { + READ_DATA_SCOPE(); + SERIALISE_ELEMENT(success); + ser.EndChunk(); + } + + if(progress) + progress(1.0f); + + return success; +} + +rdcarray RemoteServer::GetResolve(const rdcarray &callstack) +{ + if(!Connected()) + return {""}; + + { + WRITE_DATA_SCOPE(); + SCOPED_SERIALISE_CHUNK(eRemoteServer_GetResolve); + SERIALISE_ELEMENT(callstack); + } + + rdcarray StackFrames; + + { + READ_DATA_SCOPE(); + RemoteServerPacket type = ser.ReadChunk(); + + if(type == eRemoteServer_GetResolve) + { + SERIALISE_ELEMENT(StackFrames); + } + else + { + RDCERR("Unexpected response to resolve request"); + } + + ser.EndChunk(); + } + + return StackFrames; +} diff --git a/renderdoc/core/remote_server.h b/renderdoc/core/remote_server.h new file mode 100644 index 000000000..3f5192f5d --- /dev/null +++ b/renderdoc/core/remote_server.h @@ -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 LocalProxies(); + + virtual rdcarray RemoteSupportedReplays(); + + virtual rdcstr GetHomeFolder(); + + virtual rdcarray ListFolder(const char *path); + + virtual ExecuteResult ExecuteAndInject(const char *a, const char *w, const char *c, + const rdcarray &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 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 GetResolve(const rdcarray &callstack); + +protected: + Network::Socket *m_Socket; + WriteSerialiser *writer; + ReadSerialiser *reader; + rdcstr m_deviceID; + + rdcarray> m_Proxies; +}; diff --git a/renderdoc/core/target_control.cpp b/renderdoc/core/target_control.cpp index f761179e4..f52768b27 100644 --- a/renderdoc/core/target_control.cpp +++ b/renderdoc/core/target_control.cpp @@ -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; diff --git a/renderdoc/renderdoc.vcxproj b/renderdoc/renderdoc.vcxproj index a6f0a7055..1525a33e0 100644 --- a/renderdoc/renderdoc.vcxproj +++ b/renderdoc/renderdoc.vcxproj @@ -188,6 +188,7 @@ + diff --git a/renderdoc/renderdoc.vcxproj.filters b/renderdoc/renderdoc.vcxproj.filters index 6ce951b35..5e353b3f7 100644 --- a/renderdoc/renderdoc.vcxproj.filters +++ b/renderdoc/renderdoc.vcxproj.filters @@ -486,6 +486,9 @@ Core + + Core\networking + diff --git a/renderdoc/replay/entry_points.cpp b/renderdoc/replay/entry_points.cpp index d32cb079d..0110c974b 100644 --- a/renderdoc/replay/entry_points.cpp +++ b/renderdoc/replay/entry_points.cpp @@ -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 *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) diff --git a/renderdoccmd/renderdoccmd.cpp b/renderdoccmd/renderdoccmd.cpp index 2fec0181a..03198ba02 100644 --- a/renderdoccmd/renderdoccmd.cpp +++ b/renderdoccmd/renderdoccmd.cpp @@ -461,8 +461,6 @@ struct RemoteServerCommand : public Command parser.add("daemon", 'd', "Go into the background."); parser.add( "host", 'h', "The interface to listen on. By default listens on all interfaces", false, ""); - parser.add("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("host"); - uint32_t port = parser.get("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("remote-host", 0, "Instead of replaying locally, replay on this host over the network.", false); - parser.add("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("remote-host") - << ":" << parser.get("remote-port") << "." << std::endl; + << "." << std::endl; IRemoteServer *remote = NULL; - ReplayStatus status = - RENDERDOC_CreateRemoteServerConnection(parser.get("remote-host").c_str(), - parser.get("remote-port"), &remote); + ReplayStatus status = RENDERDOC_CreateRemoteServerConnection( + parser.get("remote-host").c_str(), &remote); if(remote == NULL || status != ReplayStatus::Succeeded) { std::cerr << "Error: " << ToStr(status) << " - Couldn't connect to " - << parser.get("remote-host") << ":" - << parser.get("remote-port") << "." << std::endl; + << parser.get("remote-host") << "." << std::endl; std::cerr << " Have you run renderdoccmd remoteserver on '" << parser.get("remote-host") << "'?" << std::endl; return 1;