1use crate::kvasir::node::{ExecutionContext, KvasirNode};
7use crate::kvasir::nodes::PassId;
8use crate::kvasir::resource::ResourceId;
9use crate::renderer::GpuRenderer;
10
11pub struct BackdropRegionNode {
14 pub inputs: Vec<ResourceId>,
15 pub outputs: Vec<ResourceId>,
16 pub region: cvkg_core::Rect,
18 pub output_id: ResourceId,
20}
21
22impl BackdropRegionNode {
23 pub fn new(region: cvkg_core::Rect, output_id: ResourceId) -> Self {
24 Self {
25 inputs: vec![crate::kvasir::nodes::RES_SCENE],
26 outputs: vec![output_id],
27 region,
28 output_id,
29 }
30 }
31}
32
33impl KvasirNode for BackdropRegionNode {
34 fn label(&self) -> &'static str {
35 "Backdrop Region"
36 }
37 fn inputs(&self) -> &[ResourceId] {
38 &self.inputs
39 }
40 fn outputs(&self) -> &[ResourceId] {
41 &self.outputs
42 }
43 fn pass_id(&self) -> PassId {
44 PassId::BackdropRegion
45 }
46 fn execute(&self, ctx: &mut ExecutionContext) {
47 let scene_tex = match ctx.registry.get_texture(crate::kvasir::nodes::RES_SCENE) {
48 Some(v) => v,
49 None => {
50 log::error!("[BackdropRegion] Missing scene texture");
51 return;
52 }
53 };
54 let blur_tex = match ctx.registry.get_texture(self.output_id) {
55 Some(v) => v,
56 None => {
57 log::error!("[BackdropRegion] Missing blur target texture");
58 return;
59 }
60 };
61
62 let scale = ctx.scale_factor;
63 let rx = (self.region.x * scale) as u32;
64 let ry = (self.region.y * scale) as u32;
65 let rw = (self.region.width * scale) as u32;
66 let rh = (self.region.height * scale) as u32;
67
68 let _src_extent = wgpu::Extent3d {
71 width: scene_tex.width(),
72 height: scene_tex.height(),
73 depth_or_array_layers: 1,
74 };
75 let dst_extent = wgpu::Extent3d {
76 width: rw,
77 height: rh,
78 depth_or_array_layers: 1,
79 };
80 ctx.encoder.copy_texture_to_texture(
81 wgpu::TexelCopyTextureInfo {
82 texture: &scene_tex,
83 mip_level: 0,
84 origin: wgpu::Origin3d { x: rx, y: ry, z: 0 },
85 aspect: wgpu::TextureAspect::All,
86 },
87 wgpu::TexelCopyTextureInfo {
88 texture: &blur_tex,
89 mip_level: 0,
90 origin: wgpu::Origin3d::ZERO,
91 aspect: wgpu::TextureAspect::All,
92 },
93 dst_extent,
94 );
95
96 let mip_count = blur_tex.mip_level_count().min(4);
100 if mip_count >= 2 {
101 let kawase_uniform = &ctx.renderer.kawase_uniform;
103
104 for mip in 1..mip_count {
105 let src_view = {
106 let mut cache = GpuRenderer::lock_or_clear_cache(&ctx.renderer.texture_view_cache);
107 cache
108 .entry((self.output_id, (mip - 1)))
109 .or_insert_with(|| {
110 blur_tex.create_view(&wgpu::TextureViewDescriptor {
111 label: Some(&format!("blur_region_src_mip_{}", mip - 1)),
112 base_mip_level: mip - 1,
113 mip_level_count: Some(1),
114 ..Default::default()
115 })
116 })
117 .clone()
118 };
119 let dst_view = {
120 let mut cache = GpuRenderer::lock_or_clear_cache(&ctx.renderer.texture_view_cache);
121 cache
122 .entry((self.output_id, mip))
123 .or_insert_with(|| {
124 blur_tex.create_view(&wgpu::TextureViewDescriptor {
125 label: Some(&format!("blur_region_dst_mip_{}", mip)),
126 base_mip_level: mip,
127 mip_level_count: Some(1),
128 ..Default::default()
129 })
130 })
131 .clone()
132 };
133
134 let w = (rw >> mip).max(1);
135 let h = (rh >> mip).max(1);
136 let kernel = mip as f32;
137
138 let uniform_data: [f32; 8] = [
139 w as f32,
140 h as f32,
141 (mip - 1) as f32,
142 kernel,
143 0.0,
144 0.0,
145 0.0,
146 0.0,
147 ];
148 ctx.queue
149 .write_buffer(kawase_uniform, 0, bytemuck::cast_slice(&uniform_data));
150
151 let src_bg = ctx.get_or_create_bind_group(
152 (self.output_id, mip, false),
153 &ctx.renderer.kawase_bind_group_layout,
154 &[
155 wgpu::BindGroupEntry {
156 binding: 0,
157 resource: wgpu::BindingResource::Buffer(wgpu::BufferBinding {
158 buffer: kawase_uniform,
159 offset: 0,
160 size: wgpu::BufferSize::new(32),
161 }),
162 },
163 wgpu::BindGroupEntry {
164 binding: 1,
165 resource: wgpu::BindingResource::TextureView(&src_view),
166 },
167 wgpu::BindGroupEntry {
168 binding: 2,
169 resource: wgpu::BindingResource::Sampler(&ctx.renderer.sampler),
170 },
171 ],
172 Some(&format!("blur_region_kawase_bg_{}", mip)),
173 );
174
175 let mut pass = ctx.encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
176 label: Some(&format!("Backdrop Region Blur {}", mip)),
177 color_attachments: &[Some(wgpu::RenderPassColorAttachment {
178 view: &dst_view,
179 resolve_target: None,
180 ops: wgpu::Operations {
181 load: wgpu::LoadOp::Clear(wgpu::Color::TRANSPARENT),
182 store: wgpu::StoreOp::Store,
183 },
184 depth_slice: None,
185 })],
186 ..Default::default()
187 });
188 pass.set_viewport(0.0, 0.0, w as f32, h as f32, 0.0, 1.0);
189 pass.set_pipeline(&ctx.renderer.kawase_down_pipeline);
190 pass.set_bind_group(0, &src_bg, &[]);
191 pass.draw(0..3, 0..1);
192 }
193
194 for mip in (1..mip_count).rev() {
196 let src_view = {
197 let mut cache = GpuRenderer::lock_or_clear_cache(&ctx.renderer.texture_view_cache);
198 cache
199 .entry((self.output_id, mip))
200 .or_insert_with(|| {
201 blur_tex.create_view(&wgpu::TextureViewDescriptor {
202 label: Some(&format!("blur_region_src_mip_{}", mip)),
203 base_mip_level: mip,
204 mip_level_count: Some(1),
205 ..Default::default()
206 })
207 })
208 .clone()
209 };
210 let dst_view = {
211 let mut cache = GpuRenderer::lock_or_clear_cache(&ctx.renderer.texture_view_cache);
212 cache
213 .entry((self.output_id, (mip - 1)))
214 .or_insert_with(|| {
215 blur_tex.create_view(&wgpu::TextureViewDescriptor {
216 label: Some(&format!("blur_region_dst_mip_{}", mip - 1)),
217 base_mip_level: mip - 1,
218 mip_level_count: Some(1),
219 ..Default::default()
220 })
221 })
222 .clone()
223 };
224
225 let w = (rw >> (mip - 1)).max(1);
226 let h = (rh >> (mip - 1)).max(1);
227 let kernel = mip as f32;
228
229 let uniform_data: [f32; 8] =
230 [w as f32, h as f32, mip as f32, kernel, 0.0, 0.0, 0.0, 0.0];
231 ctx.queue
232 .write_buffer(kawase_uniform, 0, bytemuck::cast_slice(&uniform_data));
233
234 let src_bg = ctx.get_or_create_bind_group(
235 (self.output_id, mip, true),
236 &ctx.renderer.kawase_bind_group_layout,
237 &[
238 wgpu::BindGroupEntry {
239 binding: 0,
240 resource: wgpu::BindingResource::Buffer(wgpu::BufferBinding {
241 buffer: kawase_uniform,
242 offset: 0,
243 size: wgpu::BufferSize::new(32),
244 }),
245 },
246 wgpu::BindGroupEntry {
247 binding: 1,
248 resource: wgpu::BindingResource::TextureView(&src_view),
249 },
250 wgpu::BindGroupEntry {
251 binding: 2,
252 resource: wgpu::BindingResource::Sampler(&ctx.renderer.sampler),
253 },
254 ],
255 Some(&format!("blur_region_kawase_up_bg_{}", mip)),
256 );
257
258 let mut pass = ctx.encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
260 label: Some(&format!("Backdrop Region Blur Up {}", mip)),
261 color_attachments: &[Some(wgpu::RenderPassColorAttachment {
262 view: &dst_view,
263 resolve_target: None,
264 ops: wgpu::Operations {
265 load: wgpu::LoadOp::Clear(wgpu::Color::TRANSPARENT),
266 store: wgpu::StoreOp::Store,
267 },
268 depth_slice: None,
269 })],
270 ..Default::default()
271 });
272 pass.set_viewport(0.0, 0.0, w as f32, h as f32, 0.0, 1.0);
273 pass.set_pipeline(&ctx.renderer.kawase_up_pipeline);
274 pass.set_bind_group(0, &src_bg, &[]);
275 pass.draw(0..3, 0..1);
276 }
277 }
278 }
279}