baldurk / renderdoc-contrib

Community contributed extensions for RenderDoc
MIT License
64 stars 8 forks source link

Extension for capturing frames with minimum number of render passes #4

Closed Abdul-AZ closed 6 months ago

Abdul-AZ commented 6 months ago

Hello

I am using a script to capture only frames that contain a minimum number of render passes which I use a lot. An example use case for this is a Qt Widgets projects with a QOpenGLWidget that does not get redrawn every frame. This script keeps capturing frames until it finds a frame where that widget is drawn in.

Should I make this into an extension and open a PR or do you think no one will get a use out of this?

Script:

import renderdoc as rd
import qrenderdoc as qrd
import time
import os
import signal

app = '/path/to/your/app'
# Minimum number of render passes in wanted frame
min_number_render_passes = 1
working_directory = ''
capture_file_name = 'cap.rdc'
# Maximum number of tries before stopping
num_tries = 100
# Time in seconds before starting to capture frames
time_before_starting = 3
# Close app after script
terminate_after_success = True
terminate_after_fail    = True

# Start application and find target (assuming no other target exists)
rd.ExecuteAndInject(app, working_directory, '', [], '', rd.CaptureOptions(), False)
ident = rd.EnumerateRemoteTargets('', 0)
target = rd.CreateTargetControl('', ident, 'capture.py', True)
pid = target.GetPID()

success = False

# Wait for set time before capturing
time.sleep(time_before_starting)
for x in range(num_tries):

    # Capture and wait for frame (30 sec timeout)
    target.TriggerCapture(1)
    msg = None
    start = time.time()
    while msg is None or msg.type != rd.TargetControlMessageType.NewCapture:
        msg = target.ReceiveMessage(None)

        if time.time() - start > 30:
            break

    # Save capture to file (5 sec timeout) 
    target.CopyCapture(msg.newCapture.captureId, capture_file_name)
    msg = None
    start = time.time()
    while msg is None or msg.type != rd.TargetControlMessageType.CaptureCopied:
        msg = target.ReceiveMessage(None)

        if time.time() - start > 5:
            break

    # Open capture file
    rd.InitialiseReplay(rd.GlobalEnvironment(), [])
    cap = rd.OpenCaptureFile()
    result = cap.OpenFile(capture_file_name, '', None)
    result,controller = cap.OpenCapture(rd.ReplayOptions(), None)

    # Loop through resources and count number of render passes
    resources = controller.GetResources()
    numRenderPasses = 0
    for res in resources:
        if res.type == rd.ResourceType.RenderPass:
            numRenderPasses = numRenderPasses + 1
    print(f'Captured frame with {numRenderPasses} render passes')

    # If successfully captured required frame, open it in GUI. Otherwise keep trying
    if numRenderPasses >= min_number_render_passes:
        print('Success, loading capture into GUI')
        pyrenderdoc.LoadCapture(capture_file_name, rd.ReplayOptions(), capture_file_name, True, True)
        if terminate_after_success:
            print('Terminating application')
            os.kill(pid, signal.SIGTERM)
        target.Shutdown()
        success = True
        break
    print(f'Retrying... ({x}/{num_tries})')

if success == False:
    print('Failed to capture frame')
    if terminate_after_success:
        os.kill(pid, signal.SIGTERM)
        print('Terminating application')
baldurk commented 6 months ago

If you want to PR the extension then yes please feel free, namespace it into a folder with your username and provide a README. I never intended to filter the extensions in this repository so it will mostly be up to what people want to contribute and maintain.

That said if you want my honest view I don't think many people will get much use of this. It seems the wrong way to solve the problem - if you're not capturing the work you want then it's better to integrate the in-application API and capture around exactly the work you want. Capturing and replaying is an expensive process so it's better to capture what you want in the first place than capture, replay, and discard.

Also as a side note - this method is rather risky as mentioned in the documentation since generally it's not a good idea to try to use the low level replay API while inside the UI which expects to have control over that API.