Skip to main content

anvilkit_render/renderer/
debug.rs

1//! # 调试和性能分析工具
2//!
3//! 提供渲染调试可视化和帧统计信息。
4//!
5//! ## 核心类型
6//!
7//! - [`DebugMode`]: 调试渲染模式(线框、法线、光照等)
8//! - [`RenderStats`]: 每帧渲染统计
9//! - [`DebugOverlay`]: 调试信息叠加层
10
11use bevy_ecs::prelude::*;
12
13/// 调试渲染模式
14///
15/// # 示例
16///
17/// ```rust
18/// use anvilkit_render::renderer::debug::DebugMode;
19///
20/// let mode = DebugMode::Wireframe;
21/// assert!(!mode.is_normal());
22/// assert!(DebugMode::None.is_normal());
23/// ```
24#[derive(Debug, Clone, Copy, PartialEq, Eq)]
25pub enum DebugMode {
26    /// 正常渲染
27    None,
28    /// 线框叠加
29    Wireframe,
30    /// 法线可视化
31    Normals,
32    /// 仅漫反射
33    DiffuseOnly,
34    /// 仅镜面反射
35    SpecularOnly,
36    /// 金属度可视化
37    Metallic,
38    /// 粗糙度可视化
39    Roughness,
40    /// AO 可视化
41    AmbientOcclusion,
42    /// UV 坐标可视化
43    UVs,
44    /// 深度缓冲可视化
45    Depth,
46}
47
48impl DebugMode {
49    /// 是否为正常渲染模式
50    pub fn is_normal(&self) -> bool {
51        matches!(self, DebugMode::None)
52    }
53}
54
55impl Default for DebugMode {
56    fn default() -> Self {
57        DebugMode::None
58    }
59}
60
61/// 每帧渲染统计
62///
63/// # 示例
64///
65/// ```rust
66/// use anvilkit_render::renderer::debug::RenderStats;
67///
68/// let mut stats = RenderStats::new();
69/// stats.record_draw_call(100);
70/// stats.record_draw_call(200);
71/// assert_eq!(stats.draw_calls, 2);
72/// assert_eq!(stats.triangles, 300);
73/// ```
74#[derive(Debug, Clone, Resource)]
75pub struct RenderStats {
76    /// 绘制调用次数
77    pub draw_calls: u32,
78    /// 渲染的三角形总数
79    pub triangles: u32,
80    /// 渲染的顶点总数
81    pub vertices: u32,
82    /// 活跃的光源数
83    pub active_lights: u32,
84    /// 视锥体剔除掉的物体数
85    pub culled_objects: u32,
86    /// 可见物体数
87    pub visible_objects: u32,
88    /// 帧时间(毫秒)
89    pub frame_time_ms: f32,
90    /// FPS(基于帧时间计算)
91    pub fps: f32,
92    /// GPU 内存使用估计(字节)
93    pub gpu_memory_bytes: u64,
94}
95
96impl RenderStats {
97    pub fn new() -> Self {
98        Self {
99            draw_calls: 0,
100            triangles: 0,
101            vertices: 0,
102            active_lights: 0,
103            culled_objects: 0,
104            visible_objects: 0,
105            frame_time_ms: 0.0,
106            fps: 0.0,
107            gpu_memory_bytes: 0,
108        }
109    }
110
111    /// 记录一次绘制调用
112    pub fn record_draw_call(&mut self, triangle_count: u32) {
113        self.draw_calls += 1;
114        self.triangles += triangle_count;
115    }
116
117    /// 更新帧时间
118    pub fn update_frame_time(&mut self, dt_seconds: f32) {
119        self.frame_time_ms = dt_seconds * 1000.0;
120        self.fps = if dt_seconds > 0.0 { 1.0 / dt_seconds } else { 0.0 };
121    }
122
123    /// 帧开始时重置计数器
124    pub fn reset_frame(&mut self) {
125        self.draw_calls = 0;
126        self.triangles = 0;
127        self.vertices = 0;
128        self.culled_objects = 0;
129        self.visible_objects = 0;
130    }
131
132    /// 格式化为摘要字符串
133    pub fn summary(&self) -> String {
134        format!(
135            "FPS: {:.0} | {:.1}ms | Draw: {} | Tri: {} | Vis: {}/{}",
136            self.fps, self.frame_time_ms,
137            self.draw_calls, self.triangles,
138            self.visible_objects, self.visible_objects + self.culled_objects,
139        )
140    }
141}
142
143impl Default for RenderStats {
144    fn default() -> Self {
145        Self::new()
146    }
147}
148
149/// 调试叠加层配置
150///
151/// # 示例
152///
153/// ```rust
154/// use anvilkit_render::renderer::debug::DebugOverlay;
155///
156/// let overlay = DebugOverlay::default();
157/// assert!(!overlay.show_stats);
158/// assert!(!overlay.show_wireframe);
159/// ```
160#[derive(Debug, Clone, Resource)]
161pub struct DebugOverlay {
162    /// 是否显示统计信息
163    pub show_stats: bool,
164    /// 是否显示线框
165    pub show_wireframe: bool,
166    /// 是否显示包围盒
167    pub show_bounds: bool,
168    /// 是否显示灯光图标
169    pub show_lights: bool,
170    /// 是否显示骨骼
171    pub show_skeleton: bool,
172    /// 当前调试模式
173    pub debug_mode: DebugMode,
174}
175
176impl Default for DebugOverlay {
177    fn default() -> Self {
178        Self {
179            show_stats: false,
180            show_wireframe: false,
181            show_bounds: false,
182            show_lights: false,
183            show_skeleton: false,
184            debug_mode: DebugMode::None,
185        }
186    }
187}
188
189#[cfg(test)]
190mod tests {
191    use super::*;
192
193    #[test]
194    fn test_debug_mode() {
195        assert!(DebugMode::None.is_normal());
196        assert!(!DebugMode::Wireframe.is_normal());
197        assert!(!DebugMode::Normals.is_normal());
198    }
199
200    #[test]
201    fn test_render_stats() {
202        let mut stats = RenderStats::new();
203        stats.record_draw_call(100);
204        stats.record_draw_call(50);
205        assert_eq!(stats.draw_calls, 2);
206        assert_eq!(stats.triangles, 150);
207
208        stats.update_frame_time(1.0 / 60.0);
209        assert!((stats.fps - 60.0).abs() < 1.0);
210
211        let summary = stats.summary();
212        assert!(summary.contains("FPS:"));
213        assert!(summary.contains("Draw: 2"));
214    }
215
216    #[test]
217    fn test_render_stats_reset() {
218        let mut stats = RenderStats::new();
219        stats.record_draw_call(100);
220        stats.visible_objects = 5;
221        stats.culled_objects = 3;
222
223        stats.reset_frame();
224        assert_eq!(stats.draw_calls, 0);
225        assert_eq!(stats.triangles, 0);
226        // fps and frame_time are NOT reset (they're per-frame measurements)
227        assert_eq!(stats.visible_objects, 0);
228    }
229
230    #[test]
231    fn test_debug_overlay_default() {
232        let overlay = DebugOverlay::default();
233        assert!(!overlay.show_stats);
234        assert!(overlay.debug_mode.is_normal());
235    }
236}