1use bytemuck::{Pod, Zeroable};
4use wgpu::util::DeviceExt;
5
6use crate::isolated_plan::{IsolatedEffect, IsolatedEffectChain};
7use crate::vertices::TexVertex;
8
9#[repr(C)]
11#[derive(Clone, Copy, Pod, Zeroable)]
12struct BlurParamsGpu {
13 direction: [f32; 2],
15 tex_size: [f32; 2],
17 radius: f32,
19 _pad: f32,
21}
22
23pub struct EffectPipelines {
32 pub blur: wgpu::RenderPipeline,
34 pub blur_params_layout: wgpu::BindGroupLayout,
36 pub blur_tex_layout: wgpu::BindGroupLayout,
38 pub sampler: wgpu::Sampler,
40 pub fullscreen_buf: wgpu::Buffer,
42}
43
44impl EffectPipelines {
45 pub fn new(device: &wgpu::Device, surface_format: wgpu::TextureFormat) -> Self {
56 let blur_params_layout =
57 device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
58 label: Some("blur params layout"),
59 entries: &[wgpu::BindGroupLayoutEntry {
60 binding: 0,
61 visibility: wgpu::ShaderStages::FRAGMENT,
62 ty: wgpu::BindingType::Buffer {
63 ty: wgpu::BufferBindingType::Uniform,
64 has_dynamic_offset: false,
65 min_binding_size: None,
66 },
67 count: None,
68 }],
69 });
70 let blur_tex_layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
71 label: Some("blur tex layout"),
72 entries: &[
73 wgpu::BindGroupLayoutEntry {
74 binding: 0,
75 visibility: wgpu::ShaderStages::FRAGMENT,
76 ty: wgpu::BindingType::Texture {
77 multisampled: false,
78 view_dimension: wgpu::TextureViewDimension::D2,
79 sample_type: wgpu::TextureSampleType::Float { filterable: true },
80 },
81 count: None,
82 },
83 wgpu::BindGroupLayoutEntry {
84 binding: 1,
85 visibility: wgpu::ShaderStages::FRAGMENT,
86 ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering),
87 count: None,
88 },
89 ],
90 });
91 let blur_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
92 label: Some("blur layout"),
93 bind_group_layouts: &[&blur_params_layout, &blur_tex_layout],
94 push_constant_ranges: &[],
95 });
96 let shader = device.create_shader_module(wgpu::ShaderModuleDescriptor {
97 label: Some("blur shader"),
98 source: wgpu::ShaderSource::Wgsl(include_str!("shaders/blur.wgsl").into()),
99 });
100 let blur = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
101 label: Some("blur pipeline"),
102 layout: Some(&blur_layout),
103 vertex: wgpu::VertexState {
104 module: &shader,
105 entry_point: "vs_main",
106 compilation_options: Default::default(),
107 buffers: &[TexVertex::desc()],
108 },
109 fragment: Some(wgpu::FragmentState {
110 module: &shader,
111 entry_point: "fs_main",
112 compilation_options: Default::default(),
113 targets: &[Some(wgpu::ColorTargetState {
114 format: surface_format,
115 blend: None,
116 write_mask: wgpu::ColorWrites::ALL,
117 })],
118 }),
119 primitive: wgpu::PrimitiveState::default(),
120 depth_stencil: None,
121 multisample: wgpu::MultisampleState::default(),
122 multiview: None,
123 });
124 let sampler = device.create_sampler(&wgpu::SamplerDescriptor {
125 label: Some("blur sampler"),
126 mag_filter: wgpu::FilterMode::Linear,
127 min_filter: wgpu::FilterMode::Linear,
128 ..Default::default()
129 });
130 let fullscreen = [
131 TexVertex {
132 pos: [-1.0, -1.0],
133 uv: [0.0, 1.0],
134 tint: [1.0; 4],
135 },
136 TexVertex {
137 pos: [1.0, -1.0],
138 uv: [1.0, 1.0],
139 tint: [1.0; 4],
140 },
141 TexVertex {
142 pos: [1.0, 1.0],
143 uv: [1.0, 0.0],
144 tint: [1.0; 4],
145 },
146 TexVertex {
147 pos: [-1.0, -1.0],
148 uv: [0.0, 1.0],
149 tint: [1.0; 4],
150 },
151 TexVertex {
152 pos: [1.0, 1.0],
153 uv: [1.0, 0.0],
154 tint: [1.0; 4],
155 },
156 TexVertex {
157 pos: [-1.0, 1.0],
158 uv: [0.0, 0.0],
159 tint: [1.0; 4],
160 },
161 ];
162 let fullscreen_buf = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
163 label: Some("fullscreen tri"),
164 contents: bytemuck::cast_slice(&fullscreen),
165 usage: wgpu::BufferUsages::VERTEX,
166 });
167 Self {
168 blur,
169 blur_params_layout,
170 blur_tex_layout,
171 sampler,
172 fullscreen_buf,
173 }
174 }
175}
176
177pub struct IsolatedCompositeTable {
186 pub bind_groups: std::collections::HashMap<u16, wgpu::BindGroup>,
188}
189
190impl IsolatedCompositeTable {
191 pub fn empty() -> Self {
200 Self {
201 bind_groups: std::collections::HashMap::new(),
202 }
203 }
204
205 pub fn get(&self, id: u16) -> Option<&wgpu::BindGroup> {
214 self.bind_groups.get(&id)
215 }
216}
217
218#[allow(clippy::too_many_arguments)]
237pub fn run_effect_chain(
238 device: &wgpu::Device,
239 encoder: &mut wgpu::CommandEncoder,
240 pipelines: &EffectPipelines,
241 format: wgpu::TextureFormat,
242 src_view: &wgpu::TextureView,
243 width: u32,
244 height: u32,
245 effects: &IsolatedEffectChain,
246 pass_id: u16,
247) {
248 let blur_radius = effects
249 .effects
250 .iter()
251 .filter_map(|e| match e {
252 IsolatedEffect::Blur { radius_px } if *radius_px > 0.0 => Some(*radius_px),
253 _ => None,
254 })
255 .fold(0.0f32, f32::max);
256
257 if blur_radius <= 0.0 {
258 return;
259 }
260
261 let ping_tex = device.create_texture(&wgpu::TextureDescriptor {
262 label: Some("blur ping"),
263 size: wgpu::Extent3d {
264 width: width.max(1),
265 height: height.max(1),
266 depth_or_array_layers: 1,
267 },
268 mip_level_count: 1,
269 sample_count: 1,
270 dimension: wgpu::TextureDimension::D2,
271 format,
272 usage: wgpu::TextureUsages::RENDER_ATTACHMENT | wgpu::TextureUsages::TEXTURE_BINDING,
273 view_formats: &[],
274 });
275 let ping_view = ping_tex.create_view(&wgpu::TextureViewDescriptor::default());
276
277 blur_pass(
278 device,
279 encoder,
280 pipelines,
281 src_view,
282 &ping_view,
283 width,
284 height,
285 [1.0, 0.0],
286 blur_radius,
287 pass_id,
288 );
289 blur_pass(
290 device,
291 encoder,
292 pipelines,
293 &ping_view,
294 src_view,
295 width,
296 height,
297 [0.0, 1.0],
298 blur_radius,
299 pass_id,
300 );
301}
302
303#[allow(clippy::too_many_arguments)]
304fn blur_pass(
306 device: &wgpu::Device,
307 encoder: &mut wgpu::CommandEncoder,
308 pipelines: &EffectPipelines,
309 src: &wgpu::TextureView,
310 dst: &wgpu::TextureView,
311 width: u32,
312 height: u32,
313 direction: [f32; 2],
314 radius: f32,
315 pass_id: u16,
316) {
317 let params = BlurParamsGpu {
318 direction,
319 tex_size: [width as f32, height as f32],
320 radius,
321 _pad: 0.0,
322 };
323 let params_buf = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
324 label: Some("blur params"),
325 contents: bytemuck::bytes_of(¶ms),
326 usage: wgpu::BufferUsages::UNIFORM,
327 });
328 let params_bg = device.create_bind_group(&wgpu::BindGroupDescriptor {
329 label: Some("blur params bg"),
330 layout: &pipelines.blur_params_layout,
331 entries: &[wgpu::BindGroupEntry {
332 binding: 0,
333 resource: params_buf.as_entire_binding(),
334 }],
335 });
336 let tex_bg = device.create_bind_group(&wgpu::BindGroupDescriptor {
337 label: Some("blur tex bg"),
338 layout: &pipelines.blur_tex_layout,
339 entries: &[
340 wgpu::BindGroupEntry {
341 binding: 0,
342 resource: wgpu::BindingResource::TextureView(src),
343 },
344 wgpu::BindGroupEntry {
345 binding: 1,
346 resource: wgpu::BindingResource::Sampler(&pipelines.sampler),
347 },
348 ],
349 });
350
351 let axis = if direction[0] > 0.5 { "H" } else { "V" };
352 let pass_label = format!("blur {axis} {pass_id}");
353 let mut pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
354 label: Some(&pass_label),
355 color_attachments: &[Some(wgpu::RenderPassColorAttachment {
356 view: dst,
357 resolve_target: None,
358 ops: wgpu::Operations {
359 load: wgpu::LoadOp::Clear(wgpu::Color::TRANSPARENT),
360 store: wgpu::StoreOp::Store,
361 },
362 })],
363 depth_stencil_attachment: None,
364 timestamp_writes: None,
365 occlusion_query_set: None,
366 });
367 pass.set_pipeline(&pipelines.blur);
368 pass.set_bind_group(0, ¶ms_bg, &[]);
369 pass.set_bind_group(1, &tex_bg, &[]);
370 pass.set_vertex_buffer(0, pipelines.fullscreen_buf.slice(..));
371 pass.draw(0..6, 0..1);
372}