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