Skip to main content

smaa/
lib.rs

1//! Post-process antialiasing for wgpu-rs, using the [SMAA reference implementation](https://github.com/iryoku/smaa).
2//!
3//! # Example
4//!
5//! ```
6//! # use std::sync::Arc;
7//! # use smaa::{SmaaMode, SmaaTarget};
8//! # use winit::event::{Event, WindowEvent};
9//! # use winit::event_loop::EventLoop;
10//! # use winit::window::Window;
11//! # use winit::error::EventLoopError;
12//! # fn main() { futures::executor::block_on(run()); }
13//! # async fn run() -> Result<(), Box<dyn std::error::Error>> {
14//! // Initialize wgpu
15//! let event_loop = EventLoop::new().unwrap();
16//! let window = winit::window::Window::new(&event_loop).unwrap();
17//! let window_size = window.inner_size();
18//! let window_arc = Arc::new(window);
19//! let instance = wgpu::Instance::new(&wgpu::InstanceDescriptor::default());
20//! let surface = instance.create_surface(window_arc.clone()).unwrap();
21//! let adapter = instance.request_adapter(&Default::default()).await.unwrap();
22//! let (device, queue) = adapter.request_device(&Default::default()).await?;
23//! let swapchain_format = surface.get_capabilities(&adapter).formats[0];
24//! let mut config = wgpu::SurfaceConfiguration {
25//!     usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
26//!     format: swapchain_format,
27//!     width: window_size.width,
28//!     height: window_size.height,
29//!     present_mode: wgpu::PresentMode::AutoVsync,
30//!     alpha_mode: wgpu::CompositeAlphaMode::Opaque,
31//!     view_formats: vec![],
32//!     desired_maximum_frame_latency: 2,
33//! };
34//! surface.configure(&device, &config);
35//!
36//! // Create SMAA target
37//! let mut smaa_target = SmaaTarget::new(
38//!     &device,
39//!     &queue,
40//!     window_size.width,
41//!     window_size.height,
42//!     swapchain_format,
43//!     SmaaMode::Smaa1X,
44//! );
45//!
46//! // Main loop
47//! # Ok(
48//! event_loop.run(move |event, event_loop| {
49//!     if let Event::WindowEvent { event, .. } = event {
50//!         match event {
51//!             WindowEvent::RedrawRequested => {
52//!                 let output_frame = surface.get_current_texture().unwrap();
53//!                 let output_view = output_frame.texture.create_view(&Default::default());
54//!                 let smaa_frame = smaa_target.start_frame(&device, &queue, &output_view);
55//!
56//!                 // Render the scene into `*smaa_frame`.
57//!                 // [...]
58//!
59//!                 smaa_frame.resolve();
60//!                 output_frame.present();
61//!                 # event_loop.exit();
62//!             }
63//!             _ => {}
64//!         }
65//!     }
66//! })
67//! # ?)
68//! # }
69
70#![deny(missing_docs)]
71
72mod shader;
73use shader::{ShaderQuality, ShaderSource, ShaderStage};
74
75#[path = "../third_party/smaa/Textures/AreaTex.rs"]
76mod area_tex;
77use area_tex::*;
78
79#[path = "../third_party/smaa/Textures/SearchTex.rs"]
80mod search_tex;
81use search_tex::*;
82
83use wgpu::util::DeviceExt;
84
85/// Anti-aliasing mode. Higher values produce nicer results but run slower.
86#[non_exhaustive]
87#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
88pub enum SmaaMode {
89    /// Do not perform antialiasing.
90    Disabled,
91    /// Use SMAA 1x.
92    Smaa1X,
93}
94
95struct BindGroupLayouts {
96    edge_detect_bind_group_layout: wgpu::BindGroupLayout,
97    blend_weight_bind_group_layout: wgpu::BindGroupLayout,
98    neighborhood_blending_bind_group_layout: wgpu::BindGroupLayout,
99}
100struct Pipelines {
101    edge_detect: wgpu::RenderPipeline,
102    blend_weight: wgpu::RenderPipeline,
103    neighborhood_blending: wgpu::RenderPipeline,
104}
105struct Resources {
106    area_texture: wgpu::Texture,
107    search_texture: wgpu::Texture,
108    linear_sampler: wgpu::Sampler,
109}
110struct Targets {
111    rt_uniforms: wgpu::Buffer,
112    color_target: wgpu::TextureView,
113    edges_target: wgpu::TextureView,
114    blend_target: wgpu::TextureView,
115}
116struct BindGroups {
117    edge_detect_bind_group: wgpu::BindGroup,
118    blend_weight_bind_group: wgpu::BindGroup,
119    neighborhood_blending_bind_group: wgpu::BindGroup,
120}
121
122impl BindGroupLayouts {
123    pub fn new(device: &wgpu::Device) -> Self {
124        Self {
125            edge_detect_bind_group_layout: device.create_bind_group_layout(
126                &wgpu::BindGroupLayoutDescriptor {
127                    label: Some("smaa.bind_group_layout.edge_detect"),
128                    entries: &[
129                        wgpu::BindGroupLayoutEntry {
130                            binding: 0,
131                            visibility: wgpu::ShaderStages::FRAGMENT,
132                            ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering),
133                            count: None,
134                        },
135                        wgpu::BindGroupLayoutEntry {
136                            binding: 1,
137                            visibility: wgpu::ShaderStages::VERTEX | wgpu::ShaderStages::FRAGMENT,
138                            ty: wgpu::BindingType::Buffer {
139                                ty: wgpu::BufferBindingType::Uniform,
140                                has_dynamic_offset: false,
141                                min_binding_size: None,
142                            },
143                            count: None,
144                        },
145                        wgpu::BindGroupLayoutEntry {
146                            binding: 2,
147                            visibility: wgpu::ShaderStages::FRAGMENT,
148                            ty: wgpu::BindingType::Texture {
149                                sample_type: wgpu::TextureSampleType::Float { filterable: true },
150                                view_dimension: wgpu::TextureViewDimension::D2,
151                                multisampled: false,
152                            },
153                            count: None,
154                        },
155                    ],
156                },
157            ),
158            blend_weight_bind_group_layout: device.create_bind_group_layout(
159                &wgpu::BindGroupLayoutDescriptor {
160                    label: Some("smaa.bind_group_layout.blend_weight"),
161                    entries: &[
162                        wgpu::BindGroupLayoutEntry {
163                            binding: 0,
164                            visibility: wgpu::ShaderStages::FRAGMENT,
165                            ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering),
166                            count: None,
167                        },
168                        wgpu::BindGroupLayoutEntry {
169                            binding: 1,
170                            visibility: wgpu::ShaderStages::VERTEX | wgpu::ShaderStages::FRAGMENT,
171                            ty: wgpu::BindingType::Buffer {
172                                ty: wgpu::BufferBindingType::Uniform,
173                                has_dynamic_offset: false,
174                                min_binding_size: None,
175                            },
176                            count: None,
177                        },
178                        wgpu::BindGroupLayoutEntry {
179                            binding: 2,
180                            visibility: wgpu::ShaderStages::FRAGMENT,
181                            ty: wgpu::BindingType::Texture {
182                                sample_type: wgpu::TextureSampleType::Float { filterable: true },
183                                view_dimension: wgpu::TextureViewDimension::D2,
184                                multisampled: false,
185                            },
186                            count: None,
187                        },
188                        wgpu::BindGroupLayoutEntry {
189                            binding: 3,
190                            visibility: wgpu::ShaderStages::FRAGMENT,
191                            ty: wgpu::BindingType::Texture {
192                                sample_type: wgpu::TextureSampleType::Float { filterable: true },
193                                view_dimension: wgpu::TextureViewDimension::D2,
194                                multisampled: false,
195                            },
196                            count: None,
197                        },
198                        wgpu::BindGroupLayoutEntry {
199                            binding: 4,
200                            visibility: wgpu::ShaderStages::FRAGMENT,
201                            ty: wgpu::BindingType::Texture {
202                                sample_type: wgpu::TextureSampleType::Float { filterable: true },
203                                view_dimension: wgpu::TextureViewDimension::D2,
204                                multisampled: false,
205                            },
206                            count: None,
207                        },
208                    ],
209                },
210            ),
211            neighborhood_blending_bind_group_layout: device.create_bind_group_layout(
212                &wgpu::BindGroupLayoutDescriptor {
213                    label: Some("smaa.bind_group_layout.neighborhood_blending"),
214                    entries: &[
215                        wgpu::BindGroupLayoutEntry {
216                            binding: 0,
217                            visibility: wgpu::ShaderStages::FRAGMENT,
218                            ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering),
219                            count: None,
220                        },
221                        wgpu::BindGroupLayoutEntry {
222                            binding: 1,
223                            visibility: wgpu::ShaderStages::VERTEX | wgpu::ShaderStages::FRAGMENT,
224                            ty: wgpu::BindingType::Buffer {
225                                ty: wgpu::BufferBindingType::Uniform,
226                                has_dynamic_offset: false,
227                                min_binding_size: None,
228                            },
229                            count: None,
230                        },
231                        wgpu::BindGroupLayoutEntry {
232                            binding: 2,
233                            visibility: wgpu::ShaderStages::FRAGMENT,
234                            ty: wgpu::BindingType::Texture {
235                                sample_type: wgpu::TextureSampleType::Float { filterable: true },
236                                view_dimension: wgpu::TextureViewDimension::D2,
237                                multisampled: false,
238                            },
239                            count: None,
240                        },
241                        wgpu::BindGroupLayoutEntry {
242                            binding: 3,
243                            visibility: wgpu::ShaderStages::FRAGMENT,
244                            ty: wgpu::BindingType::Texture {
245                                sample_type: wgpu::TextureSampleType::Float { filterable: true },
246                                view_dimension: wgpu::TextureViewDimension::D2,
247                                multisampled: false,
248                            },
249                            count: None,
250                        },
251                    ],
252                },
253            ),
254        }
255    }
256}
257
258impl Pipelines {
259    pub fn new(
260        device: &wgpu::Device,
261        format: wgpu::TextureFormat,
262        layouts: &BindGroupLayouts,
263    ) -> Self {
264        let source = ShaderSource {
265            quality: ShaderQuality::High,
266        };
267
268        let edge_detect_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
269            label: Some("smaa.pipeline_layout.edge_detect"),
270            bind_group_layouts: &[&layouts.edge_detect_bind_group_layout],
271            immediate_size: 0,
272        });
273        let edge_detect_shader_vert = wgpu::VertexState {
274            module: &source.get_shader(
275                device,
276                ShaderStage::EdgeDetectionVS,
277                "smaa.shader.edge_detect.vert",
278            ),
279            entry_point: Some("main"),
280            buffers: &[],
281            compilation_options: wgpu::PipelineCompilationOptions::default(),
282        };
283        let edge_detect_shader_frag = wgpu::FragmentState {
284            module: &source.get_shader(
285                device,
286                ShaderStage::LumaEdgeDetectionPS,
287                "smaa.shader.edge_detect.frag",
288            ),
289            entry_point: Some("main"),
290            targets: &[Some(wgpu::ColorTargetState {
291                format: wgpu::TextureFormat::Rg8Unorm,
292                blend: Some(wgpu::BlendState {
293                    color: wgpu::BlendComponent::REPLACE,
294                    alpha: wgpu::BlendComponent::REPLACE,
295                }),
296                write_mask: wgpu::ColorWrites::ALL,
297            })],
298            compilation_options: wgpu::PipelineCompilationOptions::default(),
299        };
300        let edge_detect = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
301            label: Some("smaa.pipeline.edge_detect"),
302            layout: Some(&edge_detect_layout),
303            vertex: edge_detect_shader_vert,
304            fragment: Some(edge_detect_shader_frag),
305            primitive: Default::default(),
306            multisample: Default::default(),
307            depth_stencil: None,
308            multiview_mask: None,
309            cache: None
310        });
311
312        let blend_weight_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
313            label: Some("smaa.pipeline_layout.blend_weight"),
314            bind_group_layouts: &[&layouts.blend_weight_bind_group_layout],
315            immediate_size: 0,
316        });
317        let blend_weight_shader_vert = wgpu::VertexState {
318            module: &source.get_shader(
319                device,
320                ShaderStage::BlendingWeightVS,
321                "smaa.shader.blending_weight.vert",
322            ),
323            entry_point: Some("main"),
324            buffers: &[],
325            compilation_options: wgpu::PipelineCompilationOptions::default(),
326        };
327        let blend_weight_shader_frag = wgpu::FragmentState {
328            module: &source.get_shader(
329                device,
330                ShaderStage::BlendingWeightPS,
331                "smaa.shader.blending_weight.frag",
332            ),
333            entry_point: Some("main"),
334            targets: &[Some(wgpu::ColorTargetState {
335                format: wgpu::TextureFormat::Rgba8Unorm,
336                blend: Some(wgpu::BlendState {
337                    color: wgpu::BlendComponent::REPLACE,
338                    alpha: wgpu::BlendComponent::REPLACE,
339                }),
340                write_mask: wgpu::ColorWrites::ALL,
341            })],
342            compilation_options: wgpu::PipelineCompilationOptions::default(),
343        };
344        let blend_weight = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
345            label: Some("smaa.pipeline.blend_weight"),
346            layout: Some(&blend_weight_layout),
347            vertex: blend_weight_shader_vert,
348            fragment: Some(blend_weight_shader_frag),
349            primitive: Default::default(),
350            multisample: Default::default(),
351            depth_stencil: None,
352            multiview_mask: None,
353            cache: None
354        });
355
356        let neighborhood_blending_layout =
357            device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
358                label: Some("smaa.pipeline_layout.neighborhood_blending"),
359                bind_group_layouts: &[&layouts.neighborhood_blending_bind_group_layout],
360                immediate_size: 0,
361            });
362        let neighborhood_blending_vert = wgpu::VertexState {
363            module: &source.get_shader(
364                device,
365                ShaderStage::NeighborhoodBlendingVS,
366                "smaa.shader.neighborhood_blending.vert",
367            ),
368            entry_point: Some("main"),
369            buffers: &[],
370            compilation_options: wgpu::PipelineCompilationOptions::default(),
371        };
372        let neighborhood_blending_frag = wgpu::FragmentState {
373            module: &source.get_shader(
374                device,
375                ShaderStage::NeighborhoodBlendingPS,
376                "smaa.shader.neighborhood_blending.frag",
377            ),
378            entry_point: Some("main"),
379            targets: &[Some(wgpu::ColorTargetState {
380                format,
381                blend: Some(wgpu::BlendState {
382                    color: wgpu::BlendComponent::REPLACE,
383                    alpha: wgpu::BlendComponent::REPLACE,
384                }),
385                write_mask: wgpu::ColorWrites::ALL,
386            })],
387            compilation_options: wgpu::PipelineCompilationOptions::default(),
388        };
389        let neighborhood_blending =
390            device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
391                label: Some("smaa.pipeline.neighborhood_blending"),
392                layout: Some(&neighborhood_blending_layout),
393                vertex: neighborhood_blending_vert,
394                fragment: Some(neighborhood_blending_frag),
395                primitive: Default::default(),
396                multisample: Default::default(),
397                depth_stencil: None,
398                multiview_mask: None,
399                cache: None
400            });
401
402        Self {
403            edge_detect,
404            blend_weight,
405            neighborhood_blending,
406        }
407    }
408}
409impl Targets {
410    pub fn new(
411        device: &wgpu::Device,
412        width: u32,
413        height: u32,
414        format: wgpu::TextureFormat,
415    ) -> Self {
416        let size = wgpu::Extent3d {
417            width,
418            height,
419            depth_or_array_layers: 1,
420        };
421        let texture_desc = wgpu::TextureDescriptor {
422            size,
423            mip_level_count: 1,
424            sample_count: 1,
425            dimension: wgpu::TextureDimension::D2,
426            format: wgpu::TextureFormat::Rgba8Unorm,
427            usage: wgpu::TextureUsages::RENDER_ATTACHMENT | wgpu::TextureUsages::TEXTURE_BINDING,
428            label: None,
429            view_formats: &[],
430        };
431
432        let mut uniform_data = Vec::new();
433        for f in &[
434            1.0 / width as f32,
435            1.0 / height as f32,
436            width as f32,
437            height as f32,
438        ] {
439            uniform_data.extend_from_slice(&f.to_ne_bytes());
440        }
441        let rt_uniforms = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
442            label: Some("smaa.uniforms"),
443            usage: wgpu::BufferUsages::UNIFORM,
444            contents: &uniform_data,
445        });
446
447        Self {
448            rt_uniforms,
449            color_target: device
450                .create_texture(&wgpu::TextureDescriptor {
451                    format,
452                    ..texture_desc
453                })
454                .create_view(&wgpu::TextureViewDescriptor {
455                    label: Some("smaa.color_target.view"),
456                    ..Default::default()
457                }),
458            edges_target: device
459                .create_texture(&wgpu::TextureDescriptor {
460                    format: wgpu::TextureFormat::Rg8Unorm,
461                    label: Some("smaa.texture.edge_target"),
462                    ..texture_desc
463                })
464                .create_view(&wgpu::TextureViewDescriptor {
465                    label: Some("smaa.texture_view.edge_target"),
466                    ..Default::default()
467                }),
468
469            blend_target: device
470                .create_texture(&wgpu::TextureDescriptor {
471                    format: wgpu::TextureFormat::Rgba8Unorm,
472                    label: Some("smaa.texture.blend_target"),
473                    ..texture_desc
474                })
475                .create_view(&wgpu::TextureViewDescriptor {
476                    label: Some("smaa.texture_view.blend_target"),
477                    ..Default::default()
478                }),
479        }
480    }
481}
482impl Resources {
483    fn new(device: &wgpu::Device, queue: &wgpu::Queue) -> Self {
484        let area_texture = device.create_texture_with_data(
485            queue,
486            &wgpu::TextureDescriptor {
487                label: Some("smaa.texture.area"),
488                size: wgpu::Extent3d {
489                    width: AREATEX_WIDTH,
490                    height: AREATEX_HEIGHT,
491                    depth_or_array_layers: 1,
492                },
493                mip_level_count: 1,
494                sample_count: 1,
495                dimension: wgpu::TextureDimension::D2,
496                format: wgpu::TextureFormat::Rg8Unorm,
497                usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST,
498                view_formats: &[],
499            },
500            wgpu::util::TextureDataOrder::LayerMajor,
501            &AREATEX_BYTES,
502        );
503
504        let search_texture = device.create_texture_with_data(
505            queue,
506            &wgpu::TextureDescriptor {
507                label: Some("smaa.texture.search"),
508                size: wgpu::Extent3d {
509                    width: SEARCHTEX_WIDTH,
510                    height: SEARCHTEX_HEIGHT,
511                    depth_or_array_layers: 1,
512                },
513                mip_level_count: 1,
514                sample_count: 1,
515                dimension: wgpu::TextureDimension::D2,
516                format: wgpu::TextureFormat::R8Unorm,
517                usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST,
518                view_formats: &[],
519            },
520            wgpu::util::TextureDataOrder::LayerMajor,
521            &SEARCHTEX_BYTES,
522        );
523
524        let linear_sampler = device.create_sampler(&wgpu::SamplerDescriptor {
525            label: Some("smaa.sampler"),
526            mag_filter: wgpu::FilterMode::Linear,
527            min_filter: wgpu::FilterMode::Linear,
528            mipmap_filter: wgpu::MipmapFilterMode::Nearest,
529            address_mode_u: wgpu::AddressMode::ClampToEdge,
530            address_mode_v: wgpu::AddressMode::ClampToEdge,
531            ..Default::default()
532        });
533
534        Self {
535            area_texture,
536            search_texture,
537            linear_sampler,
538        }
539    }
540}
541
542impl BindGroups {
543    fn new(
544        device: &wgpu::Device,
545        layouts: &BindGroupLayouts,
546        resources: &Resources,
547        targets: &Targets,
548    ) -> Self {
549        Self {
550            edge_detect_bind_group: device.create_bind_group(&wgpu::BindGroupDescriptor {
551                label: Some("smaa.bind_group.edge_detect"),
552                layout: &layouts.edge_detect_bind_group_layout,
553                entries: &[
554                    wgpu::BindGroupEntry {
555                        binding: 0,
556                        resource: wgpu::BindingResource::Sampler(&resources.linear_sampler),
557                    },
558                    wgpu::BindGroupEntry {
559                        binding: 1,
560                        resource: wgpu::BindingResource::Buffer(wgpu::BufferBinding {
561                            buffer: &targets.rt_uniforms,
562                            offset: 0,
563                            size: None,
564                        }),
565                    },
566                    wgpu::BindGroupEntry {
567                        binding: 2,
568                        resource: wgpu::BindingResource::TextureView(&targets.color_target),
569                    },
570                ],
571            }),
572
573            blend_weight_bind_group: device.create_bind_group(&wgpu::BindGroupDescriptor {
574                label: Some("smaa.bind_group.blend_weight"),
575                layout: &layouts.blend_weight_bind_group_layout,
576                entries: &[
577                    wgpu::BindGroupEntry {
578                        binding: 0,
579                        resource: wgpu::BindingResource::Sampler(&resources.linear_sampler),
580                    },
581                    wgpu::BindGroupEntry {
582                        binding: 1,
583                        resource: wgpu::BindingResource::Buffer(wgpu::BufferBinding {
584                            buffer: &targets.rt_uniforms,
585                            offset: 0,
586                            size: None,
587                        }),
588                    },
589                    wgpu::BindGroupEntry {
590                        binding: 2,
591                        resource: wgpu::BindingResource::TextureView(&targets.edges_target),
592                    },
593                    wgpu::BindGroupEntry {
594                        binding: 3,
595                        resource: wgpu::BindingResource::TextureView(
596                            &resources.area_texture.create_view(&Default::default()),
597                        ),
598                    },
599                    wgpu::BindGroupEntry {
600                        binding: 4,
601                        resource: wgpu::BindingResource::TextureView(
602                            &resources.search_texture.create_view(&Default::default()),
603                        ),
604                    },
605                ],
606            }),
607            neighborhood_blending_bind_group: device.create_bind_group(
608                &wgpu::BindGroupDescriptor {
609                    label: Some("smaa.bind_group.neighborhood_blending"),
610                    layout: &layouts.neighborhood_blending_bind_group_layout,
611                    entries: &[
612                        wgpu::BindGroupEntry {
613                            binding: 0,
614                            resource: wgpu::BindingResource::Sampler(&resources.linear_sampler),
615                        },
616                        wgpu::BindGroupEntry {
617                            binding: 1,
618                            resource: wgpu::BindingResource::Buffer(wgpu::BufferBinding {
619                                buffer: &targets.rt_uniforms,
620                                offset: 0,
621                                size: None,
622                            }),
623                        },
624                        wgpu::BindGroupEntry {
625                            binding: 2,
626                            resource: wgpu::BindingResource::TextureView(&targets.color_target),
627                        },
628                        wgpu::BindGroupEntry {
629                            binding: 3,
630                            resource: wgpu::BindingResource::TextureView(&targets.blend_target),
631                        },
632                    ],
633                },
634            ),
635        }
636    }
637}
638
639struct SmaaTargetInner {
640    pipelines: Pipelines,
641    layouts: BindGroupLayouts,
642    resources: Resources,
643    targets: Targets,
644    bind_groups: BindGroups,
645    format: wgpu::TextureFormat,
646}
647
648/// Wraps a color buffer, which it can resolve into an antialiased image using the
649/// [Subpixel Morphological Antialiasing (SMAA)](http://www.iryoku.com/smaa) algorithm.
650pub struct SmaaTarget {
651    inner: Option<SmaaTargetInner>,
652}
653
654impl SmaaTarget {
655    /// Create a new `SmaaTarget`.
656    pub fn new(
657        device: &wgpu::Device,
658        queue: &wgpu::Queue,
659        width: u32,
660        height: u32,
661        format: wgpu::TextureFormat,
662        mode: SmaaMode,
663    ) -> Self {
664        if let SmaaMode::Disabled = mode {
665            return SmaaTarget { inner: None };
666        }
667
668        let layouts = BindGroupLayouts::new(device);
669        let pipelines = Pipelines::new(device, format, &layouts);
670        let resources = Resources::new(device, queue);
671        let targets = Targets::new(device, width, height, format);
672        let bind_groups = BindGroups::new(device, &layouts, &resources, &targets);
673
674        SmaaTarget {
675            inner: Some(SmaaTargetInner {
676                layouts,
677                pipelines,
678                resources,
679                targets,
680                bind_groups,
681                format,
682            }),
683        }
684    }
685
686    /// Resize the render target.
687    pub fn resize(&mut self, device: &wgpu::Device, width: u32, height: u32) {
688        if let Some(ref mut inner) = self.inner {
689            inner.targets = Targets::new(device, width, height, inner.format);
690            inner.bind_groups =
691                BindGroups::new(device, &inner.layouts, &inner.resources, &inner.targets);
692        }
693    }
694
695    /// Start rendering a frame. Dropping or calling resolve() the returned frame object will resolve the scene into the provided output_view.
696    pub fn start_frame<'a>(
697        &'a mut self,
698        device: &'a wgpu::Device,
699        queue: &'a wgpu::Queue,
700        output_view: &'a wgpu::TextureView,
701    ) -> SmaaFrame<'a> {
702        SmaaFrame {
703            target: self,
704            device,
705            queue,
706            output_view,
707        }
708    }
709}
710
711/// Frame that the scene should be rendered into; can be created by a SmaaTarget.
712pub struct SmaaFrame<'a> {
713    target: &'a mut SmaaTarget,
714    device: &'a wgpu::Device,
715    queue: &'a wgpu::Queue,
716    output_view: &'a wgpu::TextureView,
717}
718impl<'a> SmaaFrame<'a> {
719    /// Resolve the multisampled image into the output texture.
720    pub fn resolve(self) {
721        std::mem::drop(self);
722    }
723}
724impl<'a> std::ops::Deref for SmaaFrame<'a> {
725    type Target = wgpu::TextureView;
726    fn deref(&self) -> &Self::Target {
727        match self.target.inner {
728            None => self.output_view,
729            Some(ref inner) => &inner.targets.color_target,
730        }
731    }
732}
733impl<'a> Drop for SmaaFrame<'a> {
734    fn drop(&mut self) {
735        if let Some(ref mut inner) = self.target.inner {
736            let mut encoder = self
737                .device
738                .create_command_encoder(&wgpu::CommandEncoderDescriptor {
739                    label: Some("smaa.command_encoder"),
740                });
741            {
742                let mut rpass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
743                    color_attachments: &[Some(wgpu::RenderPassColorAttachment {
744                        view: &inner.targets.edges_target,
745                        resolve_target: None,
746                        ops: wgpu::Operations {
747                            load: wgpu::LoadOp::Clear(wgpu::Color::BLACK),
748                            store: wgpu::StoreOp::Store,
749                        },
750                        depth_slice: None,
751                    })],
752                    depth_stencil_attachment: None,
753                    label: Some("smaa.render_pass.edge_detect"),
754                    occlusion_query_set: None,
755                    timestamp_writes: None,
756                    multiview_mask: None,
757                });
758                rpass.set_pipeline(&inner.pipelines.edge_detect);
759                rpass.set_bind_group(0, &inner.bind_groups.edge_detect_bind_group, &[]);
760                rpass.draw(0..3, 0..1);
761            }
762            {
763                let mut rpass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
764                    color_attachments: &[Some(wgpu::RenderPassColorAttachment {
765                        view: &inner.targets.blend_target,
766                        resolve_target: None,
767                        ops: wgpu::Operations {
768                            load: wgpu::LoadOp::Clear(wgpu::Color::BLACK),
769                            store: wgpu::StoreOp::Store,
770                        },
771                        depth_slice: None,
772                    })],
773                    depth_stencil_attachment: None,
774                    label: Some("smaa.render_pass.blend_weight"),
775                    occlusion_query_set: None,
776                    timestamp_writes: None,
777                    multiview_mask: None,
778                });
779                rpass.set_pipeline(&inner.pipelines.blend_weight);
780                rpass.set_bind_group(0, &inner.bind_groups.blend_weight_bind_group, &[]);
781                rpass.draw(0..3, 0..1);
782            }
783            {
784                let mut rpass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
785                    color_attachments: &[Some(wgpu::RenderPassColorAttachment {
786                        view: self.output_view,
787                        resolve_target: None,
788                        ops: wgpu::Operations {
789                            load: wgpu::LoadOp::Clear(wgpu::Color::BLACK),
790                            store: wgpu::StoreOp::Store,
791                        },
792                        depth_slice: None,
793                    })],
794                    depth_stencil_attachment: None,
795                    label: Some("smaa.render_pass.neighborhood_blending"),
796                    occlusion_query_set: None,
797                    timestamp_writes: None,
798                    multiview_mask: None,
799                });
800                rpass.set_pipeline(&inner.pipelines.neighborhood_blending);
801                rpass.set_bind_group(0, &inner.bind_groups.neighborhood_blending_bind_group, &[]);
802                rpass.draw(0..3, 0..1);
803            }
804            self.queue.submit(Some(encoder.finish()));
805        }
806    }
807}