From 33c3c0c0aa774bb7462e3c42aa04c591a66376a7 Mon Sep 17 00:00:00 2001
From: Héctor Ramón Jiménez <hector0193@gmail.com>
Date: Mon, 14 Nov 2022 00:02:42 +0100
Subject: Group all solid triangles independently of color

---
 wgpu/src/buffer/dynamic.rs    |  15 +-
 wgpu/src/buffer/static.rs     |   2 +-
 wgpu/src/lib.rs               |   2 +-
 wgpu/src/shader/solid.wgsl    |  29 +-
 wgpu/src/shader/triangle.wgsl |  30 --
 wgpu/src/triangle.rs          | 671 ++++++++++++++++++++++++++++++++++--------
 wgpu/src/triangle/gradient.rs | 268 -----------------
 wgpu/src/triangle/solid.rs    | 170 -----------
 8 files changed, 582 insertions(+), 605 deletions(-)
 delete mode 100644 wgpu/src/shader/triangle.wgsl
 delete mode 100644 wgpu/src/triangle/gradient.rs
 delete mode 100644 wgpu/src/triangle/solid.rs

(limited to 'wgpu')

diff --git a/wgpu/src/buffer/dynamic.rs b/wgpu/src/buffer/dynamic.rs
index 2a675d81..18be03dd 100644
--- a/wgpu/src/buffer/dynamic.rs
+++ b/wgpu/src/buffer/dynamic.rs
@@ -1,10 +1,13 @@
 //! Utilities for uniform buffer operations.
 use encase::private::WriteInto;
 use encase::ShaderType;
+
+use std::fmt;
 use std::marker::PhantomData;
 
 /// A dynamic buffer is any type of buffer which does not have a static offset.
-pub(crate) struct Buffer<T: ShaderType> {
+#[derive(Debug)]
+pub struct Buffer<T: ShaderType> {
     offsets: Vec<wgpu::DynamicOffset>,
     cpu: Internal,
     gpu: wgpu::Buffer,
@@ -204,3 +207,13 @@ impl Internal {
         }
     }
 }
+
+impl fmt::Debug for Internal {
+    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+        match self {
+            Self::Uniform(_) => write!(f, "Internal::Uniform(_)"),
+            #[cfg(not(target_arch = "wasm32"))]
+            Self::Storage(_) => write!(f, "Internal::Storage(_)"),
+        }
+    }
+}
diff --git a/wgpu/src/buffer/static.rs b/wgpu/src/buffer/static.rs
index cf06790c..ef87422f 100644
--- a/wgpu/src/buffer/static.rs
+++ b/wgpu/src/buffer/static.rs
@@ -8,7 +8,7 @@ const DEFAULT_STATIC_BUFFER_COUNT: wgpu::BufferAddress = 128;
 /// A generic buffer struct useful for items which have no alignment requirements
 /// (e.g. Vertex, Index buffers) & no dynamic offsets.
 #[derive(Debug)]
