iced_wgpu/
triangle.rs

1//! Draw meshes of triangles.
2mod msaa;
3
4use crate::Buffer;
5use crate::core::{Point, Rectangle, Size, Transformation, Vector};
6use crate::graphics::Antialiasing;
7use crate::graphics::mesh::{self, Mesh};
8
9use rustc_hash::FxHashMap;
10use std::collections::hash_map;
11use std::sync::atomic::{self, AtomicU64};
12use std::sync::{self, Arc};
13
14const INITIAL_INDEX_COUNT: usize = 1_000;
15const INITIAL_VERTEX_COUNT: usize = 1_000;
16
17pub type Batch = Vec<Item>;
18
19#[derive(Debug)]
20pub enum Item {
21    Group {
22        transformation: Transformation,
23        meshes: Vec<Mesh>,
24    },
25    Cached {
26        transformation: Transformation,
27        cache: Cache,
28    },
29}
30
31#[derive(Debug, Clone)]
32pub struct Cache {
33    id: Id,
34    batch: Arc<[Mesh]>,
35    version: usize,
36}
37
38#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
39pub struct Id(u64);
40
41impl Cache {
42    pub fn new(meshes: Vec<Mesh>) -> Option<Self> {
43        static NEXT_ID: AtomicU64 = AtomicU64::new(0);
44
45        if meshes.is_empty() {
46            return None;
47        }
48
49        Some(Self {
50            id: Id(NEXT_ID.fetch_add(1, atomic::Ordering::Relaxed)),
51            batch: Arc::from(meshes),
52            version: 0,
53        })
54    }
55
56    pub fn update(&mut self, meshes: Vec<Mesh>) {
57        self.batch = Arc::from(meshes);
58        self.version += 1;
59    }
60}
61
62#[derive(Debug)]
63struct Upload {
64    layer: Layer,
65    transformation: Transformation,
66    version: usize,
67    batch: sync::Weak<[Mesh]>,
68}
69
70#[derive(Debug, Default)]
71pub struct Storage {
72    uploads: FxHashMap<Id, Upload>,
73}
74
75impl Storage {
76    pub fn new() -> Self {
77        Self::default()
78    }
79
80    fn get(&self, cache: &Cache) -> Option<&Upload> {
81        if cache.batch.is_empty() {
82            return None;
83        }
84
85        self.uploads.get(&cache.id)
86    }
87
88    fn prepare(
89        &mut self,
90        device: &wgpu::Device,
91        encoder: &mut wgpu::CommandEncoder,
92        belt: &mut wgpu::util::StagingBelt,
93        solid: &solid::Pipeline,
94        gradient: &gradient::Pipeline,
95        cache: &Cache,
96        new_transformation: Transformation,
97    ) {
98        match self.uploads.entry(cache.id) {
99            hash_map::Entry::Occupied(entry) => {
100                let upload = entry.into_mut();
101
102                if !cache.batch.is_empty()
103                    && (upload.version != cache.version
104                        || upload.transformation != new_transformation)
105                {
106                    upload.layer.prepare(
107                        device,
108                        encoder,
109                        belt,
110                        solid,
111                        gradient,
112                        &cache.batch,
113                        new_transformation,
114                    );
115
116                    upload.batch = Arc::downgrade(&cache.batch);
117                    upload.version = cache.version;
118                    upload.transformation = new_transformation;
119                }
120            }
121            hash_map::Entry::Vacant(entry) => {
122                let mut layer = Layer::new(device, solid, gradient);
123
124                layer.prepare(
125                    device,
126                    encoder,
127                    belt,
128                    solid,
129                    gradient,
130                    &cache.batch,
131                    new_transformation,
132                );
133
134                let _ = entry.insert(Upload {
135                    layer,
136                    transformation: new_transformation,
137                    version: 0,
138                    batch: Arc::downgrade(&cache.batch),
139                });
140
141                log::debug!(
142                    "New mesh upload: {} (total: {})",
143                    cache.id.0,
144                    self.uploads.len()
145                );
146            }
147        }
148    }
149
150    pub fn trim(&mut self) {
151        self.uploads
152            .retain(|_id, upload| upload.batch.strong_count() > 0);
153    }
154}
155
156#[derive(Debug, Clone)]
157pub struct Pipeline {
158    msaa: Option<msaa::Pipeline>,
159    solid: solid::Pipeline,
160    gradient: gradient::Pipeline,
161}
162
163pub struct State {
164    msaa: Option<msaa::State>,
165    layers: Vec<Layer>,
166    prepare_layer: usize,
167    storage: Storage,
168}
169
170impl State {
171    pub fn new(device: &wgpu::Device, pipeline: &Pipeline) -> Self {
172        Self {
173            msaa: pipeline
174                .msaa
175                .as_ref()
176                .map(|pipeline| msaa::State::new(device, pipeline)),
177            layers: Vec::new(),
178            prepare_layer: 0,
179            storage: Storage::new(),
180        }
181    }
182
183    pub fn prepare(
184        &mut self,
185        pipeline: &Pipeline,
186        device: &wgpu::Device,
187        belt: &mut wgpu::util::StagingBelt,
188        encoder: &mut wgpu::CommandEncoder,
189        items: &[Item],
190        scale: Transformation,
191        target_size: Size<u32>,
192    ) {
193        let projection = if let Some((state, pipeline)) =
194            self.msaa.as_mut().zip(pipeline.msaa.as_ref())
195        {
196            state.prepare(device, encoder, belt, pipeline, target_size) * scale
197        } else {
198            Transformation::orthographic(target_size.width, target_size.height)
199                * scale
200        };
201
202        for item in items {
203            match item {
204                Item::Group {
205                    transformation,
206                    meshes,
207                } => {
208                    if self.layers.len() <= self.prepare_layer {
209                        self.layers.push(Layer::new(
210                            device,
211                            &pipeline.solid,
212                            &pipeline.gradient,
213                        ));
214                    }
215
216                    let layer = &mut self.layers[self.prepare_layer];
217                    layer.prepare(
218                        device,
219                        encoder,
220                        belt,
221                        &pipeline.solid,
222                        &pipeline.gradient,
223                        meshes,
224                        projection * *transformation,
225                    );
226
227                    self.prepare_layer += 1;
228                }
229                Item::Cached {
230                    transformation,
231                    cache,
232                } => {
233                    self.storage.prepare(
234                        device,
235                        encoder,
236                        belt,
237                        &pipeline.solid,
238                        &pipeline.gradient,
239                        cache,
240                        projection * *transformation,
241                    );
242                }
243            }
244        }
245    }
246
247    pub fn render(
248        &mut self,
249        pipeline: &Pipeline,
250        encoder: &mut wgpu::CommandEncoder,
251        target: &wgpu::TextureView,
252        start: usize,
253        batch: &Batch,
254        bounds: Rectangle,
255        screen_transformation: Transformation,
256    ) -> usize {
257        let mut layer_count = 0;
258
259        let items = batch.iter().filter_map(|item| match item {
260            Item::Group {
261                transformation,
262                meshes,
263            } => {
264                let layer = &self.layers[start + layer_count];
265                layer_count += 1;
266
267                Some((
268                    layer,
269                    meshes.as_slice(),
270                    screen_transformation * *transformation,
271                ))
272            }
273            Item::Cached {
274                transformation,
275                cache,
276            } => {
277                let upload = self.storage.get(cache)?;
278
279                Some((
280                    &upload.layer,
281                    &cache.batch,
282                    screen_transformation * *transformation,
283                ))
284            }
285        });
286
287        render(
288            encoder,
289            target,
290            self.msaa.as_ref().zip(pipeline.msaa.as_ref()),
291            &pipeline.solid,
292            &pipeline.gradient,
293            bounds,
294            items,
295        );
296
297        layer_count
298    }
299
300    pub fn trim(&mut self) {
301        self.storage.trim();
302
303        self.prepare_layer = 0;
304    }
305}
306
307impl Pipeline {
308    pub fn new(
309        device: &wgpu::Device,
310        format: wgpu::TextureFormat,
311        antialiasing: Option<Antialiasing>,
312    ) -> Pipeline {
313        Pipeline {
314            msaa: antialiasing.map(|a| msaa::Pipeline::new(device, format, a)),
315            solid: solid::Pipeline::new(device, format, antialiasing),
316            gradient: gradient::Pipeline::new(device, format, antialiasing),
317        }
318    }
319}
320
321fn render<'a>(
322    encoder: &mut wgpu::CommandEncoder,
323    target: &wgpu::TextureView,
324    mut msaa: Option<(&msaa::State, &msaa::Pipeline)>,
325    solid: &solid::Pipeline,
326    gradient: &gradient::Pipeline,
327    bounds: Rectangle,
328    group: impl Iterator<Item = (&'a Layer, &'a [Mesh], Transformation)>,
329) {
330    {
331        let mut render_pass = if let Some((_state, pipeline)) = &mut msaa {
332            pipeline.render_pass(encoder)
333        } else {
334            encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
335                label: Some("iced_wgpu.triangle.render_pass"),
336                color_attachments: &[Some(wgpu::RenderPassColorAttachment {
337                    view: target,
338                    resolve_target: None,
339                    ops: wgpu::Operations {
340                        load: wgpu::LoadOp::Load,
341                        store: wgpu::StoreOp::Store,
342                    },
343                })],
344                depth_stencil_attachment: None,
345                timestamp_writes: None,
346                occlusion_query_set: None,
347            })
348        };
349
350        for (layer, meshes, transformation) in group {
351            layer.render(
352                solid,
353                gradient,
354                meshes,
355                bounds,
356                transformation,
357                &mut render_pass,
358            );
359        }
360    }
361
362    if let Some((state, pipeline)) = msaa {
363        state.render(pipeline, encoder, target);
364    }
365}
366
367#[derive(Debug)]
368pub struct Layer {
369    index_buffer: Buffer<u32>,
370    index_strides: Vec<u32>,
371    solid: solid::Layer,
372    gradient: gradient::Layer,
373}
374
375impl Layer {
376    fn new(
377        device: &wgpu::Device,
378        solid: &solid::Pipeline,
379        gradient: &gradient::Pipeline,
380    ) -> Self {
381        Self {
382            index_buffer: Buffer::new(
383                device,
384                "iced_wgpu.triangle.index_buffer",
385                INITIAL_INDEX_COUNT,
386                wgpu::BufferUsages::INDEX | wgpu::BufferUsages::COPY_DST,
387            ),
388            index_strides: Vec::new(),
389            solid: solid::Layer::new(device, &solid.constants_layout),
390            gradient: gradient::Layer::new(device, &gradient.constants_layout),
391        }
392    }
393
394    fn prepare(
395        &mut self,
396        device: &wgpu::Device,
397        encoder: &mut wgpu::CommandEncoder,
398        belt: &mut wgpu::util::StagingBelt,
399        solid: &solid::Pipeline,
400        gradient: &gradient::Pipeline,
401        meshes: &[Mesh],
402        transformation: Transformation,
403    ) {
404        // Count the total amount of vertices & indices we need to handle
405        let count = mesh::attribute_count_of(meshes);
406
407        // Then we ensure the current attribute buffers are big enough, resizing if necessary.
408        // We are not currently using the return value of these functions as we have no system in
409        // place to calculate mesh diff, or to know whether or not that would be more performant for
410        // the majority of use cases. Therefore we will write GPU data every frame (for now).
411        let _ = self.index_buffer.resize(device, count.indices);
412        let _ = self.solid.vertices.resize(device, count.solid_vertices);
413        let _ = self
414            .gradient
415            .vertices
416            .resize(device, count.gradient_vertices);
417
418        if self.solid.uniforms.resize(device, count.solids) {
419            self.solid.constants = solid::Layer::bind_group(
420                device,
421                &self.solid.uniforms.raw,
422                &solid.constants_layout,
423            );
424        }
425
426        if self.gradient.uniforms.resize(device, count.gradients) {
427            self.gradient.constants = gradient::Layer::bind_group(
428                device,
429                &self.gradient.uniforms.raw,
430                &gradient.constants_layout,
431            );
432        }
433
434        self.index_strides.clear();
435        self.index_buffer.clear();
436        self.solid.vertices.clear();
437        self.solid.uniforms.clear();
438        self.gradient.vertices.clear();
439        self.gradient.uniforms.clear();
440
441        let mut solid_vertex_offset = 0;
442        let mut solid_uniform_offset = 0;
443        let mut gradient_vertex_offset = 0;
444        let mut gradient_uniform_offset = 0;
445        let mut index_offset = 0;
446
447        for mesh in meshes {
448            let clip_bounds = mesh.clip_bounds() * transformation;
449            let snap_distance = clip_bounds
450                .snap()
451                .map(|snapped_bounds| {
452                    Point::new(snapped_bounds.x as f32, snapped_bounds.y as f32)
453                        - clip_bounds.position()
454                })
455                .unwrap_or(Vector::ZERO);
456
457            let uniforms = Uniforms::new(
458                transformation
459                    * mesh.transformation()
460                    * Transformation::translate(
461                        snap_distance.x,
462                        snap_distance.y,
463                    ),
464            );
465
466            let indices = mesh.indices();
467
468            index_offset += self.index_buffer.write(
469                device,
470                encoder,
471                belt,
472                index_offset,
473                indices,
474            );
475
476            self.index_strides.push(indices.len() as u32);
477
478            match mesh {
479                Mesh::Solid { buffers, .. } => {
480                    solid_vertex_offset += self.solid.vertices.write(
481                        device,
482                        encoder,
483                        belt,
484                        solid_vertex_offset,
485                        &buffers.vertices,
486                    );
487
488                    solid_uniform_offset += self.solid.uniforms.write(
489                        device,
490                        encoder,
491                        belt,
492                        solid_uniform_offset,
493                        &[uniforms],
494                    );
495                }
496                Mesh::Gradient { buffers, .. } => {
497                    gradient_vertex_offset += self.gradient.vertices.write(
498                        device,
499                        encoder,
500                        belt,
501                        gradient_vertex_offset,
502                        &buffers.vertices,
503                    );
504
505                    gradient_uniform_offset += self.gradient.uniforms.write(
506                        device,
507                        encoder,
508                        belt,
509                        gradient_uniform_offset,
510                        &[uniforms],
511                    );
512                }
513            }
514        }
515    }
516
517    fn render<'a>(
518        &'a self,
519        solid: &'a solid::Pipeline,
520        gradient: &'a gradient::Pipeline,
521        meshes: &[Mesh],
522        bounds: Rectangle,
523        transformation: Transformation,
524        render_pass: &mut wgpu::RenderPass<'a>,
525    ) {
526        let mut num_solids = 0;
527        let mut num_gradients = 0;
528        let mut last_is_solid = None;
529
530        for (index, mesh) in meshes.iter().enumerate() {
531            let Some(clip_bounds) = bounds
532                .intersection(&(mesh.clip_bounds() * transformation))
533                .and_then(Rectangle::snap)
534            else {
535                match mesh {
536                    Mesh::Solid { .. } => {
537                        num_solids += 1;
538                    }
539                    Mesh::Gradient { .. } => {
540                        num_gradients += 1;
541                    }
542                }
543                continue;
544            };
545
546            render_pass.set_scissor_rect(
547                clip_bounds.x,
548                clip_bounds.y,
549                clip_bounds.width,
550                clip_bounds.height,
551            );
552
553            match mesh {
554                Mesh::Solid { .. } => {
555                    if !last_is_solid.unwrap_or(false) {
556                        render_pass.set_pipeline(&solid.pipeline);
557
558                        last_is_solid = Some(true);
559                    }
560
561                    render_pass.set_bind_group(
562                        0,
563                        &self.solid.constants,
564                        &[(num_solids * std::mem::size_of::<Uniforms>())
565                            as u32],
566                    );
567
568                    render_pass.set_vertex_buffer(
569                        0,
570                        self.solid.vertices.slice_from_index(num_solids),
571                    );
572
573                    num_solids += 1;
574                }
575                Mesh::Gradient { .. } => {
576                    if last_is_solid.unwrap_or(true) {
577                        render_pass.set_pipeline(&gradient.pipeline);
578
579                        last_is_solid = Some(false);
580                    }
581
582                    render_pass.set_bind_group(
583                        0,
584                        &self.gradient.constants,
585                        &[(num_gradients * std::mem::size_of::<Uniforms>())
586                            as u32],
587                    );
588
589                    render_pass.set_vertex_buffer(
590                        0,
591                        self.gradient.vertices.slice_from_index(num_gradients),
592                    );
593
594                    num_gradients += 1;
595                }
596            };
597
598            render_pass.set_index_buffer(
599                self.index_buffer.slice_from_index(index),
600                wgpu::IndexFormat::Uint32,
601            );
602
603            render_pass.draw_indexed(0..self.index_strides[index], 0, 0..1);
604        }
605    }
606}
607
608fn fragment_target(
609    texture_format: wgpu::TextureFormat,
610) -> wgpu::ColorTargetState {
611    wgpu::ColorTargetState {
612        format: texture_format,
613        blend: Some(wgpu::BlendState::PREMULTIPLIED_ALPHA_BLENDING),
614        write_mask: wgpu::ColorWrites::ALL,
615    }
616}
617
618fn primitive_state() -> wgpu::PrimitiveState {
619    wgpu::PrimitiveState {
620        topology: wgpu::PrimitiveTopology::TriangleList,
621        front_face: wgpu::FrontFace::Cw,
622        ..Default::default()
623    }
624}
625
626fn multisample_state(
627    antialiasing: Option<Antialiasing>,
628) -> wgpu::MultisampleState {
629    wgpu::MultisampleState {
630        count: antialiasing.map(Antialiasing::sample_count).unwrap_or(1),
631        mask: !0,
632        alpha_to_coverage_enabled: false,
633    }
634}
635
636#[derive(Debug, Clone, Copy, bytemuck::Pod, bytemuck::Zeroable)]
637#[repr(C)]
638pub struct Uniforms {
639    transform: [f32; 16],
640    /// Uniform values must be 256-aligned;
641    /// see: [`wgpu::Limits`] `min_uniform_buffer_offset_alignment`.
642    _padding: [f32; 48],
643}
644
645impl Uniforms {
646    pub fn new(transform: Transformation) -> Self {
647        Self {
648            transform: transform.into(),
649            _padding: [0.0; 48],
650        }
651    }
652
653    pub fn entry() -> wgpu::BindGroupLayoutEntry {
654        wgpu::BindGroupLayoutEntry {
655            binding: 0,
656            visibility: wgpu::ShaderStages::VERTEX_FRAGMENT,
657            ty: wgpu::BindingType::Buffer {
658                ty: wgpu::BufferBindingType::Uniform,
659                has_dynamic_offset: true,
660                min_binding_size: wgpu::BufferSize::new(
661                    std::mem::size_of::<Self>() as u64,
662                ),
663            },
664            count: None,
665        }
666    }
667
668    pub fn min_size() -> Option<wgpu::BufferSize> {
669        wgpu::BufferSize::new(std::mem::size_of::<Self>() as u64)
670    }
671}
672
673mod solid {
674    use crate::Buffer;
675    use crate::graphics::Antialiasing;
676    use crate::graphics::mesh;
677    use crate::triangle;
678
679    #[derive(Debug, Clone)]
680    pub struct Pipeline {
681        pub pipeline: wgpu::RenderPipeline,
682        pub constants_layout: wgpu::BindGroupLayout,
683    }
684
685    #[derive(Debug)]
686    pub struct Layer {
687        pub vertices: Buffer<mesh::SolidVertex2D>,
688        pub uniforms: Buffer<triangle::Uniforms>,
689        pub constants: wgpu::BindGroup,
690    }
691
692    impl Layer {
693        pub fn new(
694            device: &wgpu::Device,
695            constants_layout: &wgpu::BindGroupLayout,
696        ) -> Self {
697            let vertices = Buffer::new(
698                device,
699                "iced_wgpu.triangle.solid.vertex_buffer",
700                triangle::INITIAL_VERTEX_COUNT,
701                wgpu::BufferUsages::VERTEX | wgpu::BufferUsages::COPY_DST,
702            );
703
704            let uniforms = Buffer::new(
705                device,
706                "iced_wgpu.triangle.solid.uniforms",
707                1,
708                wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
709            );
710
711            let constants =
712                Self::bind_group(device, &uniforms.raw, constants_layout);
713
714            Self {
715                vertices,
716                uniforms,
717                constants,
718            }
719        }
720
721        pub fn bind_group(
722            device: &wgpu::Device,
723            buffer: &wgpu::Buffer,
724            layout: &wgpu::BindGroupLayout,
725        ) -> wgpu::BindGroup {
726            device.create_bind_group(&wgpu::BindGroupDescriptor {
727                label: Some("iced_wgpu.triangle.solid.bind_group"),
728                layout,
729                entries: &[wgpu::BindGroupEntry {
730                    binding: 0,
731                    resource: wgpu::BindingResource::Buffer(
732                        wgpu::BufferBinding {
733                            buffer,
734                            offset: 0,
735                            size: triangle::Uniforms::min_size(),
736                        },
737                    ),
738                }],
739            })
740        }
741    }
742
743    impl Pipeline {
744        pub fn new(
745            device: &wgpu::Device,
746            format: wgpu::TextureFormat,
747            antialiasing: Option<Antialiasing>,
748        ) -> Self {
749            let constants_layout = device.create_bind_group_layout(
750                &wgpu::BindGroupLayoutDescriptor {
751                    label: Some("iced_wgpu.triangle.solid.bind_group_layout"),
752                    entries: &[triangle::Uniforms::entry()],
753                },
754            );
755
756            let layout = device.create_pipeline_layout(
757                &wgpu::PipelineLayoutDescriptor {
758                    label: Some("iced_wgpu.triangle.solid.pipeline_layout"),
759                    bind_group_layouts: &[&constants_layout],
760                    push_constant_ranges: &[],
761                },
762            );
763
764            let shader =
765                device.create_shader_module(wgpu::ShaderModuleDescriptor {
766                    label: Some("iced_wgpu.triangle.solid.shader"),
767                    source: wgpu::ShaderSource::Wgsl(
768                        std::borrow::Cow::Borrowed(concat!(
769                            include_str!("shader/triangle.wgsl"),
770                            "\n",
771                            include_str!("shader/triangle/solid.wgsl"),
772                            "\n",
773                            include_str!("shader/color.wgsl"),
774                        )),
775                    ),
776                });
777
778            let pipeline =
779                device.create_render_pipeline(
780                    &wgpu::RenderPipelineDescriptor {
781                        label: Some("iced_wgpu::triangle::solid pipeline"),
782                        layout: Some(&layout),
783                        vertex: wgpu::VertexState {
784                            module: &shader,
785                            entry_point: Some("solid_vs_main"),
786                            buffers: &[wgpu::VertexBufferLayout {
787                                array_stride: std::mem::size_of::<
788                                    mesh::SolidVertex2D,
789                                >(
790                                )
791                                    as u64,
792                                step_mode: wgpu::VertexStepMode::Vertex,
793                                attributes: &wgpu::vertex_attr_array!(
794                                    // Position
795                                    0 => Float32x2,
796                                    // Color
797                                    1 => Float32x4,
798                                ),
799                            }],
800                            compilation_options:
801                                wgpu::PipelineCompilationOptions::default(),
802                        },
803                        fragment: Some(wgpu::FragmentState {
804                            module: &shader,
805                            entry_point: Some("solid_fs_main"),
806                            targets: &[Some(triangle::fragment_target(format))],
807                            compilation_options:
808                                wgpu::PipelineCompilationOptions::default(),
809                        }),
810                        primitive: triangle::primitive_state(),
811                        depth_stencil: None,
812                        multisample: triangle::multisample_state(antialiasing),
813                        multiview: None,
814                        cache: None,
815                    },
816                );
817
818            Self {
819                pipeline,
820                constants_layout,
821            }
822        }
823    }
824}
825
826mod gradient {
827    use crate::Buffer;
828    use crate::graphics::Antialiasing;
829    use crate::graphics::color;
830    use crate::graphics::mesh;
831    use crate::triangle;
832
833    #[derive(Debug, Clone)]
834    pub struct Pipeline {
835        pub pipeline: wgpu::RenderPipeline,
836        pub constants_layout: wgpu::BindGroupLayout,
837    }
838
839    #[derive(Debug)]
840    pub struct Layer {
841        pub vertices: Buffer<mesh::GradientVertex2D>,
842        pub uniforms: Buffer<triangle::Uniforms>,
843        pub constants: wgpu::BindGroup,
844    }
845
846    impl Layer {
847        pub fn new(
848            device: &wgpu::Device,
849            constants_layout: &wgpu::BindGroupLayout,
850        ) -> Self {
851            let vertices = Buffer::new(
852                device,
853                "iced_wgpu.triangle.gradient.vertex_buffer",
854                triangle::INITIAL_VERTEX_COUNT,
855                wgpu::BufferUsages::VERTEX | wgpu::BufferUsages::COPY_DST,
856            );
857
858            let uniforms = Buffer::new(
859                device,
860                "iced_wgpu.triangle.gradient.uniforms",
861                1,
862                wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
863            );
864
865            let constants =
866                Self::bind_group(device, &uniforms.raw, constants_layout);
867
868            Self {
869                vertices,
870                uniforms,
871                constants,
872            }
873        }
874
875        pub fn bind_group(
876            device: &wgpu::Device,
877            uniform_buffer: &wgpu::Buffer,
878            layout: &wgpu::BindGroupLayout,
879        ) -> wgpu::BindGroup {
880            device.create_bind_group(&wgpu::BindGroupDescriptor {
881                label: Some("iced_wgpu.triangle.gradient.bind_group"),
882                layout,
883                entries: &[wgpu::BindGroupEntry {
884                    binding: 0,
885                    resource: wgpu::BindingResource::Buffer(
886                        wgpu::BufferBinding {
887                            buffer: uniform_buffer,
888                            offset: 0,
889                            size: triangle::Uniforms::min_size(),
890                        },
891                    ),
892                }],
893            })
894        }
895    }
896
897    impl Pipeline {
898        pub fn new(
899            device: &wgpu::Device,
900            format: wgpu::TextureFormat,
901            antialiasing: Option<Antialiasing>,
902        ) -> Self {
903            let constants_layout = device.create_bind_group_layout(
904                &wgpu::BindGroupLayoutDescriptor {
905                    label: Some(
906                        "iced_wgpu.triangle.gradient.bind_group_layout",
907                    ),
908                    entries: &[triangle::Uniforms::entry()],
909                },
910            );
911
912            let layout = device.create_pipeline_layout(
913                &wgpu::PipelineLayoutDescriptor {
914                    label: Some("iced_wgpu.triangle.gradient.pipeline_layout"),
915                    bind_group_layouts: &[&constants_layout],
916                    push_constant_ranges: &[],
917                },
918            );
919
920            let shader =
921                device.create_shader_module(wgpu::ShaderModuleDescriptor {
922                    label: Some("iced_wgpu.triangle.gradient.shader"),
923                    source: wgpu::ShaderSource::Wgsl(
924                        std::borrow::Cow::Borrowed(
925                            if color::GAMMA_CORRECTION {
926                                concat!(
927                                    include_str!("shader/triangle.wgsl"),
928                                    "\n",
929                                    include_str!(
930                                        "shader/triangle/gradient.wgsl"
931                                    ),
932                                    "\n",
933                                    include_str!("shader/color.wgsl"),
934                                    "\n",
935                                    include_str!("shader/color/oklab.wgsl")
936                                )
937                            } else {
938                                concat!(
939                                    include_str!("shader/triangle.wgsl"),
940                                    "\n",
941                                    include_str!(
942                                        "shader/triangle/gradient.wgsl"
943                                    ),
944                                    "\n",
945                                    include_str!("shader/color.wgsl"),
946                                    "\n",
947                                    include_str!(
948                                        "shader/color/linear_rgb.wgsl"
949                                    )
950                                )
951                            },
952                        ),
953                    ),
954                });
955
956            let pipeline = device.create_render_pipeline(
957                &wgpu::RenderPipelineDescriptor {
958                    label: Some("iced_wgpu.triangle.gradient.pipeline"),
959                    layout: Some(&layout),
960                    vertex: wgpu::VertexState {
961                        module: &shader,
962                        entry_point: Some("gradient_vs_main"),
963                        buffers: &[wgpu::VertexBufferLayout {
964                            array_stride: std::mem::size_of::<
965                                mesh::GradientVertex2D,
966                            >()
967                                as u64,
968                            step_mode: wgpu::VertexStepMode::Vertex,
969                            attributes: &wgpu::vertex_attr_array!(
970                                // Position
971                                0 => Float32x2,
972                                // Colors 1-2
973                                1 => Uint32x4,
974                                // Colors 3-4
975                                2 => Uint32x4,
976                                // Colors 5-6
977                                3 => Uint32x4,
978                                // Colors 7-8
979                                4 => Uint32x4,
980                                // Offsets
981                                5 => Uint32x4,
982                                // Direction
983                                6 => Float32x4
984                            ),
985                        }],
986                        compilation_options:
987                            wgpu::PipelineCompilationOptions::default(),
988                    },
989                    fragment: Some(wgpu::FragmentState {
990                        module: &shader,
991                        entry_point: Some("gradient_fs_main"),
992                        targets: &[Some(triangle::fragment_target(format))],
993                        compilation_options:
994                            wgpu::PipelineCompilationOptions::default(),
995                    }),
996                    primitive: triangle::primitive_state(),
997                    depth_stencil: None,
998                    multisample: triangle::multisample_state(antialiasing),
999                    multiview: None,
1000                    cache: None,
1001                },
1002            );
1003
1004            Self {
1005                pipeline,
1006                constants_layout,
1007            }
1008        }
1009    }
1010}