diff options
Diffstat (limited to 'wgpu/src')
| -rw-r--r-- | wgpu/src/backend.rs | 415 | ||||
| -rw-r--r-- | wgpu/src/buffer.rs | 86 | ||||
| -rw-r--r-- | wgpu/src/buffer/dynamic.rs | 23 | ||||
| -rw-r--r-- | wgpu/src/buffer/static.rs | 32 | ||||
| -rw-r--r-- | wgpu/src/geometry.rs | 613 | ||||
| -rw-r--r-- | wgpu/src/image.rs | 283 | ||||
| -rw-r--r-- | wgpu/src/image/atlas.rs | 215 | ||||
| -rw-r--r-- | wgpu/src/image/atlas/allocation.rs | 3 | ||||
| -rw-r--r-- | wgpu/src/image/atlas/allocator.rs | 4 | ||||
| -rw-r--r-- | wgpu/src/image/atlas/entry.rs | 9 | ||||
| -rw-r--r-- | wgpu/src/image/raster.rs | 121 | ||||
| -rw-r--r-- | wgpu/src/image/vector.rs | 183 | ||||
| -rw-r--r-- | wgpu/src/layer.rs | 281 | ||||
| -rw-r--r-- | wgpu/src/layer/image.rs | 27 | ||||
| -rw-r--r-- | wgpu/src/layer/mesh.rs | 93 | ||||
| -rw-r--r-- | wgpu/src/layer/quad.rs | 30 | ||||
| -rw-r--r-- | wgpu/src/layer/text.rs | 34 | ||||
| -rw-r--r-- | wgpu/src/lib.rs | 16 | ||||
| -rw-r--r-- | wgpu/src/quad.rs | 233 | ||||
| -rw-r--r-- | wgpu/src/settings.rs | 55 | ||||
| -rw-r--r-- | wgpu/src/text.rs | 596 | ||||
| -rw-r--r-- | wgpu/src/triangle.rs | 655 | ||||
| -rw-r--r-- | wgpu/src/triangle/msaa.rs | 8 | ||||
| -rw-r--r-- | wgpu/src/window.rs | 3 | ||||
| -rw-r--r-- | wgpu/src/window/compositor.rs | 208 | 
25 files changed, 3020 insertions, 1206 deletions
| diff --git a/wgpu/src/backend.rs b/wgpu/src/backend.rs index 6a299425..def80a81 100644 --- a/wgpu/src/backend.rs +++ b/wgpu/src/backend.rs @@ -1,14 +1,11 @@ +use crate::core; +use crate::core::{Color, Font, Point, Size}; +use crate::graphics::backend; +use crate::graphics::{Primitive, Transformation, Viewport};  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 +13,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 +28,7 @@ pub struct Backend {      #[cfg(any(feature = "image", feature = "svg"))]      image_pipeline: image::Pipeline, +    default_font: Font,      default_text_size: f32,  } @@ -36,16 +36,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 +56,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 +68,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,167 +87,245 @@ 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] = +                                    background_color.into_linear(); + +                                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, &mut render_pass); -                self.image_pipeline.draw( +                quad_layer += 1; +            } + +            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);      }  } @@ -261,9 +336,13 @@ impl iced_graphics::Backend for Backend {  }  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 +352,59 @@ impl backend::Text for Backend {          &self,          contents: &str,          size: f32, +        line_height: core::text::LineHeight,          font: Font,          bounds: Size, +        shaping: core::text::Shaping,      ) -> (f32, f32) { -        self.text_pipeline.measure(contents, size, font, bounds) +        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..c210dd4e 100644 --- a/wgpu/src/buffer.rs +++ b/wgpu/src/buffer.rs @@ -1,3 +1,89 @@  //! 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, +    raw: wgpu::Buffer, +    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, +            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.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 +        } +    } + +    pub fn write( +        &self, +        queue: &wgpu::Queue, +        offset_count: usize, +        contents: &[T], +    ) { +        queue.write_buffer( +            &self.raw, +            (std::mem::size_of::<T>() * offset_count) as u64, +            bytemuck::cast_slice(contents), +        ); +    } + +    pub fn slice( +        &self, +        bounds: impl RangeBounds<wgpu::BufferAddress>, +    ) -> wgpu::BufferSlice<'_> { +        self.raw.slice(bounds) +    } +} + +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 index 88289b98..43fc47ac 100644 --- a/wgpu/src/buffer/dynamic.rs +++ b/wgpu/src/buffer/dynamic.rs @@ -112,25 +112,8 @@ impl<T: ShaderType + WriteInto> Buffer<T> {      }      /// 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()); -        } +    pub fn write(&mut self, queue: &wgpu::Queue) { +        queue.write_buffer(&self.gpu, 0, self.cpu.get_ref());      }      // Gets the aligned offset at the given index from the CPU buffer. @@ -184,7 +167,7 @@ impl Internal {      }      /// Returns bytearray of aligned CPU buffer. -    pub(super) fn get_ref(&self) -> &Vec<u8> { +    pub(super) fn get_ref(&self) -> &[u8] {          match self {              Internal::Uniform(buf) => buf.as_ref(),              #[cfg(not(target_arch = "wasm32"))] diff --git a/wgpu/src/buffer/static.rs b/wgpu/src/buffer/static.rs index ef87422f..d8ae116e 100644 --- a/wgpu/src/buffer/static.rs +++ b/wgpu/src/buffer/static.rs @@ -2,8 +2,7 @@ use bytemuck::{Pod, Zeroable};  use std::marker::PhantomData;  use std::mem; -//128 triangles/indices -const DEFAULT_STATIC_BUFFER_COUNT: wgpu::BufferAddress = 128; +const DEFAULT_COUNT: wgpu::BufferAddress = 128;  /// A generic buffer struct useful for items which have no alignment requirements  /// (e.g. Vertex, Index buffers) & no dynamic offsets. @@ -25,7 +24,7 @@ impl<T: Pod + Zeroable> Buffer<T> {          label: &'static str,          usages: wgpu::BufferUsages,      ) -> Self { -        let size = (mem::size_of::<T>() as u64) * DEFAULT_STATIC_BUFFER_COUNT; +        let size = (mem::size_of::<T>() as u64) * DEFAULT_COUNT;          Self {              offsets: Vec::new(), @@ -57,9 +56,13 @@ impl<T: Pod + Zeroable> Buffer<T> {          let size = (mem::size_of::<T>() * new_count) as u64;          if self.size < size { +            self.size = +                (mem::size_of::<T>() * (new_count + new_count / 2)) as u64; + +            self.gpu = +                Self::gpu_buffer(device, self.label, self.size, self.usages); +              self.offsets.clear(); -            self.size = size; -            self.gpu = Self::gpu_buffer(device, self.label, size, self.usages);              true          } else {              false @@ -71,28 +74,15 @@ impl<T: Pod + Zeroable> Buffer<T> {      /// 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, +        queue: &wgpu::Queue,          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); -        } +        queue.write_buffer(&self.gpu, offset, bytes); +        self.offsets.push(offset);          bytes_size      } diff --git a/wgpu/src/geometry.rs b/wgpu/src/geometry.rs new file mode 100644 index 00000000..7e17a7ad --- /dev/null +++ b/wgpu/src/geometry.rs @@ -0,0 +1,613 @@ +//! Build and draw geometry. +use crate::core::{Gradient, Point, Rectangle, Size, Vector}; +use crate::graphics::geometry::fill::{self, Fill}; +use crate::graphics::geometry::{ +    LineCap, LineDash, LineJoin, Path, Stroke, Style, Text, +}; +use crate::graphics::primitive::{self, Primitive}; + +use lyon::geom::euclid; +use lyon::tessellation; +use std::borrow::Cow; + +/// A frame for drawing some geometry. +#[allow(missing_debug_implementations)] +pub struct Frame { +    size: Size, +    buffers: BufferStack, +    primitives: Vec<Primitive>, +    transforms: Transforms, +    fill_tessellator: tessellation::FillTessellator, +    stroke_tessellator: tessellation::StrokeTessellator, +} + +enum Buffer { +    Solid(tessellation::VertexBuffers<primitive::ColoredVertex2D, u32>), +    Gradient( +        tessellation::VertexBuffers<primitive::Vertex2D, u32>, +        Gradient, +    ), +} + +struct BufferStack { +    stack: Vec<Buffer>, +} + +impl BufferStack { +    fn new() -> Self { +        Self { stack: Vec::new() } +    } + +    fn get_mut(&mut self, style: &Style) -> &mut Buffer { +        match style { +            Style::Solid(_) => match self.stack.last() { +                Some(Buffer::Solid(_)) => {} +                _ => { +                    self.stack.push(Buffer::Solid( +                        tessellation::VertexBuffers::new(), +                    )); +                } +            }, +            Style::Gradient(gradient) => match self.stack.last() { +                Some(Buffer::Gradient(_, last)) if gradient == last => {} +                _ => { +                    self.stack.push(Buffer::Gradient( +                        tessellation::VertexBuffers::new(), +                        gradient.clone(), +                    )); +                } +            }, +        } + +        self.stack.last_mut().unwrap() +    } + +    fn get_fill<'a>( +        &'a mut self, +        style: &Style, +    ) -> Box<dyn tessellation::FillGeometryBuilder + 'a> { +        match (style, self.get_mut(style)) { +            (Style::Solid(color), Buffer::Solid(buffer)) => { +                Box::new(tessellation::BuffersBuilder::new( +                    buffer, +                    TriangleVertex2DBuilder(color.into_linear()), +                )) +            } +            (Style::Gradient(_), Buffer::Gradient(buffer, _)) => Box::new( +                tessellation::BuffersBuilder::new(buffer, Vertex2DBuilder), +            ), +            _ => unreachable!(), +        } +    } + +    fn get_stroke<'a>( +        &'a mut self, +        style: &Style, +    ) -> Box<dyn tessellation::StrokeGeometryBuilder + 'a> { +        match (style, self.get_mut(style)) { +            (Style::Solid(color), Buffer::Solid(buffer)) => { +                Box::new(tessellation::BuffersBuilder::new( +                    buffer, +                    TriangleVertex2DBuilder(color.into_linear()), +                )) +            } +            (Style::Gradient(_), Buffer::Gradient(buffer, _)) => Box::new( +                tessellation::BuffersBuilder::new(buffer, Vertex2DBuilder), +            ), +            _ => unreachable!(), +        } +    } +} + +#[derive(Debug)] +struct Transforms { +    previous: Vec<Transform>, +    current: Transform, +} + +#[derive(Debug, Clone, Copy)] +struct Transform { +    raw: lyon::math::Transform, +    is_identity: bool, +} + +impl Transform { +    /// Transforms the given [Point] by the transformation matrix. +    fn transform_point(&self, point: &mut Point) { +        let transformed = self +            .raw +            .transform_point(euclid::Point2D::new(point.x, point.y)); +        point.x = transformed.x; +        point.y = transformed.y; +    } + +    fn transform_style(&self, style: Style) -> Style { +        match style { +            Style::Solid(color) => Style::Solid(color), +            Style::Gradient(gradient) => { +                Style::Gradient(self.transform_gradient(gradient)) +            } +        } +    } + +    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); +        gradient +    } +} + +impl Frame { +    /// Creates a new empty [`Frame`] with the given dimensions. +    /// +    /// The default coordinate system of a [`Frame`] has its origin at the +    /// top-left corner of its bounds. +    pub fn new(size: Size) -> Frame { +        Frame { +            size, +            buffers: BufferStack::new(), +            primitives: Vec::new(), +            transforms: Transforms { +                previous: Vec::new(), +                current: Transform { +                    raw: lyon::math::Transform::identity(), +                    is_identity: true, +                }, +            }, +            fill_tessellator: tessellation::FillTessellator::new(), +            stroke_tessellator: tessellation::StrokeTessellator::new(), +        } +    } + +    /// Returns the width of the [`Frame`]. +    #[inline] +    pub fn width(&self) -> f32 { +        self.size.width +    } + +    /// Returns the height of the [`Frame`]. +    #[inline] +    pub fn height(&self) -> f32 { +        self.size.height +    } + +    /// Returns the dimensions of the [`Frame`]. +    #[inline] +    pub fn size(&self) -> Size { +        self.size +    } + +    /// Returns the coordinate of the center of the [`Frame`]. +    #[inline] +    pub fn center(&self) -> Point { +        Point::new(self.size.width / 2.0, self.size.height / 2.0) +    } + +    /// Draws the given [`Path`] on the [`Frame`] by filling it with the +    /// provided style. +    pub fn fill(&mut self, path: &Path, fill: impl Into<Fill>) { +        let Fill { style, rule } = fill.into(); + +        let mut buffer = self +            .buffers +            .get_fill(&self.transforms.current.transform_style(style)); + +        let options = tessellation::FillOptions::default() +            .with_fill_rule(into_fill_rule(rule)); + +        if self.transforms.current.is_identity { +            self.fill_tessellator.tessellate_path( +                path.raw(), +                &options, +                buffer.as_mut(), +            ) +        } else { +            let path = path.transform(&self.transforms.current.raw); + +            self.fill_tessellator.tessellate_path( +                path.raw(), +                &options, +                buffer.as_mut(), +            ) +        } +        .expect("Tessellate path."); +    } + +    /// Draws an axis-aligned rectangle given its top-left corner coordinate and +    /// its `Size` on the [`Frame`] by filling it with the provided style. +    pub fn fill_rectangle( +        &mut self, +        top_left: Point, +        size: Size, +        fill: impl Into<Fill>, +    ) { +        let Fill { style, rule } = fill.into(); + +        let mut buffer = self +            .buffers +            .get_fill(&self.transforms.current.transform_style(style)); + +        let top_left = +            self.transforms.current.raw.transform_point( +                lyon::math::Point::new(top_left.x, top_left.y), +            ); + +        let size = +            self.transforms.current.raw.transform_vector( +                lyon::math::Vector::new(size.width, size.height), +            ); + +        let options = tessellation::FillOptions::default() +            .with_fill_rule(into_fill_rule(rule)); + +        self.fill_tessellator +            .tessellate_rectangle( +                &lyon::math::Box2D::new(top_left, top_left + size), +                &options, +                buffer.as_mut(), +            ) +            .expect("Fill rectangle"); +    } + +    /// Draws the stroke of the given [`Path`] on the [`Frame`] with the +    /// provided style. +    pub fn stroke<'a>(&mut self, path: &Path, stroke: impl Into<Stroke<'a>>) { +        let stroke = stroke.into(); + +        let mut buffer = self +            .buffers +            .get_stroke(&self.transforms.current.transform_style(stroke.style)); + +        let mut options = tessellation::StrokeOptions::default(); +        options.line_width = stroke.width; +        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(dashed(path, stroke.line_dash)) +        }; + +        if self.transforms.current.is_identity { +            self.stroke_tessellator.tessellate_path( +                path.raw(), +                &options, +                buffer.as_mut(), +            ) +        } else { +            let path = path.transform(&self.transforms.current.raw); + +            self.stroke_tessellator.tessellate_path( +                path.raw(), +                &options, +                buffer.as_mut(), +            ) +        } +        .expect("Stroke path"); +    } + +    /// Draws the characters of the given [`Text`] on the [`Frame`], filling +    /// them with the given color. +    /// +    /// __Warning:__ Text currently does not work well with rotations and scale +    /// transforms! The position will be correctly transformed, but the +    /// resulting glyphs will not be rotated or scaled properly. +    /// +    /// Additionally, all text will be rendered on top of all the layers of +    /// a [`Canvas`]. Therefore, it is currently only meant to be used for +    /// overlays, which is the most common use case. +    /// +    /// Support for vectorial text is planned, and should address all these +    /// limitations. +    /// +    /// [`Canvas`]: crate::widget::Canvas +    pub fn fill_text(&mut self, text: impl Into<Text>) { +        let text = text.into(); + +        let position = if self.transforms.current.is_identity { +            text.position +        } else { +            let transformed = self.transforms.current.raw.transform_point( +                lyon::math::Point::new(text.position.x, text.position.y), +            ); + +            Point::new(transformed.x, transformed.y) +        }; + +        // TODO: Use vectorial text instead of primitive +        self.primitives.push(Primitive::Text { +            content: text.content, +            bounds: Rectangle { +                x: position.x, +                y: position.y, +                width: f32::INFINITY, +                height: f32::INFINITY, +            }, +            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, +        }); +    } + +    /// Stores the current transform of the [`Frame`] and executes the given +    /// drawing operations, restoring the transform afterwards. +    /// +    /// This method is useful to compose transforms and perform drawing +    /// operations in different coordinate systems. +    #[inline] +    pub fn with_save(&mut self, f: impl FnOnce(&mut Frame)) { +        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(); +    } + +    /// Executes the given drawing operations within a [`Rectangle`] region, +    /// clipping any geometry that overflows its bounds. Any transformations +    /// performed are local to the provided closure. +    /// +    /// This method is useful to perform drawing operations that need to be +    /// clipped. +    #[inline] +    pub fn with_clip(&mut self, region: Rectangle, f: impl FnOnce(&mut Frame)) { +        let mut frame = Frame::new(region.size()); + +        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 { .. })); + +        self.primitives.push(Primitive::Group { +            primitives: vec![ +                Primitive::Translate { +                    translation, +                    content: Box::new(Primitive::Group { primitives: meshes }), +                }, +                Primitive::Translate { +                    translation, +                    content: Box::new(Primitive::Clip { +                        bounds: Rectangle::with_size(size), +                        content: Box::new(Primitive::Group { +                            primitives: text, +                        }), +                    }), +                }, +            ], +        }); +    } + +    /// Applies a translation to the current transform of the [`Frame`]. +    #[inline] +    pub fn translate(&mut self, translation: Vector) { +        self.transforms.current.raw = self +            .transforms +            .current +            .raw +            .pre_translate(lyon::math::Vector::new( +                translation.x, +                translation.y, +            )); +        self.transforms.current.is_identity = false; +    } + +    /// Applies a rotation in radians to the current transform of the [`Frame`]. +    #[inline] +    pub fn rotate(&mut self, angle: f32) { +        self.transforms.current.raw = self +            .transforms +            .current +            .raw +            .pre_rotate(lyon::math::Angle::radians(angle)); +        self.transforms.current.is_identity = false; +    } + +    /// Applies a scaling to the current transform of the [`Frame`]. +    #[inline] +    pub fn scale(&mut self, scale: f32) { +        self.transforms.current.raw = +            self.transforms.current.raw.pre_scale(scale, scale); +        self.transforms.current.is_identity = false; +    } + +    /// 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> { +        for buffer in self.buffers.stack { +            match buffer { +                Buffer::Solid(buffer) => { +                    if !buffer.indices.is_empty() { +                        self.primitives.push(Primitive::SolidMesh { +                            buffers: primitive::Mesh2D { +                                vertices: buffer.vertices, +                                indices: buffer.indices, +                            }, +                            size: self.size, +                        }) +                    } +                } +                Buffer::Gradient(buffer, gradient) => { +                    if !buffer.indices.is_empty() { +                        self.primitives.push(Primitive::GradientMesh { +                            buffers: primitive::Mesh2D { +                                vertices: buffer.vertices, +                                indices: buffer.indices, +                            }, +                            size: self.size, +                            gradient, +                        }) +                    } +                } +            } +        } + +        self.primitives +    } +} + +struct Vertex2DBuilder; + +impl tessellation::FillVertexConstructor<primitive::Vertex2D> +    for Vertex2DBuilder +{ +    fn new_vertex( +        &mut self, +        vertex: tessellation::FillVertex<'_>, +    ) -> primitive::Vertex2D { +        let position = vertex.position(); + +        primitive::Vertex2D { +            position: [position.x, position.y], +        } +    } +} + +impl tessellation::StrokeVertexConstructor<primitive::Vertex2D> +    for Vertex2DBuilder +{ +    fn new_vertex( +        &mut self, +        vertex: tessellation::StrokeVertex<'_, '_>, +    ) -> primitive::Vertex2D { +        let position = vertex.position(); + +        primitive::Vertex2D { +            position: [position.x, position.y], +        } +    } +} + +struct TriangleVertex2DBuilder([f32; 4]); + +impl tessellation::FillVertexConstructor<primitive::ColoredVertex2D> +    for TriangleVertex2DBuilder +{ +    fn new_vertex( +        &mut self, +        vertex: tessellation::FillVertex<'_>, +    ) -> primitive::ColoredVertex2D { +        let position = vertex.position(); + +        primitive::ColoredVertex2D { +            position: [position.x, position.y], +            color: self.0, +        } +    } +} + +impl tessellation::StrokeVertexConstructor<primitive::ColoredVertex2D> +    for TriangleVertex2DBuilder +{ +    fn new_vertex( +        &mut self, +        vertex: tessellation::StrokeVertex<'_, '_>, +    ) -> primitive::ColoredVertex2D { +        let position = vertex.position(); + +        primitive::ColoredVertex2D { +            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 9f56c188..263bcfa2 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()); +        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"), @@ -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,10 @@ impl Pipeline {                  #[cfg(feature = "image")]                  layer::Image::Raster { handle, bounds } => {                      if let Some(atlas_entry) = raster_cache.upload( +                        device, +                        queue, +                        encoder,                          handle, -                        &mut (device, encoder),                          &mut self.texture_atlas,                      ) {                          add_instances( @@ -332,11 +394,13 @@ impl Pipeline {                      let size = [bounds.width, bounds.height];                      if let Some(atlas_entry) = vector_cache.upload( +                        device, +                        queue, +                        encoder,                          handle,                          *color,                          size,                          _scale, -                        &mut (device, encoder),                          &mut self.texture_atlas,                      ) {                          add_instances( @@ -376,68 +440,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 +470,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;      }  } @@ -507,7 +526,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 a0fdf146..366fe623 100644 --- a/wgpu/src/image/atlas.rs +++ b/wgpu/src/image/atlas.rs @@ -12,8 +12,7 @@ use allocator::Allocator;  pub const SIZE: u32 = 2048; -use iced_graphics::image; -use iced_graphics::Size; +use crate::core::Size;  #[derive(Debug)]  pub struct Atlas { @@ -37,10 +36,10 @@ impl Atlas {              sample_count: 1,              dimension: wgpu::TextureDimension::D2,              format: wgpu::TextureFormat::Rgba8UnormSrgb, -            view_formats: &[],              usage: wgpu::TextureUsages::COPY_DST                  | wgpu::TextureUsages::COPY_SRC                  | wgpu::TextureUsages::TEXTURE_BINDING, +            view_formats: &[],          });          let texture_view = texture.create_view(&wgpu::TextureViewDescriptor { @@ -63,6 +62,97 @@ impl Atlas {          self.layers.len()      } +    pub fn upload( +        &mut self, +        device: &wgpu::Device, +        queue: &wgpu::Queue, +        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, +                    queue, +                ); +            } +            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, +                        queue, +                    ); +                } +            } +        } + +        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 { @@ -184,13 +274,13 @@ impl Atlas {      fn upload_allocation(          &mut self, -        buffer: &wgpu::Buffer, +        data: &[u8],          image_width: u32,          image_height: u32,          padding: u32,          offset: usize,          allocation: &Allocation, -        encoder: &mut wgpu::CommandEncoder, +        queue: &wgpu::Queue,      ) {          let (x, y) = allocation.position();          let Size { width, height } = allocation.size(); @@ -202,15 +292,7 @@ impl Atlas {              depth_or_array_layers: 1,          }; -        encoder.copy_buffer_to_texture( -            wgpu::ImageCopyBuffer { -                buffer, -                layout: wgpu::ImageDataLayout { -                    offset: offset as u64, -                    bytes_per_row: Some(4 * image_width + padding), -                    rows_per_image: Some(image_height), -                }, -            }, +        queue.write_texture(              wgpu::ImageCopyTexture {                  texture: &self.texture,                  mip_level: 0, @@ -221,6 +303,12 @@ impl Atlas {                  },                  aspect: wgpu::TextureAspect::default(),              }, +            data, +            wgpu::ImageDataLayout { +                offset: offset as u64, +                bytes_per_row: Some(4 * image_width + padding), +                rows_per_image: Some(image_height), +            },              extent,          );      } @@ -246,10 +334,10 @@ impl Atlas {              sample_count: 1,              dimension: wgpu::TextureDimension::D2,              format: wgpu::TextureFormat::Rgba8UnormSrgb, -            view_formats: &[],              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 +386,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..9b38dce4 --- /dev/null +++ b/wgpu/src/image/raster.rs @@ -0,0 +1,121 @@ +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, +        queue: &wgpu::Queue, +        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, queue, 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/wgpu/src/image/vector.rs b/wgpu/src/image/vector.rs new file mode 100644 index 00000000..58bdf64a --- /dev/null +++ b/wgpu/src/image/vector.rs @@ -0,0 +1,183 @@ +use crate::core::svg; +use crate::core::{Color, Size}; +use crate::image::atlas::{self, Atlas}; + +use resvg::tiny_skia; +use resvg::usvg; +use std::collections::{HashMap, HashSet}; +use std::fs; + +/// Entry in cache corresponding to an svg handle +pub enum Svg { +    /// Parsed svg +    Loaded(usvg::Tree), +    /// Svg not found or failed to parse +    NotFound, +} + +impl Svg { +    /// Viewport width and height +    pub fn viewport_dimensions(&self) -> Size<u32> { +        match self { +            Svg::Loaded(tree) => { +                let size = tree.size; + +                Size::new(size.width() as u32, size.height() as u32) +            } +            Svg::NotFound => Size::new(1, 1), +        } +    } +} + +/// Caches svg vector and raster data +#[derive(Debug, Default)] +pub struct Cache { +    svgs: HashMap<u64, Svg>, +    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 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(); +        } + +        let svg = match handle.data() { +            svg::Data::Path(path) => { +                let tree = fs::read_to_string(path).ok().and_then(|contents| { +                    usvg::Tree::from_str(&contents, &usvg::Options::default()) +                        .ok() +                }); + +                tree.map(Svg::Loaded).unwrap_or(Svg::NotFound) +            } +            svg::Data::Bytes(bytes) => { +                match usvg::Tree::from_data(bytes, &usvg::Options::default()) { +                    Ok(tree) => Svg::Loaded(tree), +                    Err(_) => Svg::NotFound, +                } +            } +        }; + +        let _ = self.svgs.insert(handle.id(), svg); +        self.svgs.get(&handle.id()).unwrap() +    } + +    /// Load svg and upload raster data +    pub fn upload( +        &mut self, +        device: &wgpu::Device, +        queue: &wgpu::Queue, +        encoder: &mut wgpu::CommandEncoder, +        handle: &svg::Handle, +        color: Option<Color>, +        [width, height]: [f32; 2], +        scale: f32, +        atlas: &mut Atlas, +    ) -> Option<&atlas::Entry> { +        let id = handle.id(); + +        let (width, height) = ( +            (scale * width).ceil() as u32, +            (scale * height).ceil() as u32, +        ); + +        let color = color.map(Color::into_rgba8); +        let key = (id, width, height, color); + +        // TODO: Optimize! +        // We currently rerasterize the SVG when its size changes. This is slow +        // as heck. A GPU rasterizer like `pathfinder` may perform better. +        // It would be cool to be able to smooth resize the `svg` example. +        if self.rasterized.contains_key(&key) { +            let _ = self.svg_hits.insert(id); +            let _ = self.rasterized_hits.insert(key); + +            return self.rasterized.get(&key); +        } + +        match self.load(handle) { +            Svg::Loaded(tree) => { +                if width == 0 || height == 0 { +                    return None; +                } + +                // TODO: Optimize! +                // We currently rerasterize the SVG when its size changes. This is slow +                // as heck. A GPU rasterizer like `pathfinder` may perform better. +                // 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 { +                        resvg::FitTo::Width(width) +                    } else { +                        resvg::FitTo::Height(height) +                    }, +                    tiny_skia::Transform::default(), +                    img.as_mut(), +                )?; + +                let mut rgba = img.take(); + +                if let Some(color) = color { +                    rgba.chunks_exact_mut(4).for_each(|rgba| { +                        if rgba[3] > 0 { +                            rgba[0] = color[0]; +                            rgba[1] = color[1]; +                            rgba[2] = color[2]; +                        } +                    }); +                } + +                let allocation = atlas +                    .upload(device, queue, encoder, width, height, &rgba)?; + +                log::debug!("allocating {} {}x{}", id, width, height); + +                let _ = self.svg_hits.insert(id); +                let _ = self.rasterized_hits.insert(key); +                let _ = self.rasterized.insert(key, allocation); + +                self.rasterized.get(&key) +            } +            Svg::NotFound => None, +        } +    } + +    /// Load svg and upload raster data +    pub fn trim(&mut self, atlas: &mut Atlas) { +        let svg_hits = &self.svg_hits; +        let rasterized_hits = &self.rasterized_hits; + +        self.svgs.retain(|k, _| svg_hits.contains(k)); +        self.rasterized.retain(|k, entry| { +            let retain = rasterized_hits.contains(k); + +            if !retain { +                atlas.remove(entry); +            } + +            retain +        }); +        self.svg_hits.clear(); +        self.rasterized_hits.clear(); +    } +} + +impl std::fmt::Debug for Svg { +    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { +        match self { +            Svg::Loaded(_) => write!(f, "Svg::Loaded"), +            Svg::NotFound => write!(f, "Svg::NotFound"), +        } +    } +} diff --git a/wgpu/src/layer.rs b/wgpu/src/layer.rs new file mode 100644 index 00000000..8af72b9d --- /dev/null +++ b/wgpu/src/layer.rs @@ -0,0 +1,281 @@ +//! 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::core; +use crate::core::alignment; +use crate::core::{Background, Color, Font, Point, Rectangle, Size, Vector}; +use crate::graphics::{Primitive, Viewport}; + +/// A group of primitives that should be clipped together. +#[derive(Debug)] +pub struct Layer<'a> { +    /// The clipping bounds of the [`Layer`]. +    pub bounds: Rectangle, + +    /// The quads of the [`Layer`]. +    pub quads: Vec<Quad>, + +    /// The triangle meshes of the [`Layer`]. +    pub meshes: Vec<Mesh<'a>>, + +    /// The text of the [`Layer`]. +    pub text: Vec<Text<'a>>, + +    /// The images of the [`Layer`]. +    pub images: Vec<Image>, +} + +impl<'a> Layer<'a> { +    /// Creates a new [`Layer`] with the given clipping bounds. +    pub fn new(bounds: Rectangle) -> Self { +        Self { +            bounds, +            quads: Vec::new(), +            meshes: Vec::new(), +            text: Vec::new(), +            images: Vec::new(), +        } +    } + +    /// Creates a new [`Layer`] for the provided overlay text. +    /// +    /// This can be useful for displaying debug information. +    pub fn overlay(lines: &'a [impl AsRef<str>], viewport: &Viewport) -> Self { +        let mut overlay = +            Layer::new(Rectangle::with_size(viewport.logical_size())); + +        for (i, line) in lines.iter().enumerate() { +            let text = Text { +                content: line.as_ref(), +                bounds: Rectangle::new( +                    Point::new(11.0, 11.0 + 25.0 * i as f32), +                    Size::INFINITY, +                ), +                color: Color::new(0.9, 0.9, 0.9, 1.0), +                size: 20.0, +                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: Color::BLACK, +                ..text +            }); +        } + +        overlay +    } + +    /// Distributes the given [`Primitive`] and generates a list of layers based +    /// on its contents. +    pub fn generate( +        primitives: &'a [Primitive], +        viewport: &Viewport, +    ) -> Vec<Self> { +        let first_layer = +            Layer::new(Rectangle::with_size(viewport.logical_size())); + +        let mut layers = vec![first_layer]; + +        for primitive in primitives { +            Self::process_primitive( +                &mut layers, +                Vector::new(0.0, 0.0), +                primitive, +                0, +            ); +        } + +        layers +    } + +    fn process_primitive( +        layers: &mut Vec<Self>, +        translation: Vector, +        primitive: &'a Primitive, +        current_layer: usize, +    ) { +        match primitive { +            Primitive::Text { +                content, +                bounds, +                size, +                line_height, +                color, +                font, +                horizontal_alignment, +                vertical_alignment, +                shaping, +            } => { +                let layer = &mut layers[current_layer]; + +                layer.text.push(Text { +                    content, +                    bounds: *bounds + translation, +                    size: *size, +                    line_height: *line_height, +                    color: *color, +                    font: *font, +                    horizontal_alignment: *horizontal_alignment, +                    vertical_alignment: *vertical_alignment, +                    shaping: *shaping, +                }); +            } +            Primitive::Quad { +                bounds, +                background, +                border_radius, +                border_width, +                border_color, +            } => { +                let layer = &mut layers[current_layer]; + +                // TODO: Move some of these computations to the GPU (?) +                layer.quads.push(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_radius: *border_radius, +                    border_width: *border_width, +                    border_color: border_color.into_linear(), +                }); +            } +            Primitive::Image { handle, bounds } => { +                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]; + +                layer.images.push(Image::Vector { +                    handle: handle.clone(), +                    color: *color, +                    bounds: *bounds + translation, +                }); +            } +            Primitive::SolidMesh { 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::Solid { +                        origin: Point::new(translation.x, translation.y), +                        buffers, +                        clip_bounds, +                    }); +                } +            } +            Primitive::GradientMesh { +                buffers, +                size, +                gradient, +            } => { +                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, +                    }); +                } +            } +            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 } => { +                let layer = &mut layers[current_layer]; +                let translated_bounds = *bounds + translation; + +                // Only draw visible content +                if let Some(clip_bounds) = +                    layer.bounds.intersection(&translated_bounds) +                { +                    let clip_layer = Layer::new(clip_bounds); +                    layers.push(clip_layer); + +                    Self::process_primitive( +                        layers, +                        translation, +                        content, +                        layers.len() - 1, +                    ); +                } +            } +            Primitive::Translate { +                translation: new_translation, +                content, +            } => { +                Self::process_primitive( +                    layers, +                    translation + *new_translation, +                    content, +                    current_layer, +                ); +            } +            Primitive::Cache { content } => { +                Self::process_primitive( +                    layers, +                    translation, +                    content, +                    current_layer, +                ); +            } +            _ => { +                // Not supported! +                log::warn!( +                    "Unsupported primitive in `iced_wgpu`: {:?}", +                    primitive +                ); +            } +        } +    } +} diff --git a/wgpu/src/layer/image.rs b/wgpu/src/layer/image.rs new file mode 100644 index 00000000..0de589f8 --- /dev/null +++ b/wgpu/src/layer/image.rs @@ -0,0 +1,27 @@ +use crate::core::image; +use crate::core::svg; +use crate::core::{Color, Rectangle}; + +/// A raster or vector image. +#[derive(Debug, Clone)] +pub enum Image { +    /// A raster image. +    Raster { +        /// The handle of a raster image. +        handle: image::Handle, + +        /// The bounds of the image. +        bounds: Rectangle, +    }, +    /// A vector image. +    Vector { +        /// The handle of a vector image. +        handle: svg::Handle, + +        /// The [`Color`] filter +        color: Option<Color>, + +        /// The bounds of the image. +        bounds: Rectangle, +    }, +} diff --git a/wgpu/src/layer/mesh.rs b/wgpu/src/layer/mesh.rs new file mode 100644 index 00000000..9dd14391 --- /dev/null +++ b/wgpu/src/layer/mesh.rs @@ -0,0 +1,93 @@ +//! A collection of triangle primitives. +use crate::core::{Gradient, Point, Rectangle}; +use crate::graphics::primitive; + +/// A mesh of triangles. +#[derive(Debug, Clone, Copy)] +pub enum Mesh<'a> { +    /// A mesh of triangles with a solid color. +    Solid { +        /// The origin of the vertices of the [`Mesh`]. +        origin: Point, + +        /// The vertex and index buffers of the [`Mesh`]. +        buffers: &'a primitive::Mesh2D<primitive::ColoredVertex2D>, + +        /// The clipping bounds of the [`Mesh`]. +        clip_bounds: Rectangle<f32>, +    }, +    /// A mesh of triangles with a gradient color. +    Gradient { +        /// The origin of the vertices of the [`Mesh`]. +        origin: Point, + +        /// The vertex and index buffers of the [`Mesh`]. +        buffers: &'a primitive::Mesh2D<primitive::Vertex2D>, + +        /// The clipping bounds of the [`Mesh`]. +        clip_bounds: Rectangle<f32>, + +        /// The gradient to apply to the [`Mesh`]. +        gradient: &'a Gradient, +    }, +} + +impl Mesh<'_> { +    /// Returns the origin of the [`Mesh`]. +    pub fn origin(&self) -> Point { +        match self { +            Self::Solid { origin, .. } | Self::Gradient { origin, .. } => { +                *origin +            } +        } +    } + +    /// Returns the indices of the [`Mesh`]. +    pub fn indices(&self) -> &[u32] { +        match self { +            Self::Solid { buffers, .. } => &buffers.indices, +            Self::Gradient { buffers, .. } => &buffers.indices, +        } +    } + +    /// Returns the clip bounds of the [`Mesh`]. +    pub fn clip_bounds(&self) -> Rectangle<f32> { +        match self { +            Self::Solid { clip_bounds, .. } +            | Self::Gradient { clip_bounds, .. } => *clip_bounds, +        } +    } +} + +/// The result of counting the attributes of a set of meshes. +#[derive(Debug, Clone, Copy, Default)] +pub struct AttributeCount { +    /// The total amount of solid vertices. +    pub solid_vertices: usize, + +    /// The total amount of gradient vertices. +    pub gradient_vertices: usize, + +    /// The total amount of indices. +    pub indices: usize, +} + +/// Returns the number of total vertices & total indices of all [`Mesh`]es. +pub fn attribute_count_of<'a>(meshes: &'a [Mesh<'a>]) -> AttributeCount { +    meshes +        .iter() +        .fold(AttributeCount::default(), |mut count, mesh| { +            match mesh { +                Mesh::Solid { buffers, .. } => { +                    count.solid_vertices += buffers.vertices.len(); +                    count.indices += buffers.indices.len(); +                } +                Mesh::Gradient { buffers, .. } => { +                    count.gradient_vertices += buffers.vertices.len(); +                    count.indices += buffers.indices.len(); +                } +            } + +            count +        }) +} diff --git a/wgpu/src/layer/quad.rs b/wgpu/src/layer/quad.rs new file mode 100644 index 00000000..0d8bde9d --- /dev/null +++ b/wgpu/src/layer/quad.rs @@ -0,0 +1,30 @@ +/// A colored rectangle with a border. +/// +/// This type can be directly uploaded to GPU memory. +#[derive(Debug, Clone, Copy)] +#[repr(C)] +pub struct Quad { +    /// The position of the [`Quad`]. +    pub position: [f32; 2], + +    /// The size of the [`Quad`]. +    pub size: [f32; 2], + +    /// The color of the [`Quad`], in __linear RGB__. +    pub color: [f32; 4], + +    /// The border color of the [`Quad`], in __linear RGB__. +    pub border_color: [f32; 4], + +    /// The border radius of the [`Quad`]. +    pub border_radius: [f32; 4], + +    /// The border width of the [`Quad`]. +    pub border_width: f32, +} + +#[allow(unsafe_code)] +unsafe impl bytemuck::Zeroable for Quad {} + +#[allow(unsafe_code)] +unsafe impl bytemuck::Pod for Quad {} diff --git a/wgpu/src/layer/text.rs b/wgpu/src/layer/text.rs new file mode 100644 index 00000000..ba1bdca8 --- /dev/null +++ b/wgpu/src/layer/text.rs @@ -0,0 +1,34 @@ +use crate::core::alignment; +use crate::core::text; +use crate::core::{Color, Font, Rectangle}; + +/// A paragraph of text. +#[derive(Debug, Clone, Copy)] +pub struct Text<'a> { +    /// The content of the [`Text`]. +    pub content: &'a str, + +    /// The layout bounds of the [`Text`]. +    pub bounds: Rectangle, + +    /// The color of the [`Text`], in __linear RGB_. +    pub color: Color, + +    /// 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, + +    /// The horizontal alignment of the [`Text`]. +    pub horizontal_alignment: alignment::Horizontal, + +    /// 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 969e3199..4a92c345 100644 --- a/wgpu/src/lib.rs +++ b/wgpu/src/lib.rs @@ -37,24 +37,29 @@  #![forbid(rust_2018_idioms)]  #![allow(clippy::inherent_to_string, clippy::type_complexity)]  #![cfg_attr(docsrs, feature(doc_cfg))] - +pub mod layer;  pub mod settings;  pub mod window; +#[cfg(feature = "geometry")] +pub mod geometry; +  mod backend;  mod buffer;  mod quad;  mod text;  mod triangle; -pub use iced_graphics::{Antialiasing, Color, Error, Primitive, Viewport}; -pub use iced_native::Theme; +pub use iced_graphics as graphics; +pub use iced_graphics::core; +  pub use wgpu;  pub use backend::Backend; +pub use layer::Layer;  pub use settings::Settings; -pub(crate) use iced_graphics::Transformation; +use buffer::Buffer;  #[cfg(any(feature = "image", feature = "svg"))]  mod image; @@ -63,5 +68,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/quad.rs b/wgpu/src/quad.rs index 1343181e..8fa7359e 100644 --- a/wgpu/src/quad.rs +++ b/wgpu/src/quad.rs @@ -1,6 +1,7 @@ -use crate::Transformation; -use iced_graphics::layer; -use iced_native::Rectangle; +use crate::core::Rectangle; +use crate::graphics::Transformation; +use crate::layer; +use crate::Buffer;  use bytemuck::{Pod, Zeroable};  use std::mem; @@ -12,11 +13,11 @@ use tracing::info_span;  #[derive(Debug)]  pub struct Pipeline {      pipeline: wgpu::RenderPipeline, -    constants: wgpu::BindGroup, -    constants_buffer: wgpu::Buffer, +    constant_layout: wgpu::BindGroupLayout,      vertices: wgpu::Buffer,      indices: wgpu::Buffer, -    instances: wgpu::Buffer, +    layers: Vec<Layer>, +    prepare_layer: usize,  }  impl Pipeline { @@ -38,22 +39,6 @@ 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"), @@ -148,118 +133,146 @@ impl Pipeline {                  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, -            mapped_at_creation: false, -        }); -          Pipeline {              pipeline, -            constants, -            constants_buffer, +            constant_layout,              vertices,              indices, -            instances, +            layers: Vec::new(), +            prepare_layer: 0,          }      } -    pub fn draw( +    pub fn prepare(          &mut self,          device: &wgpu::Device, -        staging_belt: &mut wgpu::util::StagingBelt, -        encoder: &mut wgpu::CommandEncoder, +        queue: &wgpu::Queue,          instances: &[layer::Quad],          transformation: Transformation,          scale: f32, -        bounds: Rectangle<u32>, -        target: &wgpu::TextureView,      ) { -        #[cfg(feature = "tracing")] -        let _ = info_span!("Wgpu::Quad", "DRAW").entered(); +        if self.layers.len() <= self.prepare_layer { +            self.layers.push(Layer::new(device, &self.constant_layout)); +        } -        let uniforms = Uniforms::new(transformation, scale); +        let layer = &mut self.layers[self.prepare_layer]; +        layer.prepare(device, queue, instances, transformation, scale); + +        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_scissor_rect( +                bounds.x, +                bounds.y, +                bounds.width, +                bounds.height, +            ); -        { -            let mut constants_buffer = staging_belt.write_buffer( -                encoder, -                &self.constants_buffer, -                0, -                wgpu::BufferSize::new(mem::size_of::<Uniforms>() as u64) -                    .unwrap(), -                device, +            render_pass.set_index_buffer( +                self.indices.slice(..), +                wgpu::IndexFormat::Uint16,              ); +            render_pass.set_vertex_buffer(0, self.vertices.slice(..)); -            constants_buffer.copy_from_slice(bytemuck::bytes_of(&uniforms)); +            layer.draw(render_pass);          } +    } -        let mut i = 0; -        let total = instances.len(); +    pub fn end_frame(&mut self) { +        self.prepare_layer = 0; +    } +} -        while i < total { -            let end = (i + MAX_INSTANCES).min(total); -            let amount = end - i; +#[derive(Debug)] +struct Layer { +    constants: wgpu::BindGroup, +    constants_buffer: wgpu::Buffer, +    instances: Buffer<layer::Quad>, +    instance_count: usize, +} -            let instance_bytes = bytemuck::cast_slice(&instances[i..end]); +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, +        }); -            let mut instance_buffer = staging_belt.write_buffer( -                encoder, -                &self.instances, -                0, -                wgpu::BufferSize::new(instance_bytes.len() as u64).unwrap(), -                device, -            ); +        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(), +            }], +        }); -            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, -                ); -            } - -            i += MAX_INSTANCES; +        let instances = Buffer::new( +            device, +            "iced_wgpu::quad instance buffer", +            INITIAL_INSTANCES, +            wgpu::BufferUsages::VERTEX | wgpu::BufferUsages::COPY_DST, +        ); + +        Self { +            constants, +            constants_buffer, +            instances, +            instance_count: 0,          }      } + +    pub fn prepare( +        &mut self, +        device: &wgpu::Device, +        queue: &wgpu::Queue, +        instances: &[layer::Quad], +        transformation: Transformation, +        scale: f32, +    ) { +        #[cfg(feature = "tracing")] +        let _ = info_span!("Wgpu::Quad", "PREPARE").entered(); + +        let uniforms = Uniforms::new(transformation, scale); + +        queue.write_buffer( +            &self.constants_buffer, +            0, +            bytemuck::bytes_of(&uniforms), +        ); + +        let _ = self.instances.resize(device, instances.len()); +        self.instances.write(queue, 0, instances); +        self.instance_count = instances.len(); +    } + +    pub fn draw<'a>(&'a self, render_pass: &mut wgpu::RenderPass<'a>) { +        #[cfg(feature = "tracing")] +        let _ = info_span!("Wgpu::Quad", "DRAW").entered(); + +        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, +        ); +    }  }  #[repr(C)] @@ -285,7 +298,7 @@ const QUAD_VERTS: [Vertex; 4] = [      },  ]; -const MAX_INSTANCES: usize = 100_000; +const INITIAL_INSTANCES: usize = 10_000;  #[repr(C)]  #[derive(Debug, Clone, Copy, Zeroable, Pod)] 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/text.rs b/wgpu/src/text.rs index e17b84c1..714e0400 100644 --- a/wgpu/src/text.rs +++ b/wgpu/src/text.rs @@ -1,265 +1,439 @@ -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::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, +    measurement_cache: RefCell<Cache>, +    render_cache: 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), +            prepare_layer: 0, +            measurement_cache: RefCell::new(Cache::new()), +            render_cache: Cache::new(), +        } +    } -        let default_font = -            default_font.unwrap_or_else(|| font::FALLBACK.to_vec()); +    pub fn load_font(&mut self, bytes: Cow<'static, [u8]>) { +        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..." +    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 font_system = self.font_system.get_mut(); +        let renderer = &mut self.renderers[self.prepare_layer]; + +        let keys: Vec<_> = sections +            .iter() +            .map(|section| { +                let (key, _) = self.render_cache.allocate( +                    font_system, +                    Key { +                        content: section.content, +                        size: section.size * scale_factor, +                        line_height: f32::from( +                            section +                                .line_height +                                .to_absolute(Pixels(section.size)), +                        ) * scale_factor, +                        font: section.font, +                        bounds: Size { +                            width: (section.bounds.width * scale_factor).ceil(), +                            height: (section.bounds.height * scale_factor) +                                .ceil(), +                        }, +                        shaping: section.shaping, +                    },                  ); -                ab_glyph::FontArc::try_from_slice(font::FALLBACK) -                    .expect("Load fallback font") -            }); +                key +            }) +            .collect(); + +        let bounds = bounds * scale_factor; + +        let text_areas = +            sections +                .iter() +                .zip(keys.iter()) +                .filter_map(|(section, key)| { +                    let buffer = +                        self.render_cache.get(key).expect("Get cached buffer"); + +                    let (total_lines, max_width) = buffer +                        .layout_runs() +                        .enumerate() +                        .fold((0, 0.0), |(_, max), (i, buffer)| { +                            (i + 1, buffer.line_w.max(max)) +                        }); + +                    let total_height = +                        total_lines as f32 * buffer.metrics().line_height; + +                    let x = section.bounds.x * scale_factor; +                    let y = section.bounds.y * 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)?; + +                    // TODO: Subpixel glyph positioning +                    let left = left.round() as i32; +                    let top = top.round() as i32; + +                    Some(glyphon::TextArea { +                        buffer, +                        left, +                        top, +                        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] = section.color.into_linear(); + +                            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(), +        ); -        let draw_brush_builder = -            wgpu_glyph::GlyphBrushBuilder::using_font(font.clone()) -                .initial_cache_size((2048, 2048)) -                .draw_cache_multithread(multithreading); +        match result { +            Ok(()) => { +                self.prepare_layer += 1; -        #[cfg(target_arch = "wasm32")] -        let draw_brush_builder = draw_brush_builder.draw_cache_align_4x4(true); +                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 +                } +            } +        } +    } -        let draw_brush = draw_brush_builder.build(device, format); +    pub fn render<'a>( +        &'a self, +        layer: usize, +        bounds: Rectangle<u32>, +        render_pass: &mut wgpu::RenderPass<'a>, +    ) { +        let renderer = &self.renderers[layer]; -        let measure_brush = -            glyph_brush::GlyphBrushBuilder::using_font(font).build(); +        render_pass.set_scissor_rect( +            bounds.x, +            bounds.y, +            bounds.width, +            bounds.height, +        ); -        Pipeline { -            draw_brush: RefCell::new(draw_brush), -            draw_font_map: RefCell::new(HashMap::new()), -            measure_brush: RefCell::new(measure_brush), -        } +        renderer +            .render(&self.atlas, render_pass) +            .expect("Render text");      } -    pub fn queue(&mut self, section: wgpu_glyph::Section<'_>) { -        self.draw_brush.borrow_mut().queue(section); -    } +    pub fn end_frame(&mut self) { +        self.atlas.trim(); +        self.render_cache.trim(); -    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"); +        self.prepare_layer = 0;      }      pub fn measure(          &self,          content: &str,          size: f32, -        font: iced_native::Font, -        bounds: iced_native::Size, +        line_height: LineHeight, +        font: Font, +        bounds: Size, +        shaping: Shaping,      ) -> (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() -        }; +        let mut measurement_cache = self.measurement_cache.borrow_mut(); + +        let line_height = f32::from(line_height.to_absolute(Pixels(size))); + +        let (_, paragraph) = measurement_cache.allocate( +            &mut self.font_system.borrow_mut(), +            Key { +                content, +                size, +                line_height, +                font, +                bounds, +                shaping, +            }, +        ); -        if let Some(bounds) = -            self.measure_brush.borrow_mut().glyph_bounds(section) -        { -            (bounds.width().ceil(), bounds.height().ceil()) -        } else { -            (0.0, 0.0) -        } +        let (total_lines, max_width) = paragraph +            .layout_runs() +            .enumerate() +            .fold((0, 0.0), |(_, max), (i, buffer)| { +                (i + 1, buffer.line_w.max(max)) +            }); + +        (max_width, line_height * total_lines as f32)      }      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 measurement_cache = self.measurement_cache.borrow_mut(); + +        let line_height = f32::from(line_height.to_absolute(Pixels(size))); + +        let (_, paragraph) = measurement_cache.allocate( +            &mut self.font_system.borrow_mut(), +            Key { +                content, +                size, +                line_height, +                font, +                bounds, +                shaping,              },          ); -        // 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 = paragraph.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))); -                } -            } -        } +    pub fn trim_measurement_cache(&mut self) { +        self.measurement_cache.borrow_mut().trim(); +    } +} -        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) -            }); +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, +    } +} -        nearest.map(|(idx, center)| { -            Hit::NearestCharOffset(char_index(idx), point - center) -        }) +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,      } +} -    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_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,      } +} -    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; -                } +struct Cache { +    entries: FxHashMap<KeyHash, glyphon::Buffer>, +    recently_used: FxHashSet<KeyHash>, +    hasher: HashBuilder, +} -                let font = ab_glyph::FontArc::try_from_slice(bytes) -                    .expect("Load font"); +#[cfg(not(target_arch = "wasm32"))] +type HashBuilder = twox_hash::RandomXxHashBuilder64; -                let _ = self.measure_brush.borrow_mut().add_font(font.clone()); +#[cfg(target_arch = "wasm32")] +type HashBuilder = std::hash::BuildHasherDefault<twox_hash::XxHash64>; -                let font_id = self.draw_brush.borrow_mut().add_font(font); +impl Cache { +    fn new() -> Self { +        Self { +            entries: FxHashMap::default(), +            recently_used: FxHashSet::default(), +            hasher: HashBuilder::default(), +        } +    } -                let _ = self -                    .draw_font_map -                    .borrow_mut() -                    .insert(String::from(name), font_id); +    fn get(&self, key: &KeyHash) -> Option<&glyphon::Buffer> { +        self.entries.get(key) +    } -                font_id -            } +    fn allocate( +        &mut self, +        font_system: &mut glyphon::FontSystem, +        key: Key<'_>, +    ) -> (KeyHash, &mut glyphon::Buffer) { +        let hash = { +            let mut hasher = self.hasher.build_hasher(); + +            key.content.hash(&mut hasher); +            key.size.to_bits().hash(&mut hasher); +            key.line_height.to_bits().hash(&mut hasher); +            key.font.hash(&mut hasher); +            key.bounds.width.to_bits().hash(&mut hasher); +            key.bounds.height.to_bits().hash(&mut hasher); +            key.shaping.hash(&mut hasher); + +            hasher.finish() +        }; + +        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 _ = entry.insert(buffer);          } + +        let _ = self.recently_used.insert(hash); + +        (hash, self.entries.get_mut(&hash).unwrap()) +    } + +    fn trim(&mut self) { +        self.entries +            .retain(|key, _| self.recently_used.contains(key)); + +        self.recently_used.clear();      }  } + +#[derive(Debug, Clone, Copy)] +struct Key<'a> { +    content: &'a str, +    size: f32, +    line_height: f32, +    font: Font, +    bounds: Size, +    shaping: Shaping, +} + +type KeyHash = u64; diff --git a/wgpu/src/triangle.rs b/wgpu/src/triangle.rs index 162428f0..eb15a458 100644 --- a/wgpu/src/triangle.rs +++ b/wgpu/src/triangle.rs @@ -2,62 +2,68 @@  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}; + +#[cfg(not(target_arch = "wasm32"))] +use crate::core::Gradient; -use iced_graphics::layer::mesh::{self, Mesh}; -use iced_graphics::triangle::ColoredVertex2D; -use iced_graphics::Size;  #[cfg(feature = "tracing")]  use tracing::info_span;  #[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, + +    #[cfg(not(target_arch = "wasm32"))] +    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, +        #[cfg(not(target_arch = "wasm32"))] gradient: &gradient::Pipeline, +    ) -> Self { +        Self {              index_buffer: Buffer::new(                  device, -                "iced_wgpu::triangle vertex buffer", +                "iced_wgpu::triangle index buffer",                  wgpu::BufferUsages::INDEX | wgpu::BufferUsages::COPY_DST,              ),              index_strides: Vec::new(), -            solid: solid::Pipeline::new(device, format, antialiasing), +            solid: solid::Layer::new(device, &solid.constants_layout),              #[cfg(not(target_arch = "wasm32"))] -            gradient: gradient::Pipeline::new(device, format, antialiasing), +            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, +        #[cfg(not(target_arch = "wasm32"))] 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); @@ -75,6 +81,7 @@ impl Pipeline {              .resize(device, count.gradient_vertices);          // Prepare dynamic buffers & data store for writing +        self.index_buffer.clear();          self.index_strides.clear();          self.solid.vertices.clear();          self.solid.uniforms.clear(); @@ -99,13 +106,8 @@ impl Pipeline {              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 new_index_offset = +                self.index_buffer.write(queue, index_offset, indices);              index_offset += new_index_offset;              self.index_strides.push(indices.len() as u32); @@ -116,9 +118,7 @@ impl Pipeline {                      self.solid.uniforms.push(&solid::Uniforms::new(transform));                      let written_bytes = self.solid.vertices.write( -                        device, -                        staging_belt, -                        encoder, +                        queue,                          solid_vertex_offset,                          &buffers.vertices,                      ); @@ -130,9 +130,7 @@ impl Pipeline {                      buffers, gradient, ..                  } => {                      let written_bytes = self.gradient.vertices.write( -                        device, -                        staging_belt, -                        encoder, +                        queue,                          gradient_vertex_offset,                          &buffers.vertices,                      ); @@ -140,7 +138,7 @@ impl Pipeline {                      gradient_vertex_offset += written_bytes;                      match gradient { -                        iced_graphics::Gradient::Linear(linear) => { +                        Gradient::Linear(linear) => {                              use glam::{IVec4, Vec4};                              let start_offset = self.gradient.color_stop_offset; @@ -196,14 +194,14 @@ impl Pipeline {              let uniforms_resized = self.solid.uniforms.resize(device);              if uniforms_resized { -                self.solid.bind_group = solid::Pipeline::bind_group( +                self.solid.constants = solid::Layer::bind_group(                      device,                      self.solid.uniforms.raw(), -                    &self.solid.bind_group_layout, +                    &solid.constants_layout,                  )              } -            self.solid.uniforms.write(device, staging_belt, encoder); +            self.solid.uniforms.write(queue);          }          #[cfg(not(target_arch = "wasm32"))] @@ -218,22 +216,169 @@ impl Pipeline {              let storage_resized = self.gradient.storage.resize(device);              if uniforms_resized || storage_resized { -                self.gradient.bind_group = gradient::Pipeline::bind_group( +                self.gradient.constants = gradient::Layer::bind_group(                      device,                      self.gradient.uniforms.raw(),                      self.gradient.storage.raw(), -                    &self.gradient.bind_group_layout, +                    &gradient.constants_layout,                  );              }              // Write to GPU -            self.gradient.uniforms.write(device, staging_belt, encoder); -            self.gradient.storage.write(device, staging_belt, encoder); +            self.gradient.uniforms.write(queue); +            self.gradient.storage.write(queue);              // Cleanup              self.gradient.color_stop_offset = 0;              self.gradient.color_stops_pending_write.color_stops.clear();          } +    } + +    fn render<'a>( +        &'a self, +        solid: &'a solid::Pipeline, +        #[cfg(not(target_arch = "wasm32"))] gradient: &'a gradient::Pipeline, +        meshes: &[Mesh<'_>], +        scale_factor: f32, +        render_pass: &mut wgpu::RenderPass<'a>, +    ) { +        let mut num_solids = 0; +        #[cfg(not(target_arch = "wasm32"))] +        let mut num_gradients = 0; +        let mut last_is_solid = None; + +        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(&solid.pipeline); + +                        last_is_solid = Some(true); +                    } + +                    render_pass.set_bind_group( +                        0, +                        &self.solid.constants, +                        &[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(&gradient.pipeline); + +                        last_is_solid = Some(false); +                    } + +                    render_pass.set_bind_group( +                        0, +                        &self.gradient.constants, +                        &[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); +        } +    } +} + +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), + +            #[cfg(not(target_arch = "wasm32"))] +            gradient: gradient::Pipeline::new(device, format, antialiasing), + +            layers: Vec::new(), +            prepare_layer: 0, +        } +    } + +    pub fn prepare( +        &mut self, +        device: &wgpu::Device, +        queue: &wgpu::Queue, +        meshes: &[Mesh<'_>], +        transformation: Transformation, +    ) { +        #[cfg(feature = "tracing")] +        let _ = info_span!("Wgpu::Triangle", "PREPARE").entered(); + +        if self.layers.len() <= self.prepare_layer { +            self.layers.push(Layer::new( +                device, +                &self.solid, +                #[cfg(not(target_arch = "wasm32"))] +                &self.gradient, +            )); +        } + +        let layer = &mut self.layers[self.prepare_layer]; +        layer.prepare( +            device, +            queue, +            &self.solid, +            #[cfg(not(target_arch = "wasm32"))] +            &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 _ = info_span!("Wgpu::Triangle", "DRAW").entered();          // Configure render pass          { @@ -268,87 +413,26 @@ 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; - -            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); -                        } +            let layer = &mut self.layers[layer]; -                        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, +                #[cfg(not(target_arch = "wasm32"))] +                &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 +454,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), @@ -382,18 +466,71 @@ fn multisample_state(  mod solid {      use crate::buffer::dynamic;      use crate::buffer::r#static::Buffer; -    use crate::settings; +    use crate::graphics::primitive; +    use crate::graphics::{Antialiasing, Transformation};      use crate::triangle; +      use encase::ShaderType; -    use iced_graphics::Transformation;      #[derive(Debug)]      pub struct Pipeline {          pub pipeline: wgpu::RenderPipeline, -        pub vertices: Buffer<triangle::ColoredVertex2D>, +        pub constants_layout: wgpu::BindGroupLayout, +    } + +    #[derive(Debug)] +    pub struct Layer { +        pub vertices: Buffer<primitive::ColoredVertex2D>,          pub uniforms: dynamic::Buffer<Uniforms>, -        pub bind_group_layout: wgpu::BindGroupLayout, -        pub bind_group: wgpu::BindGroup, +        pub constants: wgpu::BindGroup, +    } + +    impl Layer { +        pub fn new( +            device: &wgpu::Device, +            constants_layout: &wgpu::BindGroupLayout, +        ) -> Self { +            let vertices = Buffer::new( +                device, +                "iced_wgpu::triangle::solid vertex buffer", +                wgpu::BufferUsages::VERTEX | wgpu::BufferUsages::COPY_DST, +            ); + +            let uniforms = dynamic::Buffer::uniform( +                device, +                "iced_wgpu::triangle::solid uniforms", +            ); + +            let constants = +                Self::bind_group(device, uniforms.raw(), constants_layout); + +            Self { +                vertices, +                uniforms, +                constants, +            } +        } + +        pub fn bind_group( +            device: &wgpu::Device, +            buffer: &wgpu::Buffer, +            layout: &wgpu::BindGroupLayout, +        ) -> wgpu::BindGroup { +            device.create_bind_group(&wgpu::BindGroupDescriptor { +                label: Some("iced_wgpu::triangle::solid bind group"), +                layout, +                entries: &[wgpu::BindGroupEntry { +                    binding: 0, +                    resource: wgpu::BindingResource::Buffer( +                        wgpu::BufferBinding { +                            buffer, +                            offset: 0, +                            size: Some(Uniforms::min_size()), +                        }, +                    ), +                }], +            }) +        }      }      #[derive(Debug, Clone, Copy, ShaderType)] @@ -414,20 +551,9 @@ mod solid {          pub fn new(              device: &wgpu::Device,              format: wgpu::TextureFormat, -            antialiasing: Option<settings::Antialiasing>, +            antialiasing: Option<Antialiasing>,          ) -> Self { -            let vertices = Buffer::new( -                device, -                "iced_wgpu::triangle::solid vertex buffer", -                wgpu::BufferUsages::VERTEX | wgpu::BufferUsages::COPY_DST, -            ); - -            let uniforms = dynamic::Buffer::uniform( -                device, -                "iced_wgpu::triangle::solid uniforms", -            ); - -            let bind_group_layout = device.create_bind_group_layout( +            let constants_layout = device.create_bind_group_layout(                  &wgpu::BindGroupLayoutDescriptor {                      label: Some("iced_wgpu::triangle::solid bind group layout"),                      entries: &[wgpu::BindGroupLayoutEntry { @@ -443,13 +569,10 @@ mod solid {                  },              ); -            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], +                    bind_group_layouts: &[&constants_layout],                      push_constant_ranges: &[],                  },              ); @@ -475,7 +598,7 @@ mod solid {                          entry_point: "vs_main",                          buffers: &[wgpu::VertexBufferLayout {                              array_stride: std::mem::size_of::< -                                triangle::ColoredVertex2D, +                                primitive::ColoredVertex2D,                              >()                                  as u64,                              step_mode: wgpu::VertexStepMode::Vertex, @@ -501,33 +624,9 @@ mod solid {              Self {                  pipeline, -                vertices, -                uniforms, -                bind_group_layout, -                bind_group, +                constants_layout,              }          } - -        pub fn bind_group( -            device: &wgpu::Device, -            buffer: &wgpu::Buffer, -            layout: &wgpu::BindGroupLayout, -        ) -> wgpu::BindGroup { -            device.create_bind_group(&wgpu::BindGroupDescriptor { -                label: Some("iced_wgpu::triangle::solid bind group"), -                layout, -                entries: &[wgpu::BindGroupEntry { -                    binding: 0, -                    resource: wgpu::BindingResource::Buffer( -                        wgpu::BufferBinding { -                            buffer, -                            offset: 0, -                            size: Some(Uniforms::min_size()), -                        }, -                    ), -                }], -            }) -        }      }  } @@ -535,25 +634,100 @@ mod solid {  mod gradient {      use crate::buffer::dynamic;      use crate::buffer::r#static::Buffer; -    use crate::settings; +    use crate::graphics::Antialiasing;      use crate::triangle;      use encase::ShaderType;      use glam::{IVec4, Vec4}; -    use iced_graphics::triangle::Vertex2D; +    use iced_graphics::primitive;      #[derive(Debug)]      pub struct Pipeline {          pub pipeline: wgpu::RenderPipeline, -        pub vertices: Buffer<Vertex2D>, +        pub constants_layout: wgpu::BindGroupLayout, +    } + +    #[derive(Debug)] +    pub struct Layer { +        pub vertices: Buffer<primitive::Vertex2D>,          pub uniforms: dynamic::Buffer<Uniforms>,          pub storage: dynamic::Buffer<Storage>, +        pub constants: wgpu::BindGroup,          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, +    } + +    impl Layer { +        pub fn new( +            device: &wgpu::Device, +            constants_layout: &wgpu::BindGroupLayout, +        ) -> Self { +            let vertices = Buffer::new( +                device, +                "iced_wgpu::triangle::gradient vertex buffer", +                wgpu::BufferUsages::VERTEX | wgpu::BufferUsages::COPY_DST, +            ); + +            let uniforms = dynamic::Buffer::uniform( +                device, +                "iced_wgpu::triangle::gradient uniforms", +            ); + +            // Note: with a WASM target storage buffers are not supported. Will need to use UBOs & static +            // sized array (eg like the 32-sized array on OpenGL side right now) to make gradients work +            let storage = dynamic::Buffer::storage( +                device, +                "iced_wgpu::triangle::gradient storage", +            ); + +            let constants = Self::bind_group( +                device, +                uniforms.raw(), +                storage.raw(), +                constants_layout, +            ); + +            Self { +                vertices, +                uniforms, +                storage, +                constants, +                color_stop_offset: 0, +                color_stops_pending_write: Storage { +                    color_stops: vec![], +                }, +            } +        } + +        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(), +                    }, +                ], +            }) +        }      }      #[derive(Debug, ShaderType)] @@ -582,27 +756,9 @@ mod gradient {          pub(super) fn new(              device: &wgpu::Device,              format: wgpu::TextureFormat, -            antialiasing: Option<settings::Antialiasing>, +            antialiasing: Option<Antialiasing>,          ) -> Self { -            let vertices = Buffer::new( -                device, -                "iced_wgpu::triangle::gradient vertex buffer", -                wgpu::BufferUsages::VERTEX | wgpu::BufferUsages::COPY_DST, -            ); - -            let uniforms = dynamic::Buffer::uniform( -                device, -                "iced_wgpu::triangle::gradient uniforms", -            ); - -            //Note: with a WASM target storage buffers are not supported. Will need to use UBOs & static -            // sized array (eg like the 32-sized array on OpenGL side right now) to make gradients work -            let storage = dynamic::Buffer::storage( -                device, -                "iced_wgpu::triangle::gradient storage", -            ); - -            let bind_group_layout = device.create_bind_group_layout( +            let constants_layout = device.create_bind_group_layout(                  &wgpu::BindGroupLayoutDescriptor {                      label: Some(                          "iced_wgpu::triangle::gradient bind group layout", @@ -634,19 +790,12 @@ mod gradient {                  },              ); -            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], +                    bind_group_layouts: &[&constants_layout],                      push_constant_ranges: &[],                  },              ); @@ -654,7 +803,7 @@ mod gradient {              let shader =                  device.create_shader_module(wgpu::ShaderModuleDescriptor {                      label: Some( -                        "iced_wgpu triangle gradient create shader module", +                        "iced_wgpu::triangle::gradient create shader module",                      ),                      source: wgpu::ShaderSource::Wgsl(                          std::borrow::Cow::Borrowed(include_str!( @@ -663,75 +812,43 @@ mod gradient {                      ),                  }); -            let pipeline = device.create_render_pipeline( -                &wgpu::RenderPipelineDescriptor { -                    label: Some("iced_wgpu::triangle::gradient pipeline"), -                    layout: Some(&layout), -                    vertex: wgpu::VertexState { -                        module: &shader, -                        entry_point: "vs_main", -                        buffers: &[wgpu::VertexBufferLayout { -                            array_stride: std::mem::size_of::<Vertex2D>() -                                as u64, -                            step_mode: wgpu::VertexStepMode::Vertex, -                            attributes: &wgpu::vertex_attr_array!( -                                // Position -                                0 => Float32x2, -                            ), -                        }], +            let pipeline = +                device.create_render_pipeline( +                    &wgpu::RenderPipelineDescriptor { +                        label: Some("iced_wgpu::triangle::gradient pipeline"), +                        layout: Some(&layout), +                        vertex: wgpu::VertexState { +                            module: &shader, +                            entry_point: "vs_main", +                            buffers: &[wgpu::VertexBufferLayout { +                                array_stride: std::mem::size_of::< +                                    primitive::Vertex2D, +                                >( +                                ) +                                    as u64, +                                step_mode: wgpu::VertexStepMode::Vertex, +                                attributes: &wgpu::vertex_attr_array!( +                                    // Position +                                    0 => Float32x2, +                                ), +                            }], +                        }, +                        fragment: Some(wgpu::FragmentState { +                            module: &shader, +                            entry_point: "fs_main", +                            targets: &[triangle::fragment_target(format)], +                        }), +                        primitive: triangle::primitive_state(), +                        depth_stencil: None, +                        multisample: triangle::multisample_state(antialiasing), +                        multiview: None,                      }, -                    fragment: Some(wgpu::FragmentState { -                        module: &shader, -                        entry_point: "fs_main", -                        targets: &[triangle::fragment_target(format)], -                    }), -                    primitive: triangle::primitive_state(), -                    depth_stencil: None, -                    multisample: triangle::multisample_state(antialiasing), -                    multiview: None, -                }, -            ); +                );              Self {                  pipeline, -                vertices, -                uniforms, -                storage, -                color_stop_offset: 0, -                color_stops_pending_write: Storage { -                    color_stops: vec![], -                }, -                bind_group_layout, -                bind_group, +                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 d24f8e1a..4afbdb32 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,7 +14,7 @@ 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, @@ -222,8 +222,8 @@ impl Targets {              sample_count,              dimension: wgpu::TextureDimension::D2,              format, -            view_formats: &[],              usage: wgpu::TextureUsages::RENDER_ATTACHMENT, +            view_formats: &[],          });          let resolve = device.create_texture(&wgpu::TextureDescriptor { @@ -233,9 +233,9 @@ impl Targets {              sample_count: 1,              dimension: wgpu::TextureDimension::D2,              format, -            view_formats: &[],              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 53af19bf..500458e8 100644 --- a/wgpu/src/window/compositor.rs +++ b/wgpu/src/window/compositor.rs @@ -1,9 +1,12 @@ -use crate::{Backend, Color, Error, Renderer, Settings, Viewport}; +//! Connect a window with a renderer. +use crate::core::Color; +use crate::graphics; +use crate::graphics::compositor; +use crate::graphics::{Error, Primitive, Viewport}; +use crate::{Backend, 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 +19,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. @@ -53,11 +53,12 @@ impl<Theme> Compositor<Theme> {          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,              }) @@ -112,15 +113,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,          }) @@ -128,11 +126,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) +    } +} + +/// 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> iced_graphics::window::Compositor for Compositor<Theme> { +impl<Theme> graphics::Compositor for Compositor<Theme> {      type Settings = Settings;      type Renderer = Renderer<Theme>;      type Surface = wgpu::Surface; @@ -141,13 +210,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)))      } @@ -155,13 +218,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) -                .expect("Create surface") -        } +        let mut surface = unsafe { self.instance.create_surface(window) } +            .expect("Create surface"); + +        self.configure_surface(&mut surface, width, height); + +        surface      }      fn configure_surface( @@ -201,80 +267,16 @@ 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"), -                    }, -                ); - -                let view = &frame -                    .texture -                    .create_view(&wgpu::TextureViewDescriptor::default()); - -                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) -                } -            }, -        } +        renderer.with_primitives(|backend, primitives| { +            present( +                self, +                backend, +                surface, +                primitives, +                viewport, +                background_color, +                overlay, +            ) +        })      }  } | 
