#!/usr/bin/env python3 # -*- coding: utf-8 -*- """ RenderDoc Capture Analysis Script Uses RenderDoc's Python API to extract comprehensive information from .rdc capture files. Run with: python analyze_capture.py [output.json] Requires: - Python 3.6 - renderdoc.pyd (compiled for Python 3.6) - renderdoc.dll and dependencies in the same directory or PATH """ import sys import json import os RENDERDOC_DIR = os.path.dirname(os.path.abspath(__file__)) sys.path.insert(0, RENDERDOC_DIR) os.environ["PATH"] = RENDERDOC_DIR + os.pathsep + os.environ.get("PATH", "") import renderdoc as rd def safe_str(s): if s is None: return "" try: return str(s) except: return repr(s) def extract_resource_info(resource): return { "resource_id": str(resource.resourceId), "name": safe_str(resource.name), "type": str(resource.type) if hasattr(resource, "type") else "Unknown", "format": str(resource.format) if hasattr(resource, "format") else "Unknown", "dimension": str(resource.dimension) if hasattr(resource, "dimension") else "Unknown", "width": resource.width if hasattr(resource, "width") else 0, "height": resource.height if hasattr(resource, "height") else 0, "depth": resource.depth if hasattr(resource, "depth") else 0, } def extract_action_info(action, structured_file, include_children=True): action_info = { "event_id": action.eventId, "name": safe_str( action.GetName(structured_file) if hasattr(action, "GetName") else action.customName ), "custom_name": safe_str(action.customName), "flags": str(action.flags) if hasattr(action, "flags") else "NoFlags", } if hasattr(action, "numIndices") and action.numIndices > 0: action_info["num_indices"] = action.numIndices if hasattr(action, "numInstances") and action.numInstances > 0: action_info["num_instances"] = action.numInstances if hasattr(action, "vertexOffset"): action_info["vertex_offset"] = action.vertexOffset if hasattr(action, "indexOffset"): action_info["index_offset"] = action.indexOffset if hasattr(action, "baseVertex"): action_info["base_vertex"] = action.baseVertex if include_children and hasattr(action, "children") and action.children: action_info["children"] = [ extract_action_info(child, structured_file, True) for child in action.children ] return action_info def extract_shader_variables(controller, state): shader_vars = [] shader_stages = [ (rd.ShaderStage.Vertex, "VS"), (rd.ShaderStage.Pixel, "PS"), (rd.ShaderStage.Compute, "CS"), (rd.ShaderStage.Geometry, "GS"), (rd.ShaderStage.Hull, "HS"), (rd.ShaderStage.Domain, "DS"), ] for stage, stage_name in shader_stages: try: shader = state.GetShader(stage) if shader and hasattr(shader, "reflection") and shader.reflection: cbuffers = shader.reflection.GetCBuffers() for cbuf in cbuffers: try: variables = controller.GetCBufferVariableContents( state, shader, cbuf.resourceId ) if variables: for var in variables: var_info = { "stage": stage_name, "cbuffer": safe_str(cbuf.name), "name": safe_str(var.name) if hasattr(var, "name") else "", "type": str(var.type) if hasattr(var, "type") else "Unknown", } if hasattr(var, "value"): var_info["value"] = str(var.value)[:500] shader_vars.append(var_info) except Exception as e: pass except: pass return shader_vars def analyze_capture(filename): if not os.path.exists(filename): return {"error": f"File not found: {filename}"} cap = rd.OpenCaptureFile() result = cap.OpenFile(filename, "", None) if result != rd.ResultCode.Succeeded: return {"error": f"Couldn't open file: {result}"} if not cap.LocalReplaySupport(): return {"error": "Capture cannot be replayed locally"} result, controller = cap.OpenCapture(rd.ReplayOptions(), None) if result != rd.ResultCode.Succeeded: return {"error": f"Couldn't init replay: {result}"} try: structured_file = controller.GetStructuredFile() capture_info = { "filename": os.path.basename(filename), "full_path": os.path.abspath(filename), "file_size": os.path.getsize(filename), "renderdoc_version": rd.GetVersionString(), } frame_info = {} try: frame_desc = controller.GetFrameInfo() if frame_desc: frame_info = { "frame_number": frame_desc.frameNumber if hasattr(frame_desc, "frameNumber") else 0, } except: pass root_actions = controller.GetRootActions() actions = [] draw_call_count = 0 def process_actions(action_list): nonlocal draw_call_count for action in action_list: action_info = extract_action_info(action, structured_file, False) if hasattr(action, "flags") and action.flags & rd.ActionFlags.Drawcall: draw_call_count += 1 actions.append(action_info) if root_actions: process_actions(root_actions) frame_info["draw_call_count"] = draw_call_count resources = [] try: resource_list = controller.GetResourceList() if resource_list: resources = [extract_resource_info(res) for res in resource_list] except: pass state = controller.GetPipelineState() shader_variables = extract_shader_variables(controller, state) debug_messages = [] try: messages = controller.GetDebugMessages() if messages: for msg in messages: debug_messages.append( { "event_id": msg.eventId if hasattr(msg, "eventId") else 0, "severity": str(msg.severity) if hasattr(msg, "severity") else "Unknown", "category": str(msg.category) if hasattr(msg, "category") else "Unknown", "message": safe_str(msg.description) if hasattr(msg, "description") else "", } ) except: pass result_data = { "capture_info": capture_info, "frame_info": frame_info, "actions": actions, "resources": resources, "shader_variables": shader_variables, "debug_messages": debug_messages, } finally: controller.Shutdown() cap.Shutdown() rd.ShutdownReplay() return result_data def main(): if len(sys.argv) < 2: print("Usage: python analyze_capture.py [output.json]") sys.exit(1) filename = sys.argv[1] output_path = sys.argv[2] if len(sys.argv) > 2 else None print(f"Analyzing: {filename}", file=sys.stderr) result = analyze_capture(filename) if output_path: with open(output_path, "w", encoding="utf-8") as f: json.dump(result, f, indent=2, ensure_ascii=False) print(f"Saved to: {output_path}", file=sys.stderr) else: print(json.dumps(result, indent=2, ensure_ascii=False)) if __name__ == "__main__": main()