1use crate::kvasir::node::{ExecutionContext, KvasirNode};
2use crate::kvasir::nodes::{PassId, RES_BLOOM_A, RES_SCENE};
3use crate::kvasir::resource::ResourceId;
4
5pub struct BloomExtractNode {
6 pub inputs: Vec<ResourceId>,
7 pub outputs: Vec<ResourceId>,
8}
9
10impl BloomExtractNode {
11 pub fn new() -> Self {
12 Self {
13 inputs: vec![RES_SCENE],
14 outputs: vec![RES_BLOOM_A],
15 }
16 }
17}
18
19impl KvasirNode for BloomExtractNode {
20 fn label(&self) -> &'static str {
21 "Bloom Extract"
22 }
23
24 fn inputs(&self) -> &[ResourceId] {
25 &self.inputs
26 }
27
28 fn outputs(&self) -> &[ResourceId] {
29 &self.outputs
30 }
31
32 fn pass_id(&self) -> PassId {
33 PassId::BloomExtract
34 }
35
36 fn execute(&self, ctx: &mut ExecutionContext) {
37 let bloom_texture = match ctx.registry.get_texture(RES_BLOOM_A) {
38 Some(v) => v,
39 None => {
40 log::error!("Missing texture for {}", stringify!(RES_BLOOM_A));
41 return;
42 }
43 };
44 let bloom_view = bloom_texture.create_view(&wgpu::TextureViewDescriptor {
46 label: Some("bloom_extract_mip0"),
47 base_mip_level: 0,
48 mip_level_count: Some(1),
49 ..Default::default()
50 });
51
52 let scene_view = match ctx.registry.get_texture_view(RES_SCENE) {
54 Some(v) => v,
55 None => {
56 log::error!("Missing texture view for {}", stringify!(RES_SCENE));
57 return;
58 }
59 };
60 let bg = ctx.get_or_create_bind_group(
61 (RES_SCENE, 0, false),
62 &ctx.renderer.texture_bind_group_layout,
63 &[
64 wgpu::BindGroupEntry {
65 binding: 0,
66 resource: wgpu::BindingResource::TextureViewArray(&vec![&scene_view; 32]),
67 },
68 wgpu::BindGroupEntry {
69 binding: 1,
70 resource: wgpu::BindingResource::Sampler(&ctx.renderer.dummy_sampler),
71 },
72 ],
73 Some("bloom_extract_scene_bg"),
74 );
75
76 let mut p = ctx.encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
77 label: Some("Surtr Bloom Extract"),
78 color_attachments: &[Some(wgpu::RenderPassColorAttachment {
79 view: &bloom_view,
80 resolve_target: None,
81 ops: wgpu::Operations {
82 load: wgpu::LoadOp::Clear(wgpu::Color {
83 r: 0.0,
84 g: 0.0,
85 b: 0.0,
86 a: 0.0,
87 }),
88 store: wgpu::StoreOp::Store,
89 },
90 depth_slice: None,
91 })],
92 ..Default::default()
93 });
94
95 p.set_pipeline(&ctx.renderer.bloom_extract_pipeline);
96 p.set_bind_group(0, &bg, &[]);
97 p.set_bind_group(1, &ctx.renderer.dummy_env_bind_group, &[]);
98 p.set_bind_group(2, &ctx.renderer.berserker_bind_group, &[]);
99 p.draw(0..3, 0..1);
100 }
101}
102
103pub struct BloomBlurNode {
104 pub inputs: Vec<ResourceId>,
105 pub outputs: Vec<ResourceId>,
106 pub width: u32,
107 pub height: u32,
108}
109
110impl BloomBlurNode {
111 pub fn new(width: u32, height: u32) -> Self {
112 Self {
113 inputs: vec![RES_BLOOM_A],
114 outputs: vec![RES_BLOOM_A],
115 width,
116 height,
117 }
118 }
119}
120
121impl KvasirNode for BloomBlurNode {
122 fn label(&self) -> &'static str {
123 "Bloom Blur"
124 }
125
126 fn inputs(&self) -> &[ResourceId] {
127 &self.inputs
128 }
129
130 fn outputs(&self) -> &[ResourceId] {
131 &self.outputs
132 }
133
134 fn pass_id(&self) -> PassId {
135 PassId::BloomBlur
136 }
137
138 fn execute(&self, ctx: &mut ExecutionContext) {
139 let bloom_tex = match ctx.registry.get_texture(RES_BLOOM_A) {
140 Some(v) => v,
141 None => {
142 log::error!("Missing texture for {}", stringify!(RES_BLOOM_A));
143 return;
144 }
145 };
146
147 let num_mips = bloom_tex.mip_level_count();
149 if num_mips < 2 {
150 return;
151 }
152
153 let kawase_uniform = &ctx.renderer.kawase_uniform;
155
156 let effective_mips = (num_mips as usize).min(5);
158 let mip_views: Vec<wgpu::TextureView> = (0..effective_mips)
159 .map(|mip| {
160 bloom_tex.create_view(&wgpu::TextureViewDescriptor {
161 label: Some(&format!("bloom_mip_{}", mip)),
162 base_mip_level: mip as u32,
163 mip_level_count: Some(1),
164 ..Default::default()
165 })
166 })
167 .collect();
168
169 let bloom_width = self.width;
170 let bloom_height = self.height;
171
172 let mut mip_scales = Vec::with_capacity(effective_mips);
174 for i in 0..effective_mips {
175 let div = (1u32 << i) as f32;
176 mip_scales.push((
177 bloom_width as f32 / div,
178 bloom_height as f32 / div,
179 (i + 1) as f32,
180 ));
181 }
182
183 for mip in 1..effective_mips {
185 let kernel_width = mip_scales[mip].2;
186 let uniform_data: [f32; 8] = [
187 mip_scales[(mip - 1)].0,
188 mip_scales[(mip - 1)].1,
189 (mip - 1) as f32,
190 kernel_width,
191 0.0,
192 0.0,
193 0.0,
194 0.0,
195 ];
196 ctx.queue
197 .write_buffer(kawase_uniform, 0, bytemuck::cast_slice(&uniform_data));
198
199 let w = mip_scales[mip].0.max(1.0) as u32;
200 let h = mip_scales[mip].1.max(1.0) as u32;
201
202 let bg = ctx.get_or_create_bind_group(
204 (RES_BLOOM_A, mip as u32, false),
205 &ctx.renderer.kawase_bind_group_layout,
206 &[
207 wgpu::BindGroupEntry {
208 binding: 0,
209 resource: wgpu::BindingResource::Buffer(wgpu::BufferBinding {
210 buffer: kawase_uniform,
211 offset: 0,
212 size: wgpu::BufferSize::new(32),
213 }),
214 },
215 wgpu::BindGroupEntry {
216 binding: 1,
217 resource: wgpu::BindingResource::TextureView(&mip_views[(mip - 1)]),
218 },
219 wgpu::BindGroupEntry {
220 binding: 2,
221 resource: wgpu::BindingResource::Sampler(&ctx.renderer.sampler),
222 },
223 ],
224 Some(&format!("kawase_bloom_bg_{}", mip)),
225 );
226
227 let mut p = ctx.encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
228 label: Some(&format!("Kawase Bloom Down {}", mip)),
229 color_attachments: &[Some(wgpu::RenderPassColorAttachment {
230 view: &mip_views[mip],
231 resolve_target: None,
232 ops: wgpu::Operations {
233 load: wgpu::LoadOp::Clear(wgpu::Color {
234 r: 0.0,
235 g: 0.0,
236 b: 0.0,
237 a: 0.0,
238 }),
239 store: wgpu::StoreOp::Store,
240 },
241 depth_slice: None,
242 })],
243 ..Default::default()
244 });
245 p.set_viewport(0.0, 0.0, w as f32, h as f32, 0.0, 1.0);
246 p.set_pipeline(&ctx.renderer.kawase_down_pipeline);
247 p.set_bind_group(0, &bg, &[]);
248 p.draw(0..3, 0..1);
249 }
250
251 for mip in (1..effective_mips).rev() {
253 let kernel_width = mip_scales[mip].2;
254 let uniform_data: [f32; 8] = [
255 mip_scales[mip].0,
256 mip_scales[mip].1,
257 mip as f32,
258 kernel_width,
259 0.0,
260 0.0,
261 0.0,
262 0.0,
263 ];
264 ctx.queue
265 .write_buffer(kawase_uniform, 0, bytemuck::cast_slice(&uniform_data));
266
267 let w = mip_scales[(mip - 1)].0.max(1.0) as u32;
268 let h = mip_scales[(mip - 1)].1.max(1.0) as u32;
269
270 let bg = ctx.get_or_create_bind_group(
272 (RES_BLOOM_A, (mip + 100) as u32, false), &ctx.renderer.kawase_bind_group_layout,
274 &[
275 wgpu::BindGroupEntry {
276 binding: 0,
277 resource: wgpu::BindingResource::Buffer(wgpu::BufferBinding {
278 buffer: kawase_uniform,
279 offset: 0,
280 size: wgpu::BufferSize::new(32),
281 }),
282 },
283 wgpu::BindGroupEntry {
284 binding: 1,
285 resource: wgpu::BindingResource::TextureView(&mip_views[mip]),
286 },
287 wgpu::BindGroupEntry {
288 binding: 2,
289 resource: wgpu::BindingResource::Sampler(&ctx.renderer.sampler),
290 },
291 ],
292 Some(&format!("kawase_bloom_up_{}", mip)),
293 );
294
295 let mut p = ctx.encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
297 label: Some(&format!("Kawase Bloom Up {}", mip)),
298 color_attachments: &[Some(wgpu::RenderPassColorAttachment {
299 view: &mip_views[(mip - 1)],
300 resolve_target: None,
301 ops: wgpu::Operations {
302 load: wgpu::LoadOp::Clear(wgpu::Color::TRANSPARENT),
303 store: wgpu::StoreOp::Store,
304 },
305 depth_slice: None,
306 })],
307 ..Default::default()
308 });
309 p.set_viewport(0.0, 0.0, w as f32, h as f32, 0.0, 1.0);
310 p.set_pipeline(&ctx.renderer.kawase_up_pipeline);
311 p.set_bind_group(0, &bg, &[]);
312 p.draw(0..3, 0..1);
313 }
314
315 log::trace!(
316 "[Kvasir] bloom_blur: Kawase pyramid ({}x{})",
317 bloom_width,
318 bloom_height
319 );
320 }
321}