1use bytemuck::{Pod, Zeroable};
14use wgpu::util::DeviceExt;
15
16use crate::application::Application;
17use crate::color::Color;
18use crate::filter::{Filter, FilterContext};
19use crate::texture::render_texture::RenderTexture;
20
21#[derive(Debug, Clone, Copy)]
23pub struct DropShadowFilter {
24 pub offset: glam::Vec2,
26 pub blur: f32,
28 pub color: Color,
30}
31
32impl Default for DropShadowFilter {
33 fn default() -> Self {
34 Self {
35 offset: glam::Vec2::new(4.0, 4.0),
36 blur: 6.0,
37 color: Color::rgba(0.0, 0.0, 0.0, 0.5),
38 }
39 }
40}
41
42#[repr(C)]
43#[derive(Clone, Copy, Pod, Zeroable)]
44struct ExtractUniforms {
45 offset: [f32; 2],
46 _pad: [f32; 2],
47 color: [f32; 4],
48}
49
50impl Filter for DropShadowFilter {
51 fn passes(&self) -> u32 {
52 1
53 }
54
55 fn render_pass(
56 &self,
57 ctx: &mut FilterContext<'_>,
58 input: &RenderTexture,
59 output: &RenderTexture,
60 _pass: u32,
61 ) {
62 let app = ctx.app;
63 let format = output.format();
64 let scratch_a = RenderTexture::with_format(app, input.width(), input.height(), format);
65 let scratch_b = RenderTexture::with_format(app, input.width(), input.height(), format);
66
67 run_extract(app, ctx.encoder, input, &scratch_a, self.offset, self.color);
68 super::blur::run_blur_pass(
69 app,
70 ctx.encoder,
71 &scratch_a,
72 &scratch_b,
73 [1.0, 0.0],
74 self.blur,
75 );
76 super::blur::run_blur_pass(
77 app,
78 ctx.encoder,
79 &scratch_b,
80 &scratch_a,
81 [0.0, 1.0],
82 self.blur,
83 );
84 run_composite(app, ctx.encoder, &scratch_a, input, output);
85 }
86}
87
88fn run_extract(
89 app: &Application,
90 encoder: &mut wgpu::CommandEncoder,
91 input: &RenderTexture,
92 output: &RenderTexture,
93 offset: glam::Vec2,
94 color: Color,
95) {
96 let device = app.device();
97 let cache = extract_pipeline(app, output.format());
98
99 let uniforms = ExtractUniforms {
100 offset: [offset.x, offset.y],
101 _pad: [0.0, 0.0],
102 color: [color.r, color.g, color.b, color.a],
103 };
104 let uniform_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
105 label: Some("wisp::ds_extract uniforms"),
106 contents: bytemuck::bytes_of(&uniforms),
107 usage: wgpu::BufferUsages::UNIFORM,
108 });
109
110 let uniform_bg = device.create_bind_group(&wgpu::BindGroupDescriptor {
111 label: Some("wisp::ds_extract uniform bg"),
112 layout: &cache.uniform_layout,
113 entries: &[wgpu::BindGroupEntry {
114 binding: 0,
115 resource: uniform_buffer.as_entire_binding(),
116 }],
117 });
118 let texture_bg = device.create_bind_group(&wgpu::BindGroupDescriptor {
119 label: Some("wisp::ds_extract texture bg"),
120 layout: &cache.texture_layout,
121 entries: &[
122 wgpu::BindGroupEntry {
123 binding: 0,
124 resource: wgpu::BindingResource::TextureView(input.view()),
125 },
126 wgpu::BindGroupEntry {
127 binding: 1,
128 resource: wgpu::BindingResource::Sampler(input.sampler()),
129 },
130 ],
131 });
132
133 let mut pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
134 label: Some("wisp::ds_extract pass"),
135 color_attachments: &[Some(wgpu::RenderPassColorAttachment {
136 view: output.view(),
137 resolve_target: None,
138 ops: wgpu::Operations {
139 load: wgpu::LoadOp::Clear(wgpu::Color::TRANSPARENT),
140 store: wgpu::StoreOp::Store,
141 },
142 })],
143 depth_stencil_attachment: None,
144 timestamp_writes: None,
145 occlusion_query_set: None,
146 });
147 pass.set_pipeline(&cache.pipeline);
148 pass.set_bind_group(0, &uniform_bg, &[]);
149 pass.set_bind_group(1, &texture_bg, &[]);
150 pass.draw(0..3, 0..1);
151}
152
153fn run_composite(
154 app: &Application,
155 encoder: &mut wgpu::CommandEncoder,
156 shadow: &RenderTexture,
157 source: &RenderTexture,
158 output: &RenderTexture,
159) {
160 let device = app.device();
161 let cache = composite_pipeline(app, output.format());
162
163 let bg = device.create_bind_group(&wgpu::BindGroupDescriptor {
164 label: Some("wisp::ds_composite bg"),
165 layout: &cache.bind_layout,
166 entries: &[
167 wgpu::BindGroupEntry {
168 binding: 0,
169 resource: wgpu::BindingResource::TextureView(shadow.view()),
170 },
171 wgpu::BindGroupEntry {
172 binding: 1,
173 resource: wgpu::BindingResource::TextureView(source.view()),
174 },
175 wgpu::BindGroupEntry {
176 binding: 2,
177 resource: wgpu::BindingResource::Sampler(source.sampler()),
178 },
179 ],
180 });
181
182 let mut pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
183 label: Some("wisp::ds_composite pass"),
184 color_attachments: &[Some(wgpu::RenderPassColorAttachment {
185 view: output.view(),
186 resolve_target: None,
187 ops: wgpu::Operations {
188 load: wgpu::LoadOp::Clear(wgpu::Color::TRANSPARENT),
189 store: wgpu::StoreOp::Store,
190 },
191 })],
192 depth_stencil_attachment: None,
193 timestamp_writes: None,
194 occlusion_query_set: None,
195 });
196 pass.set_pipeline(&cache.pipeline);
197 pass.set_bind_group(0, &bg, &[]);
198 pass.draw(0..3, 0..1);
199}
200
201struct ExtractCache {
202 pipeline: wgpu::RenderPipeline,
203 uniform_layout: wgpu::BindGroupLayout,
204 texture_layout: wgpu::BindGroupLayout,
205}
206
207fn extract_pipeline(app: &Application, format: wgpu::TextureFormat) -> ExtractCache {
208 let device = app.device();
209 let shader = device.create_shader_module(wgpu::ShaderModuleDescriptor {
210 label: Some("wisp::ds_extract"),
211 source: wgpu::ShaderSource::Wgsl(
212 include_str!("../../shaders/filter_drop_shadow_extract.wgsl").into(),
213 ),
214 });
215
216 let uniform_layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
217 label: Some("wisp::ds_extract uniform layout"),
218 entries: &[wgpu::BindGroupLayoutEntry {
219 binding: 0,
220 visibility: wgpu::ShaderStages::FRAGMENT,
221 ty: wgpu::BindingType::Buffer {
222 ty: wgpu::BufferBindingType::Uniform,
223 has_dynamic_offset: false,
224 min_binding_size: None,
225 },
226 count: None,
227 }],
228 });
229 let texture_layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
230 label: Some("wisp::ds_extract texture layout"),
231 entries: &[
232 wgpu::BindGroupLayoutEntry {
233 binding: 0,
234 visibility: wgpu::ShaderStages::FRAGMENT,
235 ty: wgpu::BindingType::Texture {
236 sample_type: wgpu::TextureSampleType::Float { filterable: true },
237 view_dimension: wgpu::TextureViewDimension::D2,
238 multisampled: false,
239 },
240 count: None,
241 },
242 wgpu::BindGroupLayoutEntry {
243 binding: 1,
244 visibility: wgpu::ShaderStages::FRAGMENT,
245 ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering),
246 count: None,
247 },
248 ],
249 });
250
251 let layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
252 label: Some("wisp::ds_extract pipeline layout"),
253 bind_group_layouts: &[&uniform_layout, &texture_layout],
254 push_constant_ranges: &[],
255 });
256 let pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
257 label: Some("wisp::ds_extract pipeline"),
258 layout: Some(&layout),
259 vertex: wgpu::VertexState {
260 module: &shader,
261 entry_point: Some("main_vs"),
262 buffers: &[],
263 compilation_options: wgpu::PipelineCompilationOptions::default(),
264 },
265 fragment: Some(wgpu::FragmentState {
266 module: &shader,
267 entry_point: Some("main_fs"),
268 targets: &[Some(wgpu::ColorTargetState {
269 format,
270 blend: None,
271 write_mask: wgpu::ColorWrites::ALL,
272 })],
273 compilation_options: wgpu::PipelineCompilationOptions::default(),
274 }),
275 primitive: wgpu::PrimitiveState::default(),
276 depth_stencil: None,
277 multisample: wgpu::MultisampleState::default(),
278 multiview: None,
279 cache: None,
280 });
281
282 ExtractCache {
283 pipeline,
284 uniform_layout,
285 texture_layout,
286 }
287}
288
289struct CompositeCache {
290 pipeline: wgpu::RenderPipeline,
291 bind_layout: wgpu::BindGroupLayout,
292}
293
294fn composite_pipeline(app: &Application, format: wgpu::TextureFormat) -> CompositeCache {
295 let device = app.device();
296 let shader = device.create_shader_module(wgpu::ShaderModuleDescriptor {
297 label: Some("wisp::ds_composite"),
298 source: wgpu::ShaderSource::Wgsl(
299 include_str!("../../shaders/filter_drop_shadow_composite.wgsl").into(),
300 ),
301 });
302
303 let bind_layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
304 label: Some("wisp::ds_composite layout"),
305 entries: &[
306 wgpu::BindGroupLayoutEntry {
307 binding: 0,
308 visibility: wgpu::ShaderStages::FRAGMENT,
309 ty: wgpu::BindingType::Texture {
310 sample_type: wgpu::TextureSampleType::Float { filterable: true },
311 view_dimension: wgpu::TextureViewDimension::D2,
312 multisampled: false,
313 },
314 count: None,
315 },
316 wgpu::BindGroupLayoutEntry {
317 binding: 1,
318 visibility: wgpu::ShaderStages::FRAGMENT,
319 ty: wgpu::BindingType::Texture {
320 sample_type: wgpu::TextureSampleType::Float { filterable: true },
321 view_dimension: wgpu::TextureViewDimension::D2,
322 multisampled: false,
323 },
324 count: None,
325 },
326 wgpu::BindGroupLayoutEntry {
327 binding: 2,
328 visibility: wgpu::ShaderStages::FRAGMENT,
329 ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering),
330 count: None,
331 },
332 ],
333 });
334
335 let layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
336 label: Some("wisp::ds_composite pipeline layout"),
337 bind_group_layouts: &[&bind_layout],
338 push_constant_ranges: &[],
339 });
340 let pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
341 label: Some("wisp::ds_composite pipeline"),
342 layout: Some(&layout),
343 vertex: wgpu::VertexState {
344 module: &shader,
345 entry_point: Some("main_vs"),
346 buffers: &[],
347 compilation_options: wgpu::PipelineCompilationOptions::default(),
348 },
349 fragment: Some(wgpu::FragmentState {
350 module: &shader,
351 entry_point: Some("main_fs"),
352 targets: &[Some(wgpu::ColorTargetState {
353 format,
354 blend: None,
355 write_mask: wgpu::ColorWrites::ALL,
356 })],
357 compilation_options: wgpu::PipelineCompilationOptions::default(),
358 }),
359 primitive: wgpu::PrimitiveState::default(),
360 depth_stencil: None,
361 multisample: wgpu::MultisampleState::default(),
362 multiview: None,
363 cache: None,
364 });
365
366 CompositeCache {
367 pipeline,
368 bind_layout,
369 }
370}