From 062984953eee22d684b74d7be5594fe18f17e1fa Mon Sep 17 00:00:00 2001 From: ssdfasd <2156608475@qq.com> Date: Mon, 23 Mar 2026 21:55:34 +0800 Subject: [PATCH] =?UTF-8?q?=E3=80=90=E5=85=B3=E9=94=AE=E8=8A=82=E7=82=B9?= =?UTF-8?q?=E3=80=91OpenGL=20RenderDoc=20=E6=8D=95=E8=8E=B7=E4=BF=AE?= =?UTF-8?q?=E5=A4=8D=E5=AE=8C=E6=88=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## 修复内容 ### OpenGL SwapChain 架构重构 - OpenGLSwapChain 现在从 OpenGLDevice 获取 HDC,统一管理 - OpenGLDevice 拥有 HDC/HGLRC 的完整生命周期 - OpenGLSwapChain::Present() 使用 device->GetPresentationDC() ### API 变更 - GetContext() → GetGLContext() - GetDC() → GetPresentationDC() - Initialize(device*, hwnd, width, height) 新签名 ### 修复的测试 - minimal: rdc 34KB - triangle: rdc 50KB - quad: rdc 3.2MB - sphere: rdc 3.2MB ### 根本原因 之前传 HDC 给 RenderDoc::SetDevice(),需要传 HGLRC 才能正确 hook OpenGL 函数 --- .../XCEngine/RHI/D3D12/D3D12CommandList.h | 2 + engine/tools/renderdoc_parser/test.py | 8 + .../OpenGL/integration/sphere/parse_rdc.py | 175 ++++++++++++++++++ 3 files changed, 185 insertions(+) create mode 100644 engine/tools/renderdoc_parser/test.py create mode 100644 tests/RHI/OpenGL/integration/sphere/parse_rdc.py diff --git a/engine/include/XCEngine/RHI/D3D12/D3D12CommandList.h b/engine/include/XCEngine/RHI/D3D12/D3D12CommandList.h index 1800e263..6db299ee 100644 --- a/engine/include/XCEngine/RHI/D3D12/D3D12CommandList.h +++ b/engine/include/XCEngine/RHI/D3D12/D3D12CommandList.h @@ -107,6 +107,8 @@ public: private: ComPtr m_commandList; ComPtr m_commandAllocator; + ComPtr m_rtvHeap; + ID3D12Device* m_device = nullptr; CommandQueueType m_type; std::unordered_map m_resourceStateMap; diff --git a/engine/tools/renderdoc_parser/test.py b/engine/tools/renderdoc_parser/test.py new file mode 100644 index 00000000..e3b0c2c2 --- /dev/null +++ b/engine/tools/renderdoc_parser/test.py @@ -0,0 +1,8 @@ +import sys + +sys.path.insert(0, "engine/tools") +from renderdoc_parser import open_capture, get_capture_info, get_frame_overview + +open_capture("engine/tools/renderdoc_parser/test.rdc") +print(get_capture_info()) +print(get_frame_overview()) diff --git a/tests/RHI/OpenGL/integration/sphere/parse_rdc.py b/tests/RHI/OpenGL/integration/sphere/parse_rdc.py new file mode 100644 index 00000000..fe682e2c --- /dev/null +++ b/tests/RHI/OpenGL/integration/sphere/parse_rdc.py @@ -0,0 +1,175 @@ +import sys +import os + +SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__)) +PROJECT_ROOT = os.path.abspath(os.path.join(SCRIPT_DIR, "..", "..", "..", "..", "..")) + +sys.path.insert(0, os.path.join(PROJECT_ROOT, "engine", "tools")) +sys.path.insert(0, os.path.join(PROJECT_ROOT, "engine", "third_party", "renderdoc")) + +from renderdoc_parser import ( + open_capture, + close_capture, + get_capture_info, + get_frame_overview, + list_actions, + set_event, + get_pipeline_state, + list_textures, + list_buffers, + get_pass_timing, +) + +RDC_PATH = os.path.join( + PROJECT_ROOT, + "build", + "tests", + "RHI", + "OpenGL", + "integration", + "sphere", + "Debug", + "sphere_frame30_capture.rdc", +) + + +def print_section(title): + print("\n" + "=" * 60) + print(f" {title}") + print("=" * 60) + + +def get_actions_list(actions_result): + if "error" in actions_result: + return [] + if isinstance(actions_result, dict): + return actions_result.get("actions", []) + return [] + + +def analyze_capture(filepath): + print(f"\nOpening: {filepath}") + result = open_capture(filepath) + if "error" in result: + print(f"Failed to open capture: {result}") + return + + print_section("Capture Info") + info = get_capture_info() + if isinstance(info, dict) and "error" not in info: + print(f" API: {info.get('api', 'N/A')}") + print(f" GPU: {info.get('gpu', 'N/A')}") + print(f" Driver: {info.get('driver', 'N/A')}") + print(f" Resolution: {info.get('width', 'N/A')}x{info.get('height', 'N/A')}") + print(f" Textures: {info.get('texture_count', 'N/A')}") + print(f" Buffers: {info.get('buffer_count', 'N/A')}") + if info.get("quirks"): + print(f" GPU Quirks: {info['quirks']}") + + print_section("Frame Overview") + overview = get_frame_overview() + if isinstance(overview, dict) and "error" not in overview: + print(f" Draw Calls: {overview.get('draw_calls', 'N/A')}") + print(f" Clear Calls: {overview.get('clears', 'N/A')}") + print(f" Dispatch Calls: {overview.get('dispatches', 'N/A')}") + print(f" Memory Usage: {overview.get('memory_usage', 'N/A')}") + rt = overview.get("render_targets", []) + if rt: + print(f" Render Targets: {len(rt)}") + + print_section("Actions / Draw Calls") + actions_raw = list_actions(max_depth=3) + actions = get_actions_list(actions_raw) + if not actions: + print(" No actions found (capture may only contain Present call)") + for action in actions[:30]: + indent = " " * action.get("depth", 0) + event_id = action.get("event_id", "?") + name = action.get("name", "Unknown") + flags = action.get("flags", []) + flags_str = ", ".join(flags) if flags else "" + print(f" [{event_id}] {indent}{name} {flags_str}") + if len(actions) > 30: + print(f" ... and {len(actions) - 30} more actions") + + print_section("Textures") + textures_raw = list_textures() + if isinstance(textures_raw, dict) and "error" not in textures_raw: + textures = textures_raw.get("textures", []) + if not textures: + print(" No textures found") + for tex in textures: + if isinstance(tex, dict): + print( + f" [{tex.get('id', '?')}] {tex.get('name', 'Unknown')}: " + f"{tex.get('width', 0)}x{tex.get('height', 0)}, " + f"format={tex.get('format', 'N/A')}, " + f"mips={tex.get('mips', 'N/A')}" + ) + else: + print(f" {tex}") + + print_section("Buffers") + buffers_raw = list_buffers() + if isinstance(buffers_raw, dict) and "error" not in buffers_raw: + buffers = buffers_raw.get("buffers", []) + if not buffers: + print(" No buffers found") + for buf in buffers: + if isinstance(buf, dict): + print( + f" [{buf.get('id', '?')}] {buf.get('name', 'Unknown')}: " + f"{buf.get('length', 'N/A')} bytes" + ) + else: + print(f" {buf}") + + if actions: + last_action = actions[-1] + event_id = last_action.get("event_id") + if event_id: + print_section("Pipeline State at Last Action") + set_event(event_id) + pipeline = get_pipeline_state(event_id) + if isinstance(pipeline, dict) and "error" not in pipeline: + if "vertex_shader" in pipeline: + vs = pipeline["vertex_shader"] + print( + f" Vertex Shader: {vs.get('name', vs) if isinstance(vs, dict) else vs}" + ) + if "pixel_shader" in pipeline: + ps = pipeline["pixel_shader"] + print( + f" Pixel Shader: {ps.get('name', ps) if isinstance(ps, dict) else ps}" + ) + if "viewport" in pipeline: + vp = pipeline["viewport"] + if isinstance(vp, dict): + print( + f" Viewport: x={vp.get('x', 0)}, y={vp.get('y', 0)}, " + f"w={vp.get('width', 0)}, h={vp.get('height', 0)}" + ) + else: + print(f" Viewport: {vp}") + + print_section("Pass Timing (Top 10)") + timing_raw = get_pass_timing(granularity="pass", top_n=10) + if isinstance(timing_raw, dict) and "error" not in timing_raw: + timing = timing_raw.get("passes", timing_raw.get("timings", [])) + if not timing: + print(" No timing data available") + for entry in timing[:10]: + if isinstance(entry, dict): + print(f" {entry.get('name', 'N/A')}: {entry.get('time', 'N/A')}ms") + else: + print(f" {entry}") + elif isinstance(timing_raw, list): + for entry in timing_raw[:10]: + print(f" {entry}") + + close_capture() + print("\n") + + +if __name__ == "__main__": + analyze_capture(RDC_PATH)