Skip to main content

anvilkit_render/renderer/
state.rs

1//! # 渲染状态共享资源
2//!
3//! 在 GPU 初始化后由 RenderApp 插入到 ECS World,
4//! 供渲染系统读取表面信息和场景 Uniform。
5
6use bevy_ecs::prelude::*;
7
8/// GPU 端单个光源数据 (64 字节)
9///
10/// | 字段 | 含义 |
11/// |------|------|
12/// | position_type | xyz=位置(点光/聚光), w=类型 (0=方向光, 1=点光, 2=聚光) |
13/// | direction_range | xyz=方向, w=衰减距离 |
14/// | color_intensity | rgb=颜色(linear), w=强度 |
15/// | params | x=inner_cone_cos, y=outer_cone_cos, z=0, w=0 |
16#[repr(C)]
17#[derive(Copy, Clone, Debug, bytemuck::Pod, bytemuck::Zeroable)]
18pub struct GpuLight {
19    pub position_type: [f32; 4],
20    pub direction_range: [f32; 4],
21    pub color_intensity: [f32; 4],
22    pub params: [f32; 4],
23}
24
25impl Default for GpuLight {
26    fn default() -> Self {
27        Self {
28            position_type: [0.0; 4],
29            direction_range: [0.0, -1.0, 0.0, 0.0],
30            color_intensity: [0.0; 4],
31            params: [0.0; 4],
32        }
33    }
34}
35
36/// 最大光源数量
37pub const MAX_LIGHTS: usize = 8;
38
39/// PBR 场景 Uniform (848 字节)
40///
41/// 包含 per-object 变换、材质参数和多光源数据。
42/// 前 256 字节与旧布局兼容(light_dir/light_color 保留但多光源路径不使用)。
43#[repr(C)]
44#[derive(Copy, Clone, bytemuck::Pod, bytemuck::Zeroable)]
45pub struct PbrSceneUniform {
46    pub model: [[f32; 4]; 4],           // 64 bytes
47    pub view_proj: [[f32; 4]; 4],       // 64 bytes
48    pub normal_matrix: [[f32; 4]; 4],   // 64 bytes
49    pub camera_pos: [f32; 4],           // 16 bytes
50    pub light_dir: [f32; 4],            // 16 bytes (legacy / lights[0] shortcut)
51    pub light_color: [f32; 4],          // 16 bytes (legacy / lights[0] shortcut)
52    pub material_params: [f32; 4],      // 16 bytes (metallic, roughness, normal_scale, light_count)
53    // Multi-light array
54    pub lights: [GpuLight; MAX_LIGHTS], // 512 bytes (8 * 64)
55    // Shadow mapping
56    pub shadow_view_proj: [[f32; 4]; 4], // 64 bytes
57    // Emissive
58    pub emissive_factor: [f32; 4],       // 16 bytes (rgb + 0)
59}
60
61impl Default for PbrSceneUniform {
62    fn default() -> Self {
63        Self {
64            model: glam::Mat4::IDENTITY.to_cols_array_2d(),
65            view_proj: glam::Mat4::IDENTITY.to_cols_array_2d(),
66            normal_matrix: glam::Mat4::IDENTITY.to_cols_array_2d(),
67            camera_pos: [0.0; 4],
68            light_dir: [0.0, -1.0, 0.0, 0.0],
69            light_color: [1.0, 1.0, 1.0, 3.0],
70            material_params: [0.0, 0.5, 1.0, 0.0],
71            lights: [GpuLight::default(); MAX_LIGHTS],
72            shadow_view_proj: glam::Mat4::IDENTITY.to_cols_array_2d(),
73            emissive_factor: [0.0; 4],
74        }
75    }
76}
77
78/// 共享渲染状态
79///
80/// 持有与 GPU 表面和场景 Uniform 相关的资源。
81/// RenderApp 在 GPU 初始化后将其插入 World。
82#[derive(Resource)]
83pub struct RenderState {
84    pub surface_format: wgpu::TextureFormat,
85    pub surface_size: (u32, u32),
86    pub scene_uniform_buffer: wgpu::Buffer,
87    pub scene_bind_group: wgpu::BindGroup,
88    pub scene_bind_group_layout: wgpu::BindGroupLayout,
89    pub depth_texture_view: wgpu::TextureView,
90    // HDR multi-pass rendering
91    pub hdr_texture_view: wgpu::TextureView,
92    pub tonemap_pipeline: wgpu::RenderPipeline,
93    pub tonemap_bind_group: wgpu::BindGroup,
94    pub tonemap_bind_group_layout: wgpu::BindGroupLayout,
95    // IBL + Shadow (group 2)
96    pub ibl_shadow_bind_group: wgpu::BindGroup,
97    pub ibl_shadow_bind_group_layout: wgpu::BindGroupLayout,
98    // Shadow pass
99    pub shadow_pipeline: wgpu::RenderPipeline,
100    pub shadow_map_view: wgpu::TextureView,
101    // MSAA
102    pub hdr_msaa_texture_view: wgpu::TextureView,
103}
104
105#[cfg(test)]
106mod tests {
107    use super::*;
108
109    #[test]
110    fn test_pbr_scene_uniform_size() {
111        assert_eq!(std::mem::size_of::<PbrSceneUniform>(), 848);
112    }
113
114    #[test]
115    fn test_gpu_light_size() {
116        assert_eq!(std::mem::size_of::<GpuLight>(), 64);
117    }
118
119    #[test]
120    fn test_pbr_scene_uniform_default() {
121        let u = PbrSceneUniform::default();
122        // model should be identity
123        assert_eq!(u.model[0][0], 1.0);
124        assert_eq!(u.model[1][1], 1.0);
125        // default light direction points down
126        assert_eq!(u.light_dir[1], -1.0);
127        // default roughness = 0.5
128        assert_eq!(u.material_params[1], 0.5);
129        // default normal_scale = 1.0
130        assert_eq!(u.material_params[2], 1.0);
131    }
132}