diff options
Diffstat (limited to '')
35 files changed, 3506 insertions, 2213 deletions
| diff --git a/wgpu/Cargo.toml b/wgpu/Cargo.toml index f1e22cf6..22cfad55 100644 --- a/wgpu/Cargo.toml +++ b/wgpu/Cargo.toml @@ -1,6 +1,6 @@  [package]  name = "iced_wgpu" -version = "0.9.0" +version = "0.10.0"  authors = ["Héctor Ramón Jiménez <hector0193@gmail.com>"]  edition = "2021"  description = "A wgpu renderer for Iced" @@ -8,58 +8,59 @@ license = "MIT AND OFL-1.1"  repository = "https://github.com/iced-rs/iced"  [features] -svg = ["iced_graphics/svg"] +geometry = ["iced_graphics/geometry", "lyon"]  image = ["iced_graphics/image"] -png = ["iced_graphics/png"] -jpeg = ["iced_graphics/jpeg"] -jpeg_rayon = ["iced_graphics/jpeg_rayon"] -gif = ["iced_graphics/gif"] -webp = ["iced_graphics/webp"] -pnm = ["iced_graphics/pnm"] -ico = ["iced_graphics/ico"] -bmp = ["iced_graphics/bmp"] -hdr = ["iced_graphics/hdr"] -dds = ["iced_graphics/dds"] -farbfeld = ["iced_graphics/farbfeld"] -canvas = ["iced_graphics/canvas"] -qr_code = ["iced_graphics/qr_code"] -default_system_font = ["iced_graphics/font-source"] -spirv = ["wgpu/spirv"] -webgl = ["wgpu/webgl"] +svg = ["resvg"] +web-colors = ["iced_graphics/web-colors"]  [dependencies] -wgpu = "0.14" -wgpu_glyph = "0.18" -glyph_brush = "0.7" +wgpu = "0.16"  raw-window-handle = "0.5"  log = "0.4"  guillotiere = "0.6"  futures = "0.3"  bitflags = "1.2" +once_cell = "1.0" +rustc-hash = "1.1" + +[target.'cfg(target_arch = "wasm32")'.dependencies] +wgpu = { version = "0.16", features = ["webgl"] } + +[dependencies.twox-hash] +version = "1.6" +default-features = false + +[target.'cfg(not(target_arch = "wasm32"))'.dependencies.twox-hash] +version = "1.6.1" +features = ["std"]  [dependencies.bytemuck]  version = "1.9"  features = ["derive"] -[dependencies.iced_native] -version = "0.9" -path = "../native" -  [dependencies.iced_graphics] -version = "0.7" +version = "0.8"  path = "../graphics" -features = ["font-fallback", "font-icons"] -[dependencies.tracing] -version = "0.1.6" +[dependencies.glyphon] +version = "0.2" +git = "https://github.com/hecrj/glyphon.git" +rev = "8324f20158a62f8520bad4ed09f6aa5552f8f2a6" + +[dependencies.glam] +version = "0.24" + +[dependencies.lyon] +version = "1.0"  optional = true -[dependencies.encase] -version = "0.3.0" -features = ["glam"] +[dependencies.resvg] +version = "0.35" +optional = true -[dependencies.glam] -version = "0.21.3" +[dependencies.tracing] +version = "0.1.6" +optional = true  [package.metadata.docs.rs]  rustdoc-args = ["--cfg", "docsrs"] diff --git a/wgpu/README.md b/wgpu/README.md index 3e6af103..f8c88374 100644 --- a/wgpu/README.md +++ b/wgpu/README.md @@ -30,7 +30,7 @@ Currently, `iced_wgpu` supports the following primitives:  Add `iced_wgpu` as a dependency in your `Cargo.toml`:  ```toml -iced_wgpu = "0.9" +iced_wgpu = "0.10"  ```  __Iced moves fast and the `master` branch can contain breaking changes!__ If diff --git a/wgpu/fonts/Iced-Icons.ttf b/wgpu/fonts/Iced-Icons.ttfBinary files differ new file mode 100644 index 00000000..e3273141 --- /dev/null +++ b/wgpu/fonts/Iced-Icons.ttf diff --git a/wgpu/src/backend.rs b/wgpu/src/backend.rs index 6a299425..4a0c54f0 100644 --- a/wgpu/src/backend.rs +++ b/wgpu/src/backend.rs @@ -1,14 +1,13 @@ +use crate::core; +use crate::core::{Color, Font, Point, Size}; +use crate::graphics::backend; +use crate::graphics::color; +use crate::graphics::{Transformation, Viewport}; +use crate::primitive::{self, Primitive};  use crate::quad;  use crate::text;  use crate::triangle; -use crate::{Settings, Transformation}; - -use iced_graphics::backend; -use iced_graphics::font; -use iced_graphics::layer::Layer; -use iced_graphics::{Primitive, Viewport}; -use iced_native::alignment; -use iced_native::{Font, Size}; +use crate::{Layer, Settings};  #[cfg(feature = "tracing")]  use tracing::info_span; @@ -16,11 +15,13 @@ use tracing::info_span;  #[cfg(any(feature = "image", feature = "svg"))]  use crate::image; +use std::borrow::Cow; +  /// A [`wgpu`] graphics backend for [`iced`].  ///  /// [`wgpu`]: https://github.com/gfx-rs/wgpu-rs  /// [`iced`]: https://github.com/iced-rs/iced -#[derive(Debug)] +#[allow(missing_debug_implementations)]  pub struct Backend {      quad_pipeline: quad::Pipeline,      text_pipeline: text::Pipeline, @@ -29,6 +30,7 @@ pub struct Backend {      #[cfg(any(feature = "image", feature = "svg"))]      image_pipeline: image::Pipeline, +    default_font: Font,      default_text_size: f32,  } @@ -36,16 +38,11 @@ impl Backend {      /// Creates a new [`Backend`].      pub fn new(          device: &wgpu::Device, +        queue: &wgpu::Queue,          settings: Settings,          format: wgpu::TextureFormat,      ) -> Self { -        let text_pipeline = text::Pipeline::new( -            device, -            format, -            settings.default_font, -            settings.text_multithreading, -        ); - +        let text_pipeline = text::Pipeline::new(device, queue, format);          let quad_pipeline = quad::Pipeline::new(device, format);          let triangle_pipeline =              triangle::Pipeline::new(device, format, settings.antialiasing); @@ -61,6 +58,7 @@ impl Backend {              #[cfg(any(feature = "image", feature = "svg"))]              image_pipeline, +            default_font: settings.default_font,              default_text_size: settings.default_text_size,          }      } @@ -72,8 +70,9 @@ impl Backend {      pub fn present<T: AsRef<str>>(          &mut self,          device: &wgpu::Device, -        staging_belt: &mut wgpu::util::StagingBelt, +        queue: &wgpu::Queue,          encoder: &mut wgpu::CommandEncoder, +        clear_color: Option<Color>,          frame: &wgpu::TextureView,          primitives: &[Primitive],          viewport: &Viewport, @@ -90,180 +89,268 @@ impl Backend {          let mut layers = Layer::generate(primitives, viewport);          layers.push(Layer::overlay(overlay_text, viewport)); +        self.prepare( +            device, +            queue, +            encoder, +            scale_factor, +            transformation, +            &layers, +        ); + +        while !self.prepare_text( +            device, +            queue, +            scale_factor, +            target_size, +            &layers, +        ) {} + +        self.render( +            device, +            encoder, +            frame, +            clear_color, +            scale_factor, +            target_size, +            &layers, +        ); + +        self.quad_pipeline.end_frame(); +        self.text_pipeline.end_frame(); +        self.triangle_pipeline.end_frame(); + +        #[cfg(any(feature = "image", feature = "svg"))] +        self.image_pipeline.end_frame(); +    } + +    fn prepare_text( +        &mut self, +        device: &wgpu::Device, +        queue: &wgpu::Queue, +        scale_factor: f32, +        target_size: Size<u32>, +        layers: &[Layer<'_>], +    ) -> bool {          for layer in layers { -            self.flush( -                device, -                scale_factor, -                transformation, -                &layer, -                staging_belt, -                encoder, -                frame, -                target_size, -            ); +            let bounds = (layer.bounds * scale_factor).snap(); + +            if bounds.width < 1 || bounds.height < 1 { +                continue; +            } + +            if !layer.text.is_empty() +                && !self.text_pipeline.prepare( +                    device, +                    queue, +                    &layer.text, +                    layer.bounds, +                    scale_factor, +                    target_size, +                ) +            { +                return false; +            }          } -        #[cfg(any(feature = "image", feature = "svg"))] -        self.image_pipeline.trim_cache(device, encoder); +        true      } -    fn flush( +    fn prepare(          &mut self,          device: &wgpu::Device, +        queue: &wgpu::Queue, +        _encoder: &mut wgpu::CommandEncoder,          scale_factor: f32,          transformation: Transformation, -        layer: &Layer<'_>, -        staging_belt: &mut wgpu::util::StagingBelt, +        layers: &[Layer<'_>], +    ) { +        for layer in layers { +            let bounds = (layer.bounds * scale_factor).snap(); + +            if bounds.width < 1 || bounds.height < 1 { +                continue; +            } + +            if !layer.quads.is_empty() { +                self.quad_pipeline.prepare( +                    device, +                    queue, +                    &layer.quads, +                    transformation, +                    scale_factor, +                ); +            } + +            if !layer.meshes.is_empty() { +                let scaled = transformation +                    * Transformation::scale(scale_factor, scale_factor); + +                self.triangle_pipeline.prepare( +                    device, +                    queue, +                    &layer.meshes, +                    scaled, +                ); +            } + +            #[cfg(any(feature = "image", feature = "svg"))] +            { +                if !layer.images.is_empty() { +                    let scaled = transformation +                        * Transformation::scale(scale_factor, scale_factor); + +                    self.image_pipeline.prepare( +                        device, +                        queue, +                        _encoder, +                        &layer.images, +                        scaled, +                        scale_factor, +                    ); +                } +            } +        } +    } + +    fn render( +        &mut self, +        device: &wgpu::Device,          encoder: &mut wgpu::CommandEncoder,          target: &wgpu::TextureView, +        clear_color: Option<Color>, +        scale_factor: f32,          target_size: Size<u32>, +        layers: &[Layer<'_>],      ) { -        let bounds = (layer.bounds * scale_factor).snap(); +        use std::mem::ManuallyDrop; -        if bounds.width < 1 || bounds.height < 1 { -            return; -        } +        let mut quad_layer = 0; +        let mut triangle_layer = 0; +        #[cfg(any(feature = "image", feature = "svg"))] +        let mut image_layer = 0; +        let mut text_layer = 0; + +        let mut render_pass = ManuallyDrop::new(encoder.begin_render_pass( +            &wgpu::RenderPassDescriptor { +                label: Some("iced_wgpu::quad render pass"), +                color_attachments: &[Some(wgpu::RenderPassColorAttachment { +                    view: target, +                    resolve_target: None, +                    ops: wgpu::Operations { +                        load: match clear_color { +                            Some(background_color) => wgpu::LoadOp::Clear({ +                                let [r, g, b, a] = +                                    color::pack(background_color).components(); + +                                wgpu::Color { +                                    r: f64::from(r), +                                    g: f64::from(g), +                                    b: f64::from(b), +                                    a: f64::from(a), +                                } +                            }), +                            None => wgpu::LoadOp::Load, +                        }, +                        store: true, +                    }, +                })], +                depth_stencil_attachment: None, +            }, +        )); -        if !layer.quads.is_empty() { -            self.quad_pipeline.draw( -                device, -                staging_belt, -                encoder, -                &layer.quads, -                transformation, -                scale_factor, -                bounds, -                target, -            ); -        } +        for layer in layers { +            let bounds = (layer.bounds * scale_factor).snap(); -        if !layer.meshes.is_empty() { -            let scaled = transformation -                * Transformation::scale(scale_factor, scale_factor); - -            self.triangle_pipeline.draw( -                device, -                staging_belt, -                encoder, -                target, -                target_size, -                scaled, -                scale_factor, -                &layer.meshes, -            ); -        } +            if bounds.width < 1 || bounds.height < 1 { +                return; +            } -        #[cfg(any(feature = "image", feature = "svg"))] -        { -            if !layer.images.is_empty() { -                let scaled = transformation -                    * Transformation::scale(scale_factor, scale_factor); +            if !layer.quads.is_empty() { +                self.quad_pipeline.render( +                    quad_layer, +                    bounds, +                    &layer.quads, +                    &mut render_pass, +                ); + +                quad_layer += 1; +            } -                self.image_pipeline.draw( +            if !layer.meshes.is_empty() { +                let _ = ManuallyDrop::into_inner(render_pass); + +                self.triangle_pipeline.render(                      device, -                    staging_belt,                      encoder, -                    &layer.images, -                    scaled, -                    bounds,                      target, +                    triangle_layer, +                    target_size, +                    &layer.meshes,                      scale_factor,                  ); + +                triangle_layer += 1; + +                render_pass = ManuallyDrop::new(encoder.begin_render_pass( +                    &wgpu::RenderPassDescriptor { +                        label: Some("iced_wgpu::quad render pass"), +                        color_attachments: &[Some( +                            wgpu::RenderPassColorAttachment { +                                view: target, +                                resolve_target: None, +                                ops: wgpu::Operations { +                                    load: wgpu::LoadOp::Load, +                                    store: true, +                                }, +                            }, +                        )], +                        depth_stencil_attachment: None, +                    }, +                ));              } -        } -        if !layer.text.is_empty() { -            for text in layer.text.iter() { -                // Target physical coordinates directly to avoid blurry text -                let text = wgpu_glyph::Section { -                    // TODO: We `round` here to avoid rerasterizing text when -                    // its position changes slightly. This can make text feel a -                    // bit "jumpy". We may be able to do better once we improve -                    // our text rendering/caching pipeline. -                    screen_position: ( -                        (text.bounds.x * scale_factor).round(), -                        (text.bounds.y * scale_factor).round(), -                    ), -                    // TODO: Fix precision issues with some scale factors. -                    // -                    // The `ceil` here can cause some words to render on the -                    // same line when they should not. -                    // -                    // Ideally, `wgpu_glyph` should be able to compute layout -                    // using logical positions, and then apply the proper -                    // scaling when rendering. This would ensure that both -                    // measuring and rendering follow the same layout rules. -                    bounds: ( -                        (text.bounds.width * scale_factor).ceil(), -                        (text.bounds.height * scale_factor).ceil(), -                    ), -                    text: vec![wgpu_glyph::Text { -                        text: text.content, -                        scale: wgpu_glyph::ab_glyph::PxScale { -                            x: text.size * scale_factor, -                            y: text.size * scale_factor, -                        }, -                        font_id: self.text_pipeline.find_font(text.font), -                        extra: wgpu_glyph::Extra { -                            color: text.color, -                            z: 0.0, -                        }, -                    }], -                    layout: wgpu_glyph::Layout::default() -                        .h_align(match text.horizontal_alignment { -                            alignment::Horizontal::Left => { -                                wgpu_glyph::HorizontalAlign::Left -                            } -                            alignment::Horizontal::Center => { -                                wgpu_glyph::HorizontalAlign::Center -                            } -                            alignment::Horizontal::Right => { -                                wgpu_glyph::HorizontalAlign::Right -                            } -                        }) -                        .v_align(match text.vertical_alignment { -                            alignment::Vertical::Top => { -                                wgpu_glyph::VerticalAlign::Top -                            } -                            alignment::Vertical::Center => { -                                wgpu_glyph::VerticalAlign::Center -                            } -                            alignment::Vertical::Bottom => { -                                wgpu_glyph::VerticalAlign::Bottom -                            } -                        }), -                }; - -                self.text_pipeline.queue(text); +            #[cfg(any(feature = "image", feature = "svg"))] +            { +                if !layer.images.is_empty() { +                    self.image_pipeline.render( +                        image_layer, +                        bounds, +                        &mut render_pass, +                    ); + +                    image_layer += 1; +                }              } -            self.text_pipeline.draw_queued( -                device, -                staging_belt, -                encoder, -                target, -                transformation, -                wgpu_glyph::Region { -                    x: bounds.x, -                    y: bounds.y, -                    width: bounds.width, -                    height: bounds.height, -                }, -            ); +            if !layer.text.is_empty() { +                self.text_pipeline +                    .render(text_layer, bounds, &mut render_pass); + +                text_layer += 1; +            }          } + +        let _ = ManuallyDrop::into_inner(render_pass);      }  } -impl iced_graphics::Backend for Backend { +impl crate::graphics::Backend for Backend { +    type Primitive = primitive::Custom; +      fn trim_measurements(&mut self) { -        self.text_pipeline.trim_measurement_cache() +        self.text_pipeline.trim_measurements();      }  }  impl backend::Text for Backend { -    const ICON_FONT: Font = font::ICONS; -    const CHECKMARK_ICON: char = font::CHECKMARK_ICON; -    const ARROW_DOWN_ICON: char = font::ARROW_DOWN_ICON; +    const ICON_FONT: Font = Font::with_name("Iced-Icons"); +    const CHECKMARK_ICON: char = '\u{f00c}'; +    const ARROW_DOWN_ICON: char = '\u{e800}'; + +    fn default_font(&self) -> Font { +        self.default_font +    }      fn default_size(&self) -> f32 {          self.default_text_size @@ -273,45 +360,59 @@ impl backend::Text for Backend {          &self,          contents: &str,          size: f32, +        line_height: core::text::LineHeight,          font: Font,          bounds: Size, -    ) -> (f32, f32) { -        self.text_pipeline.measure(contents, size, font, bounds) +        shaping: core::text::Shaping, +    ) -> Size { +        self.text_pipeline.measure( +            contents, +            size, +            line_height, +            font, +            bounds, +            shaping, +        )      }      fn hit_test(          &self,          contents: &str,          size: f32, +        line_height: core::text::LineHeight,          font: Font,          bounds: Size, -        point: iced_native::Point, +        shaping: core::text::Shaping, +        point: Point,          nearest_only: bool, -    ) -> Option<text::Hit> { +    ) -> Option<core::text::Hit> {          self.text_pipeline.hit_test(              contents,              size, +            line_height,              font,              bounds, +            shaping,              point,              nearest_only,          )      } + +    fn load_font(&mut self, font: Cow<'static, [u8]>) { +        self.text_pipeline.load_font(font); +    }  }  #[cfg(feature = "image")]  impl backend::Image for Backend { -    fn dimensions(&self, handle: &iced_native::image::Handle) -> Size<u32> { +    fn dimensions(&self, handle: &core::image::Handle) -> Size<u32> {          self.image_pipeline.dimensions(handle)      }  }  #[cfg(feature = "svg")]  impl backend::Svg for Backend { -    fn viewport_dimensions( -        &self, -        handle: &iced_native::svg::Handle, -    ) -> Size<u32> { +    fn viewport_dimensions(&self, handle: &core::svg::Handle) -> Size<u32> {          self.image_pipeline.viewport_dimensions(handle)      }  } diff --git a/wgpu/src/buffer.rs b/wgpu/src/buffer.rs index 7c092d0b..94122187 100644 --- a/wgpu/src/buffer.rs +++ b/wgpu/src/buffer.rs @@ -1,3 +1,106 @@ -//! Utilities for buffer operations. -pub mod dynamic; -pub mod r#static; +use std::marker::PhantomData; +use std::ops::RangeBounds; + +#[derive(Debug)] +pub struct Buffer<T> { +    label: &'static str, +    size: u64, +    usage: wgpu::BufferUsages, +    pub(crate) raw: wgpu::Buffer, +    offsets: Vec<wgpu::BufferAddress>, +    type_: PhantomData<T>, +} + +impl<T: bytemuck::Pod> Buffer<T> { +    pub fn new( +        device: &wgpu::Device, +        label: &'static str, +        amount: usize, +        usage: wgpu::BufferUsages, +    ) -> Self { +        let size = next_copy_size::<T>(amount); + +        let raw = device.create_buffer(&wgpu::BufferDescriptor { +            label: Some(label), +            size, +            usage, +            mapped_at_creation: false, +        }); + +        Self { +            label, +            size, +            usage, +            raw, +            offsets: Vec::new(), +            type_: PhantomData, +        } +    } + +    pub fn resize(&mut self, device: &wgpu::Device, new_count: usize) -> bool { +        let new_size = (std::mem::size_of::<T>() * new_count) as u64; + +        if self.size < new_size { +            self.offsets.clear(); + +            self.raw = device.create_buffer(&wgpu::BufferDescriptor { +                label: Some(self.label), +                size: new_size, +                usage: self.usage, +                mapped_at_creation: false, +            }); + +            self.size = new_size; + +            true +        } else { +            false +        } +    } + +    /// Returns the size of the written bytes. +    pub fn write( +        &mut self, +        queue: &wgpu::Queue, +        offset: usize, +        contents: &[T], +    ) -> usize { +        let bytes: &[u8] = bytemuck::cast_slice(contents); +        queue.write_buffer(&self.raw, offset as u64, bytes); + +        self.offsets.push(offset as u64); + +        bytes.len() +    } + +    pub fn slice( +        &self, +        bounds: impl RangeBounds<wgpu::BufferAddress>, +    ) -> wgpu::BufferSlice<'_> { +        self.raw.slice(bounds) +    } + +    /// Returns the slice calculated from the offset stored at the given index. +    pub fn slice_from_index(&self, index: usize) -> wgpu::BufferSlice<'_> { +        self.raw.slice(self.offset_at(index)..) +    } + +    /// Clears any temporary data (i.e. offsets) from the buffer. +    pub fn clear(&mut self) { +        self.offsets.clear() +    } + +    /// Returns the offset at `index`, if it exists. +    fn offset_at(&self, index: usize) -> &wgpu::BufferAddress { +        self.offsets.get(index).expect("No offset at index.") +    } +} + +fn next_copy_size<T>(amount: usize) -> u64 { +    let align_mask = wgpu::COPY_BUFFER_ALIGNMENT - 1; + +    (((std::mem::size_of::<T>() * amount).next_power_of_two() as u64 +        + align_mask) +        & !align_mask) +        .max(wgpu::COPY_BUFFER_ALIGNMENT) +} diff --git a/wgpu/src/buffer/dynamic.rs b/wgpu/src/buffer/dynamic.rs deleted file mode 100644 index 88289b98..00000000 --- a/wgpu/src/buffer/dynamic.rs +++ /dev/null @@ -1,219 +0,0 @@ -//! 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. -#[derive(Debug)] -pub struct Buffer<T: ShaderType> { -    offsets: Vec<wgpu::DynamicOffset>, -    cpu: Internal, -    gpu: wgpu::Buffer, -    label: &'static str, -    size: u64, -    _data: PhantomData<T>, -} - -impl<T: ShaderType + WriteInto> Buffer<T> { -    /// Creates a new dynamic uniform buffer. -    pub fn uniform(device: &wgpu::Device, label: &'static str) -> Self { -        Buffer::new( -            device, -            Internal::Uniform(encase::DynamicUniformBuffer::new(Vec::new())), -            label, -            wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST, -        ) -    } - -    #[cfg(not(target_arch = "wasm32"))] -    /// Creates a new dynamic storage buffer. -    pub fn storage(device: &wgpu::Device, label: &'static str) -> Self { -        Buffer::new( -            device, -            Internal::Storage(encase::DynamicStorageBuffer::new(Vec::new())), -            label, -            wgpu::BufferUsages::STORAGE | wgpu::BufferUsages::COPY_DST, -        ) -    } - -    fn new( -        device: &wgpu::Device, -        dynamic_buffer_type: Internal, -        label: &'static str, -        usage: wgpu::BufferUsages, -    ) -> Self { -        let initial_size = u64::from(T::min_size()); - -        Self { -            offsets: Vec::new(), -            cpu: dynamic_buffer_type, -            gpu: Buffer::<T>::create_gpu_buffer( -                device, -                label, -                usage, -                initial_size, -            ), -            label, -            size: initial_size, -            _data: Default::default(), -        } -    } - -    fn create_gpu_buffer( -        device: &wgpu::Device, -        label: &'static str, -        usage: wgpu::BufferUsages, -        size: u64, -    ) -> wgpu::Buffer { -        device.create_buffer(&wgpu::BufferDescriptor { -            label: Some(label), -            size, -            usage, -            mapped_at_creation: false, -        }) -    } - -    /// Write a new value to the CPU buffer with proper alignment. Stores the returned offset value -    /// in the buffer for future use. -    pub fn push(&mut self, value: &T) { -        //this write operation on the cpu buffer will adjust for uniform alignment requirements -        let offset = self.cpu.write(value); -        self.offsets.push(offset); -    } - -    /// Resize buffer contents if necessary. This will re-create the GPU buffer if current size is -    /// less than the newly computed size from the CPU buffer. -    /// -    /// If the gpu buffer is resized, its bind group will need to be recreated! -    pub fn resize(&mut self, device: &wgpu::Device) -> bool { -        let new_size = self.cpu.get_ref().len() as u64; - -        if self.size < new_size { -            let usages = match self.cpu { -                Internal::Uniform(_) => { -                    wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST -                } -                #[cfg(not(target_arch = "wasm32"))] -                Internal::Storage(_) => { -                    wgpu::BufferUsages::STORAGE | wgpu::BufferUsages::COPY_DST -                } -            }; - -            self.gpu = Buffer::<T>::create_gpu_buffer( -                device, self.label, usages, new_size, -            ); -            self.size = new_size; -            true -        } else { -            false -        } -    } - -    /// Write the contents of this dynamic buffer to the GPU via staging belt command. -    pub fn write( -        &mut self, -        device: &wgpu::Device, -        staging_belt: &mut wgpu::util::StagingBelt, -        encoder: &mut wgpu::CommandEncoder, -    ) { -        let size = self.cpu.get_ref().len(); - -        if let Some(buffer_size) = wgpu::BufferSize::new(size as u64) { -            let mut buffer = staging_belt.write_buffer( -                encoder, -                &self.gpu, -                0, -                buffer_size, -                device, -            ); - -            buffer.copy_from_slice(self.cpu.get_ref()); -        } -    } - -    // Gets the aligned offset at the given index from the CPU buffer. -    pub fn offset_at_index(&self, index: usize) -> wgpu::DynamicOffset { -        let offset = self -            .offsets -            .get(index) -            .copied() -            .expect("Index not found in offsets."); - -        offset -    } - -    /// Returns a reference to the GPU buffer. -    pub fn raw(&self) -> &wgpu::Buffer { -        &self.gpu -    } - -    /// Reset the buffer. -    pub fn clear(&mut self) { -        self.offsets.clear(); -        self.cpu.clear(); -    } -} - -// Currently supported dynamic buffers. -enum Internal { -    Uniform(encase::DynamicUniformBuffer<Vec<u8>>), -    #[cfg(not(target_arch = "wasm32"))] -    //storage buffers are not supported on wgpu wasm target (yet) -    Storage(encase::DynamicStorageBuffer<Vec<u8>>), -} - -impl Internal { -    /// Writes the current value to its CPU buffer with proper alignment. -    pub(super) fn write<T: ShaderType + WriteInto>( -        &mut self, -        value: &T, -    ) -> wgpu::DynamicOffset { -        match self { -            Internal::Uniform(buf) => buf -                .write(value) -                .expect("Error when writing to dynamic uniform buffer.") -                as u32, -            #[cfg(not(target_arch = "wasm32"))] -            Internal::Storage(buf) => buf -                .write(value) -                .expect("Error when writing to dynamic storage buffer.") -                as u32, -        } -    } - -    /// Returns bytearray of aligned CPU buffer. -    pub(super) fn get_ref(&self) -> &Vec<u8> { -        match self { -            Internal::Uniform(buf) => buf.as_ref(), -            #[cfg(not(target_arch = "wasm32"))] -            Internal::Storage(buf) => buf.as_ref(), -        } -    } - -    /// Resets the CPU buffer. -    pub(super) fn clear(&mut self) { -        match self { -            Internal::Uniform(buf) => { -                buf.as_mut().clear(); -                buf.set_offset(0); -            } -            #[cfg(not(target_arch = "wasm32"))] -            Internal::Storage(buf) => { -                buf.as_mut().clear(); -                buf.set_offset(0); -            } -        } -    } -} - -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 deleted file mode 100644 index ef87422f..00000000 --- a/wgpu/src/buffer/static.rs +++ /dev/null @@ -1,117 +0,0 @@ -use bytemuck::{Pod, Zeroable}; -use std::marker::PhantomData; -use std::mem; - -//128 triangles/indices -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 struct Buffer<T> { -    //stored sequentially per mesh iteration; refers to the offset index in the GPU buffer -    offsets: Vec<wgpu::BufferAddress>, -    label: &'static str, -    usages: wgpu::BufferUsages, -    gpu: wgpu::Buffer, -    size: wgpu::BufferAddress, -    _data: PhantomData<T>, -} - -impl<T: Pod + Zeroable> Buffer<T> { -    /// Initialize a new static buffer. -    pub fn new( -        device: &wgpu::Device, -        label: &'static str, -        usages: wgpu::BufferUsages, -    ) -> Self { -        let size = (mem::size_of::<T>() as u64) * DEFAULT_STATIC_BUFFER_COUNT; - -        Self { -            offsets: Vec::new(), -            label, -            usages, -            gpu: Self::gpu_buffer(device, label, size, usages), -            size, -            _data: PhantomData, -        } -    } - -    fn gpu_buffer( -        device: &wgpu::Device, -        label: &'static str, -        size: wgpu::BufferAddress, -        usage: wgpu::BufferUsages, -    ) -> wgpu::Buffer { -        device.create_buffer(&wgpu::BufferDescriptor { -            label: Some(label), -            size, -            usage, -            mapped_at_creation: false, -        }) -    } - -    /// Returns whether or not the buffer needs to be recreated. This can happen whenever mesh data -    /// changes & a redraw is requested. -    pub fn resize(&mut self, device: &wgpu::Device, new_count: usize) -> bool { -        let size = (mem::size_of::<T>() * new_count) as u64; - -        if self.size < size { -            self.offsets.clear(); -            self.size = size; -            self.gpu = Self::gpu_buffer(device, self.label, size, self.usages); -            true -        } else { -            false -        } -    } - -    /// Writes the current vertex data to the gpu buffer with a memcpy & stores its offset. -    /// -    /// Returns the size of the written bytes. -    pub fn write( -        &mut self, -        device: &wgpu::Device, -        staging_belt: &mut wgpu::util::StagingBelt, -        encoder: &mut wgpu::CommandEncoder, -        offset: u64, -        content: &[T], -    ) -> u64 { -        let bytes = bytemuck::cast_slice(content); -        let bytes_size = bytes.len() as u64; - -        if let Some(buffer_size) = wgpu::BufferSize::new(bytes_size) { -            let mut buffer = staging_belt.write_buffer( -                encoder, -                &self.gpu, -                offset, -                buffer_size, -                device, -            ); - -            buffer.copy_from_slice(bytes); - -            self.offsets.push(offset); -        } - -        bytes_size -    } - -    fn offset_at(&self, index: usize) -> &wgpu::BufferAddress { -        self.offsets -            .get(index) -            .expect("Offset at index does not exist.") -    } - -    /// Returns the slice calculated from the offset stored at the given index. -    /// e.g. to calculate the slice for the 2nd mesh in the layer, this would be the offset at index -    /// 1 that we stored earlier when writing. -    pub fn slice_from_index(&self, index: usize) -> wgpu::BufferSlice<'_> { -        self.gpu.slice(self.offset_at(index)..) -    } - -    /// Clears any temporary data from the buffer. -    pub fn clear(&mut self) { -        self.offsets.clear() -    } -} diff --git a/wgpu/src/color.rs b/wgpu/src/color.rs new file mode 100644 index 00000000..a1025601 --- /dev/null +++ b/wgpu/src/color.rs @@ -0,0 +1,165 @@ +use std::borrow::Cow; + +pub fn convert( +    device: &wgpu::Device, +    encoder: &mut wgpu::CommandEncoder, +    source: wgpu::Texture, +    format: wgpu::TextureFormat, +) -> wgpu::Texture { +    if source.format() == format { +        return source; +    } + +    let sampler = device.create_sampler(&wgpu::SamplerDescriptor { +        label: Some("iced_wgpu.offscreen.sampler"), +        ..Default::default() +    }); + +    //sampler in 0 +    let sampler_layout = +        device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { +            label: Some("iced_wgpu.offscreen.blit.sampler_layout"), +            entries: &[wgpu::BindGroupLayoutEntry { +                binding: 0, +                visibility: wgpu::ShaderStages::FRAGMENT, +                ty: wgpu::BindingType::Sampler( +                    wgpu::SamplerBindingType::NonFiltering, +                ), +                count: None, +            }], +        }); + +    let sampler_bind_group = +        device.create_bind_group(&wgpu::BindGroupDescriptor { +            label: Some("iced_wgpu.offscreen.sampler.bind_group"), +            layout: &sampler_layout, +            entries: &[wgpu::BindGroupEntry { +                binding: 0, +                resource: wgpu::BindingResource::Sampler(&sampler), +            }], +        }); + +    let texture_layout = +        device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { +            label: Some("iced_wgpu.offscreen.blit.texture_layout"), +            entries: &[wgpu::BindGroupLayoutEntry { +                binding: 0, +                visibility: wgpu::ShaderStages::FRAGMENT, +                ty: wgpu::BindingType::Texture { +                    sample_type: wgpu::TextureSampleType::Float { +                        filterable: false, +                    }, +                    view_dimension: wgpu::TextureViewDimension::D2, +                    multisampled: false, +                }, +                count: None, +            }], +        }); + +    let pipeline_layout = +        device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor { +            label: Some("iced_wgpu.offscreen.blit.pipeline_layout"), +            bind_group_layouts: &[&sampler_layout, &texture_layout], +            push_constant_ranges: &[], +        }); + +    let shader = device.create_shader_module(wgpu::ShaderModuleDescriptor { +        label: Some("iced_wgpu.offscreen.blit.shader"), +        source: wgpu::ShaderSource::Wgsl(Cow::Borrowed(include_str!( +            "shader/blit.wgsl" +        ))), +    }); + +    let pipeline = +        device.create_render_pipeline(&wgpu::RenderPipelineDescriptor { +            label: Some("iced_wgpu.offscreen.blit.pipeline"), +            layout: Some(&pipeline_layout), +            vertex: wgpu::VertexState { +                module: &shader, +                entry_point: "vs_main", +                buffers: &[], +            }, +            fragment: Some(wgpu::FragmentState { +                module: &shader, +                entry_point: "fs_main", +                targets: &[Some(wgpu::ColorTargetState { +                    format, +                    blend: Some(wgpu::BlendState { +                        color: wgpu::BlendComponent { +                            src_factor: wgpu::BlendFactor::SrcAlpha, +                            dst_factor: wgpu::BlendFactor::OneMinusSrcAlpha, +                            operation: wgpu::BlendOperation::Add, +                        }, +                        alpha: wgpu::BlendComponent { +                            src_factor: wgpu::BlendFactor::One, +                            dst_factor: wgpu::BlendFactor::OneMinusSrcAlpha, +                            operation: wgpu::BlendOperation::Add, +                        }, +                    }), +                    write_mask: wgpu::ColorWrites::ALL, +                })], +            }), +            primitive: wgpu::PrimitiveState { +                topology: wgpu::PrimitiveTopology::TriangleList, +                front_face: wgpu::FrontFace::Cw, +                ..Default::default() +            }, +            depth_stencil: None, +            multisample: Default::default(), +            multiview: None, +        }); + +    let texture = device.create_texture(&wgpu::TextureDescriptor { +        label: Some("iced_wgpu.offscreen.conversion.source_texture"), +        size: source.size(), +        mip_level_count: 1, +        sample_count: 1, +        dimension: wgpu::TextureDimension::D2, +        format, +        usage: wgpu::TextureUsages::RENDER_ATTACHMENT +            | wgpu::TextureUsages::COPY_SRC, +        view_formats: &[], +    }); + +    let view = &texture.create_view(&wgpu::TextureViewDescriptor::default()); + +    let texture_bind_group = +        device.create_bind_group(&wgpu::BindGroupDescriptor { +            label: Some("iced_wgpu.offscreen.blit.texture_bind_group"), +            layout: &texture_layout, +            entries: &[wgpu::BindGroupEntry { +                binding: 0, +                resource: wgpu::BindingResource::TextureView( +                    &source +                        .create_view(&wgpu::TextureViewDescriptor::default()), +                ), +            }], +        }); + +    let mut pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor { +        label: Some("iced_wgpu.offscreen.blit.render_pass"), +        color_attachments: &[Some(wgpu::RenderPassColorAttachment { +            view, +            resolve_target: None, +            ops: wgpu::Operations { +                load: wgpu::LoadOp::Load, +                store: true, +            }, +        })], +        depth_stencil_attachment: None, +    }); + +    pass.set_pipeline(&pipeline); +    pass.set_bind_group(0, &sampler_bind_group, &[]); +    pass.set_bind_group(1, &texture_bind_group, &[]); +    pass.draw(0..6, 0..1); + +    texture +} + +#[derive(Debug, Copy, Clone, bytemuck::Pod, bytemuck::Zeroable)] +#[repr(C)] +struct Vertex { +    ndc: [f32; 2], +    uv: [f32; 2], +} diff --git a/graphics/src/widget/canvas/frame.rs b/wgpu/src/geometry.rs index d68548ae..e421e0b0 100644 --- a/graphics/src/widget/canvas/frame.rs +++ b/wgpu/src/geometry.rs @@ -1,17 +1,19 @@ -use crate::gradient::Gradient; -use crate::triangle; -use crate::widget::canvas::{path, Fill, Geometry, Path, Stroke, Style, Text}; -use crate::Primitive; - -use iced_native::{Point, Rectangle, Size, Vector}; +//! Build and draw geometry. +use crate::core::{Point, Rectangle, Size, Vector}; +use crate::graphics::color; +use crate::graphics::geometry::fill::{self, Fill}; +use crate::graphics::geometry::{ +    LineCap, LineDash, LineJoin, Path, Stroke, Style, Text, +}; +use crate::graphics::gradient::{self, Gradient}; +use crate::graphics::mesh::{self, Mesh}; +use crate::primitive::{self, Primitive};  use lyon::geom::euclid;  use lyon::tessellation;  use std::borrow::Cow; -/// The frame of a [`Canvas`]. -/// -/// [`Canvas`]: crate::widget::Canvas +/// A frame for drawing some geometry.  #[allow(missing_debug_implementations)]  pub struct Frame {      size: Size, @@ -23,11 +25,8 @@ pub struct Frame {  }  enum Buffer { -    Solid(tessellation::VertexBuffers<triangle::ColoredVertex2D, u32>), -    Gradient( -        tessellation::VertexBuffers<triangle::Vertex2D, u32>, -        Gradient, -    ), +    Solid(tessellation::VertexBuffers<mesh::SolidVertex2D, u32>), +    Gradient(tessellation::VertexBuffers<mesh::GradientVertex2D, u32>),  }  struct BufferStack { @@ -49,12 +48,11 @@ impl BufferStack {                      ));                  }              }, -            Style::Gradient(gradient) => match self.stack.last() { -                Some(Buffer::Gradient(_, last)) if gradient == last => {} +            Style::Gradient(_) => match self.stack.last() { +                Some(Buffer::Gradient(_)) => {}                  _ => {                      self.stack.push(Buffer::Gradient(                          tessellation::VertexBuffers::new(), -                        gradient.clone(),                      ));                  }              }, @@ -71,12 +69,17 @@ impl BufferStack {              (Style::Solid(color), Buffer::Solid(buffer)) => {                  Box::new(tessellation::BuffersBuilder::new(                      buffer, -                    TriangleVertex2DBuilder(color.into_linear()), +                    TriangleVertex2DBuilder(color::pack(*color)), +                )) +            } +            (Style::Gradient(gradient), Buffer::Gradient(buffer)) => { +                Box::new(tessellation::BuffersBuilder::new( +                    buffer, +                    GradientVertex2DBuilder { +                        gradient: gradient.pack(), +                    },                  ))              } -            (Style::Gradient(_), Buffer::Gradient(buffer, _)) => Box::new( -                tessellation::BuffersBuilder::new(buffer, Vertex2DBuilder), -            ),              _ => unreachable!(),          }      } @@ -89,12 +92,17 @@ impl BufferStack {              (Style::Solid(color), Buffer::Solid(buffer)) => {                  Box::new(tessellation::BuffersBuilder::new(                      buffer, -                    TriangleVertex2DBuilder(color.into_linear()), +                    TriangleVertex2DBuilder(color::pack(*color)), +                )) +            } +            (Style::Gradient(gradient), Buffer::Gradient(buffer)) => { +                Box::new(tessellation::BuffersBuilder::new( +                    buffer, +                    GradientVertex2DBuilder { +                        gradient: gradient.pack(), +                    },                  ))              } -            (Style::Gradient(_), Buffer::Gradient(buffer, _)) => Box::new( -                tessellation::BuffersBuilder::new(buffer, Vertex2DBuilder), -            ),              _ => unreachable!(),          }      } @@ -132,11 +140,13 @@ impl Transform {      }      fn transform_gradient(&self, mut gradient: Gradient) -> Gradient { -        let (start, end) = match &mut gradient { -            Gradient::Linear(linear) => (&mut linear.start, &mut linear.end), -        }; -        self.transform_point(start); -        self.transform_point(end); +        match &mut gradient { +            Gradient::Linear(linear) => { +                self.transform_point(&mut linear.start); +                self.transform_point(&mut linear.end); +            } +        } +          gradient      }  } @@ -196,8 +206,8 @@ impl Frame {              .buffers              .get_fill(&self.transforms.current.transform_style(style)); -        let options = -            tessellation::FillOptions::default().with_fill_rule(rule.into()); +        let options = tessellation::FillOptions::default() +            .with_fill_rule(into_fill_rule(rule));          if self.transforms.current.is_identity {              self.fill_tessellator.tessellate_path( @@ -206,7 +216,7 @@ impl Frame {                  buffer.as_mut(),              )          } else { -            let path = path.transformed(&self.transforms.current.raw); +            let path = path.transform(&self.transforms.current.raw);              self.fill_tessellator.tessellate_path(                  path.raw(), @@ -241,8 +251,8 @@ impl Frame {                  lyon::math::Vector::new(size.width, size.height),              ); -        let options = -            tessellation::FillOptions::default().with_fill_rule(rule.into()); +        let options = tessellation::FillOptions::default() +            .with_fill_rule(into_fill_rule(rule));          self.fill_tessellator              .tessellate_rectangle( @@ -264,14 +274,14 @@ impl Frame {          let mut options = tessellation::StrokeOptions::default();          options.line_width = stroke.width; -        options.start_cap = stroke.line_cap.into(); -        options.end_cap = stroke.line_cap.into(); -        options.line_join = stroke.line_join.into(); +        options.start_cap = into_line_cap(stroke.line_cap); +        options.end_cap = into_line_cap(stroke.line_cap); +        options.line_join = into_line_join(stroke.line_join);          let path = if stroke.line_dash.segments.is_empty() {              Cow::Borrowed(path)          } else { -            Cow::Owned(path::dashed(path, stroke.line_dash)) +            Cow::Owned(dashed(path, stroke.line_dash))          };          if self.transforms.current.is_identity { @@ -281,7 +291,7 @@ impl Frame {                  buffer.as_mut(),              )          } else { -            let path = path.transformed(&self.transforms.current.raw); +            let path = path.transform(&self.transforms.current.raw);              self.stroke_tessellator.tessellate_path(                  path.raw(), @@ -331,9 +341,11 @@ impl Frame {              },              color: text.color,              size: text.size, +            line_height: text.line_height,              font: text.font,              horizontal_alignment: text.horizontal_alignment,              vertical_alignment: text.vertical_alignment, +            shaping: text.shaping,          });      } @@ -344,10 +356,20 @@ impl Frame {      /// operations in different coordinate systems.      #[inline]      pub fn with_save(&mut self, f: impl FnOnce(&mut Frame)) { -        self.transforms.previous.push(self.transforms.current); +        self.push_transform();          f(self); +        self.pop_transform(); +    } + +    /// Pushes the current transform in the transform stack. +    pub fn push_transform(&mut self) { +        self.transforms.previous.push(self.transforms.current); +    } + +    /// Pops a transform from the transform stack and sets it as the current transform. +    pub fn pop_transform(&mut self) {          self.transforms.current = self.transforms.previous.pop().unwrap();      } @@ -363,14 +385,21 @@ impl Frame {          f(&mut frame); +        let origin = Point::new(region.x, region.y); + +        self.clip(frame, origin); +    } + +    /// Draws the clipped contents of the given [`Frame`] with origin at the given [`Point`]. +    pub fn clip(&mut self, frame: Frame, at: Point) { +        let size = frame.size();          let primitives = frame.into_primitives(); +        let translation = Vector::new(at.x, at.y);          let (text, meshes) = primitives              .into_iter()              .partition(|primitive| matches!(primitive, Primitive::Text { .. })); -        let translation = Vector::new(region.x, region.y); -          self.primitives.push(Primitive::Group {              primitives: vec![                  Primitive::Translate { @@ -380,7 +409,7 @@ impl Frame {                  Primitive::Translate {                      translation,                      content: Box::new(Primitive::Clip { -                        bounds: Rectangle::with_size(region.size()), +                        bounds: Rectangle::with_size(size),                          content: Box::new(Primitive::Group {                              primitives: text,                          }), @@ -423,11 +452,11 @@ impl Frame {          self.transforms.current.is_identity = false;      } -    /// Produces the [`Geometry`] representing everything drawn on the [`Frame`]. -    pub fn into_geometry(self) -> Geometry { -        Geometry::from_primitive(Primitive::Group { +    /// Produces the [`Primitive`] representing everything drawn on the [`Frame`]. +    pub fn into_primitive(self) -> Primitive { +        Primitive::Group {              primitives: self.into_primitives(), -        }) +        }      }      fn into_primitives(mut self) -> Vec<Primitive> { @@ -435,25 +464,28 @@ impl Frame {              match buffer {                  Buffer::Solid(buffer) => {                      if !buffer.indices.is_empty() { -                        self.primitives.push(Primitive::SolidMesh { -                            buffers: triangle::Mesh2D { -                                vertices: buffer.vertices, -                                indices: buffer.indices, -                            }, -                            size: self.size, -                        }) +                        self.primitives.push(Primitive::Custom( +                            primitive::Custom::Mesh(Mesh::Solid { +                                buffers: mesh::Indexed { +                                    vertices: buffer.vertices, +                                    indices: buffer.indices, +                                }, +                                size: self.size, +                            }), +                        ))                      }                  } -                Buffer::Gradient(buffer, gradient) => { +                Buffer::Gradient(buffer) => {                      if !buffer.indices.is_empty() { -                        self.primitives.push(Primitive::GradientMesh { -                            buffers: triangle::Mesh2D { -                                vertices: buffer.vertices, -                                indices: buffer.indices, -                            }, -                            size: self.size, -                            gradient, -                        }) +                        self.primitives.push(Primitive::Custom( +                            primitive::Custom::Mesh(Mesh::Gradient { +                                buffers: mesh::Indexed { +                                    vertices: buffer.vertices, +                                    indices: buffer.indices, +                                }, +                                size: self.size, +                            }), +                        ))                      }                  }              } @@ -463,68 +495,137 @@ impl Frame {      }  } -struct Vertex2DBuilder; +struct GradientVertex2DBuilder { +    gradient: gradient::Packed, +} -impl tessellation::FillVertexConstructor<triangle::Vertex2D> -    for Vertex2DBuilder +impl tessellation::FillVertexConstructor<mesh::GradientVertex2D> +    for GradientVertex2DBuilder  {      fn new_vertex(          &mut self,          vertex: tessellation::FillVertex<'_>, -    ) -> triangle::Vertex2D { +    ) -> mesh::GradientVertex2D {          let position = vertex.position(); -        triangle::Vertex2D { +        mesh::GradientVertex2D {              position: [position.x, position.y], +            gradient: self.gradient,          }      }  } -impl tessellation::StrokeVertexConstructor<triangle::Vertex2D> -    for Vertex2DBuilder +impl tessellation::StrokeVertexConstructor<mesh::GradientVertex2D> +    for GradientVertex2DBuilder  {      fn new_vertex(          &mut self,          vertex: tessellation::StrokeVertex<'_, '_>, -    ) -> triangle::Vertex2D { +    ) -> mesh::GradientVertex2D {          let position = vertex.position(); -        triangle::Vertex2D { +        mesh::GradientVertex2D {              position: [position.x, position.y], +            gradient: self.gradient,          }      }  } -struct TriangleVertex2DBuilder([f32; 4]); +struct TriangleVertex2DBuilder(color::Packed); -impl tessellation::FillVertexConstructor<triangle::ColoredVertex2D> +impl tessellation::FillVertexConstructor<mesh::SolidVertex2D>      for TriangleVertex2DBuilder  {      fn new_vertex(          &mut self,          vertex: tessellation::FillVertex<'_>, -    ) -> triangle::ColoredVertex2D { +    ) -> mesh::SolidVertex2D {          let position = vertex.position(); -        triangle::ColoredVertex2D { +        mesh::SolidVertex2D {              position: [position.x, position.y],              color: self.0,          }      }  } -impl tessellation::StrokeVertexConstructor<triangle::ColoredVertex2D> +impl tessellation::StrokeVertexConstructor<mesh::SolidVertex2D>      for TriangleVertex2DBuilder  {      fn new_vertex(          &mut self,          vertex: tessellation::StrokeVertex<'_, '_>, -    ) -> triangle::ColoredVertex2D { +    ) -> mesh::SolidVertex2D {          let position = vertex.position(); -        triangle::ColoredVertex2D { +        mesh::SolidVertex2D {              position: [position.x, position.y],              color: self.0,          }      }  } + +fn into_line_join(line_join: LineJoin) -> lyon::tessellation::LineJoin { +    match line_join { +        LineJoin::Miter => lyon::tessellation::LineJoin::Miter, +        LineJoin::Round => lyon::tessellation::LineJoin::Round, +        LineJoin::Bevel => lyon::tessellation::LineJoin::Bevel, +    } +} + +fn into_line_cap(line_cap: LineCap) -> lyon::tessellation::LineCap { +    match line_cap { +        LineCap::Butt => lyon::tessellation::LineCap::Butt, +        LineCap::Square => lyon::tessellation::LineCap::Square, +        LineCap::Round => lyon::tessellation::LineCap::Round, +    } +} + +fn into_fill_rule(rule: fill::Rule) -> lyon::tessellation::FillRule { +    match rule { +        fill::Rule::NonZero => lyon::tessellation::FillRule::NonZero, +        fill::Rule::EvenOdd => lyon::tessellation::FillRule::EvenOdd, +    } +} + +pub(super) fn dashed(path: &Path, line_dash: LineDash<'_>) -> Path { +    use lyon::algorithms::walk::{ +        walk_along_path, RepeatedPattern, WalkerEvent, +    }; +    use lyon::path::iterator::PathIterator; + +    Path::new(|builder| { +        let segments_odd = (line_dash.segments.len() % 2 == 1) +            .then(|| [line_dash.segments, line_dash.segments].concat()); + +        let mut draw_line = false; + +        walk_along_path( +            path.raw().iter().flattened(0.01), +            0.0, +            lyon::tessellation::StrokeOptions::DEFAULT_TOLERANCE, +            &mut RepeatedPattern { +                callback: |event: WalkerEvent<'_>| { +                    let point = Point { +                        x: event.position.x, +                        y: event.position.y, +                    }; + +                    if draw_line { +                        builder.line_to(point); +                    } else { +                        builder.move_to(point); +                    } + +                    draw_line = !draw_line; + +                    true +                }, +                index: line_dash.offset, +                intervals: segments_odd +                    .as_deref() +                    .unwrap_or(line_dash.segments), +            }, +        ); +    }) +} diff --git a/wgpu/src/image.rs b/wgpu/src/image.rs index a5e63b17..553ba330 100644 --- a/wgpu/src/image.rs +++ b/wgpu/src/image.rs @@ -1,16 +1,17 @@  mod atlas;  #[cfg(feature = "image")] -use iced_graphics::image::raster; +mod raster;  #[cfg(feature = "svg")] -use iced_graphics::image::vector; +mod vector; -use crate::Transformation;  use atlas::Atlas; -use iced_graphics::layer; -use iced_native::{Rectangle, Size}; +use crate::core::{Rectangle, Size}; +use crate::graphics::Transformation; +use crate::layer; +use crate::Buffer;  use std::cell::RefCell;  use std::mem; @@ -18,10 +19,10 @@ use std::mem;  use bytemuck::{Pod, Zeroable};  #[cfg(feature = "image")] -use iced_native::image; +use crate::core::image;  #[cfg(feature = "svg")] -use iced_native::svg; +use crate::core::svg;  #[cfg(feature = "tracing")]  use tracing::info_span; @@ -29,20 +30,112 @@ use tracing::info_span;  #[derive(Debug)]  pub struct Pipeline {      #[cfg(feature = "image")] -    raster_cache: RefCell<raster::Cache<Atlas>>, +    raster_cache: RefCell<raster::Cache>,      #[cfg(feature = "svg")] -    vector_cache: RefCell<vector::Cache<Atlas>>, +    vector_cache: RefCell<vector::Cache>,      pipeline: wgpu::RenderPipeline, -    uniforms: wgpu::Buffer,      vertices: wgpu::Buffer,      indices: wgpu::Buffer, -    instances: wgpu::Buffer, -    constants: wgpu::BindGroup, +    sampler: wgpu::Sampler,      texture: wgpu::BindGroup,      texture_version: usize, -    texture_layout: wgpu::BindGroupLayout,      texture_atlas: Atlas, +    texture_layout: wgpu::BindGroupLayout, +    constant_layout: wgpu::BindGroupLayout, + +    layers: Vec<Layer>, +    prepare_layer: usize, +} + +#[derive(Debug)] +struct Layer { +    uniforms: wgpu::Buffer, +    constants: wgpu::BindGroup, +    instances: Buffer<Instance>, +    instance_count: usize, +} + +impl Layer { +    fn new( +        device: &wgpu::Device, +        constant_layout: &wgpu::BindGroupLayout, +        sampler: &wgpu::Sampler, +    ) -> Self { +        let uniforms = device.create_buffer(&wgpu::BufferDescriptor { +            label: Some("iced_wgpu::image uniforms buffer"), +            size: mem::size_of::<Uniforms>() as u64, +            usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST, +            mapped_at_creation: false, +        }); + +        let constants = device.create_bind_group(&wgpu::BindGroupDescriptor { +            label: Some("iced_wgpu::image constants bind group"), +            layout: constant_layout, +            entries: &[ +                wgpu::BindGroupEntry { +                    binding: 0, +                    resource: wgpu::BindingResource::Buffer( +                        wgpu::BufferBinding { +                            buffer: &uniforms, +                            offset: 0, +                            size: None, +                        }, +                    ), +                }, +                wgpu::BindGroupEntry { +                    binding: 1, +                    resource: wgpu::BindingResource::Sampler(sampler), +                }, +            ], +        }); + +        let instances = Buffer::new( +            device, +            "iced_wgpu::image instance buffer", +            Instance::INITIAL, +            wgpu::BufferUsages::VERTEX | wgpu::BufferUsages::COPY_DST, +        ); + +        Self { +            uniforms, +            constants, +            instances, +            instance_count: 0, +        } +    } + +    fn prepare( +        &mut self, +        device: &wgpu::Device, +        queue: &wgpu::Queue, +        instances: &[Instance], +        transformation: Transformation, +    ) { +        queue.write_buffer( +            &self.uniforms, +            0, +            bytemuck::bytes_of(&Uniforms { +                transform: transformation.into(), +            }), +        ); + +        let _ = self.instances.resize(device, instances.len()); +        let _ = self.instances.write(queue, 0, instances); + +        self.instance_count = instances.len(); +    } + +    fn render<'a>(&'a self, render_pass: &mut wgpu::RenderPass<'a>) { +        render_pass.set_bind_group(0, &self.constants, &[]); +        render_pass.set_vertex_buffer(1, self.instances.slice(..)); + +        render_pass.draw_indexed( +            0..QUAD_INDICES.len() as u32, +            0, +            0..self.instance_count as u32, +        ); +    }  }  impl Pipeline { @@ -86,35 +179,6 @@ impl Pipeline {                  ],              }); -        let uniforms_buffer = device.create_buffer(&wgpu::BufferDescriptor { -            label: Some("iced_wgpu::image uniforms buffer"), -            size: mem::size_of::<Uniforms>() as u64, -            usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST, -            mapped_at_creation: false, -        }); - -        let constant_bind_group = -            device.create_bind_group(&wgpu::BindGroupDescriptor { -                label: Some("iced_wgpu::image constants bind group"), -                layout: &constant_layout, -                entries: &[ -                    wgpu::BindGroupEntry { -                        binding: 0, -                        resource: wgpu::BindingResource::Buffer( -                            wgpu::BufferBinding { -                                buffer: &uniforms_buffer, -                                offset: 0, -                                size: None, -                            }, -                        ), -                    }, -                    wgpu::BindGroupEntry { -                        binding: 1, -                        resource: wgpu::BindingResource::Sampler(&sampler), -                    }, -                ], -            }); -          let texture_layout =              device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {                  label: Some("iced_wgpu::image texture atlas layout"), @@ -141,7 +205,7 @@ impl Pipeline {          let shader =              device.create_shader_module(wgpu::ShaderModuleDescriptor { -                label: Some("iced_wgpu::image::shader"), +                label: Some("iced_wgpu image shader"),                  source: wgpu::ShaderSource::Wgsl(std::borrow::Cow::Borrowed(                      include_str!("shader/image.wgsl"),                  )), @@ -214,7 +278,7 @@ impl Pipeline {          let vertices =              device.create_buffer_init(&wgpu::util::BufferInitDescriptor {                  label: Some("iced_wgpu::image vertex buffer"), -                contents: bytemuck::cast_slice(&QUAD_VERTS), +                contents: bytemuck::cast_slice(&QUAD_VERTICES),                  usage: wgpu::BufferUsages::VERTEX,              }); @@ -225,13 +289,6 @@ impl Pipeline {                  usage: wgpu::BufferUsages::INDEX,              }); -        let instances = device.create_buffer(&wgpu::BufferDescriptor { -            label: Some("iced_wgpu::image instance buffer"), -            size: mem::size_of::<Instance>() as u64 * Instance::MAX as u64, -            usage: wgpu::BufferUsages::VERTEX | wgpu::BufferUsages::COPY_DST, -            mapped_at_creation: false, -        }); -          let texture_atlas = Atlas::new(device);          let texture = device.create_bind_group(&wgpu::BindGroupDescriptor { @@ -253,15 +310,17 @@ impl Pipeline {              vector_cache: RefCell::new(vector::Cache::default()),              pipeline, -            uniforms: uniforms_buffer,              vertices,              indices, -            instances, -            constants: constant_bind_group, +            sampler,              texture,              texture_version: texture_atlas.layer_count(), -            texture_layout,              texture_atlas, +            texture_layout, +            constant_layout, + +            layers: Vec::new(), +            prepare_layer: 0,          }      } @@ -281,18 +340,19 @@ impl Pipeline {          svg.viewport_dimensions()      } -    pub fn draw( +    pub fn prepare(          &mut self,          device: &wgpu::Device, -        staging_belt: &mut wgpu::util::StagingBelt, +        queue: &wgpu::Queue,          encoder: &mut wgpu::CommandEncoder,          images: &[layer::Image],          transformation: Transformation, -        bounds: Rectangle<u32>, -        target: &wgpu::TextureView,          _scale: f32,      ) {          #[cfg(feature = "tracing")] +        let _ = info_span!("Wgpu::Image", "PREPARE").entered(); + +        #[cfg(feature = "tracing")]          let _ = info_span!("Wgpu::Image", "DRAW").entered();          let instances: &mut Vec<Instance> = &mut Vec::new(); @@ -308,8 +368,9 @@ impl Pipeline {                  #[cfg(feature = "image")]                  layer::Image::Raster { handle, bounds } => {                      if let Some(atlas_entry) = raster_cache.upload( +                        device, +                        encoder,                          handle, -                        &mut (device, encoder),                          &mut self.texture_atlas,                      ) {                          add_instances( @@ -332,11 +393,12 @@ impl Pipeline {                      let size = [bounds.width, bounds.height];                      if let Some(atlas_entry) = vector_cache.upload( +                        device, +                        encoder,                          handle,                          *color,                          size,                          _scale, -                        &mut (device, encoder),                          &mut self.texture_atlas,                      ) {                          add_instances( @@ -376,68 +438,28 @@ impl Pipeline {              self.texture_version = texture_version;          } -        { -            let mut uniforms_buffer = staging_belt.write_buffer( -                encoder, -                &self.uniforms, -                0, -                wgpu::BufferSize::new(mem::size_of::<Uniforms>() as u64) -                    .unwrap(), +        if self.layers.len() <= self.prepare_layer { +            self.layers.push(Layer::new(                  device, -            ); - -            uniforms_buffer.copy_from_slice(bytemuck::bytes_of(&Uniforms { -                transform: transformation.into(), -            })); +                &self.constant_layout, +                &self.sampler, +            ));          } -        let mut i = 0; -        let total = instances.len(); - -        while i < total { -            let end = (i + Instance::MAX).min(total); -            let amount = end - i; - -            let mut instances_buffer = staging_belt.write_buffer( -                encoder, -                &self.instances, -                0, -                wgpu::BufferSize::new( -                    (amount * std::mem::size_of::<Instance>()) as u64, -                ) -                .unwrap(), -                device, -            ); +        let layer = &mut self.layers[self.prepare_layer]; +        layer.prepare(device, queue, instances, transformation); -            instances_buffer.copy_from_slice(bytemuck::cast_slice( -                &instances[i..i + amount], -            )); - -            let mut render_pass = -                encoder.begin_render_pass(&wgpu::RenderPassDescriptor { -                    label: Some("iced_wgpu::image render pass"), -                    color_attachments: &[Some( -                        wgpu::RenderPassColorAttachment { -                            view: target, -                            resolve_target: None, -                            ops: wgpu::Operations { -                                load: wgpu::LoadOp::Load, -                                store: true, -                            }, -                        }, -                    )], -                    depth_stencil_attachment: None, -                }); +        self.prepare_layer += 1; +    } +    pub fn render<'a>( +        &'a self, +        layer: usize, +        bounds: Rectangle<u32>, +        render_pass: &mut wgpu::RenderPass<'a>, +    ) { +        if let Some(layer) = self.layers.get(layer) {              render_pass.set_pipeline(&self.pipeline); -            render_pass.set_bind_group(0, &self.constants, &[]); -            render_pass.set_bind_group(1, &self.texture, &[]); -            render_pass.set_index_buffer( -                self.indices.slice(..), -                wgpu::IndexFormat::Uint16, -            ); -            render_pass.set_vertex_buffer(0, self.vertices.slice(..)); -            render_pass.set_vertex_buffer(1, self.instances.slice(..));              render_pass.set_scissor_rect(                  bounds.x, @@ -446,30 +468,25 @@ impl Pipeline {                  bounds.height,              ); -            render_pass.draw_indexed( -                0..QUAD_INDICES.len() as u32, -                0, -                0..amount as u32, +            render_pass.set_bind_group(1, &self.texture, &[]); +            render_pass.set_index_buffer( +                self.indices.slice(..), +                wgpu::IndexFormat::Uint16,              ); +            render_pass.set_vertex_buffer(0, self.vertices.slice(..)); -            i += Instance::MAX; +            layer.render(render_pass);          }      } -    pub fn trim_cache( -        &mut self, -        device: &wgpu::Device, -        encoder: &mut wgpu::CommandEncoder, -    ) { +    pub fn end_frame(&mut self) {          #[cfg(feature = "image")] -        self.raster_cache -            .borrow_mut() -            .trim(&mut self.texture_atlas, &mut (device, encoder)); +        self.raster_cache.borrow_mut().trim(&mut self.texture_atlas);          #[cfg(feature = "svg")] -        self.vector_cache -            .borrow_mut() -            .trim(&mut self.texture_atlas, &mut (device, encoder)); +        self.vector_cache.borrow_mut().trim(&mut self.texture_atlas); + +        self.prepare_layer = 0;      }  } @@ -481,7 +498,7 @@ pub struct Vertex {  const QUAD_INDICES: [u16; 6] = [0, 1, 2, 0, 2, 3]; -const QUAD_VERTS: [Vertex; 4] = [ +const QUAD_VERTICES: [Vertex; 4] = [      Vertex {          _position: [0.0, 0.0],      }, @@ -507,7 +524,7 @@ struct Instance {  }  impl Instance { -    pub const MAX: usize = 1_000; +    pub const INITIAL: usize = 1_000;  }  #[repr(C)] diff --git a/wgpu/src/image/atlas.rs b/wgpu/src/image/atlas.rs index eafe2f96..e3de1290 100644 --- a/wgpu/src/image/atlas.rs +++ b/wgpu/src/image/atlas.rs @@ -12,10 +12,8 @@ use allocator::Allocator;  pub const SIZE: u32 = 2048; -use iced_graphics::image; -use iced_graphics::Size; - -use std::num::NonZeroU32; +use crate::core::Size; +use crate::graphics::color;  #[derive(Debug)]  pub struct Atlas { @@ -38,10 +36,15 @@ impl Atlas {              mip_level_count: 1,              sample_count: 1,              dimension: wgpu::TextureDimension::D2, -            format: wgpu::TextureFormat::Rgba8UnormSrgb, +            format: if color::GAMMA_CORRECTION { +                wgpu::TextureFormat::Rgba8UnormSrgb +            } else { +                wgpu::TextureFormat::Rgba8Unorm +            },              usage: wgpu::TextureUsages::COPY_DST                  | wgpu::TextureUsages::COPY_SRC                  | wgpu::TextureUsages::TEXTURE_BINDING, +            view_formats: &[],          });          let texture_view = texture.create_view(&wgpu::TextureViewDescriptor { @@ -64,6 +67,98 @@ impl Atlas {          self.layers.len()      } +    pub fn upload( +        &mut self, +        device: &wgpu::Device, +        encoder: &mut wgpu::CommandEncoder, +        width: u32, +        height: u32, +        data: &[u8], +    ) -> Option<Entry> { +        let entry = { +            let current_size = self.layers.len(); +            let entry = self.allocate(width, height)?; + +            // We grow the internal texture after allocating if necessary +            let new_layers = self.layers.len() - current_size; +            self.grow(new_layers, device, encoder); + +            entry +        }; + +        log::info!("Allocated atlas entry: {:?}", entry); + +        // It is a webgpu requirement that: +        //   BufferCopyView.layout.bytes_per_row % wgpu::COPY_BYTES_PER_ROW_ALIGNMENT == 0 +        // So we calculate padded_width by rounding width up to the next +        // multiple of wgpu::COPY_BYTES_PER_ROW_ALIGNMENT. +        let align = wgpu::COPY_BYTES_PER_ROW_ALIGNMENT; +        let padding = (align - (4 * width) % align) % align; +        let padded_width = (4 * width + padding) as usize; +        let padded_data_size = padded_width * height as usize; + +        let mut padded_data = vec![0; padded_data_size]; + +        for row in 0..height as usize { +            let offset = row * padded_width; + +            padded_data[offset..offset + 4 * width as usize].copy_from_slice( +                &data[row * 4 * width as usize..(row + 1) * 4 * width as usize], +            ) +        } + +        match &entry { +            Entry::Contiguous(allocation) => { +                self.upload_allocation( +                    &padded_data, +                    width, +                    height, +                    padding, +                    0, +                    allocation, +                    device, +                    encoder, +                ); +            } +            Entry::Fragmented { fragments, .. } => { +                for fragment in fragments { +                    let (x, y) = fragment.position; +                    let offset = (y * padded_width as u32 + 4 * x) as usize; + +                    self.upload_allocation( +                        &padded_data, +                        width, +                        height, +                        padding, +                        offset, +                        &fragment.allocation, +                        device, +                        encoder, +                    ); +                } +            } +        } + +        log::info!("Current atlas: {:?}", self); + +        Some(entry) +    } + +    pub fn remove(&mut self, entry: &Entry) { +        log::info!("Removing atlas entry: {:?}", entry); + +        match entry { +            Entry::Contiguous(allocation) => { +                self.deallocate(allocation); +            } +            Entry::Fragmented { fragments, .. } => { +                for fragment in fragments { +                    self.deallocate(&fragment.allocation); +                } +            } +        } +    } +      fn allocate(&mut self, width: u32, height: u32) -> Option<Entry> {          // Allocate one layer if texture fits perfectly          if width == SIZE && height == SIZE { @@ -185,14 +280,17 @@ impl Atlas {      fn upload_allocation(          &mut self, -        buffer: &wgpu::Buffer, +        data: &[u8],          image_width: u32,          image_height: u32,          padding: u32,          offset: usize,          allocation: &Allocation, +        device: &wgpu::Device,          encoder: &mut wgpu::CommandEncoder,      ) { +        use wgpu::util::DeviceExt; +          let (x, y) = allocation.position();          let Size { width, height } = allocation.size();          let layer = allocation.layer(); @@ -203,13 +301,20 @@ impl Atlas {              depth_or_array_layers: 1,          }; +        let buffer = +            device.create_buffer_init(&wgpu::util::BufferInitDescriptor { +                label: Some("image upload buffer"), +                contents: data, +                usage: wgpu::BufferUsages::COPY_SRC, +            }); +          encoder.copy_buffer_to_texture(              wgpu::ImageCopyBuffer { -                buffer, +                buffer: &buffer,                  layout: wgpu::ImageDataLayout {                      offset: offset as u64, -                    bytes_per_row: NonZeroU32::new(4 * image_width + padding), -                    rows_per_image: NonZeroU32::new(image_height), +                    bytes_per_row: Some(4 * image_width + padding), +                    rows_per_image: Some(image_height),                  },              },              wgpu::ImageCopyTexture { @@ -246,10 +351,15 @@ impl Atlas {              mip_level_count: 1,              sample_count: 1,              dimension: wgpu::TextureDimension::D2, -            format: wgpu::TextureFormat::Rgba8UnormSrgb, +            format: if color::GAMMA_CORRECTION { +                wgpu::TextureFormat::Rgba8UnormSrgb +            } else { +                wgpu::TextureFormat::Rgba8Unorm +            },              usage: wgpu::TextureUsages::COPY_DST                  | wgpu::TextureUsages::COPY_SRC                  | wgpu::TextureUsages::TEXTURE_BINDING, +            view_formats: &[],          });          let amount_to_copy = self.layers.len() - amount; @@ -298,100 +408,3 @@ impl Atlas {              });      }  } - -impl image::Storage for Atlas { -    type Entry = Entry; -    type State<'a> = (&'a wgpu::Device, &'a mut wgpu::CommandEncoder); - -    fn upload( -        &mut self, -        width: u32, -        height: u32, -        data: &[u8], -        (device, encoder): &mut Self::State<'_>, -    ) -> Option<Self::Entry> { -        use wgpu::util::DeviceExt; - -        let entry = { -            let current_size = self.layers.len(); -            let entry = self.allocate(width, height)?; - -            // We grow the internal texture after allocating if necessary -            let new_layers = self.layers.len() - current_size; -            self.grow(new_layers, device, encoder); - -            entry -        }; - -        log::info!("Allocated atlas entry: {:?}", entry); - -        // It is a webgpu requirement that: -        //   BufferCopyView.layout.bytes_per_row % wgpu::COPY_BYTES_PER_ROW_ALIGNMENT == 0 -        // So we calculate padded_width by rounding width up to the next -        // multiple of wgpu::COPY_BYTES_PER_ROW_ALIGNMENT. -        let align = wgpu::COPY_BYTES_PER_ROW_ALIGNMENT; -        let padding = (align - (4 * width) % align) % align; -        let padded_width = (4 * width + padding) as usize; -        let padded_data_size = padded_width * height as usize; - -        let mut padded_data = vec![0; padded_data_size]; - -        for row in 0..height as usize { -            let offset = row * padded_width; - -            padded_data[offset..offset + 4 * width as usize].copy_from_slice( -                &data[row * 4 * width as usize..(row + 1) * 4 * width as usize], -            ) -        } - -        let buffer = -            device.create_buffer_init(&wgpu::util::BufferInitDescriptor { -                label: Some("iced_wgpu::image staging buffer"), -                contents: &padded_data, -                usage: wgpu::BufferUsages::COPY_SRC, -            }); - -        match &entry { -            Entry::Contiguous(allocation) => { -                self.upload_allocation( -                    &buffer, width, height, padding, 0, allocation, encoder, -                ); -            } -            Entry::Fragmented { fragments, .. } => { -                for fragment in fragments { -                    let (x, y) = fragment.position; -                    let offset = (y * padded_width as u32 + 4 * x) as usize; - -                    self.upload_allocation( -                        &buffer, -                        width, -                        height, -                        padding, -                        offset, -                        &fragment.allocation, -                        encoder, -                    ); -                } -            } -        } - -        log::info!("Current atlas: {:?}", self); - -        Some(entry) -    } - -    fn remove(&mut self, entry: &Entry, _: &mut Self::State<'_>) { -        log::info!("Removing atlas entry: {:?}", entry); - -        match entry { -            Entry::Contiguous(allocation) => { -                self.deallocate(allocation); -            } -            Entry::Fragmented { fragments, .. } => { -                for fragment in fragments { -                    self.deallocate(&fragment.allocation); -                } -            } -        } -    } -} diff --git a/wgpu/src/image/atlas/allocation.rs b/wgpu/src/image/atlas/allocation.rs index 43aba875..11289771 100644 --- a/wgpu/src/image/atlas/allocation.rs +++ b/wgpu/src/image/atlas/allocation.rs @@ -1,7 +1,6 @@ +use crate::core::Size;  use crate::image::atlas::{self, allocator}; -use iced_graphics::Size; -  #[derive(Debug)]  pub enum Allocation {      Partial { diff --git a/wgpu/src/image/atlas/allocator.rs b/wgpu/src/image/atlas/allocator.rs index 03effdcb..204a5c26 100644 --- a/wgpu/src/image/atlas/allocator.rs +++ b/wgpu/src/image/atlas/allocator.rs @@ -46,10 +46,10 @@ impl Region {          (rectangle.min.x as u32, rectangle.min.y as u32)      } -    pub fn size(&self) -> iced_graphics::Size<u32> { +    pub fn size(&self) -> crate::core::Size<u32> {          let size = self.allocation.rectangle.size(); -        iced_graphics::Size::new(size.width as u32, size.height as u32) +        crate::core::Size::new(size.width as u32, size.height as u32)      }  } diff --git a/wgpu/src/image/atlas/entry.rs b/wgpu/src/image/atlas/entry.rs index 69c05a50..7e4c92a2 100644 --- a/wgpu/src/image/atlas/entry.rs +++ b/wgpu/src/image/atlas/entry.rs @@ -1,8 +1,6 @@ +use crate::core::Size;  use crate::image::atlas; -use iced_graphics::image; -use iced_graphics::Size; -  #[derive(Debug)]  pub enum Entry {      Contiguous(atlas::Allocation), @@ -12,8 +10,9 @@ pub enum Entry {      },  } -impl image::storage::Entry for Entry { -    fn size(&self) -> Size<u32> { +impl Entry { +    #[cfg(feature = "image")] +    pub fn size(&self) -> Size<u32> {          match self {              Entry::Contiguous(allocation) => allocation.size(),              Entry::Fragmented { size, .. } => *size, diff --git a/wgpu/src/image/raster.rs b/wgpu/src/image/raster.rs new file mode 100644 index 00000000..a6cba76a --- /dev/null +++ b/wgpu/src/image/raster.rs @@ -0,0 +1,119 @@ +use crate::core::image; +use crate::core::Size; +use crate::graphics; +use crate::graphics::image::image_rs; +use crate::image::atlas::{self, Atlas}; + +use std::collections::{HashMap, HashSet}; + +/// Entry in cache corresponding to an image handle +#[derive(Debug)] +pub enum Memory { +    /// Image data on host +    Host(image_rs::ImageBuffer<image_rs::Rgba<u8>, Vec<u8>>), +    /// Storage entry +    Device(atlas::Entry), +    /// Image not found +    NotFound, +    /// Invalid image data +    Invalid, +} + +impl Memory { +    /// Width and height of image +    pub fn dimensions(&self) -> Size<u32> { +        match self { +            Memory::Host(image) => { +                let (width, height) = image.dimensions(); + +                Size::new(width, height) +            } +            Memory::Device(entry) => entry.size(), +            Memory::NotFound => Size::new(1, 1), +            Memory::Invalid => Size::new(1, 1), +        } +    } +} + +/// Caches image raster data +#[derive(Debug, Default)] +pub struct Cache { +    map: HashMap<u64, Memory>, +    hits: HashSet<u64>, +} + +impl Cache { +    /// Load image +    pub fn load(&mut self, handle: &image::Handle) -> &mut Memory { +        if self.contains(handle) { +            return self.get(handle).unwrap(); +        } + +        let memory = match graphics::image::load(handle) { +            Ok(image) => Memory::Host(image.to_rgba8()), +            Err(image_rs::error::ImageError::IoError(_)) => Memory::NotFound, +            Err(_) => Memory::Invalid, +        }; + +        self.insert(handle, memory); +        self.get(handle).unwrap() +    } + +    /// Load image and upload raster data +    pub fn upload( +        &mut self, +        device: &wgpu::Device, +        encoder: &mut wgpu::CommandEncoder, +        handle: &image::Handle, +        atlas: &mut Atlas, +    ) -> Option<&atlas::Entry> { +        let memory = self.load(handle); + +        if let Memory::Host(image) = memory { +            let (width, height) = image.dimensions(); + +            let entry = atlas.upload(device, encoder, width, height, image)?; + +            *memory = Memory::Device(entry); +        } + +        if let Memory::Device(allocation) = memory { +            Some(allocation) +        } else { +            None +        } +    } + +    /// Trim cache misses from cache +    pub fn trim(&mut self, atlas: &mut Atlas) { +        let hits = &self.hits; + +        self.map.retain(|k, memory| { +            let retain = hits.contains(k); + +            if !retain { +                if let Memory::Device(entry) = memory { +                    atlas.remove(entry); +                } +            } + +            retain +        }); + +        self.hits.clear(); +    } + +    fn get(&mut self, handle: &image::Handle) -> Option<&mut Memory> { +        let _ = self.hits.insert(handle.id()); + +        self.map.get_mut(&handle.id()) +    } + +    fn insert(&mut self, handle: &image::Handle, memory: Memory) { +        let _ = self.map.insert(handle.id(), memory); +    } + +    fn contains(&self, handle: &image::Handle) -> bool { +        self.map.contains_key(&handle.id()) +    } +} diff --git a/graphics/src/image/vector.rs b/wgpu/src/image/vector.rs index c950ccd6..2c03d36b 100644 --- a/graphics/src/image/vector.rs +++ b/wgpu/src/image/vector.rs @@ -1,9 +1,6 @@ -//! Vector image loading and caching -use crate::image::Storage; -use crate::Color; - -use iced_native::svg; -use iced_native::Size; +use crate::core::svg; +use crate::core::{Color, Size}; +use crate::image::atlas::{self, Atlas};  use resvg::tiny_skia;  use resvg::usvg; @@ -33,19 +30,21 @@ impl Svg {  }  /// Caches svg vector and raster data -#[derive(Debug)] -pub struct Cache<T: Storage> { +#[derive(Debug, Default)] +pub struct Cache {      svgs: HashMap<u64, Svg>, -    rasterized: HashMap<(u64, u32, u32, ColorFilter), T::Entry>, +    rasterized: HashMap<(u64, u32, u32, ColorFilter), atlas::Entry>,      svg_hits: HashSet<u64>,      rasterized_hits: HashSet<(u64, u32, u32, ColorFilter)>,  }  type ColorFilter = Option<[u8; 4]>; -impl<T: Storage> Cache<T> { +impl Cache {      /// Load svg      pub fn load(&mut self, handle: &svg::Handle) -> &Svg { +        use usvg::TreeParsing; +          if self.svgs.contains_key(&handle.id()) {              return self.svgs.get(&handle.id()).unwrap();          } @@ -74,13 +73,14 @@ impl<T: Storage> Cache<T> {      /// Load svg and upload raster data      pub fn upload(          &mut self, +        device: &wgpu::Device, +        encoder: &mut wgpu::CommandEncoder,          handle: &svg::Handle,          color: Option<Color>,          [width, height]: [f32; 2],          scale: f32, -        state: &mut T::State<'_>, -        storage: &mut T, -    ) -> Option<&T::Entry> { +        atlas: &mut Atlas, +    ) -> Option<&atlas::Entry> {          let id = handle.id();          let (width, height) = ( @@ -114,16 +114,28 @@ impl<T: Storage> Cache<T> {                  // It would be cool to be able to smooth resize the `svg` example.                  let mut img = tiny_skia::Pixmap::new(width, height)?; -                resvg::render( -                    tree, -                    if width > height { -                        usvg::FitTo::Width(width) -                    } else { -                        usvg::FitTo::Height(height) -                    }, -                    tiny_skia::Transform::default(), -                    img.as_mut(), -                )?; +                let tree_size = tree.size.to_int_size(); + +                let target_size = if width > height { +                    tree_size.scale_to_width(width) +                } else { +                    tree_size.scale_to_height(height) +                }; + +                let transform = if let Some(target_size) = target_size { +                    let tree_size = tree_size.to_size(); +                    let target_size = target_size.to_size(); + +                    tiny_skia::Transform::from_scale( +                        target_size.width() / tree_size.width(), +                        target_size.height() / tree_size.height(), +                    ) +                } else { +                    tiny_skia::Transform::default() +                }; + +                resvg::Tree::from_usvg(tree) +                    .render(transform, &mut img.as_mut());                  let mut rgba = img.take(); @@ -137,7 +149,9 @@ impl<T: Storage> Cache<T> {                      });                  } -                let allocation = storage.upload(width, height, &rgba, state)?; +                let allocation = +                    atlas.upload(device, encoder, width, height, &rgba)?; +                  log::debug!("allocating {} {}x{}", id, width, height);                  let _ = self.svg_hits.insert(id); @@ -151,7 +165,7 @@ impl<T: Storage> Cache<T> {      }      /// Load svg and upload raster data -    pub fn trim(&mut self, storage: &mut T, state: &mut T::State<'_>) { +    pub fn trim(&mut self, atlas: &mut Atlas) {          let svg_hits = &self.svg_hits;          let rasterized_hits = &self.rasterized_hits; @@ -160,7 +174,7 @@ impl<T: Storage> Cache<T> {              let retain = rasterized_hits.contains(k);              if !retain { -                storage.remove(entry, state); +                atlas.remove(entry);              }              retain @@ -170,17 +184,6 @@ impl<T: Storage> Cache<T> {      }  } -impl<T: Storage> Default for Cache<T> { -    fn default() -> Self { -        Self { -            svgs: HashMap::new(), -            rasterized: HashMap::new(), -            svg_hits: HashSet::new(), -            rasterized_hits: HashSet::new(), -        } -    } -} -  impl std::fmt::Debug for Svg {      fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {          match self { diff --git a/graphics/src/layer.rs b/wgpu/src/layer.rs index 1d453caa..b8f32db1 100644 --- a/graphics/src/layer.rs +++ b/wgpu/src/layer.rs @@ -1,19 +1,21 @@  //! Organize rendering primitives into a flattened list of layers.  mod image; -mod quad;  mod text;  pub mod mesh;  pub use image::Image;  pub use mesh::Mesh; -pub use quad::Quad;  pub use text::Text; -use crate::alignment; -use crate::{ -    Background, Font, Point, Primitive, Rectangle, Size, Vector, Viewport, -}; +use crate::core; +use crate::core::alignment; +use crate::core::{Color, Font, Point, Rectangle, Size, Vector}; +use crate::graphics; +use crate::graphics::color; +use crate::graphics::Viewport; +use crate::primitive::{self, Primitive}; +use crate::quad::{self, Quad};  /// A group of primitives that should be clipped together.  #[derive(Debug)] @@ -22,7 +24,7 @@ pub struct Layer<'a> {      pub bounds: Rectangle,      /// The quads of the [`Layer`]. -    pub quads: Vec<Quad>, +    pub quads: quad::Batch,      /// The triangle meshes of the [`Layer`].      pub meshes: Vec<Mesh<'a>>, @@ -39,7 +41,7 @@ impl<'a> Layer<'a> {      pub fn new(bounds: Rectangle) -> Self {          Self {              bounds, -            quads: Vec::new(), +            quads: quad::Batch::default(),              meshes: Vec::new(),              text: Vec::new(),              images: Vec::new(), @@ -60,18 +62,20 @@ impl<'a> Layer<'a> {                      Point::new(11.0, 11.0 + 25.0 * i as f32),                      Size::INFINITY,                  ), -                color: [0.9, 0.9, 0.9, 1.0], +                color: Color::new(0.9, 0.9, 0.9, 1.0),                  size: 20.0, -                font: Font::Default, +                line_height: core::text::LineHeight::default(), +                font: Font::MONOSPACE,                  horizontal_alignment: alignment::Horizontal::Left,                  vertical_alignment: alignment::Vertical::Top, +                shaping: core::text::Shaping::Basic,              };              overlay.text.push(text);              overlay.text.push(Text {                  bounds: text.bounds + Vector::new(-1.0, -1.0), -                color: [0.0, 0.0, 0.0, 1.0], +                color: Color::BLACK,                  ..text              });          } @@ -109,26 +113,16 @@ impl<'a> Layer<'a> {          current_layer: usize,      ) {          match primitive { -            Primitive::None => {} -            Primitive::Group { primitives } => { -                // TODO: Inspect a bit and regroup (?) -                for primitive in primitives { -                    Self::process_primitive( -                        layers, -                        translation, -                        primitive, -                        current_layer, -                    ) -                } -            }              Primitive::Text {                  content,                  bounds,                  size, +                line_height,                  color,                  font,                  horizontal_alignment,                  vertical_alignment, +                shaping,              } => {                  let layer = &mut layers[current_layer]; @@ -136,10 +130,12 @@ impl<'a> Layer<'a> {                      content,                      bounds: *bounds + translation,                      size: *size, -                    color: color.into_linear(), +                    line_height: *line_height, +                    color: *color,                      font: *font,                      horizontal_alignment: *horizontal_alignment,                      vertical_alignment: *vertical_alignment, +                    shaping: *shaping,                  });              }              Primitive::Quad { @@ -151,58 +147,49 @@ impl<'a> Layer<'a> {              } => {                  let layer = &mut layers[current_layer]; -                // TODO: Move some of these computations to the GPU (?) -                layer.quads.push(Quad { +                let quad = Quad {                      position: [                          bounds.x + translation.x,                          bounds.y + translation.y,                      ],                      size: [bounds.width, bounds.height], -                    color: match background { -                        Background::Color(color) => color.into_linear(), -                    }, +                    border_color: color::pack(*border_color),                      border_radius: *border_radius,                      border_width: *border_width, -                    border_color: border_color.into_linear(), -                }); +                }; + +                layer.quads.add(quad, background);              } -            Primitive::SolidMesh { buffers, size } => { +            Primitive::Image { handle, bounds } => {                  let layer = &mut layers[current_layer]; -                let bounds = Rectangle::new( -                    Point::new(translation.x, translation.y), -                    *size, -                ); - -                // Only draw visible content -                if let Some(clip_bounds) = layer.bounds.intersection(&bounds) { -                    layer.meshes.push(Mesh::Solid { -                        origin: Point::new(translation.x, translation.y), -                        buffers, -                        clip_bounds, -                    }); -                } +                layer.images.push(Image::Raster { +                    handle: handle.clone(), +                    bounds: *bounds + translation, +                });              } -            Primitive::GradientMesh { -                buffers, -                size, -                gradient, +            Primitive::Svg { +                handle, +                color, +                bounds,              } => {                  let layer = &mut layers[current_layer]; -                let bounds = Rectangle::new( -                    Point::new(translation.x, translation.y), -                    *size, -                ); - -                // Only draw visible content -                if let Some(clip_bounds) = layer.bounds.intersection(&bounds) { -                    layer.meshes.push(Mesh::Gradient { -                        origin: Point::new(translation.x, translation.y), -                        buffers, -                        clip_bounds, -                        gradient, -                    }); +                layer.images.push(Image::Vector { +                    handle: handle.clone(), +                    color: *color, +                    bounds: *bounds + translation, +                }); +            } +            Primitive::Group { primitives } => { +                // TODO: Inspect a bit and regroup (?) +                for primitive in primitives { +                    Self::process_primitive( +                        layers, +                        translation, +                        primitive, +                        current_layer, +                    )                  }              }              Primitive::Clip { bounds, content } => { @@ -235,35 +222,62 @@ impl<'a> Layer<'a> {                      current_layer,                  );              } -            Primitive::Cached { cache } => { +            Primitive::Cache { content } => {                  Self::process_primitive(                      layers,                      translation, -                    cache, +                    content,                      current_layer,                  );              } -            Primitive::Image { handle, bounds } => { -                let layer = &mut layers[current_layer]; +            Primitive::Custom(custom) => match custom { +                primitive::Custom::Mesh(mesh) => match mesh { +                    graphics::Mesh::Solid { buffers, size } => { +                        let layer = &mut layers[current_layer]; -                layer.images.push(Image::Raster { -                    handle: handle.clone(), -                    bounds: *bounds + translation, -                }); -            } -            Primitive::Svg { -                handle, -                color, -                bounds, -            } => { -                let layer = &mut layers[current_layer]; +                        let bounds = Rectangle::new( +                            Point::new(translation.x, translation.y), +                            *size, +                        ); -                layer.images.push(Image::Vector { -                    handle: handle.clone(), -                    color: *color, -                    bounds: *bounds + translation, -                }); -            } +                        // Only draw visible content +                        if let Some(clip_bounds) = +                            layer.bounds.intersection(&bounds) +                        { +                            layer.meshes.push(Mesh::Solid { +                                origin: Point::new( +                                    translation.x, +                                    translation.y, +                                ), +                                buffers, +                                clip_bounds, +                            }); +                        } +                    } +                    graphics::Mesh::Gradient { buffers, size } => { +                        let layer = &mut layers[current_layer]; + +                        let bounds = Rectangle::new( +                            Point::new(translation.x, translation.y), +                            *size, +                        ); + +                        // Only draw visible content +                        if let Some(clip_bounds) = +                            layer.bounds.intersection(&bounds) +                        { +                            layer.meshes.push(Mesh::Gradient { +                                origin: Point::new( +                                    translation.x, +                                    translation.y, +                                ), +                                buffers, +                                clip_bounds, +                            }); +                        } +                    } +                }, +            },          }      }  } diff --git a/graphics/src/layer/image.rs b/wgpu/src/layer/image.rs index 3eff2397..0de589f8 100644 --- a/graphics/src/layer/image.rs +++ b/wgpu/src/layer/image.rs @@ -1,6 +1,6 @@ -use crate::{Color, Rectangle}; - -use iced_native::{image, svg}; +use crate::core::image; +use crate::core::svg; +use crate::core::{Color, Rectangle};  /// A raster or vector image.  #[derive(Debug, Clone)] diff --git a/graphics/src/layer/mesh.rs b/wgpu/src/layer/mesh.rs index 7661c5c9..7c6206cd 100644 --- a/graphics/src/layer/mesh.rs +++ b/wgpu/src/layer/mesh.rs @@ -1,6 +1,6 @@  //! A collection of triangle primitives. -use crate::triangle; -use crate::{Gradient, Point, Rectangle}; +use crate::core::{Point, Rectangle}; +use crate::graphics::mesh;  /// A mesh of triangles.  #[derive(Debug, Clone, Copy)] @@ -11,7 +11,7 @@ pub enum Mesh<'a> {          origin: Point,          /// The vertex and index buffers of the [`Mesh`]. -        buffers: &'a triangle::Mesh2D<triangle::ColoredVertex2D>, +        buffers: &'a mesh::Indexed<mesh::SolidVertex2D>,          /// The clipping bounds of the [`Mesh`].          clip_bounds: Rectangle<f32>, @@ -22,13 +22,10 @@ pub enum Mesh<'a> {          origin: Point,          /// The vertex and index buffers of the [`Mesh`]. -        buffers: &'a triangle::Mesh2D<triangle::Vertex2D>, +        buffers: &'a mesh::Indexed<mesh::GradientVertex2D>,          /// The clipping bounds of the [`Mesh`].          clip_bounds: Rectangle<f32>, - -        /// The gradient to apply to the [`Mesh`]. -        gradient: &'a Gradient,      },  } @@ -65,9 +62,15 @@ pub struct AttributeCount {      /// The total amount of solid vertices.      pub solid_vertices: usize, +    /// The total amount of solid meshes. +    pub solids: usize, +      /// The total amount of gradient vertices.      pub gradient_vertices: usize, +    /// The total amount of gradient meshes. +    pub gradients: usize, +      /// The total amount of indices.      pub indices: usize,  } @@ -79,10 +82,12 @@ pub fn attribute_count_of<'a>(meshes: &'a [Mesh<'a>]) -> AttributeCount {          .fold(AttributeCount::default(), |mut count, mesh| {              match mesh {                  Mesh::Solid { buffers, .. } => { +                    count.solids += 1;                      count.solid_vertices += buffers.vertices.len();                      count.indices += buffers.indices.len();                  }                  Mesh::Gradient { buffers, .. } => { +                    count.gradients += 1;                      count.gradient_vertices += buffers.vertices.len();                      count.indices += buffers.indices.len();                  } diff --git a/graphics/src/layer/text.rs b/wgpu/src/layer/text.rs index 74f7a676..ba1bdca8 100644 --- a/graphics/src/layer/text.rs +++ b/wgpu/src/layer/text.rs @@ -1,4 +1,6 @@ -use crate::{alignment, Font, Rectangle}; +use crate::core::alignment; +use crate::core::text; +use crate::core::{Color, Font, Rectangle};  /// A paragraph of text.  #[derive(Debug, Clone, Copy)] @@ -10,11 +12,14 @@ pub struct Text<'a> {      pub bounds: Rectangle,      /// The color of the [`Text`], in __linear RGB_. -    pub color: [f32; 4], +    pub color: Color, -    /// The size of the [`Text`]. +    /// The size of the [`Text`] in logical pixels.      pub size: f32, +    /// The line height of the [`Text`]. +    pub line_height: text::LineHeight, +      /// The font of the [`Text`].      pub font: Font, @@ -23,4 +28,7 @@ pub struct Text<'a> {      /// The vertical alignment of the [`Text`].      pub vertical_alignment: alignment::Vertical, + +    /// The shaping strategy of the text. +    pub shaping: text::Shaping,  } diff --git a/wgpu/src/lib.rs b/wgpu/src/lib.rs index 1a293681..deb223ef 100644 --- a/wgpu/src/lib.rs +++ b/wgpu/src/lib.rs @@ -16,7 +16,7 @@  //! - Meshes of triangles, useful to draw geometry freely.  //!  //! [Iced]: https://github.com/iced-rs/iced -//! [`iced_native`]: https://github.com/iced-rs/iced/tree/0.8/native +//! [`iced_native`]: https://github.com/iced-rs/iced/tree/0.9/native  //! [`wgpu`]: https://github.com/gfx-rs/wgpu-rs  //! [WebGPU API]: https://gpuweb.github.io/gpuweb/  //! [`wgpu_glyph`]: https://github.com/hecrj/wgpu_glyph @@ -36,26 +36,34 @@  )]  #![forbid(rust_2018_idioms)]  #![allow(clippy::inherent_to_string, clippy::type_complexity)] -#![cfg_attr(docsrs, feature(doc_cfg))] - +#![cfg_attr(docsrs, feature(doc_auto_cfg))] +pub mod layer; +pub mod primitive;  pub mod settings;  pub mod window; +#[cfg(feature = "geometry")] +pub mod geometry; +  mod backend;  mod buffer; +mod color;  mod quad;  mod text;  mod triangle; -pub use iced_graphics::{Antialiasing, Color, Error, Primitive, Viewport}; -pub use iced_native::Theme; +use buffer::Buffer; + +pub use iced_graphics as graphics; +pub use iced_graphics::core; +  pub use wgpu;  pub use backend::Backend; +pub use layer::Layer; +pub use primitive::Primitive;  pub use settings::Settings; -pub(crate) use iced_graphics::Transformation; -  #[cfg(any(feature = "image", feature = "svg"))]  mod image; @@ -63,5 +71,4 @@ mod image;  ///  /// [`wgpu`]: https://github.com/gfx-rs/wgpu-rs  /// [`iced`]: https://github.com/iced-rs/iced -pub type Renderer<Theme = iced_native::Theme> = -    iced_graphics::Renderer<Backend, Theme>; +pub type Renderer<Theme> = iced_graphics::Renderer<Backend, Theme>; diff --git a/wgpu/src/primitive.rs b/wgpu/src/primitive.rs new file mode 100644 index 00000000..8dbf3008 --- /dev/null +++ b/wgpu/src/primitive.rs @@ -0,0 +1,21 @@ +//! Draw using different graphical primitives. +use crate::core::Rectangle; +use crate::graphics::{Damage, Mesh}; + +/// The graphical primitives supported by `iced_wgpu`. +pub type Primitive = crate::graphics::Primitive<Custom>; + +/// The custom primitives supported by `iced_wgpu`. +#[derive(Debug, Clone, PartialEq)] +pub enum Custom { +    /// A mesh primitive. +    Mesh(Mesh), +} + +impl Damage for Custom { +    fn bounds(&self) -> Rectangle { +        match self { +            Self::Mesh(mesh) => mesh.bounds(), +        } +    } +} diff --git a/wgpu/src/quad.rs b/wgpu/src/quad.rs index 2f5fcc6b..37d0c623 100644 --- a/wgpu/src/quad.rs +++ b/wgpu/src/quad.rs @@ -1,22 +1,32 @@ -use crate::Transformation; -use iced_graphics::layer; -use iced_native::Rectangle; +mod gradient; +mod solid; + +use gradient::Gradient; +use solid::Solid; + +use crate::core::{Background, Rectangle}; +use crate::graphics::color; +use crate::graphics::{self, Transformation};  use bytemuck::{Pod, Zeroable}; -use std::mem;  use wgpu::util::DeviceExt; +use std::mem; +  #[cfg(feature = "tracing")]  use tracing::info_span; +const INITIAL_INSTANCES: usize = 2_000; +  #[derive(Debug)]  pub struct Pipeline { -    pipeline: wgpu::RenderPipeline, -    constants: wgpu::BindGroup, -    constants_buffer: wgpu::Buffer, +    solid: solid::Pipeline, +    gradient: gradient::Pipeline, +    constant_layout: wgpu::BindGroupLayout,      vertices: wgpu::Buffer,      indices: wgpu::Buffer, -    instances: wgpu::Buffer, +    layers: Vec<Layer>, +    prepare_layer: usize,  }  impl Pipeline { @@ -38,239 +48,292 @@ impl Pipeline {                  }],              }); -        let constants_buffer = device.create_buffer(&wgpu::BufferDescriptor { -            label: Some("iced_wgpu::quad uniforms buffer"), -            size: mem::size_of::<Uniforms>() as wgpu::BufferAddress, -            usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST, -            mapped_at_creation: false, -        }); - -        let constants = device.create_bind_group(&wgpu::BindGroupDescriptor { -            label: Some("iced_wgpu::quad uniforms bind group"), -            layout: &constant_layout, -            entries: &[wgpu::BindGroupEntry { -                binding: 0, -                resource: constants_buffer.as_entire_binding(), -            }], -        }); - -        let layout = -            device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor { -                label: Some("iced_wgpu::quad pipeline layout"), -                push_constant_ranges: &[], -                bind_group_layouts: &[&constant_layout], -            }); - -        let shader = -            device.create_shader_module(wgpu::ShaderModuleDescriptor { -                label: Some("iced_wgpu::quad::shader"), -                source: wgpu::ShaderSource::Wgsl(std::borrow::Cow::Borrowed( -                    include_str!("shader/quad.wgsl"), -                )), -            }); - -        let pipeline = -            device.create_render_pipeline(&wgpu::RenderPipelineDescriptor { -                label: Some("iced_wgpu::quad pipeline"), -                layout: Some(&layout), -                vertex: wgpu::VertexState { -                    module: &shader, -                    entry_point: "vs_main", -                    buffers: &[ -                        wgpu::VertexBufferLayout { -                            array_stride: mem::size_of::<Vertex>() as u64, -                            step_mode: wgpu::VertexStepMode::Vertex, -                            attributes: &[wgpu::VertexAttribute { -                                shader_location: 0, -                                format: wgpu::VertexFormat::Float32x2, -                                offset: 0, -                            }], -                        }, -                        wgpu::VertexBufferLayout { -                            array_stride: mem::size_of::<layer::Quad>() as u64, -                            step_mode: wgpu::VertexStepMode::Instance, -                            attributes: &wgpu::vertex_attr_array!( -                                1 => Float32x2, -                                2 => Float32x2, -                                3 => Float32x4, -                                4 => Float32x4, -                                5 => Float32x4, -                                6 => Float32, -                            ), -                        }, -                    ], -                }, -                fragment: Some(wgpu::FragmentState { -                    module: &shader, -                    entry_point: "fs_main", -                    targets: &[Some(wgpu::ColorTargetState { -                        format, -                        blend: Some(wgpu::BlendState { -                            color: wgpu::BlendComponent { -                                src_factor: wgpu::BlendFactor::SrcAlpha, -                                dst_factor: wgpu::BlendFactor::OneMinusSrcAlpha, -                                operation: wgpu::BlendOperation::Add, -                            }, -                            alpha: wgpu::BlendComponent { -                                src_factor: wgpu::BlendFactor::One, -                                dst_factor: wgpu::BlendFactor::OneMinusSrcAlpha, -                                operation: wgpu::BlendOperation::Add, -                            }, -                        }), -                        write_mask: wgpu::ColorWrites::ALL, -                    })], -                }), -                primitive: wgpu::PrimitiveState { -                    topology: wgpu::PrimitiveTopology::TriangleList, -                    front_face: wgpu::FrontFace::Cw, -                    ..Default::default() -                }, -                depth_stencil: None, -                multisample: wgpu::MultisampleState { -                    count: 1, -                    mask: !0, -                    alpha_to_coverage_enabled: false, -                }, -                multiview: None, -            }); -          let vertices =              device.create_buffer_init(&wgpu::util::BufferInitDescriptor {                  label: Some("iced_wgpu::quad vertex buffer"), -                contents: bytemuck::cast_slice(&QUAD_VERTS), +                contents: bytemuck::cast_slice(&VERTICES),                  usage: wgpu::BufferUsages::VERTEX,              });          let indices =              device.create_buffer_init(&wgpu::util::BufferInitDescriptor {                  label: Some("iced_wgpu::quad index buffer"), -                contents: bytemuck::cast_slice(&QUAD_INDICES), +                contents: bytemuck::cast_slice(&INDICES),                  usage: wgpu::BufferUsages::INDEX,              }); -        let instances = device.create_buffer(&wgpu::BufferDescriptor { -            label: Some("iced_wgpu::quad instance buffer"), -            size: mem::size_of::<layer::Quad>() as u64 * MAX_INSTANCES as u64, -            usage: wgpu::BufferUsages::VERTEX | wgpu::BufferUsages::COPY_DST, +        Self { +            vertices, +            indices, +            solid: solid::Pipeline::new(device, format, &constant_layout), +            gradient: gradient::Pipeline::new(device, format, &constant_layout), +            layers: Vec::new(), +            prepare_layer: 0, +            constant_layout, +        } +    } + +    pub fn prepare( +        &mut self, +        device: &wgpu::Device, +        queue: &wgpu::Queue, +        quads: &Batch, +        transformation: Transformation, +        scale: f32, +    ) { +        if self.layers.len() <= self.prepare_layer { +            self.layers.push(Layer::new(device, &self.constant_layout)); +        } + +        let layer = &mut self.layers[self.prepare_layer]; +        layer.prepare(device, queue, quads, transformation, scale); + +        self.prepare_layer += 1; +    } + +    pub fn render<'a>( +        &'a self, +        layer: usize, +        bounds: Rectangle<u32>, +        quads: &Batch, +        render_pass: &mut wgpu::RenderPass<'a>, +    ) { +        if let Some(layer) = self.layers.get(layer) { +            render_pass.set_scissor_rect( +                bounds.x, +                bounds.y, +                bounds.width, +                bounds.height, +            ); +            render_pass.set_index_buffer( +                self.indices.slice(..), +                wgpu::IndexFormat::Uint16, +            ); +            render_pass.set_vertex_buffer(0, self.vertices.slice(..)); + +            let mut solid_offset = 0; +            let mut gradient_offset = 0; + +            for (kind, count) in &quads.order { +                match kind { +                    Kind::Solid => { +                        self.solid.render( +                            render_pass, +                            &layer.constants, +                            &layer.solid, +                            solid_offset..(solid_offset + count), +                        ); + +                        solid_offset += count; +                    } +                    Kind::Gradient => { +                        self.gradient.render( +                            render_pass, +                            &layer.constants, +                            &layer.gradient, +                            gradient_offset..(gradient_offset + count), +                        ); + +                        gradient_offset += count; +                    } +                } +            } +        } +    } + +    pub fn end_frame(&mut self) { +        self.prepare_layer = 0; +    } +} + +#[derive(Debug)] +struct Layer { +    constants: wgpu::BindGroup, +    constants_buffer: wgpu::Buffer, +    solid: solid::Layer, +    gradient: gradient::Layer, +} + +impl Layer { +    pub fn new( +        device: &wgpu::Device, +        constant_layout: &wgpu::BindGroupLayout, +    ) -> Self { +        let constants_buffer = device.create_buffer(&wgpu::BufferDescriptor { +            label: Some("iced_wgpu::quad uniforms buffer"), +            size: mem::size_of::<Uniforms>() as wgpu::BufferAddress, +            usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,              mapped_at_creation: false,          }); -        Pipeline { -            pipeline, +        let constants = device.create_bind_group(&wgpu::BindGroupDescriptor { +            label: Some("iced_wgpu::quad uniforms bind group"), +            layout: constant_layout, +            entries: &[wgpu::BindGroupEntry { +                binding: 0, +                resource: constants_buffer.as_entire_binding(), +            }], +        }); + +        Self {              constants,              constants_buffer, -            vertices, -            indices, -            instances, +            solid: solid::Layer::new(device), +            gradient: gradient::Layer::new(device),          }      } -    pub fn draw( +    pub fn prepare(          &mut self,          device: &wgpu::Device, -        staging_belt: &mut wgpu::util::StagingBelt, -        encoder: &mut wgpu::CommandEncoder, -        instances: &[layer::Quad], +        queue: &wgpu::Queue, +        quads: &Batch,          transformation: Transformation,          scale: f32, -        bounds: Rectangle<u32>, -        target: &wgpu::TextureView,      ) {          #[cfg(feature = "tracing")] -        let _ = info_span!("Wgpu::Quad", "DRAW").entered(); +        let _ = info_span!("Wgpu::Quad", "PREPARE").entered();          let uniforms = Uniforms::new(transformation, scale); -        { -            let mut constants_buffer = staging_belt.write_buffer( -                encoder, -                &self.constants_buffer, -                0, -                wgpu::BufferSize::new(mem::size_of::<Uniforms>() as u64) -                    .unwrap(), -                device, -            ); +        queue.write_buffer( +            &self.constants_buffer, +            0, +            bytemuck::bytes_of(&uniforms), +        ); -            constants_buffer.copy_from_slice(bytemuck::bytes_of(&uniforms)); -        } +        self.solid.prepare(device, queue, &quads.solids); +        self.gradient.prepare(device, queue, &quads.gradients); +    } +} -        let mut i = 0; -        let total = instances.len(); +/// The properties of a quad. +#[derive(Clone, Copy, Debug, Pod, Zeroable)] +#[repr(C)] +pub struct Quad { +    /// The position of the [`Quad`]. +    pub position: [f32; 2], -        while i < total { -            let end = (i + MAX_INSTANCES).min(total); -            let amount = end - i; +    /// The size of the [`Quad`]. +    pub size: [f32; 2], -            let instance_bytes = bytemuck::cast_slice(&instances[i..end]); +    /// The border color of the [`Quad`], in __linear RGB__. +    pub border_color: color::Packed, -            let mut instance_buffer = staging_belt.write_buffer( -                encoder, -                &self.instances, -                0, -                wgpu::BufferSize::new(instance_bytes.len() as u64).unwrap(), -                device, -            ); +    /// The border radii of the [`Quad`]. +    pub border_radius: [f32; 4], + +    /// The border width of the [`Quad`]. +    pub border_width: f32, +} + +/// A group of [`Quad`]s rendered together. +#[derive(Default, Debug)] +pub struct Batch { +    /// The solid quads of the [`Layer`]. +    solids: Vec<Solid>, + +    /// The gradient quads of the [`Layer`]. +    gradients: Vec<Gradient>, + +    /// The quad order of the [`Layer`]; stored as a tuple of the quad type & its count. +    order: Vec<(Kind, usize)>, +} + +impl Batch { +    /// Returns true if there are no quads of any type in [`Quads`]. +    pub fn is_empty(&self) -> bool { +        self.solids.is_empty() && self.gradients.is_empty() +    } + +    /// Adds a [`Quad`] with the provided `Background` type to the quad [`Layer`]. +    pub fn add(&mut self, quad: Quad, background: &Background) { +        let kind = match background { +            Background::Color(color) => { +                self.solids.push(Solid { +                    color: color::pack(*color), +                    quad, +                }); -            instance_buffer.copy_from_slice(instance_bytes); - -            #[cfg(feature = "tracing")] -            let _ = info_span!("Wgpu::Quad", "BEGIN_RENDER_PASS").enter(); - -            { -                let mut render_pass = -                    encoder.begin_render_pass(&wgpu::RenderPassDescriptor { -                        label: Some("iced_wgpu::quad render pass"), -                        color_attachments: &[Some( -                            wgpu::RenderPassColorAttachment { -                                view: target, -                                resolve_target: None, -                                ops: wgpu::Operations { -                                    load: wgpu::LoadOp::Load, -                                    store: true, -                                }, -                            }, -                        )], -                        depth_stencil_attachment: None, -                    }); - -                render_pass.set_pipeline(&self.pipeline); -                render_pass.set_bind_group(0, &self.constants, &[]); -                render_pass.set_index_buffer( -                    self.indices.slice(..), -                    wgpu::IndexFormat::Uint16, -                ); -                render_pass.set_vertex_buffer(0, self.vertices.slice(..)); -                render_pass.set_vertex_buffer(1, self.instances.slice(..)); - -                render_pass.set_scissor_rect( -                    bounds.x, -                    bounds.y, -                    bounds.width, -                    // TODO: Address anti-aliasing adjustments properly -                    bounds.height, -                ); - -                render_pass.draw_indexed( -                    0..QUAD_INDICES.len() as u32, -                    0, -                    0..amount as u32, -                ); +                Kind::Solid              } +            Background::Gradient(gradient) => { +                self.gradients.push(Gradient { +                    gradient: graphics::gradient::pack( +                        gradient, +                        Rectangle::new(quad.position.into(), quad.size.into()), +                    ), +                    quad, +                }); + +                Kind::Gradient +            } +        }; -            i += MAX_INSTANCES; +        match self.order.last_mut() { +            Some((last_kind, count)) if kind == *last_kind => { +                *count += 1; +            } +            _ => { +                self.order.push((kind, 1)); +            }          }      }  } +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +/// The kind of a quad. +enum Kind { +    /// A solid quad +    Solid, +    /// A gradient quad +    Gradient, +} + +fn color_target_state( +    format: wgpu::TextureFormat, +) -> [Option<wgpu::ColorTargetState>; 1] { +    [Some(wgpu::ColorTargetState { +        format, +        blend: Some(wgpu::BlendState { +            color: wgpu::BlendComponent { +                src_factor: wgpu::BlendFactor::SrcAlpha, +                dst_factor: wgpu::BlendFactor::OneMinusSrcAlpha, +                operation: wgpu::BlendOperation::Add, +            }, +            alpha: wgpu::BlendComponent { +                src_factor: wgpu::BlendFactor::One, +                dst_factor: wgpu::BlendFactor::OneMinusSrcAlpha, +                operation: wgpu::BlendOperation::Add, +            }, +        }), +        write_mask: wgpu::ColorWrites::ALL, +    })] +} +  #[repr(C)] -#[derive(Clone, Copy, Zeroable, Pod)] +#[derive(Clone, Copy, bytemuck::Zeroable, bytemuck::Pod)]  pub struct Vertex {      _position: [f32; 2],  } -const QUAD_INDICES: [u16; 6] = [0, 1, 2, 0, 2, 3]; +impl Vertex { +    fn buffer_layout<'a>() -> wgpu::VertexBufferLayout<'a> { +        wgpu::VertexBufferLayout { +            array_stride: mem::size_of::<Self>() as u64, +            step_mode: wgpu::VertexStepMode::Vertex, +            attributes: &[wgpu::VertexAttribute { +                shader_location: 0, +                format: wgpu::VertexFormat::Float32x2, +                offset: 0, +            }], +        } +    } +} + +const INDICES: [u16; 6] = [0, 1, 2, 0, 2, 3]; -const QUAD_VERTS: [Vertex; 4] = [ +const VERTICES: [Vertex; 4] = [      Vertex {          _position: [0.0, 0.0],      }, @@ -285,10 +348,8 @@ const QUAD_VERTS: [Vertex; 4] = [      },  ]; -const MAX_INSTANCES: usize = 100_000; -  #[repr(C)] -#[derive(Debug, Clone, Copy, Zeroable, Pod)] +#[derive(Debug, Clone, Copy, bytemuck::Zeroable, bytemuck::Pod)]  struct Uniforms {      transform: [f32; 16],      scale: f32, diff --git a/wgpu/src/quad/gradient.rs b/wgpu/src/quad/gradient.rs new file mode 100644 index 00000000..6db37252 --- /dev/null +++ b/wgpu/src/quad/gradient.rs @@ -0,0 +1,165 @@ +use crate::graphics::gradient; +use crate::quad::{self, Quad}; +use crate::Buffer; + +use bytemuck::{Pod, Zeroable}; +use std::ops::Range; + +/// A quad filled with interpolated colors. +#[derive(Clone, Copy, Debug)] +#[repr(C)] +pub struct Gradient { +    /// The background gradient data of the quad. +    pub gradient: gradient::Packed, + +    /// The [`Quad`] data of the [`Gradient`]. +    pub quad: Quad, +} + +#[allow(unsafe_code)] +unsafe impl Pod for Gradient {} + +#[allow(unsafe_code)] +unsafe impl Zeroable for Gradient {} + +#[derive(Debug)] +pub struct Layer { +    instances: Buffer<Gradient>, +    instance_count: usize, +} + +impl Layer { +    pub fn new(device: &wgpu::Device) -> Self { +        let instances = Buffer::new( +            device, +            "iced_wgpu.quad.gradient.buffer", +            quad::INITIAL_INSTANCES, +            wgpu::BufferUsages::VERTEX | wgpu::BufferUsages::COPY_DST, +        ); + +        Self { +            instances, +            instance_count: 0, +        } +    } + +    pub fn prepare( +        &mut self, +        device: &wgpu::Device, +        queue: &wgpu::Queue, +        instances: &[Gradient], +    ) { +        let _ = self.instances.resize(device, instances.len()); +        let _ = self.instances.write(queue, 0, instances); + +        self.instance_count = instances.len(); +    } +} + +#[derive(Debug)] +pub struct Pipeline { +    pipeline: wgpu::RenderPipeline, +} + +impl Pipeline { +    pub fn new( +        device: &wgpu::Device, +        format: wgpu::TextureFormat, +        constants_layout: &wgpu::BindGroupLayout, +    ) -> Self { +        let layout = +            device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor { +                label: Some("iced_wgpu.quad.gradient.pipeline"), +                push_constant_ranges: &[], +                bind_group_layouts: &[constants_layout], +            }); + +        let shader = +            device.create_shader_module(wgpu::ShaderModuleDescriptor { +                label: Some("iced_wgpu.quad.gradient.shader"), +                source: wgpu::ShaderSource::Wgsl(std::borrow::Cow::Borrowed( +                    include_str!("../shader/quad.wgsl"), +                )), +            }); + +        let pipeline = +            device.create_render_pipeline(&wgpu::RenderPipelineDescriptor { +                label: Some("iced_wgpu.quad.gradient.pipeline"), +                layout: Some(&layout), +                vertex: wgpu::VertexState { +                    module: &shader, +                    entry_point: "gradient_vs_main", +                    buffers: &[ +                        quad::Vertex::buffer_layout(), +                        wgpu::VertexBufferLayout { +                            array_stride: std::mem::size_of::<Gradient>() +                                as u64, +                            step_mode: wgpu::VertexStepMode::Instance, +                            attributes: &wgpu::vertex_attr_array!( +                                // Colors 1-2 +                                1 => Uint32x4, +                                // Colors 3-4 +                                2 => Uint32x4, +                                // Colors 5-6 +                                3 => Uint32x4, +                                // Colors 7-8 +                                4 => Uint32x4, +                                // Offsets 1-8 +                                5 => Uint32x4, +                                // Direction +                                6 => Float32x4, +                                // Position & Scale +                                7 => Float32x4, +                                // Border color +                                8 => Float32x4, +                                // Border radius +                                9 => Float32x4, +                                // Border width +                                10 => Float32 +                            ), +                        }, +                    ], +                }, +                fragment: Some(wgpu::FragmentState { +                    module: &shader, +                    entry_point: "gradient_fs_main", +                    targets: &quad::color_target_state(format), +                }), +                primitive: wgpu::PrimitiveState { +                    topology: wgpu::PrimitiveTopology::TriangleList, +                    front_face: wgpu::FrontFace::Cw, +                    ..Default::default() +                }, +                depth_stencil: None, +                multisample: wgpu::MultisampleState { +                    count: 1, +                    mask: !0, +                    alpha_to_coverage_enabled: false, +                }, +                multiview: None, +            }); + +        Self { pipeline } +    } + +    pub fn render<'a>( +        &'a self, +        render_pass: &mut wgpu::RenderPass<'a>, +        constants: &'a wgpu::BindGroup, +        layer: &'a Layer, +        range: Range<usize>, +    ) { +        #[cfg(feature = "tracing")] +        let _ = tracing::info_span!("Wgpu::Quad::Gradient", "DRAW").entered(); + +        render_pass.set_pipeline(&self.pipeline); +        render_pass.set_bind_group(0, constants, &[]); +        render_pass.set_vertex_buffer(1, layer.instances.slice(..)); + +        render_pass.draw_indexed( +            0..quad::INDICES.len() as u32, +            0, +            range.start as u32..range.end as u32, +        ); +    } +} diff --git a/wgpu/src/quad/solid.rs b/wgpu/src/quad/solid.rs new file mode 100644 index 00000000..f8f1e3a5 --- /dev/null +++ b/wgpu/src/quad/solid.rs @@ -0,0 +1,150 @@ +use crate::graphics::color; +use crate::quad::{self, Quad}; +use crate::Buffer; + +use bytemuck::{Pod, Zeroable}; +use std::ops::Range; + +/// A quad filled with a solid color. +#[derive(Clone, Copy, Debug, Pod, Zeroable)] +#[repr(C)] +pub struct Solid { +    /// The background color data of the quad. +    pub color: color::Packed, + +    /// The [`Quad`] data of the [`Solid`]. +    pub quad: Quad, +} + +#[derive(Debug)] +pub struct Layer { +    instances: Buffer<Solid>, +    instance_count: usize, +} + +impl Layer { +    pub fn new(device: &wgpu::Device) -> Self { +        let instances = Buffer::new( +            device, +            "iced_wgpu.quad.solid.buffer", +            quad::INITIAL_INSTANCES, +            wgpu::BufferUsages::VERTEX | wgpu::BufferUsages::COPY_DST, +        ); + +        Self { +            instances, +            instance_count: 0, +        } +    } + +    pub fn prepare( +        &mut self, +        device: &wgpu::Device, +        queue: &wgpu::Queue, +        instances: &[Solid], +    ) { +        let _ = self.instances.resize(device, instances.len()); +        let _ = self.instances.write(queue, 0, instances); + +        self.instance_count = instances.len(); +    } +} + +#[derive(Debug)] +pub struct Pipeline { +    pipeline: wgpu::RenderPipeline, +} + +impl Pipeline { +    pub fn new( +        device: &wgpu::Device, +        format: wgpu::TextureFormat, +        constants_layout: &wgpu::BindGroupLayout, +    ) -> Self { +        let layout = +            device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor { +                label: Some("iced_wgpu.quad.solid.pipeline"), +                push_constant_ranges: &[], +                bind_group_layouts: &[constants_layout], +            }); + +        let shader = +            device.create_shader_module(wgpu::ShaderModuleDescriptor { +                label: Some("iced_wgpu.quad.solid.shader"), +                source: wgpu::ShaderSource::Wgsl(std::borrow::Cow::Borrowed( +                    include_str!("../shader/quad.wgsl"), +                )), +            }); + +        let pipeline = +            device.create_render_pipeline(&wgpu::RenderPipelineDescriptor { +                label: Some("iced_wgpu.quad.solid.pipeline"), +                layout: Some(&layout), +                vertex: wgpu::VertexState { +                    module: &shader, +                    entry_point: "solid_vs_main", +                    buffers: &[ +                        quad::Vertex::buffer_layout(), +                        wgpu::VertexBufferLayout { +                            array_stride: std::mem::size_of::<Solid>() as u64, +                            step_mode: wgpu::VertexStepMode::Instance, +                            attributes: &wgpu::vertex_attr_array!( +                                // Color +                                1 => Float32x4, +                                // Position +                                2 => Float32x2, +                                // Size +                                3 => Float32x2, +                                // Border color +                                4 => Float32x4, +                                // Border radius +                                5 => Float32x4, +                                // Border width +                                6 => Float32, +                            ), +                        }, +                    ], +                }, +                fragment: Some(wgpu::FragmentState { +                    module: &shader, +                    entry_point: "solid_fs_main", +                    targets: &quad::color_target_state(format), +                }), +                primitive: wgpu::PrimitiveState { +                    topology: wgpu::PrimitiveTopology::TriangleList, +                    front_face: wgpu::FrontFace::Cw, +                    ..Default::default() +                }, +                depth_stencil: None, +                multisample: wgpu::MultisampleState { +                    count: 1, +                    mask: !0, +                    alpha_to_coverage_enabled: false, +                }, +                multiview: None, +            }); + +        Self { pipeline } +    } + +    pub fn render<'a>( +        &'a self, +        render_pass: &mut wgpu::RenderPass<'a>, +        constants: &'a wgpu::BindGroup, +        layer: &'a Layer, +        range: Range<usize>, +    ) { +        #[cfg(feature = "tracing")] +        let _ = tracing::info_span!("Wgpu::Quad::Solid", "DRAW").entered(); + +        render_pass.set_pipeline(&self.pipeline); +        render_pass.set_bind_group(0, constants, &[]); +        render_pass.set_vertex_buffer(1, layer.instances.slice(..)); + +        render_pass.draw_indexed( +            0..quad::INDICES.len() as u32, +            0, +            range.start as u32..range.end as u32, +        ); +    } +} diff --git a/wgpu/src/settings.rs b/wgpu/src/settings.rs index 5ef79499..266a2c87 100644 --- a/wgpu/src/settings.rs +++ b/wgpu/src/settings.rs @@ -1,12 +1,11 @@  //! Configure a renderer. -use std::fmt; - -pub use crate::Antialiasing; +use crate::core::Font; +use crate::graphics::Antialiasing;  /// The settings of a [`Backend`].  ///  /// [`Backend`]: crate::Backend -#[derive(Clone, Copy, PartialEq)] +#[derive(Debug, Clone, Copy, PartialEq)]  pub struct Settings {      /// The present mode of the [`Backend`].      /// @@ -16,42 +15,20 @@ pub struct Settings {      /// The internal graphics backend to use.      pub internal_backend: wgpu::Backends, -    /// The bytes of the font that will be used by default. -    /// -    /// If `None` is provided, a default system font will be chosen. -    pub default_font: Option<&'static [u8]>, +    /// The default [`Font`] to use. +    pub default_font: Font,      /// The default size of text.      ///      /// By default, it will be set to `16.0`.      pub default_text_size: f32, -    /// If enabled, spread text workload in multiple threads when multiple cores -    /// are available. -    /// -    /// By default, it is disabled. -    pub text_multithreading: bool, -      /// The antialiasing strategy that will be used for triangle primitives.      ///      /// By default, it is `None`.      pub antialiasing: Option<Antialiasing>,  } -impl fmt::Debug for Settings { -    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { -        f.debug_struct("Settings") -            .field("present_mode", &self.present_mode) -            .field("internal_backend", &self.internal_backend) -            // Instead of printing the font bytes, we simply show a `bool` indicating if using a default font or not. -            .field("default_font", &self.default_font.is_some()) -            .field("default_text_size", &self.default_text_size) -            .field("text_multithreading", &self.text_multithreading) -            .field("antialiasing", &self.antialiasing) -            .finish() -    } -} -  impl Settings {      /// Creates new [`Settings`] using environment configuration.      /// @@ -69,7 +46,7 @@ impl Settings {      ///     - `primary`      pub fn from_env() -> Self {          Settings { -            internal_backend: backend_from_env() +            internal_backend: wgpu::util::backend_bits_from_env()                  .unwrap_or(wgpu::Backends::all()),              ..Self::default()          } @@ -81,25 +58,9 @@ impl Default for Settings {          Settings {              present_mode: wgpu::PresentMode::AutoVsync,              internal_backend: wgpu::Backends::all(), -            default_font: None, -            default_text_size: 20.0, -            text_multithreading: false, +            default_font: Font::default(), +            default_text_size: 16.0,              antialiasing: None,          }      }  } - -fn backend_from_env() -> Option<wgpu::Backends> { -    std::env::var("WGPU_BACKEND").ok().map(|backend| { -        match backend.to_lowercase().as_str() { -            "vulkan" => wgpu::Backends::VULKAN, -            "metal" => wgpu::Backends::METAL, -            "dx12" => wgpu::Backends::DX12, -            "dx11" => wgpu::Backends::DX11, -            "gl" => wgpu::Backends::GL, -            "webgpu" => wgpu::Backends::BROWSER_WEBGPU, -            "primary" => wgpu::Backends::PRIMARY, -            other => panic!("Unknown backend: {other}"), -        } -    }) -} diff --git a/wgpu/src/shader/gradient.wgsl b/wgpu/src/shader/gradient.wgsl deleted file mode 100644 index 63825aec..00000000 --- a/wgpu/src/shader/gradient.wgsl +++ /dev/null @@ -1,88 +0,0 @@ -struct Uniforms { -    transform: mat4x4<f32>, -    //xy = start, wz = end -    position: vec4<f32>, -    //x = start stop, y = end stop, zw = padding -    stop_range: vec4<i32>, -} - -struct Stop { -    color: vec4<f32>, -    offset: f32, -}; - -@group(0) @binding(0) -var<uniform> uniforms: Uniforms; - -@group(0) @binding(1) -var<storage, read> color_stops: array<Stop>; - -struct VertexOutput { -    @builtin(position) position: vec4<f32>, -    @location(0) raw_position: vec2<f32> -} - -@vertex -fn vs_main(@location(0) input: vec2<f32>) -> VertexOutput { -    var output: VertexOutput; -    output.position = uniforms.transform * vec4<f32>(input.xy, 0.0, 1.0); -    output.raw_position = input; - -    return output; -} - -//TODO: rewrite without branching -@fragment -fn fs_main(input: VertexOutput) -> @location(0) vec4<f32> { -    let start = uniforms.position.xy; -    let end = uniforms.position.zw; -    let start_stop = uniforms.stop_range.x; -    let end_stop = uniforms.stop_range.y; - -    let v1 = end - start; -    let v2 = input.raw_position.xy - start; -    let unit = normalize(v1); -    let offset = dot(unit, v2) / length(v1); - -    let min_stop = color_stops[start_stop]; -    let max_stop = color_stops[end_stop]; - -    var color: vec4<f32>; - -    if (offset <= min_stop.offset) { -        color = min_stop.color; -    } else if (offset >= max_stop.offset) { -        color = max_stop.color; -    } else { -        var min = min_stop; -        var max = max_stop; -        var min_index = start_stop; -        var max_index = end_stop; - -        loop { -            if (min_index >= max_index - 1) { -                break; -            } - -            let index = min_index + (max_index - min_index) / 2; - -            let stop = color_stops[index]; - -            if (offset <= stop.offset) { -                max = stop; -                max_index = index; -            } else { -                min = stop; -                min_index = index; -            } -        } - -        color = mix(min.color, max.color, smoothstep( -            min.offset, -            max.offset, -            offset -        )); -    } - -    return color; -} diff --git a/wgpu/src/shader/quad.wgsl b/wgpu/src/shader/quad.wgsl index cf4f7e4d..fb402158 100644 --- a/wgpu/src/shader/quad.wgsl +++ b/wgpu/src/shader/quad.wgsl @@ -5,17 +5,57 @@ struct Globals {  @group(0) @binding(0) var<uniform> globals: Globals; -struct VertexInput { +fn distance_alg( +    frag_coord: vec2<f32>, +    position: vec2<f32>, +    size: vec2<f32>, +    radius: f32 +) -> f32 { +    var inner_size: vec2<f32> = size - vec2<f32>(radius, radius) * 2.0; +    var top_left: vec2<f32> = position + vec2<f32>(radius, radius); +    var bottom_right: vec2<f32> = top_left + inner_size; + +    var top_left_distance: vec2<f32> = top_left - frag_coord; +    var bottom_right_distance: vec2<f32> = frag_coord - bottom_right; + +    var dist: vec2<f32> = vec2<f32>( +        max(max(top_left_distance.x, bottom_right_distance.x), 0.0), +        max(max(top_left_distance.y, bottom_right_distance.y), 0.0) +    ); + +    return sqrt(dist.x * dist.x + dist.y * dist.y); +} + +// Based on the fragement position and the center of the quad, select one of the 4 radi. +// Order matches CSS border radius attribute: +// radi.x = top-left, radi.y = top-right, radi.z = bottom-right, radi.w = bottom-left +fn select_border_radius(radi: vec4<f32>, position: vec2<f32>, center: vec2<f32>) -> f32 { +    var rx = radi.x; +    var ry = radi.y; +    rx = select(radi.x, radi.y, position.x > center.x); +    ry = select(radi.w, radi.z, position.x > center.x); +    rx = select(rx, ry, position.y > center.y); +    return rx; +} + +fn unpack_u32(color: vec2<u32>) -> vec4<f32> { +    let rg: vec2<f32> = unpack2x16float(color.x); +    let ba: vec2<f32> = unpack2x16float(color.y); + +    return vec4<f32>(rg.y, rg.x, ba.y, ba.x); +} + +struct SolidVertexInput {      @location(0) v_pos: vec2<f32>, -    @location(1) pos: vec2<f32>, -    @location(2) scale: vec2<f32>, -    @location(3) color: vec4<f32>, +    @location(1) color: vec4<f32>, +    @location(2) pos: vec2<f32>, +    @location(3) scale: vec2<f32>,      @location(4) border_color: vec4<f32>,      @location(5) border_radius: vec4<f32>,      @location(6) border_width: f32,  } -struct VertexOutput { +struct SolidVertexOutput {      @builtin(position) position: vec4<f32>,      @location(0) color: vec4<f32>,      @location(1) border_color: vec4<f32>, @@ -26,8 +66,8 @@ struct VertexOutput {  }  @vertex -fn vs_main(input: VertexInput) -> VertexOutput { -    var out: VertexOutput; +fn solid_vs_main(input: SolidVertexInput) -> SolidVertexOutput { +    var out: SolidVertexOutput;      var pos: vec2<f32> = input.pos * globals.scale;      var scale: vec2<f32> = input.scale * globals.scale; @@ -47,54 +87,20 @@ fn vs_main(input: VertexInput) -> VertexOutput {          vec4<f32>(pos - vec2<f32>(0.5, 0.5), 0.0, 1.0)      ); +    out.position = globals.transform * transform * vec4<f32>(input.v_pos, 0.0, 1.0);      out.color = input.color;      out.border_color = input.border_color;      out.pos = pos;      out.scale = scale;      out.border_radius = border_radius * globals.scale;      out.border_width = input.border_width * globals.scale; -    out.position = globals.transform * transform * vec4<f32>(input.v_pos, 0.0, 1.0);      return out;  } -fn distance_alg( -    frag_coord: vec2<f32>, -    position: vec2<f32>, -    size: vec2<f32>, -    radius: f32 -) -> f32 { -    var inner_size: vec2<f32> = size - vec2<f32>(radius, radius) * 2.0; -    var top_left: vec2<f32> = position + vec2<f32>(radius, radius); -    var bottom_right: vec2<f32> = top_left + inner_size; - -    var top_left_distance: vec2<f32> = top_left - frag_coord; -    var bottom_right_distance: vec2<f32> = frag_coord - bottom_right; - -    var dist: vec2<f32> = vec2<f32>( -        max(max(top_left_distance.x, bottom_right_distance.x), 0.0), -        max(max(top_left_distance.y, bottom_right_distance.y), 0.0) -    ); - -    return sqrt(dist.x * dist.x + dist.y * dist.y); -} - -// Based on the fragement position and the center of the quad, select one of the 4 radi. -// Order matches CSS border radius attribute: -// radi.x = top-left, radi.y = top-right, radi.z = bottom-right, radi.w = bottom-left -fn select_border_radius(radi: vec4<f32>, position: vec2<f32>, center: vec2<f32>) -> f32 { -    var rx = radi.x; -    var ry = radi.y; -    rx = select(radi.x, radi.y, position.x > center.x); -    ry = select(radi.w, radi.z, position.x > center.x); -    rx = select(rx, ry, position.y > center.y); -    return rx; -} - -  @fragment -fn fs_main( -    input: VertexOutput +fn solid_fs_main( +    input: SolidVertexOutput  ) -> @location(0) vec4<f32> {      var mixed_color: vec4<f32> = input.color; @@ -138,3 +144,202 @@ fn fs_main(      return vec4<f32>(mixed_color.x, mixed_color.y, mixed_color.z, mixed_color.w * radius_alpha);  } + +struct GradientVertexInput { +    @location(0) v_pos: vec2<f32>, +    @location(1) colors_1: vec4<u32>, +    @location(2) colors_2: vec4<u32>, +    @location(3) colors_3: vec4<u32>, +    @location(4) colors_4: vec4<u32>, +    @location(5) offsets: vec4<u32>, +    @location(6) direction: vec4<f32>, +    @location(7) position_and_scale: vec4<f32>, +    @location(8) border_color: vec4<f32>, +    @location(9) border_radius: vec4<f32>, +    @location(10) border_width: f32, +} + +struct GradientVertexOutput { +    @builtin(position) position: vec4<f32>, +    @location(1) colors_1: vec4<u32>, +    @location(2) colors_2: vec4<u32>, +    @location(3) colors_3: vec4<u32>, +    @location(4) colors_4: vec4<u32>, +    @location(5) offsets: vec4<u32>, +    @location(6) direction: vec4<f32>, +    @location(7) position_and_scale: vec4<f32>, +    @location(8) border_color: vec4<f32>, +    @location(9) border_radius: vec4<f32>, +    @location(10) border_width: f32, +} + +@vertex +fn gradient_vs_main(input: GradientVertexInput) -> GradientVertexOutput { +    var out: GradientVertexOutput; + +    var pos: vec2<f32> = input.position_and_scale.xy * globals.scale; +    var scale: vec2<f32> = input.position_and_scale.zw * globals.scale; + +    var min_border_radius = min(input.position_and_scale.z, input.position_and_scale.w) * 0.5; +    var border_radius: vec4<f32> = vec4<f32>( +        min(input.border_radius.x, min_border_radius), +        min(input.border_radius.y, min_border_radius), +        min(input.border_radius.z, min_border_radius), +        min(input.border_radius.w, min_border_radius) +    ); + +    var transform: mat4x4<f32> = mat4x4<f32>( +        vec4<f32>(scale.x + 1.0, 0.0, 0.0, 0.0), +        vec4<f32>(0.0, scale.y + 1.0, 0.0, 0.0), +        vec4<f32>(0.0, 0.0, 1.0, 0.0), +        vec4<f32>(pos - vec2<f32>(0.5, 0.5), 0.0, 1.0) +    ); + +    out.position = globals.transform * transform * vec4<f32>(input.v_pos, 0.0, 1.0); +    out.colors_1 = input.colors_1; +    out.colors_2 = input.colors_2; +    out.colors_3 = input.colors_3; +    out.colors_4 = input.colors_4; +    out.offsets = input.offsets; +    out.direction = input.direction * globals.scale; +    out.position_and_scale = vec4<f32>(pos, scale); +    out.border_color = input.border_color; +    out.border_radius = border_radius * globals.scale; +    out.border_width = input.border_width * globals.scale; + +    return out; +} + +fn random(coords: vec2<f32>) -> f32 { +    return fract(sin(dot(coords, vec2(12.9898,78.233))) * 43758.5453); +} + +/// Returns the current interpolated color with a max 8-stop gradient +fn gradient( +    raw_position: vec2<f32>, +    direction: vec4<f32>, +    colors: array<vec4<f32>, 8>, +    offsets: array<f32, 8>, +    last_index: i32 +) -> vec4<f32> { +    let start = direction.xy; +    let end = direction.zw; + +    let v1 = end - start; +    let v2 = raw_position - start; +    let unit = normalize(v1); +    let coord_offset = dot(unit, v2) / length(v1); + +    //need to store these as a var to use dynamic indexing in a loop +    //this is already added to wgsl spec but not in wgpu yet +    var colors_arr = colors; +    var offsets_arr = offsets; + +    var color: vec4<f32>; + +    let noise_granularity: f32 = 0.3/255.0; + +    for (var i: i32 = 0; i < last_index; i++) { +        let curr_offset = offsets_arr[i]; +        let next_offset = offsets_arr[i+1]; + +        if (coord_offset <= offsets_arr[0]) { +            color = colors_arr[0]; +        } + +        if (curr_offset <= coord_offset && coord_offset <= next_offset) { +            color = mix(colors_arr[i], colors_arr[i+1], smoothstep( +                curr_offset, +                next_offset, +                coord_offset, +            )); +        } + +        if (coord_offset >= offsets_arr[last_index]) { +            color = colors_arr[last_index]; +        } +    } + +    return color + mix(-noise_granularity, noise_granularity, random(raw_position)); +} + +@fragment +fn gradient_fs_main(input: GradientVertexOutput) -> @location(0) vec4<f32> { +    let colors = array<vec4<f32>, 8>( +        unpack_u32(input.colors_1.xy), +        unpack_u32(input.colors_1.zw), +        unpack_u32(input.colors_2.xy), +        unpack_u32(input.colors_2.zw), +        unpack_u32(input.colors_3.xy), +        unpack_u32(input.colors_3.zw), +        unpack_u32(input.colors_4.xy), +        unpack_u32(input.colors_4.zw), +    ); + +    let offsets_1: vec4<f32> = unpack_u32(input.offsets.xy); +    let offsets_2: vec4<f32> = unpack_u32(input.offsets.zw); + +    var offsets = array<f32, 8>( +        offsets_1.x, +        offsets_1.y, +        offsets_1.z, +        offsets_1.w, +        offsets_2.x, +        offsets_2.y, +        offsets_2.z, +        offsets_2.w, +    ); + +    //TODO could just pass this in to the shader but is probably more performant to just check it here +    var last_index = 7; +    for (var i: i32 = 0; i <= 7; i++) { +        if (offsets[i] > 1.0) { +            last_index = i - 1; +            break; +        } +    } + +    var mixed_color: vec4<f32> = gradient(input.position.xy, input.direction, colors, offsets, last_index); + +    let pos = input.position_and_scale.xy; +    let scale = input.position_and_scale.zw; + +    var border_radius = select_border_radius( +        input.border_radius, +        input.position.xy, +        (pos + scale * 0.5).xy +    ); + +    if (input.border_width > 0.0) { +        var internal_border: f32 = max(border_radius - input.border_width, 0.0); + +        var internal_distance: f32 = distance_alg( +            input.position.xy, +            pos + vec2<f32>(input.border_width, input.border_width), +            scale - vec2<f32>(input.border_width * 2.0, input.border_width * 2.0), +            internal_border +        ); + +        var border_mix: f32 = smoothstep( +            max(internal_border - 0.5, 0.0), +            internal_border + 0.5, +            internal_distance +        ); + +        mixed_color = mix(mixed_color, input.border_color, vec4<f32>(border_mix, border_mix, border_mix, border_mix)); +    } + +    var dist: f32 = distance_alg( +        input.position.xy, +        pos, +        scale, +        border_radius +    ); + +    var radius_alpha: f32 = 1.0 - smoothstep( +        max(border_radius - 0.5, 0.0), +        border_radius + 0.5, +        dist); + +    return vec4<f32>(mixed_color.x, mixed_color.y, mixed_color.z, mixed_color.w * radius_alpha); +} diff --git a/wgpu/src/shader/solid.wgsl b/wgpu/src/shader/solid.wgsl deleted file mode 100644 index b24402f8..00000000 --- a/wgpu/src/shader/solid.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/shader/triangle.wgsl b/wgpu/src/shader/triangle.wgsl new file mode 100644 index 00000000..9f512d14 --- /dev/null +++ b/wgpu/src/shader/triangle.wgsl @@ -0,0 +1,165 @@ +struct Globals { +    transform: mat4x4<f32>, +} + +@group(0) @binding(0) var<uniform> globals: Globals; + +fn unpack_u32(color: vec2<u32>) -> vec4<f32> { +    let rg: vec2<f32> = unpack2x16float(color.x); +    let ba: vec2<f32> = unpack2x16float(color.y); + +    return vec4<f32>(rg.y, rg.x, ba.y, ba.x); +} + +struct SolidVertexInput { +    @location(0) position: vec2<f32>, +    @location(1) color: vec4<f32>, +} + +struct SolidVertexOutput { +    @builtin(position) position: vec4<f32>, +    @location(0) color: vec4<f32>, +} + +@vertex +fn solid_vs_main(input: SolidVertexInput) -> SolidVertexOutput { +    var out: SolidVertexOutput; + +    out.color = input.color; +    out.position = globals.transform * vec4<f32>(input.position, 0.0, 1.0); + +    return out; +} + +@fragment +fn solid_fs_main(input: SolidVertexOutput) -> @location(0) vec4<f32> { +    return input.color; +} + +struct GradientVertexInput { +    @location(0) v_pos: vec2<f32>, +    @location(1) colors_1: vec4<u32>, +    @location(2) colors_2: vec4<u32>, +    @location(3) colors_3: vec4<u32>, +    @location(4) colors_4: vec4<u32>, +    @location(5) offsets: vec4<u32>, +    @location(6) direction: vec4<f32>, +} + +struct GradientVertexOutput { +    @builtin(position) position: vec4<f32>, +    @location(0) raw_position: vec2<f32>, +    @location(1) colors_1: vec4<u32>, +    @location(2) colors_2: vec4<u32>, +    @location(3) colors_3: vec4<u32>, +    @location(4) colors_4: vec4<u32>, +    @location(5) offsets: vec4<u32>, +    @location(6) direction: vec4<f32>, +} + +@vertex +fn gradient_vs_main(input: GradientVertexInput) -> GradientVertexOutput { +    var output: GradientVertexOutput; + +    output.position = globals.transform * vec4<f32>(input.v_pos, 0.0, 1.0); +    output.raw_position = input.v_pos; +    output.colors_1 = input.colors_1; +    output.colors_2 = input.colors_2; +    output.colors_3 = input.colors_3; +    output.colors_4 = input.colors_4; +    output.offsets = input.offsets; +    output.direction = input.direction; + +    return output; +} + +fn random(coords: vec2<f32>) -> f32 { +    return fract(sin(dot(coords, vec2(12.9898,78.233))) * 43758.5453); +} + +/// Returns the current interpolated color with a max 8-stop gradient +fn gradient( +    raw_position: vec2<f32>, +    direction: vec4<f32>, +    colors: array<vec4<f32>, 8>, +    offsets: array<f32, 8>, +    last_index: i32 +) -> vec4<f32> { +    let start = direction.xy; +    let end = direction.zw; + +    let v1 = end - start; +    let v2 = raw_position - start; +    let unit = normalize(v1); +    let coord_offset = dot(unit, v2) / length(v1); + +    //need to store these as a var to use dynamic indexing in a loop +    //this is already added to wgsl spec but not in wgpu yet +    var colors_arr = colors; +    var offsets_arr = offsets; + +    var color: vec4<f32>; + +    let noise_granularity: f32 = 0.3/255.0; + +    for (var i: i32 = 0; i < last_index; i++) { +        let curr_offset = offsets_arr[i]; +        let next_offset = offsets_arr[i+1]; + +        if (coord_offset <= offsets_arr[0]) { +            color = colors_arr[0]; +        } + +        if (curr_offset <= coord_offset && coord_offset <= next_offset) { +            color = mix(colors_arr[i], colors_arr[i+1], smoothstep( +                curr_offset, +                next_offset, +                coord_offset, +            )); +        } + +        if (coord_offset >= offsets_arr[last_index]) { +            color = colors_arr[last_index]; +        } +    } + +    return color + mix(-noise_granularity, noise_granularity, random(raw_position)); +} + +@fragment +fn gradient_fs_main(input: GradientVertexOutput) -> @location(0) vec4<f32> { +    let colors = array<vec4<f32>, 8>( +        unpack_u32(input.colors_1.xy), +        unpack_u32(input.colors_1.zw), +        unpack_u32(input.colors_2.xy), +        unpack_u32(input.colors_2.zw), +        unpack_u32(input.colors_3.xy), +        unpack_u32(input.colors_3.zw), +        unpack_u32(input.colors_4.xy), +        unpack_u32(input.colors_4.zw), +    ); + +    let offsets_1: vec4<f32> = unpack_u32(input.offsets.xy); +    let offsets_2: vec4<f32> = unpack_u32(input.offsets.zw); + +    var offsets = array<f32, 8>( +        offsets_1.x, +        offsets_1.y, +        offsets_1.z, +        offsets_1.w, +        offsets_2.x, +        offsets_2.y, +        offsets_2.z, +        offsets_2.w, +    ); + +    var last_index = 7; +    for (var i: i32 = 0; i <= 7; i++) { +        if (offsets[i] >= 1.0) { +            last_index = i; +            break; +        } +    } + +    return gradient(input.raw_position, input.direction, colors, offsets, last_index); +} diff --git a/wgpu/src/text.rs b/wgpu/src/text.rs index e17b84c1..65d3b818 100644 --- a/wgpu/src/text.rs +++ b/wgpu/src/text.rs @@ -1,265 +1,506 @@ -use crate::Transformation; - -use iced_graphics::font; - -use std::{cell::RefCell, collections::HashMap}; -use wgpu_glyph::ab_glyph; - -pub use iced_native::text::Hit; - -#[derive(Debug)] +use crate::core::alignment; +use crate::core::font::{self, Font}; +use crate::core::text::{Hit, LineHeight, Shaping}; +use crate::core::{Pixels, Point, Rectangle, Size}; +use crate::graphics::color; +use crate::layer::Text; + +use rustc_hash::{FxHashMap, FxHashSet}; +use std::borrow::Cow; +use std::cell::RefCell; +use std::collections::hash_map; +use std::hash::{BuildHasher, Hash, Hasher}; +use std::sync::Arc; + +#[allow(missing_debug_implementations)]  pub struct Pipeline { -    draw_brush: RefCell<wgpu_glyph::GlyphBrush<()>>, -    draw_font_map: RefCell<HashMap<String, wgpu_glyph::FontId>>, -    measure_brush: RefCell<glyph_brush::GlyphBrush<()>>, +    font_system: RefCell<glyphon::FontSystem>, +    renderers: Vec<glyphon::TextRenderer>, +    atlas: glyphon::TextAtlas, +    prepare_layer: usize, +    cache: RefCell<Cache>,  }  impl Pipeline {      pub fn new(          device: &wgpu::Device, +        queue: &wgpu::Queue,          format: wgpu::TextureFormat, -        default_font: Option<&[u8]>, -        multithreading: bool,      ) -> Self { -        let default_font = default_font.map(|slice| slice.to_vec()); - -        // TODO: Font customization -        #[cfg(not(target_os = "ios"))] -        #[cfg(feature = "default_system_font")] -        let default_font = { -            default_font.or_else(|| { -                font::Source::new() -                    .load(&[font::Family::SansSerif, font::Family::Serif]) -                    .ok() -            }) -        }; +        Pipeline { +            font_system: RefCell::new(glyphon::FontSystem::new_with_fonts( +                [glyphon::fontdb::Source::Binary(Arc::new( +                    include_bytes!("../fonts/Iced-Icons.ttf").as_slice(), +                ))] +                .into_iter(), +            )), +            renderers: Vec::new(), +            atlas: glyphon::TextAtlas::new( +                device, +                queue, +                format, +                if color::GAMMA_CORRECTION { +                    glyphon::ColorMode::Accurate +                } else { +                    glyphon::ColorMode::Web +                }, +            ), +            prepare_layer: 0, +            cache: RefCell::new(Cache::new()), +        } +    } -        let default_font = -            default_font.unwrap_or_else(|| font::FALLBACK.to_vec()); +    pub fn load_font(&mut self, bytes: Cow<'static, [u8]>) { +        let _ = self.font_system.get_mut().db_mut().load_font_source( +            glyphon::fontdb::Source::Binary(Arc::new(bytes.into_owned())), +        ); -        let font = ab_glyph::FontArc::try_from_vec(default_font) -            .unwrap_or_else(|_| { -                log::warn!( -                    "System font failed to load. Falling back to \ -                    embedded font..." -                ); +        self.cache = RefCell::new(Cache::new()); +    } -                ab_glyph::FontArc::try_from_slice(font::FALLBACK) -                    .expect("Load fallback font") -            }); +    pub fn prepare( +        &mut self, +        device: &wgpu::Device, +        queue: &wgpu::Queue, +        sections: &[Text<'_>], +        bounds: Rectangle, +        scale_factor: f32, +        target_size: Size<u32>, +    ) -> bool { +        if self.renderers.len() <= self.prepare_layer { +            self.renderers.push(glyphon::TextRenderer::new( +                &mut self.atlas, +                device, +                Default::default(), +                None, +            )); +        } -        let draw_brush_builder = -            wgpu_glyph::GlyphBrushBuilder::using_font(font.clone()) -                .initial_cache_size((2048, 2048)) -                .draw_cache_multithread(multithreading); +        let font_system = self.font_system.get_mut(); +        let renderer = &mut self.renderers[self.prepare_layer]; +        let cache = self.cache.get_mut(); -        #[cfg(target_arch = "wasm32")] -        let draw_brush_builder = draw_brush_builder.draw_cache_align_4x4(true); +        if self.prepare_layer == 0 { +            cache.trim(Purpose::Drawing); +        } -        let draw_brush = draw_brush_builder.build(device, format); +        let keys: Vec<_> = sections +            .iter() +            .map(|section| { +                let (key, _) = cache.allocate( +                    font_system, +                    Key { +                        content: section.content, +                        size: section.size, +                        line_height: f32::from( +                            section +                                .line_height +                                .to_absolute(Pixels(section.size)), +                        ), +                        font: section.font, +                        bounds: Size { +                            width: section.bounds.width, +                            height: section.bounds.height, +                        }, +                        shaping: section.shaping, +                    }, +                    Purpose::Drawing, +                ); -        let measure_brush = -            glyph_brush::GlyphBrushBuilder::using_font(font).build(); +                key +            }) +            .collect(); + +        let bounds = bounds * scale_factor; + +        let text_areas = +            sections +                .iter() +                .zip(keys.iter()) +                .filter_map(|(section, key)| { +                    let entry = cache.get(key).expect("Get cached buffer"); + +                    let x = section.bounds.x * scale_factor; +                    let y = section.bounds.y * scale_factor; + +                    let max_width = entry.bounds.width * scale_factor; +                    let total_height = entry.bounds.height * scale_factor; + +                    let left = match section.horizontal_alignment { +                        alignment::Horizontal::Left => x, +                        alignment::Horizontal::Center => x - max_width / 2.0, +                        alignment::Horizontal::Right => x - max_width, +                    }; + +                    let top = match section.vertical_alignment { +                        alignment::Vertical::Top => y, +                        alignment::Vertical::Center => y - total_height / 2.0, +                        alignment::Vertical::Bottom => y - total_height, +                    }; + +                    let section_bounds = Rectangle { +                        x: left, +                        y: top, +                        width: section.bounds.width * scale_factor, +                        height: section.bounds.height * scale_factor, +                    }; + +                    let clip_bounds = bounds.intersection(§ion_bounds)?; + +                    Some(glyphon::TextArea { +                        buffer: &entry.buffer, +                        left, +                        top, +                        scale: scale_factor, +                        bounds: glyphon::TextBounds { +                            left: clip_bounds.x as i32, +                            top: clip_bounds.y as i32, +                            right: (clip_bounds.x + clip_bounds.width) as i32, +                            bottom: (clip_bounds.y + clip_bounds.height) as i32, +                        }, +                        default_color: { +                            let [r, g, b, a] = +                                color::pack(section.color).components(); + +                            glyphon::Color::rgba( +                                (r * 255.0) as u8, +                                (g * 255.0) as u8, +                                (b * 255.0) as u8, +                                (a * 255.0) as u8, +                            ) +                        }, +                    }) +                }); + +        let result = renderer.prepare( +            device, +            queue, +            font_system, +            &mut self.atlas, +            glyphon::Resolution { +                width: target_size.width, +                height: target_size.height, +            }, +            text_areas, +            &mut glyphon::SwashCache::new(), +        ); -        Pipeline { -            draw_brush: RefCell::new(draw_brush), -            draw_font_map: RefCell::new(HashMap::new()), -            measure_brush: RefCell::new(measure_brush), +        match result { +            Ok(()) => { +                self.prepare_layer += 1; + +                true +            } +            Err(glyphon::PrepareError::AtlasFull(content_type)) => { +                self.prepare_layer = 0; + +                #[allow(clippy::needless_bool)] +                if self.atlas.grow(device, content_type) { +                    false +                } else { +                    // If the atlas cannot grow, then all bets are off. +                    // Instead of panicking, we will just pray that the result +                    // will be somewhat readable... +                    true +                } +            }          }      } -    pub fn queue(&mut self, section: wgpu_glyph::Section<'_>) { -        self.draw_brush.borrow_mut().queue(section); +    pub fn render<'a>( +        &'a self, +        layer: usize, +        bounds: Rectangle<u32>, +        render_pass: &mut wgpu::RenderPass<'a>, +    ) { +        let renderer = &self.renderers[layer]; + +        render_pass.set_scissor_rect( +            bounds.x, +            bounds.y, +            bounds.width, +            bounds.height, +        ); + +        renderer +            .render(&self.atlas, render_pass) +            .expect("Render text");      } -    pub fn draw_queued( -        &mut self, -        device: &wgpu::Device, -        staging_belt: &mut wgpu::util::StagingBelt, -        encoder: &mut wgpu::CommandEncoder, -        target: &wgpu::TextureView, -        transformation: Transformation, -        region: wgpu_glyph::Region, -    ) { -        self.draw_brush -            .borrow_mut() -            .draw_queued_with_transform_and_scissoring( -                device, -                staging_belt, -                encoder, -                target, -                transformation.into(), -                region, -            ) -            .expect("Draw text"); +    pub fn end_frame(&mut self) { +        self.atlas.trim(); + +        self.prepare_layer = 0; +    } + +    pub fn trim_measurements(&mut self) { +        self.cache.get_mut().trim(Purpose::Measuring);      }      pub fn measure(          &self,          content: &str,          size: f32, -        font: iced_native::Font, -        bounds: iced_native::Size, -    ) -> (f32, f32) { -        use wgpu_glyph::GlyphCruncher; - -        let wgpu_glyph::FontId(font_id) = self.find_font(font); - -        let section = wgpu_glyph::Section { -            bounds: (bounds.width, bounds.height), -            text: vec![wgpu_glyph::Text { -                text: content, -                scale: size.into(), -                font_id: wgpu_glyph::FontId(font_id), -                extra: wgpu_glyph::Extra::default(), -            }], -            ..Default::default() -        }; +        line_height: LineHeight, +        font: Font, +        bounds: Size, +        shaping: Shaping, +    ) -> Size { +        let mut cache = self.cache.borrow_mut(); + +        let line_height = f32::from(line_height.to_absolute(Pixels(size))); + +        let (_, entry) = cache.allocate( +            &mut self.font_system.borrow_mut(), +            Key { +                content, +                size, +                line_height, +                font, +                bounds, +                shaping, +            }, +            Purpose::Measuring, +        ); -        if let Some(bounds) = -            self.measure_brush.borrow_mut().glyph_bounds(section) -        { -            (bounds.width().ceil(), bounds.height().ceil()) -        } else { -            (0.0, 0.0) -        } +        entry.bounds      }      pub fn hit_test(          &self,          content: &str,          size: f32, -        font: iced_native::Font, -        bounds: iced_native::Size, -        point: iced_native::Point, -        nearest_only: bool, +        line_height: LineHeight, +        font: Font, +        bounds: Size, +        shaping: Shaping, +        point: Point, +        _nearest_only: bool,      ) -> Option<Hit> { -        use wgpu_glyph::GlyphCruncher; - -        let wgpu_glyph::FontId(font_id) = self.find_font(font); - -        let section = wgpu_glyph::Section { -            bounds: (bounds.width, bounds.height), -            text: vec![wgpu_glyph::Text { -                text: content, -                scale: size.into(), -                font_id: wgpu_glyph::FontId(font_id), -                extra: wgpu_glyph::Extra::default(), -            }], -            ..Default::default() -        }; - -        let mut mb = self.measure_brush.borrow_mut(); - -        // The underlying type is FontArc, so clones are cheap. -        use wgpu_glyph::ab_glyph::{Font, ScaleFont}; -        let font = mb.fonts()[font_id].clone().into_scaled(size); - -        // Implements an iterator over the glyph bounding boxes. -        let bounds = mb.glyphs(section).map( -            |wgpu_glyph::SectionGlyph { -                 byte_index, glyph, .. -             }| { -                ( -                    *byte_index, -                    iced_native::Rectangle::new( -                        iced_native::Point::new( -                            glyph.position.x - font.h_side_bearing(glyph.id), -                            glyph.position.y - font.ascent(), -                        ), -                        iced_native::Size::new( -                            font.h_advance(glyph.id), -                            font.ascent() - font.descent(), -                        ), -                    ), -                ) +        let mut cache = self.cache.borrow_mut(); + +        let line_height = f32::from(line_height.to_absolute(Pixels(size))); + +        let (_, entry) = cache.allocate( +            &mut self.font_system.borrow_mut(), +            Key { +                content, +                size, +                line_height, +                font, +                bounds, +                shaping,              }, +            Purpose::Measuring,          ); -        // Implements computation of the character index based on the byte index -        // within the input string. -        let char_index = |byte_index| { -            let mut b_count = 0; -            for (i, utf8_len) in -                content.chars().map(|c| c.len_utf8()).enumerate() -            { -                if byte_index < (b_count + utf8_len) { -                    return i; -                } -                b_count += utf8_len; -            } +        let cursor = entry.buffer.hit(point.x, point.y)?; -            byte_index -        }; +        Some(Hit::CharOffset(cursor.index)) +    } +} -        if !nearest_only { -            for (idx, bounds) in bounds.clone() { -                if bounds.contains(point) { -                    return Some(Hit::CharOffset(char_index(idx))); -                } -            } -        } +fn measure(buffer: &glyphon::Buffer) -> Size { +    let (width, total_lines) = buffer +        .layout_runs() +        .fold((0.0, 0usize), |(width, total_lines), run| { +            (run.line_w.max(width), total_lines + 1) +        }); + +    Size::new(width, total_lines as f32 * buffer.metrics().line_height) +} -        let nearest = bounds -            .map(|(index, bounds)| (index, bounds.center())) -            .min_by(|(_, center_a), (_, center_b)| { -                center_a -                    .distance(point) -                    .partial_cmp(¢er_b.distance(point)) -                    .unwrap_or(std::cmp::Ordering::Greater) -            }); - -        nearest.map(|(idx, center)| { -            Hit::NearestCharOffset(char_index(idx), point - center) -        }) +fn to_family(family: font::Family) -> glyphon::Family<'static> { +    match family { +        font::Family::Name(name) => glyphon::Family::Name(name), +        font::Family::SansSerif => glyphon::Family::SansSerif, +        font::Family::Serif => glyphon::Family::Serif, +        font::Family::Cursive => glyphon::Family::Cursive, +        font::Family::Fantasy => glyphon::Family::Fantasy, +        font::Family::Monospace => glyphon::Family::Monospace,      } +} -    pub fn trim_measurement_cache(&mut self) { -        // TODO: We should probably use a `GlyphCalculator` for this. However, -        // it uses a lifetimed `GlyphCalculatorGuard` with side-effects on drop. -        // This makes stuff quite inconvenient. A manual method for trimming the -        // cache would make our lives easier. -        loop { -            let action = self -                .measure_brush -                .borrow_mut() -                .process_queued(|_, _| {}, |_| {}); - -            match action { -                Ok(_) => break, -                Err(glyph_brush::BrushError::TextureTooSmall { suggested }) => { -                    let (width, height) = suggested; - -                    self.measure_brush -                        .borrow_mut() -                        .resize_texture(width, height); -                } -            } +fn to_weight(weight: font::Weight) -> glyphon::Weight { +    match weight { +        font::Weight::Thin => glyphon::Weight::THIN, +        font::Weight::ExtraLight => glyphon::Weight::EXTRA_LIGHT, +        font::Weight::Light => glyphon::Weight::LIGHT, +        font::Weight::Normal => glyphon::Weight::NORMAL, +        font::Weight::Medium => glyphon::Weight::MEDIUM, +        font::Weight::Semibold => glyphon::Weight::SEMIBOLD, +        font::Weight::Bold => glyphon::Weight::BOLD, +        font::Weight::ExtraBold => glyphon::Weight::EXTRA_BOLD, +        font::Weight::Black => glyphon::Weight::BLACK, +    } +} + +fn to_stretch(stretch: font::Stretch) -> glyphon::Stretch { +    match stretch { +        font::Stretch::UltraCondensed => glyphon::Stretch::UltraCondensed, +        font::Stretch::ExtraCondensed => glyphon::Stretch::ExtraCondensed, +        font::Stretch::Condensed => glyphon::Stretch::Condensed, +        font::Stretch::SemiCondensed => glyphon::Stretch::SemiCondensed, +        font::Stretch::Normal => glyphon::Stretch::Normal, +        font::Stretch::SemiExpanded => glyphon::Stretch::SemiExpanded, +        font::Stretch::Expanded => glyphon::Stretch::Expanded, +        font::Stretch::ExtraExpanded => glyphon::Stretch::ExtraExpanded, +        font::Stretch::UltraExpanded => glyphon::Stretch::UltraExpanded, +    } +} + +fn to_shaping(shaping: Shaping) -> glyphon::Shaping { +    match shaping { +        Shaping::Basic => glyphon::Shaping::Basic, +        Shaping::Advanced => glyphon::Shaping::Advanced, +    } +} + +struct Cache { +    entries: FxHashMap<KeyHash, Entry>, +    aliases: FxHashMap<KeyHash, KeyHash>, +    recently_measured: FxHashSet<KeyHash>, +    recently_drawn: FxHashSet<KeyHash>, +    hasher: HashBuilder, +} + +struct Entry { +    buffer: glyphon::Buffer, +    bounds: Size, +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +enum Purpose { +    Measuring, +    Drawing, +} + +#[cfg(not(target_arch = "wasm32"))] +type HashBuilder = twox_hash::RandomXxHashBuilder64; + +#[cfg(target_arch = "wasm32")] +type HashBuilder = std::hash::BuildHasherDefault<twox_hash::XxHash64>; + +impl Cache { +    fn new() -> Self { +        Self { +            entries: FxHashMap::default(), +            aliases: FxHashMap::default(), +            recently_measured: FxHashSet::default(), +            recently_drawn: FxHashSet::default(), +            hasher: HashBuilder::default(),          }      } -    pub fn find_font(&self, font: iced_native::Font) -> wgpu_glyph::FontId { -        match font { -            iced_native::Font::Default => wgpu_glyph::FontId(0), -            iced_native::Font::External { name, bytes } => { -                if let Some(font_id) = self.draw_font_map.borrow().get(name) { -                    return *font_id; -                } +    fn get(&self, key: &KeyHash) -> Option<&Entry> { +        self.entries.get(key) +    } -                let font = ab_glyph::FontArc::try_from_slice(bytes) -                    .expect("Load font"); +    fn allocate( +        &mut self, +        font_system: &mut glyphon::FontSystem, +        key: Key<'_>, +        purpose: Purpose, +    ) -> (KeyHash, &mut Entry) { +        let hash = key.hash(self.hasher.build_hasher()); + +        let recently_used = match purpose { +            Purpose::Measuring => &mut self.recently_measured, +            Purpose::Drawing => &mut self.recently_drawn, +        }; -                let _ = self.measure_brush.borrow_mut().add_font(font.clone()); +        if let Some(hash) = self.aliases.get(&hash) { +            let _ = recently_used.insert(*hash); -                let font_id = self.draw_brush.borrow_mut().add_font(font); +            return (*hash, self.entries.get_mut(hash).unwrap()); +        } -                let _ = self -                    .draw_font_map -                    .borrow_mut() -                    .insert(String::from(name), font_id); +        if let hash_map::Entry::Vacant(entry) = self.entries.entry(hash) { +            let metrics = glyphon::Metrics::new(key.size, key.line_height); +            let mut buffer = glyphon::Buffer::new(font_system, metrics); + +            buffer.set_size( +                font_system, +                key.bounds.width, +                key.bounds.height.max(key.line_height), +            ); +            buffer.set_text( +                font_system, +                key.content, +                glyphon::Attrs::new() +                    .family(to_family(key.font.family)) +                    .weight(to_weight(key.font.weight)) +                    .stretch(to_stretch(key.font.stretch)), +                to_shaping(key.shaping), +            ); + +            let bounds = measure(&buffer); +            let _ = entry.insert(Entry { buffer, bounds }); + +            for bounds in [ +                bounds, +                Size { +                    width: key.bounds.width, +                    ..bounds +                }, +            ] { +                if key.bounds != bounds { +                    let _ = self.aliases.insert( +                        Key { bounds, ..key }.hash(self.hasher.build_hasher()), +                        hash, +                    ); +                } +            } +        } + +        let _ = recently_used.insert(hash); + +        (hash, self.entries.get_mut(&hash).unwrap()) +    } -                font_id +    fn trim(&mut self, purpose: Purpose) { +        self.entries.retain(|key, _| { +            self.recently_measured.contains(key) +                || self.recently_drawn.contains(key) +        }); +        self.aliases.retain(|_, value| { +            self.recently_measured.contains(value) +                || self.recently_drawn.contains(value) +        }); + +        match purpose { +            Purpose::Measuring => { +                self.recently_measured.clear(); +            } +            Purpose::Drawing => { +                self.recently_drawn.clear();              }          }      }  } + +#[derive(Debug, Clone, Copy)] +struct Key<'a> { +    content: &'a str, +    size: f32, +    line_height: f32, +    font: Font, +    bounds: Size, +    shaping: Shaping, +} + +impl Key<'_> { +    fn hash<H: Hasher>(self, mut hasher: H) -> KeyHash { +        self.content.hash(&mut hasher); +        self.size.to_bits().hash(&mut hasher); +        self.line_height.to_bits().hash(&mut hasher); +        self.font.hash(&mut hasher); +        self.bounds.width.to_bits().hash(&mut hasher); +        self.bounds.height.to_bits().hash(&mut hasher); +        self.shaping.hash(&mut hasher); + +        hasher.finish() +    } +} + +type KeyHash = u64; diff --git a/wgpu/src/triangle.rs b/wgpu/src/triangle.rs index efdd214b..d8b23dfe 100644 --- a/wgpu/src/triangle.rs +++ b/wgpu/src/triangle.rs @@ -1,63 +1,59 @@  //! Draw meshes of triangles.  mod msaa; -use crate::buffer::r#static::Buffer; -use crate::settings; -use crate::Transformation; +use crate::core::Size; +use crate::graphics::{Antialiasing, Transformation}; +use crate::layer::mesh::{self, Mesh}; +use crate::Buffer; -use iced_graphics::layer::mesh::{self, Mesh}; -use iced_graphics::triangle::ColoredVertex2D; -use iced_graphics::Size; -#[cfg(feature = "tracing")] -use tracing::info_span; +const INITIAL_INDEX_COUNT: usize = 1_000; +const INITIAL_VERTEX_COUNT: usize = 1_000;  #[derive(Debug)]  pub struct Pipeline {      blit: Option<msaa::Blit>, -    index_buffer: Buffer<u32>, -    index_strides: Vec<u32>,      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, +    layers: Vec<Layer>, +    prepare_layer: usize,  } -impl Pipeline { -    pub fn new( +#[derive(Debug)] +struct Layer { +    index_buffer: Buffer<u32>, +    index_strides: Vec<u32>, +    solid: solid::Layer, +    gradient: gradient::Layer, +} + +impl Layer { +    fn new(          device: &wgpu::Device, -        format: wgpu::TextureFormat, -        antialiasing: Option<settings::Antialiasing>, -    ) -> Pipeline { -        Pipeline { -            blit: antialiasing.map(|a| msaa::Blit::new(device, format, a)), +        solid: &solid::Pipeline, +        gradient: &gradient::Pipeline, +    ) -> Self { +        Self {              index_buffer: Buffer::new(                  device, -                "iced_wgpu::triangle vertex buffer", +                "iced_wgpu.triangle.index_buffer", +                INITIAL_INDEX_COUNT,                  wgpu::BufferUsages::INDEX | wgpu::BufferUsages::COPY_DST,              ),              index_strides: Vec::new(), -            solid: solid::Pipeline::new(device, format, antialiasing), - -            #[cfg(not(target_arch = "wasm32"))] -            gradient: gradient::Pipeline::new(device, format, antialiasing), +            solid: solid::Layer::new(device, &solid.constants_layout), +            gradient: gradient::Layer::new(device, &gradient.constants_layout),          }      } -    pub fn draw( +    fn prepare(          &mut self,          device: &wgpu::Device, -        staging_belt: &mut wgpu::util::StagingBelt, -        encoder: &mut wgpu::CommandEncoder, -        target: &wgpu::TextureView, -        target_size: Size<u32>, -        transformation: Transformation, -        scale_factor: f32, +        queue: &wgpu::Queue, +        solid: &solid::Pipeline, +        gradient: &gradient::Pipeline,          meshes: &[Mesh<'_>], +        transformation: Transformation,      ) { -        #[cfg(feature = "tracing")] -        let _ = info_span!("Wgpu::Triangle", "DRAW").entered(); -          // Count the total amount of vertices & indices we need to handle          let count = mesh::attribute_count_of(meshes); @@ -67,175 +63,220 @@ impl Pipeline {          // 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); - -        #[cfg(not(target_arch = "wasm32"))]          let _ = self              .gradient              .vertices              .resize(device, count.gradient_vertices); -        // Prepare dynamic buffers & data store for writing +        if self.solid.uniforms.resize(device, count.solids) { +            self.solid.constants = solid::Layer::bind_group( +                device, +                &self.solid.uniforms.raw, +                &solid.constants_layout, +            ); +        } + +        if self.gradient.uniforms.resize(device, count.gradients) { +            self.gradient.constants = gradient::Layer::bind_group( +                device, +                &self.gradient.uniforms.raw, +                &gradient.constants_layout, +            ); +        } +          self.index_strides.clear(); +        self.index_buffer.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(); -        } +        self.gradient.vertices.clear(); +        self.gradient.uniforms.clear();          let mut solid_vertex_offset = 0; -        let mut index_offset = 0; - -        #[cfg(not(target_arch = "wasm32"))] +        let mut solid_uniform_offset = 0;          let mut gradient_vertex_offset = 0; +        let mut gradient_uniform_offset = 0; +        let mut index_offset = 0;          for mesh in meshes {              let origin = mesh.origin();              let indices = mesh.indices(); -            let transform = -                transformation * Transformation::translate(origin.x, origin.y); - -            let new_index_offset = self.index_buffer.write( -                device, -                staging_belt, -                encoder, -                index_offset, -                indices, +            let uniforms = Uniforms::new( +                transformation * Transformation::translate(origin.x, origin.y),              ); -            index_offset += new_index_offset; +            index_offset += +                self.index_buffer.write(queue, index_offset, indices);              self.index_strides.push(indices.len() as u32); -            //push uniform data to CPU buffers              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 += self.solid.vertices.write( +                        queue,                          solid_vertex_offset,                          &buffers.vertices,                      ); -                    solid_vertex_offset += written_bytes; +                    solid_uniform_offset += self.solid.uniforms.write( +                        queue, +                        solid_uniform_offset, +                        &[uniforms], +                    );                  } -                #[cfg(not(target_arch = "wasm32"))] -                Mesh::Gradient { -                    buffers, gradient, .. -                } => { -                    let written_bytes = self.gradient.vertices.write( -                        device, -                        staging_belt, -                        encoder, +                Mesh::Gradient { buffers, .. } => { +                    gradient_vertex_offset += self.gradient.vertices.write( +                        queue,                          gradient_vertex_offset,                          &buffers.vertices,                      ); -                    gradient_vertex_offset += written_bytes; +                    gradient_uniform_offset += self.gradient.uniforms.write( +                        queue, +                        gradient_uniform_offset, +                        &[uniforms], +                    ); +                } +            } +        } +    } -                    match gradient { -                        iced_graphics::Gradient::Linear(linear) => { -                            use glam::{IVec4, Vec4}; +    fn render<'a>( +        &'a self, +        solid: &'a solid::Pipeline, +        gradient: &'a gradient::Pipeline, +        meshes: &[Mesh<'_>], +        scale_factor: f32, +        render_pass: &mut wgpu::RenderPass<'a>, +    ) { +        let mut num_solids = 0; +        let mut num_gradients = 0; +        let mut last_is_solid = None; -                            let start_offset = self.gradient.color_stop_offset; -                            let end_offset = (linear.color_stops.len() as i32) -                                + start_offset -                                - 1; +        for (index, mesh) in meshes.iter().enumerate() { +            let clip_bounds = (mesh.clip_bounds() * scale_factor).snap(); -                            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); -                        } +            if clip_bounds.width < 1 || clip_bounds.height < 1 { +                continue; +            } + +            render_pass.set_scissor_rect( +                clip_bounds.x, +                clip_bounds.y, +                clip_bounds.width, +                clip_bounds.height, +            ); + +            match mesh { +                Mesh::Solid { .. } => { +                    if !last_is_solid.unwrap_or(false) { +                        render_pass.set_pipeline(&solid.pipeline); + +                        last_is_solid = Some(true);                      } + +                    render_pass.set_bind_group( +                        0, +                        &self.solid.constants, +                        &[(num_solids * std::mem::size_of::<Uniforms>()) +                            as u32], +                    ); + +                    render_pass.set_vertex_buffer( +                        0, +                        self.solid.vertices.slice_from_index(num_solids), +                    ); + +                    num_solids += 1;                  } -                #[cfg(target_arch = "wasm32")] -                Mesh::Gradient { .. } => {} -            } -        } +                Mesh::Gradient { .. } => { +                    if last_is_solid.unwrap_or(true) { +                        render_pass.set_pipeline(&gradient.pipeline); -        // Write uniform data to GPU -        if count.solid_vertices > 0 { -            let uniforms_resized = self.solid.uniforms.resize(device); +                        last_is_solid = Some(false); +                    } -            if uniforms_resized { -                self.solid.bind_group = solid::Pipeline::bind_group( -                    device, -                    self.solid.uniforms.raw(), -                    &self.solid.bind_group_layout, -                ) -            } +                    render_pass.set_bind_group( +                        0, +                        &self.gradient.constants, +                        &[(num_gradients * std::mem::size_of::<Uniforms>()) +                            as u32], +                    ); + +                    render_pass.set_vertex_buffer( +                        0, +                        self.gradient.vertices.slice_from_index(num_gradients), +                    ); + +                    num_gradients += 1; +                } +            }; -            self.solid.uniforms.write(device, staging_belt, encoder); +            render_pass.set_index_buffer( +                self.index_buffer.slice_from_index(index), +                wgpu::IndexFormat::Uint32, +            ); + +            render_pass.draw_indexed(0..self.index_strides[index], 0, 0..1);          } +    } +} -        #[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, -                ); -            } +impl Pipeline { +    pub fn new( +        device: &wgpu::Device, +        format: wgpu::TextureFormat, +        antialiasing: Option<Antialiasing>, +    ) -> Pipeline { +        Pipeline { +            blit: antialiasing.map(|a| msaa::Blit::new(device, format, a)), +            solid: solid::Pipeline::new(device, format, antialiasing), +            gradient: gradient::Pipeline::new(device, format, antialiasing), +            layers: Vec::new(), +            prepare_layer: 0, +        } +    } -            // Write to GPU -            self.gradient.uniforms.write(device, staging_belt, encoder); -            self.gradient.storage.write(device, staging_belt, encoder); +    pub fn prepare( +        &mut self, +        device: &wgpu::Device, +        queue: &wgpu::Queue, +        meshes: &[Mesh<'_>], +        transformation: Transformation, +    ) { +        #[cfg(feature = "tracing")] +        let _ = tracing::info_span!("Wgpu::Triangle", "PREPARE").entered(); -            // Cleanup -            self.gradient.color_stop_offset = 0; -            self.gradient.color_stops_pending_write.color_stops.clear(); +        if self.layers.len() <= self.prepare_layer { +            self.layers +                .push(Layer::new(device, &self.solid, &self.gradient));          } -        // Configure render pass +        let layer = &mut self.layers[self.prepare_layer]; +        layer.prepare( +            device, +            queue, +            &self.solid, +            &self.gradient, +            meshes, +            transformation, +        ); + +        self.prepare_layer += 1; +    } + +    pub fn render( +        &mut self, +        device: &wgpu::Device, +        encoder: &mut wgpu::CommandEncoder, +        target: &wgpu::TextureView, +        layer: usize, +        target_size: Size<u32>, +        meshes: &[Mesh<'_>], +        scale_factor: f32, +    ) { +        #[cfg(feature = "tracing")] +        let _ = tracing::info_span!("Wgpu::Triangle", "DRAW").entered(); +          {              let (attachment, resolve_target, load) = if let Some(blit) =                  &mut self.blit @@ -252,12 +293,9 @@ impl Pipeline {                  (target, None, wgpu::LoadOp::Load)              }; -            #[cfg(feature = "tracing")] -            let _ = info_span!("Wgpu::Triangle", "BEGIN_RENDER_PASS").enter(); -              let mut render_pass =                  encoder.begin_render_pass(&wgpu::RenderPassDescriptor { -                    label: Some("iced_wgpu::triangle render pass"), +                    label: Some("iced_wgpu.triangle.render_pass"),                      color_attachments: &[Some(                          wgpu::RenderPassColorAttachment {                              view: attachment, @@ -268,87 +306,25 @@ impl Pipeline {                      depth_stencil_attachment: None,                  }); -            let mut num_solids = 0; -            #[cfg(not(target_arch = "wasm32"))] -            let mut num_gradients = 0; -            let mut last_is_solid = None; +            let layer = &mut self.layers[layer]; -            for (index, mesh) in meshes.iter().enumerate() { -                let clip_bounds = (mesh.clip_bounds() * scale_factor).snap(); - -                render_pass.set_scissor_rect( -                    clip_bounds.x, -                    clip_bounds.y, -                    clip_bounds.width, -                    clip_bounds.height, -                ); - -                match mesh { -                    Mesh::Solid { .. } => { -                        if !last_is_solid.unwrap_or(false) { -                            render_pass.set_pipeline(&self.solid.pipeline); - -                            last_is_solid = Some(true); -                        } - -                        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"))] -                    Mesh::Gradient { .. } => { -                        if last_is_solid.unwrap_or(true) { -                            render_pass.set_pipeline(&self.gradient.pipeline); - -                            last_is_solid = Some(false); -                        } - -                        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_index_buffer( -                    self.index_buffer.slice_from_index(index), -                    wgpu::IndexFormat::Uint32, -                ); - -                render_pass.draw_indexed(0..self.index_strides[index], 0, 0..1); -            } +            layer.render( +                &self.solid, +                &self.gradient, +                meshes, +                scale_factor, +                &mut render_pass, +            );          } -        self.index_buffer.clear(); -          if let Some(blit) = &mut self.blit {              blit.draw(encoder, target);          }      } + +    pub fn end_frame(&mut self) { +        self.prepare_layer = 0; +    }  }  fn fragment_target( @@ -370,7 +346,7 @@ fn primitive_state() -> wgpu::PrimitiveState {  }  fn multisample_state( -    antialiasing: Option<settings::Antialiasing>, +    antialiasing: Option<Antialiasing>,  ) -> wgpu::MultisampleState {      wgpu::MultisampleState {          count: antialiasing.map(|a| a.sample_count()).unwrap_or(1), @@ -379,132 +355,88 @@ fn multisample_state(      }  } +#[derive(Debug, Clone, Copy, bytemuck::Pod, bytemuck::Zeroable)] +#[repr(C)] +pub struct Uniforms { +    transform: [f32; 16], +    /// Uniform values must be 256-aligned; +    /// see: [`wgpu::Limits`] `min_uniform_buffer_offset_alignment`. +    _padding: [f32; 48], +} + +impl Uniforms { +    pub fn new(transform: Transformation) -> Self { +        Self { +            transform: transform.into(), +            _padding: [0.0; 48], +        } +    } + +    pub fn entry() -> wgpu::BindGroupLayoutEntry { +        wgpu::BindGroupLayoutEntry { +            binding: 0, +            visibility: wgpu::ShaderStages::VERTEX_FRAGMENT, +            ty: wgpu::BindingType::Buffer { +                ty: wgpu::BufferBindingType::Uniform, +                has_dynamic_offset: true, +                min_binding_size: wgpu::BufferSize::new( +                    std::mem::size_of::<Self>() as u64, +                ), +            }, +            count: None, +        } +    } + +    pub fn min_size() -> Option<wgpu::BufferSize> { +        wgpu::BufferSize::new(std::mem::size_of::<Self>() as u64) +    } +} +  mod solid { -    use crate::buffer::dynamic; -    use crate::buffer::r#static::Buffer; -    use crate::settings; +    use crate::graphics::mesh; +    use crate::graphics::Antialiasing;      use crate::triangle; -    use encase::ShaderType; -    use iced_graphics::Transformation; +    use crate::Buffer;      #[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, +        pub constants_layout: wgpu::BindGroupLayout,      } -    impl Uniforms { -        pub fn new(transform: Transformation) -> Self { -            Self { -                transform: transform.into(), -            } -        } +    #[derive(Debug)] +    pub struct Layer { +        pub vertices: Buffer<mesh::SolidVertex2D>, +        pub uniforms: Buffer<triangle::Uniforms>, +        pub constants: wgpu::BindGroup,      } -    impl Pipeline { -        /// Creates a new [SolidPipeline] using `solid.wgsl` shader. +    impl Layer {          pub fn new(              device: &wgpu::Device, -            format: wgpu::TextureFormat, -            antialiasing: Option<settings::Antialiasing>, +            constants_layout: &wgpu::BindGroupLayout,          ) -> Self {              let vertices = Buffer::new(                  device, -                "iced_wgpu::triangle::solid vertex buffer", +                "iced_wgpu.triangle.solid.vertex_buffer", +                triangle::INITIAL_VERTEX_COUNT,                  wgpu::BufferUsages::VERTEX | wgpu::BufferUsages::COPY_DST,              ); -            let uniforms = dynamic::Buffer::uniform( +            let uniforms = Buffer::new(                  device, -                "iced_wgpu::triangle::solid uniforms", +                "iced_wgpu.triangle.solid.uniforms", +                1, +                wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,              ); -            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, -                }, -            ); +            let constants = +                Self::bind_group(device, &uniforms.raw, constants_layout);              Self { -                pipeline,                  vertices,                  uniforms, -                bind_group_layout, -                bind_group, +                constants,              }          } @@ -514,7 +446,7 @@ mod solid {              layout: &wgpu::BindGroupLayout,          ) -> wgpu::BindGroup {              device.create_bind_group(&wgpu::BindGroupDescriptor { -                label: Some("iced_wgpu::triangle::solid bind group"), +                label: Some("iced_wgpu.triangle.solid.bind_group"),                  layout,                  entries: &[wgpu::BindGroupEntry {                      binding: 0, @@ -522,167 +454,225 @@ mod solid {                          wgpu::BufferBinding {                              buffer,                              offset: 0, -                            size: Some(Uniforms::min_size()), +                            size: triangle::Uniforms::min_size(),                          },                      ),                  }],              })          }      } + +    impl Pipeline { +        pub fn new( +            device: &wgpu::Device, +            format: wgpu::TextureFormat, +            antialiasing: Option<Antialiasing>, +        ) -> Self { +            let constants_layout = device.create_bind_group_layout( +                &wgpu::BindGroupLayoutDescriptor { +                    label: Some("iced_wgpu.triangle.solid.bind_group_layout"), +                    entries: &[triangle::Uniforms::entry()], +                }, +            ); + +            let layout = device.create_pipeline_layout( +                &wgpu::PipelineLayoutDescriptor { +                    label: Some("iced_wgpu.triangle.solid.pipeline_layout"), +                    bind_group_layouts: &[&constants_layout], +                    push_constant_ranges: &[], +                }, +            ); + +            let shader = +                device.create_shader_module(wgpu::ShaderModuleDescriptor { +                    label: Some("iced_wgpu.triangle.solid.shader"), +                    source: wgpu::ShaderSource::Wgsl( +                        std::borrow::Cow::Borrowed(include_str!( +                            "shader/triangle.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: "solid_vs_main", +                            buffers: &[wgpu::VertexBufferLayout { +                                array_stride: std::mem::size_of::< +                                    mesh::SolidVertex2D, +                                >( +                                ) +                                    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: "solid_fs_main", +                            targets: &[triangle::fragment_target(format)], +                        }), +                        primitive: triangle::primitive_state(), +                        depth_stencil: None, +                        multisample: triangle::multisample_state(antialiasing), +                        multiview: None, +                    }, +                ); + +            Self { +                pipeline, +                constants_layout, +            } +        } +    }  } -#[cfg(not(target_arch = "wasm32"))]  mod gradient { -    use crate::buffer::dynamic; -    use crate::buffer::r#static::Buffer; -    use crate::settings; +    use crate::graphics::mesh; +    use crate::graphics::Antialiasing;      use crate::triangle; - -    use encase::ShaderType; -    use glam::{IVec4, Vec4}; -    use iced_graphics::triangle::Vertex2D; +    use crate::Buffer;      #[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, +        pub constants_layout: wgpu::BindGroupLayout,      } -    #[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>, +    #[derive(Debug)] +    pub struct Layer { +        pub vertices: Buffer<mesh::GradientVertex2D>, +        pub uniforms: Buffer<triangle::Uniforms>, +        pub constants: wgpu::BindGroup,      } -    impl Pipeline { -        /// Creates a new [GradientPipeline] using `gradient.wgsl` shader. -        pub(super) fn new( +    impl Layer { +        pub fn new(              device: &wgpu::Device, -            format: wgpu::TextureFormat, -            antialiasing: Option<settings::Antialiasing>, +            constants_layout: &wgpu::BindGroupLayout,          ) -> Self {              let vertices = Buffer::new(                  device, -                "iced_wgpu::triangle::gradient vertex buffer", +                "iced_wgpu.triangle.gradient.vertex_buffer", +                triangle::INITIAL_VERTEX_COUNT,                  wgpu::BufferUsages::VERTEX | wgpu::BufferUsages::COPY_DST,              ); -            let uniforms = dynamic::Buffer::uniform( +            let uniforms = Buffer::new(                  device, -                "iced_wgpu::triangle::gradient uniforms", +                "iced_wgpu.triangle.gradient.uniforms", +                1, +                wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,              ); -            //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 constants = +                Self::bind_group(device, &uniforms.raw, constants_layout); -            let bind_group_layout = device.create_bind_group_layout( +            Self { +                vertices, +                uniforms, +                constants, +            } +        } + +        pub fn bind_group( +            device: &wgpu::Device, +            uniform_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: triangle::Uniforms::min_size(), +                        }, +                    ), +                }], +            }) +        } +    } + +    impl Pipeline { +        pub fn new( +            device: &wgpu::Device, +            format: wgpu::TextureFormat, +            antialiasing: Option<Antialiasing>, +        ) -> Self { +            let constants_layout = device.create_bind_group_layout(                  &wgpu::BindGroupLayoutDescriptor {                      label: Some( -                        "iced_wgpu::triangle::gradient bind group layout", +                        "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, -                        }, -                    ], +                    entries: &[triangle::Uniforms::entry()],                  },              ); -            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], +                    label: Some("iced_wgpu.triangle.gradient.pipeline_layout"), +                    bind_group_layouts: &[&constants_layout],                      push_constant_ranges: &[],                  },              );              let shader =                  device.create_shader_module(wgpu::ShaderModuleDescriptor { -                    label: Some( -                        "iced_wgpu::triangle::gradient create shader module", -                    ), +                    label: Some("iced_wgpu.triangle.gradient.shader"),                      source: wgpu::ShaderSource::Wgsl(                          std::borrow::Cow::Borrowed(include_str!( -                            "shader/gradient.wgsl" +                            "shader/triangle.wgsl"                          )),                      ),                  });              let pipeline = device.create_render_pipeline(                  &wgpu::RenderPipelineDescriptor { -                    label: Some("iced_wgpu::triangle::gradient pipeline"), +                    label: Some("iced_wgpu.triangle.gradient.pipeline"),                      layout: Some(&layout),                      vertex: wgpu::VertexState {                          module: &shader, -                        entry_point: "vs_main", +                        entry_point: "gradient_vs_main",                          buffers: &[wgpu::VertexBufferLayout { -                            array_stride: std::mem::size_of::<Vertex2D>() +                            array_stride: std::mem::size_of::< +                                mesh::GradientVertex2D, +                            >()                                  as u64,                              step_mode: wgpu::VertexStepMode::Vertex,                              attributes: &wgpu::vertex_attr_array!(                                  // Position                                  0 => Float32x2, +                                // Colors 1-2 +                                1 => Uint32x4, +                                // Colors 3-4 +                                2 => Uint32x4, +                                // Colors 5-6 +                                3 => Uint32x4, +                                // Colors 7-8 +                                4 => Uint32x4, +                                // Offsets +                                5 => Uint32x4, +                                // Direction +                                6 => Float32x4                              ),                          }],                      },                      fragment: Some(wgpu::FragmentState {                          module: &shader, -                        entry_point: "fs_main", +                        entry_point: "gradient_fs_main",                          targets: &[triangle::fragment_target(format)],                      }),                      primitive: triangle::primitive_state(), @@ -694,44 +684,8 @@ mod gradient {              Self {                  pipeline, -                vertices, -                uniforms, -                storage, -                color_stop_offset: 0, -                color_stops_pending_write: Storage { -                    color_stops: vec![], -                }, -                bind_group_layout, -                bind_group, +                constants_layout,              }          } - -        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/msaa.rs b/wgpu/src/triangle/msaa.rs index a3016ff8..320b5b12 100644 --- a/wgpu/src/triangle/msaa.rs +++ b/wgpu/src/triangle/msaa.rs @@ -1,4 +1,4 @@ -use crate::settings; +use crate::graphics;  #[derive(Debug)]  pub struct Blit { @@ -14,17 +14,10 @@ impl Blit {      pub fn new(          device: &wgpu::Device,          format: wgpu::TextureFormat, -        antialiasing: settings::Antialiasing, +        antialiasing: graphics::Antialiasing,      ) -> Blit { -        let sampler = device.create_sampler(&wgpu::SamplerDescriptor { -            address_mode_u: wgpu::AddressMode::ClampToEdge, -            address_mode_v: wgpu::AddressMode::ClampToEdge, -            address_mode_w: wgpu::AddressMode::ClampToEdge, -            mag_filter: wgpu::FilterMode::Nearest, -            min_filter: wgpu::FilterMode::Nearest, -            mipmap_filter: wgpu::FilterMode::Nearest, -            ..Default::default() -        }); +        let sampler = +            device.create_sampler(&wgpu::SamplerDescriptor::default());          let constant_layout =              device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { @@ -75,7 +68,7 @@ impl Blit {          let shader =              device.create_shader_module(wgpu::ShaderModuleDescriptor { -                label: Some("iced_wgpu::triangle::blit_shader"), +                label: Some("iced_wgpu triangle blit_shader"),                  source: wgpu::ShaderSource::Wgsl(std::borrow::Cow::Borrowed(                      include_str!("../shader/blit.wgsl"),                  )), @@ -223,6 +216,7 @@ impl Targets {              dimension: wgpu::TextureDimension::D2,              format,              usage: wgpu::TextureUsages::RENDER_ATTACHMENT, +            view_formats: &[],          });          let resolve = device.create_texture(&wgpu::TextureDescriptor { @@ -234,6 +228,7 @@ impl Targets {              format,              usage: wgpu::TextureUsages::RENDER_ATTACHMENT                  | wgpu::TextureUsages::TEXTURE_BINDING, +            view_formats: &[],          });          let attachment = diff --git a/wgpu/src/window.rs b/wgpu/src/window.rs index aac5fb9e..9545a14e 100644 --- a/wgpu/src/window.rs +++ b/wgpu/src/window.rs @@ -1,4 +1,5 @@  //! Display rendering results on windows. -mod compositor; +pub mod compositor;  pub use compositor::Compositor; +pub use wgpu::Surface; diff --git a/wgpu/src/window/compositor.rs b/wgpu/src/window/compositor.rs index 6d0c36f6..cd5b20cc 100644 --- a/wgpu/src/window/compositor.rs +++ b/wgpu/src/window/compositor.rs @@ -1,9 +1,13 @@ -use crate::{Backend, Color, Error, Renderer, Settings, Viewport}; +//! Connect a window with a renderer. +use crate::core::{Color, Size}; +use crate::graphics; +use crate::graphics::color; +use crate::graphics::compositor; +use crate::graphics::{Error, Viewport}; +use crate::{Backend, Primitive, Renderer, Settings};  use futures::stream::{self, StreamExt}; -use iced_graphics::compositor; -use iced_native::futures;  use raw_window_handle::{HasRawDisplayHandle, HasRawWindowHandle};  use std::marker::PhantomData; @@ -16,14 +20,11 @@ pub struct Compositor<Theme> {      adapter: wgpu::Adapter,      device: wgpu::Device,      queue: wgpu::Queue, -    staging_belt: wgpu::util::StagingBelt,      format: wgpu::TextureFormat,      theme: PhantomData<Theme>,  }  impl<Theme> Compositor<Theme> { -    const CHUNK_SIZE: u64 = 10 * 1024; -      /// Requests a new [`Compositor`] with the given [`Settings`].      ///      /// Returns `None` if no compatible graphics adapter could be found. @@ -31,7 +32,10 @@ impl<Theme> Compositor<Theme> {          settings: Settings,          compatible_window: Option<&W>,      ) -> Option<Self> { -        let instance = wgpu::Instance::new(settings.internal_backend); +        let instance = wgpu::Instance::new(wgpu::InstanceDescriptor { +            backends: settings.internal_backend, +            ..Default::default() +        });          log::info!("{:#?}", settings); @@ -46,15 +50,16 @@ impl<Theme> Compositor<Theme> {          #[allow(unsafe_code)]          let compatible_surface = compatible_window -            .map(|window| unsafe { instance.create_surface(window) }); +            .and_then(|window| unsafe { instance.create_surface(window).ok() });          let adapter = instance              .request_adapter(&wgpu::RequestAdapterOptions { -                power_preference: if settings.antialiasing.is_none() { -                    wgpu::PowerPreference::LowPower -                } else { -                    wgpu::PowerPreference::HighPerformance -                }, +                power_preference: wgpu::util::power_preference_from_env() +                    .unwrap_or(if settings.antialiasing.is_none() { +                        wgpu::PowerPreference::LowPower +                    } else { +                        wgpu::PowerPreference::HighPerformance +                    }),                  compatible_surface: compatible_surface.as_ref(),                  force_fallback_adapter: false,              }) @@ -63,7 +68,21 @@ impl<Theme> Compositor<Theme> {          log::info!("Selected: {:#?}", adapter.get_info());          let format = compatible_surface.as_ref().and_then(|surface| { -            surface.get_supported_formats(&adapter).first().copied() +            let capabilities = surface.get_capabilities(&adapter); + +            let mut formats = capabilities.formats.iter().copied(); + +            let format = if color::GAMMA_CORRECTION { +                formats.find(wgpu::TextureFormat::is_srgb) +            } else { +                formats.find(|format| !wgpu::TextureFormat::is_srgb(format)) +            }; + +            format.or_else(|| { +                log::warn!("No format found!"); + +                capabilities.formats.first().copied() +            })          })?;          log::info!("Selected format: {:?}", format); @@ -98,15 +117,12 @@ impl<Theme> Compositor<Theme> {              .next()              .await?; -        let staging_belt = wgpu::util::StagingBelt::new(Self::CHUNK_SIZE); -          Some(Compositor {              instance,              settings,              adapter,              device,              queue, -            staging_belt,              format,              theme: PhantomData,          }) @@ -114,11 +130,82 @@ impl<Theme> Compositor<Theme> {      /// Creates a new rendering [`Backend`] for this [`Compositor`].      pub fn create_backend(&self) -> Backend { -        Backend::new(&self.device, self.settings, self.format) +        Backend::new(&self.device, &self.queue, self.settings, self.format)      }  } -impl<Theme> iced_graphics::window::Compositor for Compositor<Theme> { +/// Creates a [`Compositor`] and its [`Backend`] for the given [`Settings`] and +/// window. +pub fn new<Theme, W: HasRawWindowHandle + HasRawDisplayHandle>( +    settings: Settings, +    compatible_window: Option<&W>, +) -> Result<(Compositor<Theme>, Backend), Error> { +    let compositor = futures::executor::block_on(Compositor::request( +        settings, +        compatible_window, +    )) +    .ok_or(Error::GraphicsAdapterNotFound)?; + +    let backend = compositor.create_backend(); + +    Ok((compositor, backend)) +} + +/// Presents the given primitives with the given [`Compositor`] and [`Backend`]. +pub fn present<Theme, T: AsRef<str>>( +    compositor: &mut Compositor<Theme>, +    backend: &mut Backend, +    surface: &mut wgpu::Surface, +    primitives: &[Primitive], +    viewport: &Viewport, +    background_color: Color, +    overlay: &[T], +) -> Result<(), compositor::SurfaceError> { +    match surface.get_current_texture() { +        Ok(frame) => { +            let mut encoder = compositor.device.create_command_encoder( +                &wgpu::CommandEncoderDescriptor { +                    label: Some("iced_wgpu encoder"), +                }, +            ); + +            let view = &frame +                .texture +                .create_view(&wgpu::TextureViewDescriptor::default()); + +            backend.present( +                &compositor.device, +                &compositor.queue, +                &mut encoder, +                Some(background_color), +                view, +                primitives, +                viewport, +                overlay, +            ); + +            // Submit work +            let _submission = compositor.queue.submit(Some(encoder.finish())); +            frame.present(); + +            Ok(()) +        } +        Err(error) => match error { +            wgpu::SurfaceError::Timeout => { +                Err(compositor::SurfaceError::Timeout) +            } +            wgpu::SurfaceError::Outdated => { +                Err(compositor::SurfaceError::Outdated) +            } +            wgpu::SurfaceError::Lost => Err(compositor::SurfaceError::Lost), +            wgpu::SurfaceError::OutOfMemory => { +                Err(compositor::SurfaceError::OutOfMemory) +            } +        }, +    } +} + +impl<Theme> graphics::Compositor for Compositor<Theme> {      type Settings = Settings;      type Renderer = Renderer<Theme>;      type Surface = wgpu::Surface; @@ -127,13 +214,7 @@ impl<Theme> iced_graphics::window::Compositor for Compositor<Theme> {          settings: Self::Settings,          compatible_window: Option<&W>,      ) -> Result<(Self, Self::Renderer), Error> { -        let compositor = futures::executor::block_on(Self::request( -            settings, -            compatible_window, -        )) -        .ok_or(Error::GraphicsAdapterNotFound)?; - -        let backend = compositor.create_backend(); +        let (compositor, backend) = new(settings, compatible_window)?;          Ok((compositor, Renderer::new(backend)))      } @@ -141,11 +222,16 @@ impl<Theme> iced_graphics::window::Compositor for Compositor<Theme> {      fn create_surface<W: HasRawWindowHandle + HasRawDisplayHandle>(          &mut self,          window: &W, +        width: u32, +        height: u32,      ) -> wgpu::Surface {          #[allow(unsafe_code)] -        unsafe { -            self.instance.create_surface(window) -        } +        let mut surface = unsafe { self.instance.create_surface(window) } +            .expect("Create surface"); + +        self.configure_surface(&mut surface, width, height); + +        surface      }      fn configure_surface( @@ -163,6 +249,7 @@ impl<Theme> iced_graphics::window::Compositor for Compositor<Theme> {                  width,                  height,                  alpha_mode: wgpu::CompositeAlphaMode::Auto, +                view_formats: vec![],              },          );      } @@ -184,80 +271,166 @@ impl<Theme> iced_graphics::window::Compositor for Compositor<Theme> {          background_color: Color,          overlay: &[T],      ) -> Result<(), compositor::SurfaceError> { -        match surface.get_current_texture() { -            Ok(frame) => { -                let mut encoder = self.device.create_command_encoder( -                    &wgpu::CommandEncoderDescriptor { -                        label: Some("iced_wgpu encoder"), -                    }, -                ); +        renderer.with_primitives(|backend, primitives| { +            present( +                self, +                backend, +                surface, +                primitives, +                viewport, +                background_color, +                overlay, +            ) +        }) +    } -                let view = &frame -                    .texture -                    .create_view(&wgpu::TextureViewDescriptor::default()); +    fn screenshot<T: AsRef<str>>( +        &mut self, +        renderer: &mut Self::Renderer, +        _surface: &mut Self::Surface, +        viewport: &Viewport, +        background_color: Color, +        overlay: &[T], +    ) -> Vec<u8> { +        renderer.with_primitives(|backend, primitives| { +            screenshot( +                self, +                backend, +                primitives, +                viewport, +                background_color, +                overlay, +            ) +        }) +    } +} -                let _ = -                    encoder.begin_render_pass(&wgpu::RenderPassDescriptor { -                        label: Some( -                            "iced_wgpu::window::Compositor render pass", -                        ), -                        color_attachments: &[Some( -                            wgpu::RenderPassColorAttachment { -                                view, -                                resolve_target: None, -                                ops: wgpu::Operations { -                                    load: wgpu::LoadOp::Clear({ -                                        let [r, g, b, a] = -                                            background_color.into_linear(); - -                                        wgpu::Color { -                                            r: f64::from(r), -                                            g: f64::from(g), -                                            b: f64::from(b), -                                            a: f64::from(a), -                                        } -                                    }), -                                    store: true, -                                }, -                            }, -                        )], -                        depth_stencil_attachment: None, -                    }); - -                renderer.with_primitives(|backend, primitives| { -                    backend.present( -                        &self.device, -                        &mut self.staging_belt, -                        &mut encoder, -                        view, -                        primitives, -                        viewport, -                        overlay, -                    ); -                }); - -                // Submit work -                self.staging_belt.finish(); -                let _submission = self.queue.submit(Some(encoder.finish())); -                frame.present(); - -                // Recall staging buffers -                self.staging_belt.recall(); - -                Ok(()) -            } -            Err(error) => match error { -                wgpu::SurfaceError::Timeout => { -                    Err(compositor::SurfaceError::Timeout) -                } -                wgpu::SurfaceError::Outdated => { -                    Err(compositor::SurfaceError::Outdated) -                } -                wgpu::SurfaceError::Lost => Err(compositor::SurfaceError::Lost), -                wgpu::SurfaceError::OutOfMemory => { -                    Err(compositor::SurfaceError::OutOfMemory) -                } +/// Renders the current surface to an offscreen buffer. +/// +/// Returns RGBA bytes of the texture data. +pub fn screenshot<Theme, T: AsRef<str>>( +    compositor: &Compositor<Theme>, +    backend: &mut Backend, +    primitives: &[Primitive], +    viewport: &Viewport, +    background_color: Color, +    overlay: &[T], +) -> Vec<u8> { +    let mut encoder = compositor.device.create_command_encoder( +        &wgpu::CommandEncoderDescriptor { +            label: Some("iced_wgpu.offscreen.encoder"), +        }, +    ); + +    let dimensions = BufferDimensions::new(viewport.physical_size()); + +    let texture_extent = wgpu::Extent3d { +        width: dimensions.width, +        height: dimensions.height, +        depth_or_array_layers: 1, +    }; + +    let texture = compositor.device.create_texture(&wgpu::TextureDescriptor { +        label: Some("iced_wgpu.offscreen.source_texture"), +        size: texture_extent, +        mip_level_count: 1, +        sample_count: 1, +        dimension: wgpu::TextureDimension::D2, +        format: compositor.format, +        usage: wgpu::TextureUsages::RENDER_ATTACHMENT +            | wgpu::TextureUsages::COPY_SRC +            | wgpu::TextureUsages::TEXTURE_BINDING, +        view_formats: &[], +    }); + +    let view = texture.create_view(&wgpu::TextureViewDescriptor::default()); + +    backend.present( +        &compositor.device, +        &compositor.queue, +        &mut encoder, +        Some(background_color), +        &view, +        primitives, +        viewport, +        overlay, +    ); + +    let texture = crate::color::convert( +        &compositor.device, +        &mut encoder, +        texture, +        if color::GAMMA_CORRECTION { +            wgpu::TextureFormat::Rgba8UnormSrgb +        } else { +            wgpu::TextureFormat::Rgba8Unorm +        }, +    ); + +    let output_buffer = +        compositor.device.create_buffer(&wgpu::BufferDescriptor { +            label: Some("iced_wgpu.offscreen.output_texture_buffer"), +            size: (dimensions.padded_bytes_per_row * dimensions.height as usize) +                as u64, +            usage: wgpu::BufferUsages::MAP_READ | wgpu::BufferUsages::COPY_DST, +            mapped_at_creation: false, +        }); + +    encoder.copy_texture_to_buffer( +        texture.as_image_copy(), +        wgpu::ImageCopyBuffer { +            buffer: &output_buffer, +            layout: wgpu::ImageDataLayout { +                offset: 0, +                bytes_per_row: Some(dimensions.padded_bytes_per_row as u32), +                rows_per_image: None,              }, +        }, +        texture_extent, +    ); + +    let index = compositor.queue.submit(Some(encoder.finish())); + +    let slice = output_buffer.slice(..); +    slice.map_async(wgpu::MapMode::Read, |_| {}); + +    let _ = compositor +        .device +        .poll(wgpu::Maintain::WaitForSubmissionIndex(index)); + +    let mapped_buffer = slice.get_mapped_range(); + +    mapped_buffer.chunks(dimensions.padded_bytes_per_row).fold( +        vec![], +        |mut acc, row| { +            acc.extend(&row[..dimensions.unpadded_bytes_per_row]); +            acc +        }, +    ) +} + +#[derive(Clone, Copy, Debug)] +struct BufferDimensions { +    width: u32, +    height: u32, +    unpadded_bytes_per_row: usize, +    padded_bytes_per_row: usize, +} + +impl BufferDimensions { +    fn new(size: Size<u32>) -> Self { +        let unpadded_bytes_per_row = size.width as usize * 4; //slice of buffer per row; always RGBA +        let alignment = wgpu::COPY_BYTES_PER_ROW_ALIGNMENT as usize; //256 +        let padded_bytes_per_row_padding = +            (alignment - unpadded_bytes_per_row % alignment) % alignment; +        let padded_bytes_per_row = +            unpadded_bytes_per_row + padded_bytes_per_row_padding; + +        Self { +            width: size.width, +            height: size.height, +            unpadded_bytes_per_row, +            padded_bytes_per_row,          }      }  } | 
