1use bytemuck::{Pod, Zeroable};
4use wgpu::util::DeviceExt;
5
6use crate::application::Application;
7use crate::filter::{Filter, FilterContext};
8use crate::texture::render_texture::RenderTexture;
9
10#[derive(Debug, Clone, Copy)]
16pub struct BlurFilter {
17 pub radius: f32,
19}
20
21impl BlurFilter {
22 #[must_use]
24 pub const fn new(radius: f32) -> Self {
25 Self { radius }
26 }
27}
28
29impl Default for BlurFilter {
30 fn default() -> Self {
31 Self { radius: 4.0 }
32 }
33}
34
35#[repr(C)]
36#[derive(Clone, Copy, Pod, Zeroable)]
37struct BlurUniforms {
38 direction: [f32; 2],
39 radius: f32,
40 _pad: f32,
41}
42
43impl Filter for BlurFilter {
44 fn passes(&self) -> u32 {
45 2
46 }
47
48 fn render_pass(
49 &self,
50 ctx: &mut FilterContext<'_>,
51 input: &RenderTexture,
52 output: &RenderTexture,
53 pass: u32,
54 ) {
55 let direction = if pass == 0 {
56 [1.0_f32, 0.0]
57 } else {
58 [0.0, 1.0]
59 };
60 run_blur_pass(ctx.app, ctx.encoder, input, output, direction, self.radius);
61 }
62}
63
64pub(crate) fn run_blur_pass(
65 app: &Application,
66 encoder: &mut wgpu::CommandEncoder,
67 input: &RenderTexture,
68 output: &RenderTexture,
69 direction: [f32; 2],
70 radius: f32,
71) {
72 let device = app.device();
73 let pipeline_cache = filter_pipeline(app, output.format());
74
75 let uniforms = BlurUniforms {
76 direction,
77 radius,
78 _pad: 0.0,
79 };
80 let uniform_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
81 label: Some("wisp::blur uniforms"),
82 contents: bytemuck::bytes_of(&uniforms),
83 usage: wgpu::BufferUsages::UNIFORM,
84 });
85
86 let uniform_bg = device.create_bind_group(&wgpu::BindGroupDescriptor {
87 label: Some("wisp::blur uniform bg"),
88 layout: &pipeline_cache.uniform_layout,
89 entries: &[wgpu::BindGroupEntry {
90 binding: 0,
91 resource: uniform_buffer.as_entire_binding(),
92 }],
93 });
94
95 let texture_bg = device.create_bind_group(&wgpu::BindGroupDescriptor {
96 label: Some("wisp::blur texture bg"),
97 layout: &pipeline_cache.texture_layout,
98 entries: &[
99 wgpu::BindGroupEntry {
100 binding: 0,
101 resource: wgpu::BindingResource::TextureView(input.view()),
102 },
103 wgpu::BindGroupEntry {
104 binding: 1,
105 resource: wgpu::BindingResource::Sampler(input.sampler()),
106 },
107 ],
108 });
109
110 let mut pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
111 label: Some("wisp::blur pass"),
112 color_attachments: &[Some(wgpu::RenderPassColorAttachment {
113 view: output.view(),
114 resolve_target: None,
115 ops: wgpu::Operations {
116 load: wgpu::LoadOp::Load,
117 store: wgpu::StoreOp::Store,
118 },
119 })],
120 depth_stencil_attachment: None,
121 timestamp_writes: None,
122 occlusion_query_set: None,
123 });
124 pass.set_pipeline(&pipeline_cache.pipeline);
125 pass.set_bind_group(0, &uniform_bg, &[]);
126 pass.set_bind_group(1, &texture_bg, &[]);
127 pass.draw(0..3, 0..1);
128}
129
130struct BlurPipelineCache {
133 pipeline: wgpu::RenderPipeline,
134 uniform_layout: wgpu::BindGroupLayout,
135 texture_layout: wgpu::BindGroupLayout,
136}
137
138fn filter_pipeline(app: &Application, format: wgpu::TextureFormat) -> BlurPipelineCache {
139 let device = app.device();
140 let shader = device.create_shader_module(wgpu::ShaderModuleDescriptor {
141 label: Some("wisp::filter_blur"),
142 source: wgpu::ShaderSource::Wgsl(include_str!("../../shaders/filter_blur.wgsl").into()),
143 });
144
145 let uniform_layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
146 label: Some("wisp::blur uniforms layout"),
147 entries: &[wgpu::BindGroupLayoutEntry {
148 binding: 0,
149 visibility: wgpu::ShaderStages::FRAGMENT,
150 ty: wgpu::BindingType::Buffer {
151 ty: wgpu::BufferBindingType::Uniform,
152 has_dynamic_offset: false,
153 min_binding_size: None,
154 },
155 count: None,
156 }],
157 });
158 let texture_layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
159 label: Some("wisp::blur texture layout"),
160 entries: &[
161 wgpu::BindGroupLayoutEntry {
162 binding: 0,
163 visibility: wgpu::ShaderStages::FRAGMENT,
164 ty: wgpu::BindingType::Texture {
165 sample_type: wgpu::TextureSampleType::Float { filterable: true },
166 view_dimension: wgpu::TextureViewDimension::D2,
167 multisampled: false,
168 },
169 count: None,
170 },
171 wgpu::BindGroupLayoutEntry {
172 binding: 1,
173 visibility: wgpu::ShaderStages::FRAGMENT,
174 ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering),
175 count: None,
176 },
177 ],
178 });
179
180 let pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
181 label: Some("wisp::blur pipeline layout"),
182 bind_group_layouts: &[&uniform_layout, &texture_layout],
183 push_constant_ranges: &[],
184 });
185
186 let pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
187 label: Some("wisp::blur pipeline"),
188 layout: Some(&pipeline_layout),
189 vertex: wgpu::VertexState {
190 module: &shader,
191 entry_point: Some("main_vs"),
192 buffers: &[],
193 compilation_options: wgpu::PipelineCompilationOptions::default(),
194 },
195 fragment: Some(wgpu::FragmentState {
196 module: &shader,
197 entry_point: Some("main_fs"),
198 targets: &[Some(wgpu::ColorTargetState {
199 format,
200 blend: None,
201 write_mask: wgpu::ColorWrites::ALL,
202 })],
203 compilation_options: wgpu::PipelineCompilationOptions::default(),
204 }),
205 primitive: wgpu::PrimitiveState::default(),
206 depth_stencil: None,
207 multisample: wgpu::MultisampleState::default(),
208 multiview: None,
209 cache: None,
210 });
211
212 BlurPipelineCache {
213 pipeline,
214 uniform_layout,
215 texture_layout,
216 }
217}