diff --git a/.gitignore b/.gitignore index 45aca3cf..b94d7752 100644 --- a/.gitignore +++ b/.gitignore @@ -13,3 +13,6 @@ Release/ CMakeCache.txt cmake_install.cmake Res/NanoVDB/ +*.rdc +*.pyd +*.dll diff --git a/MVS/RenderDoc/analyze_capture.py b/MVS/RenderDoc/analyze_capture.py new file mode 100644 index 00000000..fdb63942 --- /dev/null +++ b/MVS/RenderDoc/analyze_capture.py @@ -0,0 +1,255 @@ +#!/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() diff --git a/tests/RHI/OpenGL/integration/sphere/sphere.ppm b/tests/RHI/OpenGL/integration/sphere/sphere.ppm new file mode 100644 index 00000000..a4b372ee Binary files /dev/null and b/tests/RHI/OpenGL/integration/sphere/sphere.ppm differ