-pub(crate) struct Buffer<T> {
+pub struct Buffer<T> {
     //stored sequentially per mesh iteration; refers to the offset index in the GPU buffer
     offsets: Vec<wgpu::BufferAddress>,
     label: &'static str,
diff --git a/wgpu/src/lib.rs b/wgpu/src/lib.rs
index f4436e9d..74152945 100644
--- a/wgpu/src/lib.rs
+++ b/wgpu/src/lib.rs
@@ -39,13 +39,13 @@
 #![cfg_attr(docsrs, feature(doc_cfg))]
 
 pub mod settings;
-pub mod triangle;
 pub mod window;
 
 mod backend;
 mod buffer;
 mod quad;
 mod text;
+mod triangle;
 
 pub use iced_graphics::{Antialiasing, Color, Error, Primitive, Viewport};
 pub use iced_native::Theme;
diff --git a/wgpu/src/shader/solid.wgsl b/wgpu/src/shader/solid.wgsl
index 68a8fea3..b24402f8 100644
--- a/wgpu/src/shader/solid.wgsl
+++ b/wgpu/src/shader/solid.wgsl
@@ -1,17 +1,30 @@
-struct Uniforms {
+struct Globals {
     transform: mat4x4<f32>,
-    color: vec4<f32>
 }
 
-@group(0) @binding(0)
-var<uniform> uniforms: Uniforms;
+@group(0) @binding(0) var<uniform> globals: Globals;
+
+struct VertexInput {
+    @location(0) position: vec2<f32>,
+    @location(1) color: vec4<f32>,
+}
+
+struct VertexOutput {
+    @builtin(position) position: vec4<f32>,
+    @location(0) color: vec4<f32>,
+}
 
 @vertex
-fn vs_main(@location(0) input: vec2<f32>) -> @builtin(position) vec4<f32> {
-    return uniforms.transform * vec4<f32>(input.xy, 0.0, 1.0);
+fn vs_main(input: VertexInput) -> VertexOutput {
+    var out: VertexOutput;
+
+    out.color = input.color;
+    out.position = globals.transform * vec4<f32>(input.position, 0.0, 1.0);
+
+    return out;
 }
 
 @fragment
-fn fs_main() -> @location(0) vec4<f32> {
-    return uniforms.color;
+fn fs_main(input: VertexOutput) -> @location(0) vec4<f32> {
+    return input.color;
 }
diff --git a/wgpu/src/shader/triangle.wgsl b/wgpu/src/shader/triangle.wgsl
deleted file mode 100644
index b24402f8..00000000
--- a/wgpu/src/shader/triangle.wgsl
+++ /dev/null
@@ -1,30 +0,0 @@
-struct Globals {
-    transform: mat4x4<f32>,
-}
-
-@group(0) @binding(0) var<uniform> globals: Globals;
-
-struct VertexInput {
-    @location(0) position: vec2<f32>,
-    @location(1) color: vec4<f32>,
-}
-
-struct VertexOutput {
-    @builtin(position) position: vec4<f32>,
-    @location(0) color: vec4<f32>,
-}
-
-@vertex
-fn vs_main(input: VertexInput) -> VertexOutput {
-    var out: VertexOutput;
-
-    out.color = input.color;
-    out.position = globals.transform * vec4<f32>(input.position, 0.0, 1.0);
-
-    return out;
-}
-
-@fragment
-fn fs_main(input: VertexOutput) -> @location(0) vec4<f32> {
-    return input.color;
-}
diff --git a/wgpu/src/triangle.rs b/wgpu/src/triangle.rs
index c51b5339..b33b488a 100644
--- a/wgpu/src/triangle.rs
+++ b/wgpu/src/triangle.rs
@@ -1,71 +1,27 @@
 //! Draw meshes of triangles.
-#[cfg(not(target_arch = "wasm32"))]
-mod gradient;
 mod msaa;
-mod solid;
 
 use crate::buffer::r#static::Buffer;
 use crate::settings;
 use crate::Transformation;
 
 use iced_graphics::layer::mesh::{self, Mesh};
-use iced_graphics::triangle::{self, Vertex2D};
+use iced_graphics::triangle::ColoredVertex2D;
 use iced_graphics::Size;
 
-use core::fmt;
-use std::fmt::Formatter;
-
-/// Triangle pipeline for all mesh layers in a [`iced_graphics::Canvas`] widget.
 #[derive(Debug)]
-pub(crate) struct Pipeline {
+pub struct Pipeline {
     blit: Option<msaa::Blit>,
-    vertex_buffer: Buffer<Vertex2D>,
     index_buffer: Buffer<u32>,
     index_strides: Vec<u32>,
-    pipelines: PipelineList,
-}
-
-/// Supported triangle pipelines for different fills.
-pub(crate) struct PipelineList {
     solid: solid::Pipeline,
+
     /// Gradients are currently not supported on WASM targets due to their need of storage buffers.
     #[cfg(not(target_arch = "wasm32"))]
     gradient: gradient::Pipeline,
 }
 
-impl fmt::Debug for PipelineList {
-    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
-        f.debug_struct("TrianglePipelines").finish()
-    }
-}
-
-impl PipelineList {
-    /// Resets each pipeline's buffers.
-    fn clear(&mut self) {
-        self.solid.buffer.clear();
-        #[cfg(not(target_arch = "wasm32"))]
-        {
-            self.gradient.uniform_buffer.clear();
-            self.gradient.storage_buffer.clear();
-        }
-    }
-
-    /// Writes the contents of each pipeline's CPU buffer to the GPU, resizing the GPU buffer
-    /// beforehand if necessary.
-    fn write(
-        &mut self,
-        device: &wgpu::Device,
-        staging_belt: &mut wgpu::util::StagingBelt,
-        encoder: &mut wgpu::CommandEncoder,
-    ) {
-        self.solid.write(device, staging_belt, encoder);
-        #[cfg(not(target_arch = "wasm32"))]
-        self.gradient.write(device, staging_belt, encoder);
-    }
-}
-
 impl Pipeline {
-    /// Creates supported pipelines, listed in [TrianglePipelines].
     pub fn new(
         device: &wgpu::Device,
         format: wgpu::TextureFormat,
@@ -73,26 +29,19 @@ impl Pipeline {
     ) -> Pipeline {
         Pipeline {
             blit: antialiasing.map(|a| msaa::Blit::new(device, format, a)),
-            vertex_buffer: Buffer::new(
-                device,
-                "iced_wgpu::triangle vertex buffer",
-                wgpu::BufferUsages::VERTEX | wgpu::BufferUsages::COPY_DST,
-            ),
             index_buffer: Buffer::new(
                 device,
                 "iced_wgpu::triangle vertex buffer",
                 wgpu::BufferUsages::INDEX | wgpu::BufferUsages::COPY_DST,
             ),
             index_strides: Vec::new(),
-            pipelines: PipelineList {
-                solid: solid::Pipeline::new(device, format, antialiasing),
-                #[cfg(not(target_arch = "wasm32"))]
-                gradient: gradient::Pipeline::new(device, format, antialiasing),
-            },
+            solid: solid::Pipeline::new(device, format, antialiasing),
+
+            #[cfg(not(target_arch = "wasm32"))]
+            gradient: gradient::Pipeline::new(device, format, antialiasing),
         }
     }
 
-    /// Draws the contents of the current layer's meshes to the [target].
     pub fn draw(
         &mut self,
         device: &wgpu::Device,
@@ -104,68 +53,185 @@ impl Pipeline {
         scale_factor: f32,
         meshes: &[Mesh<'_>],
     ) {
-        //count the total amount of vertices & indices we need to handle
-        let (total_vertices, total_indices) = mesh::attribute_count_of(meshes);
+        // Count the total amount of vertices & indices we need to handle
+        let count = mesh::attribute_count_of(meshes);
 
         // Then we ensure the current attribute buffers are big enough, resizing if necessary.
+        // We are not currently using the return value of these functions as we have no system in
+        // place to calculate mesh diff, or to know whether or not that would be more performant for
+        // the majority of use cases. Therefore we will write GPU data every frame (for now).
+        let _ = self.index_buffer.resize(device, count.indices);
+        let _ = self.solid.vertices.resize(device, count.solid_vertices);
 
-        //We are not currently using the return value of these functions as we have no system in
-        //place to calculate mesh diff, or to know whether or not that would be more performant for
-        //the majority of use cases. Therefore we will write GPU data every frame (for now).
-        let _ = self.vertex_buffer.resize(device, total_vertices);
-        let _ = self.index_buffer.resize(device, total_indices);
+        #[cfg(not(target_arch = "wasm32"))]
+        let _ = self
+            .gradient
+            .vertices
+            .resize(device, count.gradient_vertices);
 
-        //prepare dynamic buffers & data store for writing
+        // Prepare dynamic buffers & data store for writing
         self.index_strides.clear();
-        self.pipelines.clear();
+        self.solid.vertices.clear();
+        self.solid.uniforms.clear();
+
+        #[cfg(not(target_arch = "wasm32"))]
+        {
+            self.gradient.uniforms.clear();
+            self.gradient.vertices.clear();
+            self.gradient.storage.clear();
+        }
 
-        let mut vertex_offset = 0;
+        let mut solid_vertex_offset = 0;
         let mut index_offset = 0;
 
+        #[cfg(not(target_arch = "wasm32"))]
+        let mut gradient_vertex_offset = 0;
+
         for mesh in meshes {
-            let transform = transformation
-                * Transformation::translate(mesh.origin.x, mesh.origin.y);
+            let origin = mesh.origin();
+            let indices = mesh.indices();
 
-            //write to both buffers
-            let new_vertex_offset = self.vertex_buffer.write(
-                device,
-                staging_belt,
-                encoder,
-                vertex_offset,
-                &mesh.buffers.vertices,
-            );
+            let transform =
+                transformation * Transformation::translate(origin.x, origin.y);
 
             let new_index_offset = self.index_buffer.write(
                 device,
                 staging_belt,
                 encoder,
                 index_offset,
-                &mesh.buffers.indices,
+                indices,
             );
 
-            vertex_offset += new_vertex_offset;
             index_offset += new_index_offset;
-
-            self.index_strides.push(mesh.buffers.indices.len() as u32);
+            self.index_strides.push(indices.len() as u32);
 
             //push uniform data to CPU buffers
-            match mesh.style {
-                triangle::Style::Solid(color) => {
-                    self.pipelines.solid.push(transform, color);
+            match mesh {
+                Mesh::Solid { buffers, .. } => {
+                    self.solid.uniforms.push(&solid::Uniforms::new(transform));
+
+                    let written_bytes = self.solid.vertices.write(
+                        device,
+                        staging_belt,
+                        encoder,
+                        solid_vertex_offset,
+                        &buffers.vertices,
+                    );
+
+                    solid_vertex_offset += written_bytes;
                 }
                 #[cfg(not(target_arch = "wasm32"))]
-                triangle::Style::Gradient(gradient) => {
-                    self.pipelines.gradient.push(transform, gradient);
+                Mesh::Gradient {
+                    buffers, gradient, ..
+                } => {
+                    let written_bytes = self.gradient.vertices.write(
+                        device,
+                        staging_belt,
+                        encoder,
+                        gradient_vertex_offset,
+                        &buffers.vertices,
+                    );
+
+                    gradient_vertex_offset += written_bytes;
+
+                    match gradient {
+                        iced_graphics::Gradient::Linear(linear) => {
+                            use glam::{IVec4, Vec4};
+
+                            let start_offset = self.gradient.color_stop_offset;
+                            let end_offset = (linear.color_stops.len() as i32)
+                                + start_offset
+                                - 1;
+
+                            self.gradient.uniforms.push(&gradient::Uniforms {
+                                transform: transform.into(),
+                                direction: Vec4::new(
+                                    linear.start.x,
+                                    linear.start.y,
+                                    linear.end.x,
+                                    linear.end.y,
+                                ),
+                                stop_range: IVec4::new(
+                                    start_offset,
+                                    end_offset,
+                                    0,
+                                    0,
+                                ),
+                            });
+
+                            self.gradient.color_stop_offset = end_offset + 1;
+
+                            let stops: Vec<gradient::ColorStop> = linear
+                                .color_stops
+                                .iter()
+                                .map(|stop| {
+                                    let [r, g, b, a] = stop.color.into_linear();
+
+                                    gradient::ColorStop {
+                                        offset: stop.offset,
+                                        color: Vec4::new(r, g, b, a),
+                                    }
+                                })
+                                .collect();
+
+                            self.gradient
+                                .color_stops_pending_write
+                                .color_stops
+                                .extend(stops);
+                        }
+                    }
                 }
+                #[cfg(target_arch = "wasm32")]
+                Mesh::Gradient { .. } => {}
+            }
+        }
+
+        // Write uniform data to GPU
+        if count.solid_vertices > 0 {
+            let uniforms_resized = self.solid.uniforms.resize(device);
+
+            if uniforms_resized {
+                self.solid.bind_group = solid::Pipeline::bind_group(
+                    device,
+                    self.solid.uniforms.raw(),
+                    &self.solid.bind_group_layout,
+                )
             }
+
+            self.solid.uniforms.write(device, staging_belt, encoder);
         }
 
-        //write uniform data to GPU
-        self.pipelines.write(device, staging_belt, encoder);
+        #[cfg(not(target_arch = "wasm32"))]
+        if count.gradient_vertices > 0 {
+            // First write the pending color stops to the CPU buffer
+            self.gradient
+                .storage
+                .push(&self.gradient.color_stops_pending_write);
+
+            // Resize buffers if needed
+            let uniforms_resized = self.gradient.uniforms.resize(device);
+            let storage_resized = self.gradient.storage.resize(device);
+
+            if uniforms_resized || storage_resized {
+                self.gradient.bind_group = gradient::Pipeline::bind_group(
+                    device,
+                    self.gradient.uniforms.raw(),
+                    self.gradient.storage.raw(),
+                    &self.gradient.bind_group_layout,
+                );
+            }
+
+            // Write to GPU
+            self.gradient.uniforms.write(device, staging_belt, encoder);
+            self.gradient.storage.write(device, staging_belt, encoder);
 
-        //configure the render pass now that the data is uploaded to the GPU
+            // Cleanup
+            self.gradient.color_stop_offset = 0;
+            self.gradient.color_stops_pending_write.color_stops.clear();
+        }
+
+        // Configure render pass
         {
-            //configure antialiasing pass
             let (attachment, resolve_target, load) = if let Some(blit) =
                 &mut self.blit
             {
@@ -200,7 +266,7 @@ impl Pipeline {
             let mut last_is_solid = None;
 
             for (index, mesh) in meshes.iter().enumerate() {
-                let clip_bounds = (mesh.clip_bounds * scale_factor).snap();
+                let clip_bounds = (mesh.clip_bounds() * scale_factor).snap();
 
                 render_pass.set_scissor_rect(
                     clip_bounds.x,
@@ -209,47 +275,57 @@ impl Pipeline {
                     clip_bounds.height,
                 );
 
-                match mesh.style {
-                    triangle::Style::Solid(_) => {
+                match mesh {
+                    Mesh::Solid { .. } => {
                         if !last_is_solid.unwrap_or(false) {
-                            self.pipelines
-                                .solid
-                                .set_render_pass_pipeline(&mut render_pass);
+                            render_pass.set_pipeline(&self.solid.pipeline);
 
                             last_is_solid = Some(true);
                         }
 
-                        self.pipelines.solid.configure_render_pass(
-                            &mut render_pass,
-                            num_solids,
+                        render_pass.set_bind_group(
+                            0,
+                            &self.solid.bind_group,
+                            &[self.solid.uniforms.offset_at_index(num_solids)],
+                        );
+
+                        render_pass.set_vertex_buffer(
+                            0,
+                            self.solid.vertices.slice_from_index(num_solids),
                         );
 
                         num_solids += 1;
                     }
                     #[cfg(not(target_arch = "wasm32"))]
-                    triangle::Style::Gradient(_) => {
+                    Mesh::Gradient { .. } => {
                         if last_is_solid.unwrap_or(true) {
-                            self.pipelines
-                                .gradient
-                                .set_render_pass_pipeline(&mut render_pass);
+                            render_pass.set_pipeline(&self.gradient.pipeline);
 
                             last_is_solid = Some(false);
                         }
 
-                        self.pipelines.gradient.configure_render_pass(
-                            &mut render_pass,
-                            num_gradients,
+                        render_pass.set_bind_group(
+                            0,
+                            &self.gradient.bind_group,
+                            &[self
+                                .gradient
+                                .uniforms
+                                .offset_at_index(num_gradients)],
+                        );
+
+                        render_pass.set_vertex_buffer(
+                            0,
+                            self.gradient
+                                .vertices
+                                .slice_from_index(num_gradients),
                         );
 
                         num_gradients += 1;
                     }
+                    #[cfg(target_arch = "wasm32")]
+                    Mesh::Gradient { .. } => {}
                 };
 
-                render_pass.set_vertex_buffer(
-                    0,
-                    self.vertex_buffer.slice_from_index(index),
-                );
-
                 render_pass.set_index_buffer(
                     self.index_buffer.slice_from_index(index),
                     wgpu::IndexFormat::Uint32,
@@ -263,7 +339,6 @@ impl Pipeline {
             }
         }
 
-        self.vertex_buffer.clear();
         self.index_buffer.clear();
 
         if let Some(blit) = &mut self.blit {
@@ -272,19 +347,6 @@ impl Pipeline {
     }
 }
 
-//utility functions for individual pipelines with shared functionality
-fn vertex_buffer_layout<'a>() -> wgpu::VertexBufferLayout<'a> {
-    wgpu::VertexBufferLayout {
-        array_stride: std::mem::size_of::<Vertex2D>() as u64,
-        step_mode: wgpu::VertexStepMode::Vertex,
-        attributes: &[wgpu::VertexAttribute {
-            format: wgpu::VertexFormat::Float32x2,
-            offset: 0,
-            shader_location: 0,
-        }],
-    }
-}
-
 fn fragment_target(
     texture_format: wgpu::TextureFormat,
 ) -> Option<wgpu::ColorTargetState> {
@@ -312,3 +374,360 @@ fn multisample_state(
         alpha_to_coverage_enabled: false,
     }
 }
+
+mod solid {
+    use crate::buffer::dynamic;
+    use crate::buffer::r#static::Buffer;
+    use crate::settings;
+    use crate::triangle;
+    use encase::ShaderType;
+    use iced_graphics::Transformation;
+
+    #[derive(Debug)]
+    pub struct Pipeline {
+        pub pipeline: wgpu::RenderPipeline,
+        pub vertices: Buffer<triangle::ColoredVertex2D>,
+        pub uniforms: dynamic::Buffer<Uniforms>,
+        pub bind_group_layout: wgpu::BindGroupLayout,
+        pub bind_group: wgpu::BindGroup,
+    }
+
+    #[derive(Debug, Clone, Copy, ShaderType)]
+    pub struct Uniforms {
+        transform: glam::Mat4,
+    }
+
+    impl Uniforms {
+        pub fn new(transform: Transformation) -> Self {
+            Self {
+                transform: transform.into(),
+            }
+        }
+    }
+
+    impl Pipeline {
+        /// Creates a new [SolidPipeline] using `solid.wgsl` shader.
+        pub fn new(
+            device: &wgpu::Device,
+            format: wgpu::TextureFormat,
+            antialiasing: Option<settings::Antialiasing>,
+        ) -> Self {
+            let vertices = Buffer::new(
+                device,
+                "iced_wgpu::triangle::solid vertex buffer",
+                wgpu::BufferUsages::VERTEX | wgpu::BufferUsages::COPY_DST,
+            );
+
+            let uniforms = dynamic::Buffer::uniform(
+                device,
+                "iced_wgpu::triangle::solid uniforms",
+            );
+
+            let bind_group_layout = device.create_bind_group_layout(
+                &wgpu::BindGroupLayoutDescriptor {
+                    label: Some("iced_wgpu::triangle::solid bind group layout"),
+                    entries: &[wgpu::BindGroupLayoutEntry {
+                        binding: 0,
+                        visibility: wgpu::ShaderStages::VERTEX_FRAGMENT,
+                        ty: wgpu::BindingType::Buffer {
+                            ty: wgpu::BufferBindingType::Uniform,
+                            has_dynamic_offset: true,
+                            min_binding_size: Some(Uniforms::min_size()),
+                        },
+                        count: None,
+                    }],
+                },
+            );
+
+            let bind_group =
+                Self::bind_group(device, uniforms.raw(), &bind_group_layout);
+
+            let layout = device.create_pipeline_layout(
+                &wgpu::PipelineLayoutDescriptor {
+                    label: Some("iced_wgpu::triangle::solid pipeline layout"),
+                    bind_group_layouts: &[&bind_group_layout],
+                    push_constant_ranges: &[],
+                },
+            );
+
+            let shader =
+                device.create_shader_module(wgpu::ShaderModuleDescriptor {
+                    label: Some(
+                        "iced_wgpu::triangle::solid create shader module",
+                    ),
+                    source: wgpu::ShaderSource::Wgsl(
+                        std::borrow::Cow::Borrowed(include_str!(
+                            "shader/solid.wgsl"
+                        )),
+                    ),
+                });
+
+            let pipeline = device.create_render_pipeline(
+                &wgpu::RenderPipelineDescriptor {
+                    label: Some("iced_wgpu::triangle::solid pipeline"),
+                    layout: Some(&layout),
+                    vertex: wgpu::VertexState {
+                        module: &shader,
+                        entry_point: "vs_main",
+                        buffers: &[wgpu::VertexBufferLayout {
+                            array_stride: std::mem::size_of::<
+                                triangle::ColoredVertex2D,
+                            >()
+                                as u64,
+                            step_mode: wgpu::VertexStepMode::Vertex,
+                            attributes: &wgpu::vertex_attr_array!(
+                                // Position
+                                0 => Float32x2,
+                                // Color
+                                1 => Float32x4,
+                            ),
+                        }],
+                    },
+                    fragment: Some(wgpu::FragmentState {
+                        module: &shader,
+                        entry_point: "fs_main",
+                        targets: &[triangle::fragment_target(format)],
+                    }),
+                    primitive: triangle::primitive_state(),
+                    depth_stencil: None,
+                    multisample: triangle::multisample_state(antialiasing),
+                    multiview: None,
+                },
+            );
+
+            Self {
+                pipeline,
+                vertices,
+                uniforms,
+                bind_group_layout,
+                bind_group,
+            }
+        }
+
+        pub fn bind_group(
+            device: &wgpu::Device,
+            buffer: &wgpu::Buffer,
+            layout: &wgpu::BindGroupLayout,
+        ) -> wgpu::BindGroup {
+            device.create_bind_group(&wgpu::BindGroupDescriptor {
+                label: Some("iced_wgpu::triangle::solid bind group"),
+                layout,
+                entries: &[wgpu::BindGroupEntry {
+                    binding: 0,
+                    resource: wgpu::BindingResource::Buffer(
+                        wgpu::BufferBinding {
+                            buffer,
+                            offset: 0,
+                            size: Some(Uniforms::min_size()),
+                        },
+                    ),
+                }],
+            })
+        }
+    }
+}
+
+#[cfg(not(target_arch = "wasm32"))]
+mod gradient {
+    use crate::buffer::dynamic;
+    use crate::buffer::r#static::Buffer;
+    use crate::settings;
+    use crate::triangle;
+
+    use encase::ShaderType;
+    use glam::{IVec4, Vec4};
+    use iced_graphics::triangle::Vertex2D;
+
+    #[derive(Debug)]
+    pub struct Pipeline {
+        pub pipeline: wgpu::RenderPipeline,
+        pub vertices: Buffer<Vertex2D>,
+        pub uniforms: dynamic::Buffer<Uniforms>,
+        pub storage: dynamic::Buffer<Storage>,
+        pub color_stop_offset: i32,
+        //Need to store these and then write them all at once
+        //or else they will be padded to 256 and cause gaps in the storage buffer
+        pub color_stops_pending_write: Storage,
+        pub bind_group_layout: wgpu::BindGroupLayout,
+        pub bind_group: wgpu::BindGroup,
+    }
+
+    #[derive(Debug, ShaderType)]
+    pub struct Uniforms {
+        pub transform: glam::Mat4,
+        //xy = start, zw = end
+        pub direction: Vec4,
+        //x = start stop, y = end stop, zw = padding
+        pub stop_range: IVec4,
+    }
+
+    #[derive(Debug, ShaderType)]
+    pub struct ColorStop {
+        pub color: Vec4,
+        pub offset: f32,
+    }
+
+    #[derive(Debug, ShaderType)]
+    pub struct Storage {
+        #[size(runtime)]
+        pub color_stops: Vec<ColorStop>,
+    }
+
+    impl Pipeline {
+        /// Creates a new [GradientPipeline] using `gradient.wgsl` shader.
+        pub(super) fn new(
+            device: &wgpu::Device,
+            format: wgpu::TextureFormat,
+            antialiasing: Option<settings::Antialiasing>,
+        ) -> Self {
+            let vertices = Buffer::new(
+                device,
+                "iced_wgpu::triangle::gradient vertex buffer",
+                wgpu::BufferUsages::VERTEX | wgpu::BufferUsages::COPY_DST,
+            );
+
+            let uniforms = dynamic::Buffer::uniform(
+                device,
+                "iced_wgpu::triangle::gradient uniforms",
+            );
+
+            //Note: with a WASM target storage buffers are not supported. Will need to use UBOs & static
+            // sized array (eg like the 32-sized array on OpenGL side right now) to make gradients work
+            let storage = dynamic::Buffer::storage(
+                device,
+                "iced_wgpu::triangle::gradient storage",
+            );
+
+            let bind_group_layout = device.create_bind_group_layout(
+                &wgpu::BindGroupLayoutDescriptor {
+                    label: Some(
+                        "iced_wgpu::triangle::gradient bind group layout",
+                    ),
+                    entries: &[
+                        wgpu::BindGroupLayoutEntry {
+                            binding: 0,
+                            visibility: wgpu::ShaderStages::VERTEX_FRAGMENT,
+                            ty: wgpu::BindingType::Buffer {
+                                ty: wgpu::BufferBindingType::Uniform,
+                                has_dynamic_offset: true,
+                                min_binding_size: Some(Uniforms::min_size()),
+                            },
+                            count: None,
+                        },
+                        wgpu::BindGroupLayoutEntry {
+                            binding: 1,
+                            visibility: wgpu::ShaderStages::FRAGMENT,
+                            ty: wgpu::BindingType::Buffer {
+                                ty: wgpu::BufferBindingType::Storage {
+                                    read_only: true,
+                                },
+                                has_dynamic_offset: false,
+                                min_binding_size: Some(Storage::min_size()),
+                            },
+                            count: None,
+                        },
+                    ],
+                },
+            );
+
+            let bind_group = Pipeline::bind_group(
+                device,
+                uniforms.raw(),
+                storage.raw(),
+                &bind_group_layout,
+            );
+
+            let layout = device.create_pipeline_layout(
+                &wgpu::PipelineLayoutDescriptor {
+                    label: Some(
+                        "iced_wgpu::triangle::gradient pipeline layout",
+                    ),
+                    bind_group_layouts: &[&bind_group_layout],
+                    push_constant_ranges: &[],
+                },
+            );
+
+            let shader =
+                device.create_shader_module(wgpu::ShaderModuleDescriptor {
+                    label: Some(
+                        "iced_wgpu::triangle::gradient create shader module",
+                    ),
+                    source: wgpu::ShaderSource::Wgsl(
+                        std::borrow::Cow::Borrowed(include_str!(
+                            "shader/gradient.wgsl"
+                        )),
+                    ),
+                });
+
+            let pipeline = device.create_render_pipeline(
+                &wgpu::RenderPipelineDescriptor {
+                    label: Some("iced_wgpu::triangle::gradient pipeline"),
+                    layout: Some(&layout),
+                    vertex: wgpu::VertexState {
+                        module: &shader,
+                        entry_point: "vs_main",
+                        buffers: &[wgpu::VertexBufferLayout {
+                            array_stride: std::mem::size_of::<Vertex2D>()
+                                as u64,
+                            step_mode: wgpu::VertexStepMode::Vertex,
+                            attributes: &wgpu::vertex_attr_array!(
+                                // Position
+                                0 => Float32x2,
+                            ),
+                        }],
+                    },
+                    fragment: Some(wgpu::FragmentState {
+                        module: &shader,
+                        entry_point: "fs_main",
+                        targets: &[triangle::fragment_target(format)],
+                    }),
+                    primitive: triangle::primitive_state(),
+                    depth_stencil: None,
+                    multisample: triangle::multisample_state(antialiasing),
+                    multiview: None,
+                },
+            );
+
+            Self {
+                pipeline,
+                vertices,
+                uniforms,
+                storage,
+                color_stop_offset: 0,
+                color_stops_pending_write: Storage {
+                    color_stops: vec![],
+                },
+                bind_group_layout,
+                bind_group,
+            }
+        }
+
+        pub fn bind_group(
+            device: &wgpu::Device,
+            uniform_buffer: &wgpu::Buffer,
+            storage_buffer: &wgpu::Buffer,
+            layout: &wgpu::BindGroupLayout,
+        ) -> wgpu::BindGroup {
+            device.create_bind_group(&wgpu::BindGroupDescriptor {
+                label: Some("iced_wgpu::triangle::gradient bind group"),
+                layout,
+                entries: &[
+                    wgpu::BindGroupEntry {
+                        binding: 0,
+                        resource: wgpu::BindingResource::Buffer(
+                            wgpu::BufferBinding {
+                                buffer: uniform_buffer,
+                                offset: 0,
+                                size: Some(Uniforms::min_size()),
+                            },
+                        ),
+                    },
+                    wgpu::BindGroupEntry {
+                        binding: 1,
+                        resource: storage_buffer.as_entire_binding(),
+                    },
+                ],
+            })
+        }
+    }
+}
diff --git a/wgpu/src/triangle/gradient.rs b/wgpu/src/triangle/gradient.rs
deleted file mode 100644
index b06cbac6..00000000
--- a/wgpu/src/triangle/gradient.rs
+++ /dev/null
@@ -1,268 +0,0 @@
-use crate::buffer::dynamic;
-use crate::settings;
-use crate::triangle;
-use encase::ShaderType;
-use glam::{IVec4, Vec4};
-use iced_graphics::gradient::Gradient;
-use iced_graphics::Transformation;
-
-pub struct Pipeline {
-    pipeline: wgpu::RenderPipeline,
-    pub(super) uniform_buffer: dynamic::Buffer<Uniforms>,
-    pub(super) storage_buffer: dynamic::Buffer<Storage>,
-    color_stop_offset: i32,
-    //Need to store these and then write them all at once
-    //or else they will be padded to 256 and cause gaps in the storage buffer
-    color_stops_pending_write: Storage,
-    bind_group_layout: wgpu::BindGroupLayout,
-    bind_group: wgpu::BindGroup,
-}
-
-#[derive(Debug, ShaderType)]
-pub(super) struct Uniforms {
-    transform: glam::Mat4,
-    //xy = start, zw = end
-    direction: Vec4,
-    //x = start stop, y = end stop, zw = padding
-    stop_range: IVec4,
-}
-
-#[derive(Debug, ShaderType)]
-pub(super) struct ColorStop {
-    color: Vec4,
-    offset: f32,
-}
-
-#[derive(ShaderType)]
-pub(super) struct Storage {
-    #[size(runtime)]
-    pub color_stops: Vec<ColorStop>,
-}
-
-impl Pipeline {
-    /// Creates a new [GradientPipeline] using `gradient.wgsl` shader.
-    pub(super) fn new(
-        device: &wgpu::Device,
-        format: wgpu::TextureFormat,
-        antialiasing: Option<settings::Antialiasing>,
-    ) -> Self {
-        let uniform_buffer = dynamic::Buffer::uniform(
-            device,
-            "iced_wgpu::triangle::gradient uniforms",
-        );
-
-        //Note: with a WASM target storage buffers are not supported. Will need to use UBOs & static
-        // sized array (eg like the 32-sized array on OpenGL side right now) to make gradients work
-        let storage_buffer = dynamic::Buffer::storage(
-            device,
-            "iced_wgpu::triangle::gradient storage",
-        );
-
-        let bind_group_layout =
-            device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
-                label: Some("iced_wgpu::triangle::gradient bind group layout"),
-                entries: &[
-                    wgpu::BindGroupLayoutEntry {
-                        binding: 0,
-                        visibility: wgpu::ShaderStages::VERTEX_FRAGMENT,
-                        ty: wgpu::BindingType::Buffer {
-                            ty: wgpu::BufferBindingType::Uniform,
-                            has_dynamic_offset: true,
-                            min_binding_size: Some(Uniforms::min_size()),
-                        },
-                        count: None,
-                    },
-                    wgpu::BindGroupLayoutEntry {
-                        binding: 1,
-                        visibility: wgpu::ShaderStages::FRAGMENT,
-                        ty: wgpu::BindingType::Buffer {
-                            ty: wgpu::BufferBindingType::Storage {
-                                read_only: true,
-                            },
-                            has_dynamic_offset: false,
-                            min_binding_size: Some(Storage::min_size()),
-                        },
-                        count: None,
-                    },
-                ],
-            });
-
-        let bind_group = Pipeline::bind_group(
-            device,
-            uniform_buffer.raw(),
-            storage_buffer.raw(),
-            &bind_group_layout,
-        );
-
-        let layout =
-            device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
-                label: Some("iced_wgpu::triangle::gradient pipeline layout"),
-                bind_group_layouts: &[&bind_group_layout],
-                push_constant_ranges: &[],
-            });
-
-        let shader =
-            device.create_shader_module(wgpu::ShaderModuleDescriptor {
-                label: Some(
-                    "iced_wgpu::triangle::gradient create shader module",
-                ),
-                source: wgpu::ShaderSource::Wgsl(std::borrow::Cow::Borrowed(
-                    include_str!("../shader/gradient.wgsl"),
-                )),
-            });
-
-        let pipeline =
-            device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
-                label: Some("iced_wgpu::triangle::gradient pipeline"),
-                layout: Some(&layout),
-                vertex: wgpu::VertexState {
-                    module: &shader,
-                    entry_point: "vs_main",
-                    buffers: &[triangle::vertex_buffer_layout()],
-                },
-                fragment: Some(wgpu::FragmentState {
-                    module: &shader,
-                    entry_point: "fs_main",
-                    targets: &[triangle::fragment_target(format)],
-                }),
-                primitive: triangle::primitive_state(),
-                depth_stencil: None,
-                multisample: triangle::multisample_state(antialiasing),
-                multiview: None,
-            });
-
-        Self {
-            pipeline,
-            uniform_buffer,
-            storage_buffer,
-            color_stop_offset: 0,
-            color_stops_pending_write: Storage {
-                color_stops: vec![],
-            },
-            bind_group_layout,
-            bind_group,
-        }
-    }
-
-    /// Pushes a new gradient uniform to the CPU buffer.
-    pub fn push(&mut self, transform: Transformation, gradient: &Gradient) {
-        match gradient {
-            Gradient::Linear(linear) => {
-                let start_offset = self.color_stop_offset;
-                let end_offset =
-                    (linear.color_stops.len() as i32) + start_offset - 1;
-
-                self.uniform_buffer.push(&Uniforms {
-                    transform: transform.into(),
-                    direction: Vec4::new(
-                        linear.start.x,
-                        linear.start.y,
-                        linear.end.x,
-                        linear.end.y,
-                    ),
-                    stop_range: IVec4::new(start_offset, end_offset, 0, 0),
-                });
-
-                self.color_stop_offset = end_offset + 1;
-
-                let stops: Vec<ColorStop> = linear
-                    .color_stops
-                    .iter()
-                    .map(|stop| {
-                        let [r, g, b, a] = stop.color.into_linear();
-
-                        ColorStop {
-                            offset: stop.offset,
-                            color: Vec4::new(r, g, b, a),
-                        }
-                    })
-                    .collect();
-
-                self.color_stops_pending_write.color_stops.extend(stops);
-            }
-        }
-    }
-
-    fn bind_group(
-        device: &wgpu::Device,
-        uniform_buffer: &wgpu::Buffer,
-        storage_buffer: &wgpu::Buffer,
-        layout: &wgpu::BindGroupLayout,
-    ) -> wgpu::BindGroup {
-        device.create_bind_group(&wgpu::BindGroupDescriptor {
-            label: Some("iced_wgpu::triangle::gradient bind group"),
-            layout,
-            entries: &[
-                wgpu::BindGroupEntry {
-                    binding: 0,
-                    resource: wgpu::BindingResource::Buffer(
-                        wgpu::BufferBinding {
-                            buffer: uniform_buffer,
-                            offset: 0,
-                            size: Some(Uniforms::min_size()),
-                        },
-                    ),
-                },
-                wgpu::BindGroupEntry {
-                    binding: 1,
-                    resource: storage_buffer.as_entire_binding(),
-                },
-            ],
-        })
-    }
-
-    /// Writes the contents of the gradient CPU buffer to the GPU buffer, resizing the GPU buffer
-    /// beforehand if necessary.
-    pub fn write(
-        &mut self,
-        device: &wgpu::Device,
-        staging_belt: &mut wgpu::util::StagingBelt,
-        encoder: &mut wgpu::CommandEncoder,
-    ) {
-        //first write the pending color stops to the CPU buffer
-        self.storage_buffer.push(&self.color_stops_pending_write);
-
-        //resize buffers if needed
-        let uniforms_resized = self.uniform_buffer.resize(device);
-        let storage_resized = self.storage_buffer.resize(device);
-
-        if uniforms_resized || storage_resized {
-            //recreate bind groups if any buffers were resized
-            self.bind_group = Pipeline::bind_group(
-                device,
-                self.uniform_buffer.raw(),
-                self.storage_buffer.raw(),
-                &self.bind_group_layout,
-            );
-        }
-
-        //write to GPU
-        self.uniform_buffer.write(device, staging_belt, encoder);
-        self.storage_buffer.write(device, staging_belt, encoder);
-
-        //cleanup
-        self.color_stop_offset = 0;
-        self.color_stops_pending_write.color_stops.clear();
-    }
-
-    pub fn set_render_pass_pipeline<'a>(
-        &'a self,
-        render_pass: &mut wgpu::RenderPass<'a>,
-    ) {
-        render_pass.set_pipeline(&self.pipeline);
-    }
-
-    /// Configures the current render pass to draw the gradient at its offset stored in the
-    /// [DynamicBuffer] at [index].
-    pub fn configure_render_pass<'a>(
-        &'a self,
-        render_pass: &mut wgpu::RenderPass<'a>,
-        count: usize,
-    ) {
-        render_pass.set_bind_group(
-            0,
-            &self.bind_group,
-            &[self.uniform_buffer.offset_at_index(count)],
-        )
-    }
-}
diff --git a/wgpu/src/triangle/solid.rs b/wgpu/src/triangle/solid.rs
deleted file mode 100644
index 2e1052f2..00000000
--- a/wgpu/src/triangle/solid.rs
+++ /dev/null
@@ -1,170 +0,0 @@
-use crate::buffer::dynamic;
-use crate::triangle;
-use crate::{settings, Color};
-use encase::ShaderType;
-use glam::Vec4;
-use iced_graphics::Transformation;
-
-pub struct Pipeline {
-    pipeline: wgpu::RenderPipeline,
-    pub(super) buffer: dynamic::Buffer<Uniforms>,
-    bind_group_layout: wgpu::BindGroupLayout,
-    bind_group: wgpu::BindGroup,
-}
-
-#[derive(Debug, Clone, Copy, ShaderType)]
-pub(super) struct Uniforms {
-    transform: glam::Mat4,
-    color: Vec4,
-}
-
-impl Uniforms {
-    pub fn new(transform: Transformation, color: Color) -> Self {
-        let [r, g, b, a] = color.into_linear();
-
-        Self {
-            transform: transform.into(),
-            color: Vec4::new(r, g, b, a),
-        }
-    }
-}
-
-impl Pipeline {
-    /// Creates a new [SolidPipeline] using `solid.wgsl` shader.
-    pub fn new(
-        device: &wgpu::Device,
-        format: wgpu::TextureFormat,
-        antialiasing: Option<settings::Antialiasing>,
-    ) -> Self {
-        let buffer = dynamic::Buffer::uniform(
-            device,
-            "iced_wgpu::triangle::solid uniforms",
-        );
-
-        let bind_group_layout =
-            device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
-                label: Some("iced_wgpu::triangle::solid bind group layout"),
-                entries: &[wgpu::BindGroupLayoutEntry {
-                    binding: 0,
-                    visibility: wgpu::ShaderStages::VERTEX_FRAGMENT,
-                    ty: wgpu::BindingType::Buffer {
-                        ty: wgpu::BufferBindingType::Uniform,
-                        has_dynamic_offset: true,
-                        min_binding_size: Some(Uniforms::min_size()),
-                    },
-                    count: None,
-                }],
-            });
-
-        let bind_group =
-            Pipeline::bind_group(device, buffer.raw(), &bind_group_layout);
-
-        let layout =
-            device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
-                label: Some("iced_wgpu::triangle::solid pipeline layout"),
-                bind_group_layouts: &[&bind_group_layout],
-                push_constant_ranges: &[],
-            });
-
-        let shader =
-            device.create_shader_module(wgpu::ShaderModuleDescriptor {
-                label: Some("iced_wgpu::triangle::solid create shader module"),
-                source: wgpu::ShaderSource::Wgsl(std::borrow::Cow::Borrowed(
-                    include_str!("../shader/solid.wgsl"),
-                )),
-            });
-
-        let pipeline =
-            device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
-                label: Some("iced_wgpu::triangle::solid pipeline"),
-                layout: Some(&layout),
-                vertex: wgpu::VertexState {
-                    module: &shader,
-                    entry_point: "vs_main",
-                    buffers: &[triangle::vertex_buffer_layout()],
-                },
-                fragment: Some(wgpu::FragmentState {
-                    module: &shader,
-                    entry_point: "fs_main",
-                    targets: &[triangle::fragment_target(format)],
-                }),
-                primitive: triangle::primitive_state(),
-                depth_stencil: None,
-                multisample: triangle::multisample_state(antialiasing),
-                multiview: None,
-            });
-
-        Self {
-            pipeline,
-            buffer,
-            bind_group_layout,
-            bind_group,
-        }
-    }
-
-    fn bind_group(
-        device: &wgpu::Device,
-        buffer: &wgpu::Buffer,
-        layout: &wgpu::BindGroupLayout,
-    ) -> wgpu::BindGroup {
-        device.create_bind_group(&wgpu::BindGroupDescriptor {
-            label: Some("iced_wgpu::triangle::solid bind group"),
-            layout,
-            entries: &[wgpu::BindGroupEntry {
-                binding: 0,
-                resource: wgpu::BindingResource::Buffer(wgpu::BufferBinding {
-                    buffer,
-                    offset: 0,
-                    size: Some(Uniforms::min_size()),
-                }),
-            }],
-        })
-    }
-
-    /// Pushes a new solid uniform to the CPU buffer.
-    pub fn push(&mut self, transform: Transformation, color: &Color) {
-        self.buffer.push(&Uniforms::new(transform, *color));
-    }
-
-    /// Writes the contents of the solid CPU buffer to the GPU buffer, resizing the GPU buffer
-    /// beforehand if necessary.
-    pub fn write(
-        &mut self,
-        device: &wgpu::Device,
-        staging_belt: &mut wgpu::util::StagingBelt,
-        encoder: &mut wgpu::CommandEncoder,
-    ) {
-        let uniforms_resized = self.buffer.resize(device);
-
-        if uniforms_resized {
-            self.bind_group = Pipeline::bind_group(
-                device,
-                self.buffer.raw(),
-                &self.bind_group_layout,
-            )
-        }
-
-        self.buffer.write(device, staging_belt, encoder);
-    }
-
-    pub fn set_render_pass_pipeline<'a>(
-        &'a self,
-        render_pass: &mut wgpu::RenderPass<'a>,
-    ) {
-        render_pass.set_pipeline(&self.pipeline);
-    }
-
-    /// Configures the current render pass to draw the solid at its offset stored in the
-    /// [DynamicBuffer] at [index].
-    pub fn configure_render_pass<'a>(
-        &'a self,
-        render_pass: &mut wgpu::RenderPass<'a>,
-        count: usize,
-    ) {
-        render_pass.set_bind_group(
-            0,
-            &self.bind_group,
-            &[self.buffer.offset_at_index(count)],
-        )
-    }
-}
-- 
cgit