Skip to main content

polyscope_render/
ssaa_pass.rs

1//! SSAA (Supersampling Anti-Aliasing) downsample pass.
2
3use std::num::NonZeroU64;
4use wgpu::util::DeviceExt;
5
6/// GPU representation of SSAA downsample uniforms.
7#[repr(C)]
8#[derive(Debug, Clone, Copy, bytemuck::Pod, bytemuck::Zeroable)]
9pub struct SsaaUniforms {
10    pub ssaa_factor: u32,
11    _padding: [u32; 3],
12}
13
14/// SSAA downsample render resources.
15pub struct SsaaPass {
16    pipeline: wgpu::RenderPipeline,
17    bind_group_layout: wgpu::BindGroupLayout,
18    uniform_buffer: wgpu::Buffer,
19    sampler: wgpu::Sampler,
20    ssaa_factor: u32,
21}
22
23impl SsaaPass {
24    /// Creates a new SSAA downsample pass.
25    #[must_use]
26    pub fn new(device: &wgpu::Device, output_format: wgpu::TextureFormat) -> Self {
27        // Create bind group layout
28        let bind_group_layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
29            label: Some("SSAA Downsample Bind Group Layout"),
30            entries: &[
31                // Input texture (high-res HDR)
32                wgpu::BindGroupLayoutEntry {
33                    binding: 0,
34                    visibility: wgpu::ShaderStages::FRAGMENT,
35                    ty: wgpu::BindingType::Texture {
36                        sample_type: wgpu::TextureSampleType::Float { filterable: true },
37                        view_dimension: wgpu::TextureViewDimension::D2,
38                        multisampled: false,
39                    },
40                    count: None,
41                },
42                // Sampler
43                wgpu::BindGroupLayoutEntry {
44                    binding: 1,
45                    visibility: wgpu::ShaderStages::FRAGMENT,
46                    ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering),
47                    count: None,
48                },
49                // Uniforms
50                wgpu::BindGroupLayoutEntry {
51                    binding: 2,
52                    visibility: wgpu::ShaderStages::FRAGMENT,
53                    ty: wgpu::BindingType::Buffer {
54                        ty: wgpu::BufferBindingType::Uniform,
55                        has_dynamic_offset: false,
56                        min_binding_size: NonZeroU64::new(16),
57                    },
58                    count: None,
59                },
60            ],
61        });
62
63        // Create shader
64        let shader = device.create_shader_module(wgpu::ShaderModuleDescriptor {
65            label: Some("SSAA Downsample Shader"),
66            source: wgpu::ShaderSource::Wgsl(include_str!("shaders/ssaa_downsample.wgsl").into()),
67        });
68
69        // Create pipeline layout
70        let pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
71            label: Some("SSAA Downsample Pipeline Layout"),
72            bind_group_layouts: &[&bind_group_layout],
73            push_constant_ranges: &[],
74        });
75
76        // Create render pipeline
77        let pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
78            label: Some("SSAA Downsample Pipeline"),
79            layout: Some(&pipeline_layout),
80            vertex: wgpu::VertexState {
81                module: &shader,
82                entry_point: Some("vs_main"),
83                buffers: &[],
84                compilation_options: wgpu::PipelineCompilationOptions::default(),
85            },
86            fragment: Some(wgpu::FragmentState {
87                module: &shader,
88                entry_point: Some("fs_main"),
89                targets: &[Some(wgpu::ColorTargetState {
90                    format: output_format,
91                    blend: None,
92                    write_mask: wgpu::ColorWrites::ALL,
93                })],
94                compilation_options: wgpu::PipelineCompilationOptions::default(),
95            }),
96            primitive: wgpu::PrimitiveState {
97                topology: wgpu::PrimitiveTopology::TriangleList,
98                ..Default::default()
99            },
100            depth_stencil: None,
101            multisample: wgpu::MultisampleState::default(),
102            multiview: None,
103            cache: None,
104        });
105
106        // Create uniform buffer
107        let uniforms = SsaaUniforms {
108            ssaa_factor: 1,
109            _padding: [0; 3],
110        };
111        let uniform_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
112            label: Some("SSAA Uniform Buffer"),
113            contents: bytemuck::cast_slice(&[uniforms]),
114            usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
115        });
116
117        // Create sampler with linear filtering for smooth downsampling
118        let sampler = device.create_sampler(&wgpu::SamplerDescriptor {
119            label: Some("SSAA Sampler"),
120            mag_filter: wgpu::FilterMode::Linear,
121            min_filter: wgpu::FilterMode::Linear,
122            ..Default::default()
123        });
124
125        Self {
126            pipeline,
127            bind_group_layout,
128            uniform_buffer,
129            sampler,
130            ssaa_factor: 1,
131        }
132    }
133
134    /// Updates the SSAA factor.
135    pub fn set_ssaa_factor(&mut self, queue: &wgpu::Queue, factor: u32) {
136        // Clamp to valid values
137        let factor = factor.clamp(1, 4);
138        if factor != self.ssaa_factor {
139            self.ssaa_factor = factor;
140            let uniforms = SsaaUniforms {
141                ssaa_factor: factor,
142                _padding: [0; 3],
143            };
144            queue.write_buffer(&self.uniform_buffer, 0, bytemuck::cast_slice(&[uniforms]));
145        }
146    }
147
148    /// Returns the current SSAA factor.
149    #[must_use]
150    pub fn ssaa_factor(&self) -> u32 {
151        self.ssaa_factor
152    }
153
154    /// Creates a bind group for rendering.
155    #[must_use]
156    pub fn create_bind_group(
157        &self,
158        device: &wgpu::Device,
159        input_view: &wgpu::TextureView,
160    ) -> wgpu::BindGroup {
161        device.create_bind_group(&wgpu::BindGroupDescriptor {
162            label: Some("SSAA Bind Group"),
163            layout: &self.bind_group_layout,
164            entries: &[
165                wgpu::BindGroupEntry {
166                    binding: 0,
167                    resource: wgpu::BindingResource::TextureView(input_view),
168                },
169                wgpu::BindGroupEntry {
170                    binding: 1,
171                    resource: wgpu::BindingResource::Sampler(&self.sampler),
172                },
173                wgpu::BindGroupEntry {
174                    binding: 2,
175                    resource: self.uniform_buffer.as_entire_binding(),
176                },
177            ],
178        })
179    }
180
181    /// Renders the SSAA downsample pass.
182    pub fn render(
183        &self,
184        encoder: &mut wgpu::CommandEncoder,
185        output_view: &wgpu::TextureView,
186        bind_group: &wgpu::BindGroup,
187    ) {
188        let mut render_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
189            label: Some("SSAA Downsample Pass"),
190            color_attachments: &[Some(wgpu::RenderPassColorAttachment {
191                view: output_view,
192                resolve_target: None,
193                ops: wgpu::Operations {
194                    load: wgpu::LoadOp::Clear(wgpu::Color::BLACK),
195                    store: wgpu::StoreOp::Store,
196                },
197                depth_slice: None,
198            })],
199            depth_stencil_attachment: None,
200            ..Default::default()
201        });
202
203        render_pass.set_pipeline(&self.pipeline);
204        render_pass.set_bind_group(0, bind_group, &[]);
205        render_pass.draw(0..3, 0..1); // Fullscreen triangle
206    }
207
208    /// Renders SSAA downsample from input texture to output texture.
209    /// Convenience method that creates a bind group and renders in one call.
210    pub fn render_to_target(
211        &self,
212        device: &wgpu::Device,
213        encoder: &mut wgpu::CommandEncoder,
214        input_view: &wgpu::TextureView,
215        output_view: &wgpu::TextureView,
216    ) {
217        let bind_group = self.create_bind_group(device, input_view);
218        self.render(encoder, output_view, &bind_group);
219    }
220}