mirror of
https://github.com/baldurk/renderdoc.git
synced 2026-05-04 17:10:47 +00:00
Add an abstracted interface around android-specific handling
* This makes it easier to use the same kind of interface to manage other kinds of devices.
This commit is contained in:
@@ -31,4 +31,5 @@ In order to help with this, the examples are organised such that most code is wr
|
||||
fetch_counters
|
||||
save_texture
|
||||
decode_mesh
|
||||
display_window
|
||||
display_window
|
||||
remote_capture
|
||||
|
||||
@@ -0,0 +1,196 @@
|
||||
import renderdoc as rd
|
||||
import threading
|
||||
import time
|
||||
|
||||
# This sample is intended as an example of how to do remote capture and replay
|
||||
# as well as using device protocols to automatically enumerate remote targets.
|
||||
#
|
||||
# It is not complete since it requires filling in with custom logic to select
|
||||
# the executable and trigger the capture at the desired time
|
||||
raise RuntimeError("This sample should not be run directly, read the source")
|
||||
|
||||
protocols = rd.GetSupportedDeviceProtocols()
|
||||
|
||||
print(f"Supported device protocols: {protocols}")
|
||||
|
||||
# Protocols are optional - they allow automatic detection and management of
|
||||
# devices.
|
||||
if protocol_to_use is not None:
|
||||
# the protocol must be supported
|
||||
if protocol_to_use not in protocols:
|
||||
raise RuntimeError(f"{protocol_to_use} protocol not supported")
|
||||
|
||||
protocol = rd.GetDeviceProtocolController(protocol_to_use)
|
||||
|
||||
devices = protocol.GetDevices()
|
||||
|
||||
if len(devices) == 0:
|
||||
raise RuntimeError(f"no {protocol_to_use} devices connected")
|
||||
|
||||
# Choose the first device
|
||||
dev = devices[0]
|
||||
name = protocol.GetFriendlyName(dev)
|
||||
|
||||
print(f"Running test on {dev} - named {name}")
|
||||
|
||||
URL = protocol.GetProtocolName() + "://" + dev
|
||||
|
||||
# Protocols can enumerate devices which are not supported. Capture/replay
|
||||
# is not guaranteed to work on these devices
|
||||
if not protocol.IsSupported(URL):
|
||||
raise RuntimeError(f"{dev} doesn't support capture/replay - too old?")
|
||||
|
||||
# Protocol devices may be single-use and not support multiple captured programs
|
||||
# If so, trying to execute a program for capture is an error
|
||||
if not protocol.SupportsMultiplePrograms(URL):
|
||||
# check to see if anything is running. Just use the URL
|
||||
ident = rd.EnumerateRemoteTargets(URL, 0)
|
||||
|
||||
if ident != 0:
|
||||
raise RuntimeError(f"{name} already has a program running on {ident}")
|
||||
else:
|
||||
# If you're not using a protocol then the URL can simply be a hostname.
|
||||
# The remote server must be running already - how that is done is up
|
||||
# to you. Everything else will work the same over a normal TCP connection
|
||||
protocol = None
|
||||
URL = hostname
|
||||
|
||||
# Let's try to connect
|
||||
status,remote = rd.CreateRemoteServerConnection(URL)
|
||||
|
||||
if status == rd.ReplayStatus.NetworkIOFailed and protocol is not None:
|
||||
# If there's just no I/O, most likely the server is not running. If we have
|
||||
# a protocol, we can try to start the remote server
|
||||
print("Couldn't connect to remote server, trying to start it")
|
||||
|
||||
status = protocol.StartRemoteServer(URL)
|
||||
|
||||
if status != rd.ReplayStatus.Succeeded:
|
||||
raise RuntimeError(f"Couldn't launch remote server, got error {str(status)}")
|
||||
|
||||
# Try to connect again!
|
||||
status,remote = rd.CreateRemoteServerConnection(URL)
|
||||
|
||||
if status != rd.ReplayStatus.Succeeded:
|
||||
raise RuntimeError(f"Couldn't connect to remote server, got error {str(status)}")
|
||||
|
||||
# We now have a remote connection. This works regardless of whether it's a device
|
||||
# with a protocol or not. In fact we are done with the protocol at this point
|
||||
protocol = None
|
||||
|
||||
print("Got connection to remote server")
|
||||
|
||||
# GetHomeFolder() gives you a good default path to start with.
|
||||
# ListFolder() lists the contents of a folder and can recursively
|
||||
# browse the remote filesystem.
|
||||
home = remote.GetHomeFolder()
|
||||
paths = remote.ListFolder(home)
|
||||
|
||||
print(f"Executables in home folder '{home}':")
|
||||
|
||||
for p in paths:
|
||||
print(" - " + p.filename)
|
||||
|
||||
# Select your executable, perhaps hardcoded or browsing using the above
|
||||
# functions
|
||||
exe,workingDir,cmdLine,env,opts = select_executable()
|
||||
|
||||
print(f"Running {exe}")
|
||||
|
||||
result = remote.ExecuteAndInject(exe, workingDir, cmdLine, env, opts)
|
||||
|
||||
if result.status != rd.ReplayStatus.Succeeded:
|
||||
remote.ShutdownServerAndConnection()
|
||||
raise RuntimeError(f"Couldn't launch {exe}, got error {str(result.status)}")
|
||||
|
||||
# Spin up a thread to keep the remote server connection alive while we make a capture,
|
||||
# as it will time out after 5 seconds of inactivity
|
||||
def ping_remote(remote, kill):
|
||||
success = True
|
||||
while success and not kill.is_set():
|
||||
success = remote.Ping()
|
||||
time.sleep(1)
|
||||
|
||||
kill = threading.Event()
|
||||
ping_thread = threading.Thread(target=ping_remote, args=(remote,kill))
|
||||
ping_thread.start()
|
||||
|
||||
# Create target control connection
|
||||
target = rd.CreateTargetControl(URL, result.ident, 'remote_capture.py', True)
|
||||
|
||||
if target is None:
|
||||
kill.set()
|
||||
ping_thread.join()
|
||||
remote.ShutdownServerAndConnection()
|
||||
raise RuntimeError(f"Couldn't connect to target control for {exe}")
|
||||
|
||||
print("Connected - waiting for desired capture")
|
||||
|
||||
# Wait for the capture condition we want
|
||||
capture_condition()
|
||||
|
||||
print("Triggering capture")
|
||||
|
||||
target.TriggerCapture(1)
|
||||
|
||||
# Pump messages, keep waiting until we get a capture message. Time out after 30 seconds
|
||||
msg = None
|
||||
start = time.clock()
|
||||
while msg is None or msg.type != rd.TargetControlMessageType.NewCapture:
|
||||
msg = target.ReceiveMessage(None)
|
||||
|
||||
if time.clock() - start > 30:
|
||||
break
|
||||
|
||||
# Close the target connection, we're done either way
|
||||
target.Shutdown()
|
||||
target = None
|
||||
|
||||
# Stop the background ping thread
|
||||
kill.set()
|
||||
ping_thread.join()
|
||||
|
||||
# If we didn't get a capture, error now
|
||||
if msg.type != rd.TargetControlMessageType.NewCapture:
|
||||
remote.ShutdownServerAndConnection()
|
||||
raise RuntimeError("Didn't get new capture notification after triggering capture")
|
||||
|
||||
cap_path = msg.newCapture.path
|
||||
cap_id = msg.newCapture.captureId
|
||||
|
||||
print(f"Got new capture at {cap_path} which is frame {msg.newCapture.frameNumber} with {msg.newCapture.api}")
|
||||
|
||||
# We could save the capture locally
|
||||
# remote.CopyCaptureFromRemote(cap_path, local_path, None)
|
||||
|
||||
|
||||
# Open a replay. It's recommended to set no proxy preference, but you could
|
||||
# call remote.LocalProxies and choose an index.
|
||||
#
|
||||
# The path must be remote - if the capture isn't freshly created then you need
|
||||
# to copy it with remote.CopyCaptureToRemote()
|
||||
status,controller = remote.OpenCapture(rd.RemoteServer.NoPreference, cap_path, None)
|
||||
|
||||
if status != rd.ReplayStatus.Succeeded:
|
||||
remote.ShutdownServerAndConnection()
|
||||
raise RuntimeError(f"Couldn't open {cap_path}, got error {str(result.status)}")
|
||||
|
||||
# We can now use replay as normal.
|
||||
#
|
||||
# The replay is tunnelled over the remote connection, so you don't have to keep
|
||||
# pinging the remote connection while using the controller. Use of the remote
|
||||
# connection and controller can be interleaved though you should only access
|
||||
# them from one thread at once. If they are both unused for 5 seconds though,
|
||||
# the timeout will happen, so if the controller is idle it's advisable to ping
|
||||
# the remote connection
|
||||
|
||||
sampleCode(controller)
|
||||
|
||||
print("Shutting down")
|
||||
|
||||
controller.Shutdown()
|
||||
|
||||
# We can still use remote here - e.g. capture again, replay something else,
|
||||
# save the capture, etc
|
||||
|
||||
remote.ShutdownServerAndConnection()
|
||||
@@ -0,0 +1,118 @@
|
||||
Remote Capture and Replay
|
||||
=========================
|
||||
|
||||
This example is a bit different since it's not ready-to-run. It provides a template for how you can capture and replay on a remote machine, instead of the local machine. It also shows how to use device protocols to automatically manage devices.
|
||||
|
||||
First we can enumerate which device protocols are currently supported.
|
||||
|
||||
.. highlight:: python
|
||||
.. code:: python
|
||||
|
||||
protocols = rd.GetSupportedDeviceProtocols()
|
||||
|
||||
Each string in the list corresponds to a protocol that can be used for managing devices. If we're using one we can call :py:func:`~renderdoc.GetDeviceProtocolController` passing the protocol name and retrieve the controller.
|
||||
|
||||
The controller provides a few methods for managing devices. First we can call :py:meth:`~renderdoc.DeviceProtocolController.GetDevices` to return a list of device IDs. The format of these device IDs is protocol-dependent but will be equivalent to a normal hostname. Devices may have human-readable names obtainable via :py:meth:`~renderdoc.DeviceProtocolController.GetFriendlyName`.
|
||||
|
||||
.. highlight:: python
|
||||
.. code:: python
|
||||
|
||||
protocol = rd.GetDeviceProtocolController(protocol_to_use)
|
||||
|
||||
devices = protocol.GetDevices()
|
||||
|
||||
if len(devices) == 0:
|
||||
raise RuntimeError(f"no {protocol_to_use} devices connected")
|
||||
|
||||
# Choose the first device
|
||||
dev = devices[0]
|
||||
name = protocol.GetFriendlyName(dev)
|
||||
|
||||
print(f"Running test on {dev} - named {name}")
|
||||
|
||||
URL = protocol.GetProtocolName() + "://" + dev
|
||||
|
||||
The URL will be used the same as we would use a hostname, when connecting for target control or remote servers.
|
||||
|
||||
Note that protocols may have additional restrictions - be sure to check :py:meth:`~renderdoc.DeviceProtocolController.IsSupported` to check if the device is expected to function at all, and :py:meth:`~renderdoc.DeviceProtocolController.SupportsMultiplePrograms` to see if it supports launching multiple programs. If multiple programs are not supported, you should ensure all running capturable programs are closed before launching a new one.
|
||||
|
||||
To begin with we create a remote server connection using :py:func:`~renderdoc.CreateRemoteServerConnection`. The URL is as constructed above for protocol-based connections, or a simple hostname/IP if we're connecting directly to remote machine.
|
||||
|
||||
If the connection fails, normally we must fail but if we have a device protocol available we can attempt to launch the remote server automatically using :py:meth:`~renderdoc.DeviceProtocolController.StartRemoteServer`.
|
||||
|
||||
.. highlight:: python
|
||||
.. code:: python
|
||||
|
||||
if status == rd.ReplayStatus.NetworkIOFailed and protocol is not None:
|
||||
# If there's just no I/O, most likely the server is not running. If we have
|
||||
# a protocol, we can try to start the remote server
|
||||
print("Couldn't connect to remote server, trying to start it")
|
||||
|
||||
status = protocol.StartRemoteServer(URL)
|
||||
|
||||
if status != rd.ReplayStatus.Succeeded:
|
||||
raise RuntimeError(f"Couldn't launch remote server, got error {str(status)}")
|
||||
|
||||
# Try to connect again!
|
||||
status,remote = rd.CreateRemoteServerConnection(URL)
|
||||
|
||||
.. note::
|
||||
|
||||
The remote server connection has a default timeout of 5 seconds. If the connection is unused for 5 seconds, the other side will disconnect and subsequent use of the interface will fail.
|
||||
|
||||
Once we have a remote server connection, we can browse the remote filesystem for the executable we want to launch using :py:meth:`~renderdoc.RemoteServer.GetHomeFolder` and :py:meth:`~renderdoc.RemoteServer.ListFolder`.
|
||||
|
||||
Then once we've selected the executable, we can launch the remote program for capturing with :py:meth:`~renderdoc.RemoteServer.ExecuteAndInject`. This function is almost identical to the local :py:func:`~renderdoc.ExecuteAndInject` except that it is not possible to wait for the program to exit.
|
||||
|
||||
In our sample, we now place the remote server connection on a background thread that will ping it each second to keep the connection alive while we use a target control connection to trigger a capture in the application.
|
||||
|
||||
.. highlight:: python
|
||||
.. code:: python
|
||||
|
||||
def ping_remote(remote, kill):
|
||||
success = True
|
||||
while success and not kill.is_set():
|
||||
success = remote.Ping()
|
||||
time.sleep(1)
|
||||
|
||||
kill = threading.Event()
|
||||
ping_thread = threading.Thread(target=ping_remote, args=(remote,kill))
|
||||
ping_thread.start()
|
||||
|
||||
To connect to and control an application we use :py:func:`~renderdoc.CreateTargetControl` with the URL as before and the ident returned from :py:meth:`~renderdoc.RemoteServer.ExecuteAndInject`.
|
||||
|
||||
.. highlight:: python
|
||||
.. code:: python
|
||||
|
||||
target = rd.CreateTargetControl(URL, result.ident, 'remote_capture.py', True)
|
||||
|
||||
# Here we wait for whichever condition you want
|
||||
target.TriggerCapture(1)
|
||||
|
||||
There are a couple of ways to trigger a capture, both :py:meth:`~renderdoc.TargetControl.TriggerCapture` and :py:meth:`~renderdoc.TargetControl.QueueCapture` depending on whether you want a time-based or frame-based trigger. The application itself can also use the in-application API to trigger a capture.
|
||||
|
||||
The target control connection can be intermittently polled for messages using :py:meth:`~renderdoc.TargetControl.ReceiveMessage`, which keeps the connection alive and will return any new information such as the data for a new capture that has been created. A message of type :py:data:`~renderdoc.TargetControlMessageType.NewCapture` indicates a new capture has been created, and :py:data:`~renderdoc.TargetControlMessage.newCapture` contains the information including the path.
|
||||
|
||||
.. highlight:: python
|
||||
.. code:: python
|
||||
|
||||
msg = target.ReceiveMessage(None)
|
||||
|
||||
# Once msg.type == rd.TargetControlMessageType.NewCapture has been retrieved
|
||||
|
||||
cap_path = msg.newCapture.path
|
||||
cap_id = msg.newCapture.captureId
|
||||
|
||||
Once the capture has been found we are finished with the target control connection so we can shut it down and stop the background thread that was keeping the remote server connection alive. Using the remote server connection we can copy the capture back to the local machine with :py:meth:`~renderdoc.RemoteServer.CopyCaptureFromRemote`. Similarly if we wanted to load a previously made capture that wasn't on the remote machine :py:meth:`~renderdoc.RemoteServer.CopyCaptureToRemote` would be useful to copy it ready to be opened.
|
||||
|
||||
Finally to open the capture we use, and that returns a :py:class:`~renderdoc.ReplayController` which can be used as normal and will tunnel over the remote server connection. It can be useful to intermittently ping the remote server connection to check that it's still valid, and remote server and controller calls can be interleaved as long as they don't overlap on multiple threads.
|
||||
|
||||
Example Source
|
||||
--------------
|
||||
|
||||
.. only:: html and not htmlhelp
|
||||
|
||||
:download:`Download the example script <remote_capture.py>`.
|
||||
|
||||
.. literalinclude:: remote_capture.py
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -20,9 +20,14 @@ Remote Servers
|
||||
--------------
|
||||
|
||||
.. autofunction:: renderdoc.CreateRemoteServerConnection
|
||||
.. autofunction:: renderdoc.GetDefaultRemoteServerPort
|
||||
.. autofunction:: renderdoc.BecomeRemoteServer
|
||||
|
||||
Device Protocols
|
||||
----------------
|
||||
|
||||
.. autofunction:: renderdoc.GetSupportedDeviceProtocols
|
||||
.. autofunction:: renderdoc.GetDeviceProtocolController
|
||||
|
||||
Local Execution & Injection
|
||||
---------------------------
|
||||
|
||||
|
||||
@@ -33,6 +33,13 @@ RemoteServer
|
||||
:members:
|
||||
:undoc-members:
|
||||
|
||||
DeviceProtocolController
|
||||
------------------------
|
||||
|
||||
.. autoclass:: renderdoc.DeviceProtocolController
|
||||
:members:
|
||||
:undoc-members:
|
||||
|
||||
CaptureFile
|
||||
-----------
|
||||
|
||||
|
||||
Reference in New Issue
Block a user