iced_wgpu/triangle/
msaa.rs

1use crate::core::{Size, Transformation};
2use crate::graphics;
3
4use std::num::NonZeroU64;
5use std::sync::{Arc, RwLock};
6
7#[derive(Debug, Clone)]
8pub struct Pipeline {
9    format: wgpu::TextureFormat,
10    sampler: wgpu::Sampler,
11    raw: wgpu::RenderPipeline,
12    constant_layout: wgpu::BindGroupLayout,
13    texture_layout: wgpu::BindGroupLayout,
14    sample_count: u32,
15    targets: Arc<RwLock<Option<Targets>>>,
16}
17
18impl Pipeline {
19    pub fn new(
20        device: &wgpu::Device,
21        format: wgpu::TextureFormat,
22        antialiasing: graphics::Antialiasing,
23    ) -> Pipeline {
24        let sampler = device.create_sampler(&wgpu::SamplerDescriptor::default());
25
26        let constant_layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
27            label: Some("iced_wgpu::triangle:msaa uniforms layout"),
28            entries: &[
29                wgpu::BindGroupLayoutEntry {
30                    binding: 0,
31                    visibility: wgpu::ShaderStages::FRAGMENT,
32                    ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::NonFiltering),
33                    count: None,
34                },
35                wgpu::BindGroupLayoutEntry {
36                    binding: 1,
37                    visibility: wgpu::ShaderStages::VERTEX,
38                    ty: wgpu::BindingType::Buffer {
39                        ty: wgpu::BufferBindingType::Uniform,
40                        has_dynamic_offset: false,
41                        min_binding_size: None,
42                    },
43                    count: None,
44                },
45            ],
46        });
47
48        let texture_layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
49            label: Some("iced_wgpu::triangle::msaa texture layout"),
50            entries: &[wgpu::BindGroupLayoutEntry {
51                binding: 0,
52                visibility: wgpu::ShaderStages::FRAGMENT,
53                ty: wgpu::BindingType::Texture {
54                    sample_type: wgpu::TextureSampleType::Float { filterable: false },
55                    view_dimension: wgpu::TextureViewDimension::D2,
56                    multisampled: false,
57                },
58                count: None,
59            }],
60        });
61
62        let layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
63            label: Some("iced_wgpu::triangle::msaa pipeline layout"),
64            push_constant_ranges: &[],
65            bind_group_layouts: &[&constant_layout, &texture_layout],
66        });
67
68        let shader = device.create_shader_module(wgpu::ShaderModuleDescriptor {
69            label: Some("iced_wgpu triangle blit_shader"),
70            source: wgpu::ShaderSource::Wgsl(std::borrow::Cow::Borrowed(include_str!(
71                "../shader/blit.wgsl"
72            ))),
73        });
74
75        let pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
76            label: Some("iced_wgpu::triangle::msaa pipeline"),
77            layout: Some(&layout),
78            vertex: wgpu::VertexState {
79                module: &shader,
80                entry_point: Some("vs_main"),
81                buffers: &[],
82                compilation_options: wgpu::PipelineCompilationOptions::default(),
83            },
84            fragment: Some(wgpu::FragmentState {
85                module: &shader,
86                entry_point: Some("fs_main"),
87                targets: &[Some(wgpu::ColorTargetState {
88                    format,
89                    blend: Some(wgpu::BlendState::PREMULTIPLIED_ALPHA_BLENDING),
90                    write_mask: wgpu::ColorWrites::ALL,
91                })],
92                compilation_options: wgpu::PipelineCompilationOptions::default(),
93            }),
94            primitive: wgpu::PrimitiveState {
95                topology: wgpu::PrimitiveTopology::TriangleList,
96                front_face: wgpu::FrontFace::Cw,
97                ..Default::default()
98            },
99            depth_stencil: None,
100            multisample: wgpu::MultisampleState {
101                count: 1,
102                mask: !0,
103                alpha_to_coverage_enabled: false,
104            },
105            multiview: None,
106            cache: None,
107        });
108
109        Self {
110            format,
111            sampler,
112            raw: pipeline,
113            constant_layout,
114            texture_layout,
115            sample_count: antialiasing.sample_count(),
116            targets: Arc::new(RwLock::new(None)),
117        }
118    }
119
120    fn targets(&self, device: &wgpu::Device, region_size: Size<u32>) -> Targets {
121        let mut targets = self.targets.write().expect("Write MSAA targets");
122
123        match targets.as_mut() {
124            Some(targets)
125                if region_size.width <= targets.size.width
126                    && region_size.height <= targets.size.height => {}
127            _ => {
128                *targets = Some(Targets::new(
129                    device,
130                    self.format,
131                    &self.texture_layout,
132                    self.sample_count,
133                    region_size,
134                ));
135            }
136        }
137
138        targets.as_ref().unwrap().clone()
139    }
140
141    pub fn render_pass<'a>(&self, encoder: &'a mut wgpu::CommandEncoder) -> wgpu::RenderPass<'a> {
142        let targets = self.targets.read().expect("Read MSAA targets");
143        let targets = targets.as_ref().unwrap();
144
145        encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
146            label: Some("iced_wgpu.triangle.render_pass"),
147            color_attachments: &[Some(wgpu::RenderPassColorAttachment {
148                view: &targets.attachment,
149                depth_slice: None,
150                resolve_target: Some(&targets.resolve),
151                ops: wgpu::Operations {
152                    load: wgpu::LoadOp::Clear(wgpu::Color::TRANSPARENT),
153                    store: wgpu::StoreOp::Store,
154                },
155            })],
156            depth_stencil_attachment: None,
157            timestamp_writes: None,
158            occlusion_query_set: None,
159        })
160    }
161}
162
163#[derive(Debug, Clone)]
164struct Targets {
165    attachment: wgpu::TextureView,
166    resolve: wgpu::TextureView,
167    bind_group: wgpu::BindGroup,
168    size: Size<u32>,
169}
170
171impl Targets {
172    pub fn new(
173        device: &wgpu::Device,
174        format: wgpu::TextureFormat,
175        texture_layout: &wgpu::BindGroupLayout,
176        sample_count: u32,
177        size: Size<u32>,
178    ) -> Targets {
179        let extent = wgpu::Extent3d {
180            width: size.width,
181            height: size.height,
182            depth_or_array_layers: 1,
183        };
184
185        let attachment = device.create_texture(&wgpu::TextureDescriptor {
186            label: Some("iced_wgpu::triangle::msaa attachment"),
187            size: extent,
188            mip_level_count: 1,
189            sample_count,
190            dimension: wgpu::TextureDimension::D2,
191            format,
192            usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
193            view_formats: &[],
194        });
195
196        let resolve = device.create_texture(&wgpu::TextureDescriptor {
197            label: Some("iced_wgpu::triangle::msaa resolve target"),
198            size: extent,
199            mip_level_count: 1,
200            sample_count: 1,
201            dimension: wgpu::TextureDimension::D2,
202            format,
203            usage: wgpu::TextureUsages::RENDER_ATTACHMENT | wgpu::TextureUsages::TEXTURE_BINDING,
204            view_formats: &[],
205        });
206
207        let attachment = attachment.create_view(&wgpu::TextureViewDescriptor::default());
208
209        let resolve = resolve.create_view(&wgpu::TextureViewDescriptor::default());
210
211        let bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
212            label: Some("iced_wgpu::triangle::msaa texture bind group"),
213            layout: texture_layout,
214            entries: &[wgpu::BindGroupEntry {
215                binding: 0,
216                resource: wgpu::BindingResource::TextureView(&resolve),
217            }],
218        });
219
220        Targets {
221            attachment,
222            resolve,
223            bind_group,
224            size,
225        }
226    }
227}
228
229#[derive(Debug, Clone, Copy, PartialEq, bytemuck::Pod, bytemuck::Zeroable)]
230#[repr(C)]
231struct Ratio {
232    u: f32,
233    v: f32,
234    // Padding field for 16-byte alignment.
235    // See https://docs.rs/wgpu/latest/wgpu/struct.DownlevelFlags.html#associatedconstant.BUFFER_BINDINGS_NOT_16_BYTE_ALIGNED
236    _padding: [f32; 2],
237}
238
239pub struct State {
240    ratio: wgpu::Buffer,
241    constants: wgpu::BindGroup,
242    last_ratio: Option<Ratio>,
243}
244
245impl State {
246    pub fn new(device: &wgpu::Device, pipeline: &Pipeline) -> Self {
247        let ratio = device.create_buffer(&wgpu::BufferDescriptor {
248            label: Some("iced_wgpu::triangle::msaa ratio"),
249            size: std::mem::size_of::<Ratio>() as u64,
250            usage: wgpu::BufferUsages::COPY_DST | wgpu::BufferUsages::UNIFORM,
251            mapped_at_creation: false,
252        });
253
254        let constants = device.create_bind_group(&wgpu::BindGroupDescriptor {
255            label: Some("iced_wgpu::triangle::msaa uniforms bind group"),
256            layout: &pipeline.constant_layout,
257            entries: &[
258                wgpu::BindGroupEntry {
259                    binding: 0,
260                    resource: wgpu::BindingResource::Sampler(&pipeline.sampler),
261                },
262                wgpu::BindGroupEntry {
263                    binding: 1,
264                    resource: ratio.as_entire_binding(),
265                },
266            ],
267        });
268
269        Self {
270            ratio,
271            constants,
272            last_ratio: None,
273        }
274    }
275
276    pub fn prepare(
277        &mut self,
278        device: &wgpu::Device,
279        encoder: &mut wgpu::CommandEncoder,
280        belt: &mut wgpu::util::StagingBelt,
281        pipeline: &Pipeline,
282        region_size: Size<u32>,
283    ) -> Transformation {
284        let targets = pipeline.targets(device, region_size);
285
286        let ratio = Ratio {
287            u: region_size.width as f32 / targets.size.width as f32,
288            v: region_size.height as f32 / targets.size.height as f32,
289            _padding: [0.0; 2],
290        };
291
292        if Some(ratio) != self.last_ratio {
293            belt.write_buffer(
294                encoder,
295                &self.ratio,
296                0,
297                NonZeroU64::new(std::mem::size_of::<Ratio>() as u64).expect("non-empty ratio"),
298                device,
299            )
300            .copy_from_slice(bytemuck::bytes_of(&ratio));
301
302            self.last_ratio = Some(ratio);
303        }
304
305        Transformation::orthographic(targets.size.width, targets.size.height)
306    }
307
308    pub fn render(
309        &self,
310        pipeline: &Pipeline,
311        encoder: &mut wgpu::CommandEncoder,
312        target: &wgpu::TextureView,
313    ) {
314        let mut render_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
315            label: Some("iced_wgpu::triangle::msaa render pass"),
316            color_attachments: &[Some(wgpu::RenderPassColorAttachment {
317                view: target,
318                depth_slice: None,
319                resolve_target: None,
320                ops: wgpu::Operations {
321                    load: wgpu::LoadOp::Load,
322                    store: wgpu::StoreOp::Store,
323                },
324            })],
325            depth_stencil_attachment: None,
326            timestamp_writes: None,
327            occlusion_query_set: None,
328        });
329
330        render_pass.set_pipeline(&pipeline.raw);
331        render_pass.set_bind_group(0, &self.constants, &[]);
332        render_pass.set_bind_group(
333            1,
334            &pipeline
335                .targets
336                .read()
337                .expect("Read MSAA targets")
338                .as_ref()
339                .unwrap()
340                .bind_group,
341            &[],
342        );
343        render_pass.draw(0..6, 0..1);
344    }
345}