1use wgpu::{BindGroup, BindGroupLayout, Buffer, Device, Queue};
4
5use oxide_ecs::world::World;
6
7use super::components::{AmbientLight, DirectionalLight, PointLight};
8use super::uniform::{GpuPointLight, LightUniform, MAX_POINT_LIGHTS};
9
10pub struct LightBuffer {
11 pub uniform_buffer: Buffer,
12 pub point_light_buffer: Buffer,
13 pub bind_group_layout: BindGroupLayout,
14 pub bind_group: BindGroup,
15 point_light_capacity: usize,
16}
17
18impl LightBuffer {
19 pub fn new(device: &Device) -> Self {
20 let uniform_buffer = device.create_buffer(&wgpu::BufferDescriptor {
21 label: Some("Light Uniform Buffer"),
22 size: std::mem::size_of::<LightUniform>() as u64,
23 usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
24 mapped_at_creation: false,
25 });
26
27 let point_light_capacity = 1usize;
28 let point_light_buffer = Self::create_point_light_buffer(device, point_light_capacity);
29
30 let bind_group_layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
31 label: Some("Light Bind Group Layout"),
32 entries: &[
33 wgpu::BindGroupLayoutEntry {
34 binding: 0,
35 visibility: wgpu::ShaderStages::FRAGMENT,
36 ty: wgpu::BindingType::Buffer {
37 ty: wgpu::BufferBindingType::Uniform,
38 has_dynamic_offset: false,
39 min_binding_size: None,
40 },
41 count: None,
42 },
43 wgpu::BindGroupLayoutEntry {
44 binding: 1,
45 visibility: wgpu::ShaderStages::FRAGMENT,
46 ty: wgpu::BindingType::Buffer {
47 ty: wgpu::BufferBindingType::Storage { read_only: true },
48 has_dynamic_offset: false,
49 min_binding_size: None,
50 },
51 count: None,
52 },
53 ],
54 });
55
56 let bind_group = Self::create_bind_group(
57 device,
58 &bind_group_layout,
59 &uniform_buffer,
60 &point_light_buffer,
61 );
62
63 Self {
64 uniform_buffer,
65 point_light_buffer,
66 bind_group_layout,
67 bind_group,
68 point_light_capacity,
69 }
70 }
71
72 pub fn update(&mut self, device: &Device, queue: &Queue, world: &mut World) {
73 let ambient = {
74 let mut query = world.query::<&AmbientLight>();
75 let first = query.iter(world).next().copied();
76 first
77 };
78
79 let directional_lights: Vec<DirectionalLight> = {
80 let mut query = world.query::<&DirectionalLight>();
81 query.iter(world).copied().collect()
82 };
83
84 let point_lights: Vec<PointLight> = {
85 let mut query = world.query::<&PointLight>();
86 query.iter(world).copied().take(MAX_POINT_LIGHTS).collect()
87 };
88
89 self.ensure_point_light_capacity(device, point_lights.len().max(1));
90
91 let gpu_point_lights: Vec<GpuPointLight> = point_lights
92 .iter()
93 .map(|light| {
94 GpuPointLight::from_values(
95 light.position,
96 light.color,
97 light.intensity,
98 light.radius,
99 )
100 })
101 .collect();
102
103 let uniform = LightUniform::new(
104 ambient.as_ref(),
105 &directional_lights.iter().collect::<Vec<_>>(),
106 gpu_point_lights.len(),
107 );
108
109 queue.write_buffer(&self.uniform_buffer, 0, bytemuck::bytes_of(&uniform));
110
111 if !gpu_point_lights.is_empty() {
112 queue.write_buffer(
113 &self.point_light_buffer,
114 0,
115 bytemuck::cast_slice(&gpu_point_lights),
116 );
117 }
118 }
119
120 fn ensure_point_light_capacity(&mut self, device: &Device, required: usize) {
121 if required <= self.point_light_capacity {
122 return;
123 }
124
125 self.point_light_capacity = required.next_power_of_two();
126 self.point_light_buffer =
127 Self::create_point_light_buffer(device, self.point_light_capacity);
128 self.bind_group = Self::create_bind_group(
129 device,
130 &self.bind_group_layout,
131 &self.uniform_buffer,
132 &self.point_light_buffer,
133 );
134 }
135
136 fn create_point_light_buffer(device: &Device, capacity: usize) -> Buffer {
137 let byte_size = (capacity * std::mem::size_of::<GpuPointLight>()) as u64;
138 device.create_buffer(&wgpu::BufferDescriptor {
139 label: Some("Point Light Storage Buffer"),
140 size: byte_size.max(std::mem::size_of::<GpuPointLight>() as u64),
141 usage: wgpu::BufferUsages::STORAGE | wgpu::BufferUsages::COPY_DST,
142 mapped_at_creation: false,
143 })
144 }
145
146 fn create_bind_group(
147 device: &Device,
148 layout: &BindGroupLayout,
149 uniform_buffer: &Buffer,
150 point_light_buffer: &Buffer,
151 ) -> BindGroup {
152 device.create_bind_group(&wgpu::BindGroupDescriptor {
153 label: Some("Light Bind Group"),
154 layout,
155 entries: &[
156 wgpu::BindGroupEntry {
157 binding: 0,
158 resource: uniform_buffer.as_entire_binding(),
159 },
160 wgpu::BindGroupEntry {
161 binding: 1,
162 resource: point_light_buffer.as_entire_binding(),
163 },
164 ],
165 })
166 }
167}