diff options
Diffstat (limited to 'wgpu')
33 files changed, 3149 insertions, 2307 deletions
diff --git a/wgpu/Cargo.toml b/wgpu/Cargo.toml index 4a0d89f0..30545fa2 100644 --- a/wgpu/Cargo.toml +++ b/wgpu/Cargo.toml @@ -10,6 +10,9 @@ homepage.workspace = true  categories.workspace = true  keywords.workspace = true +[lints] +workspace = true +  [package.metadata.docs.rs]  rustdoc-args = ["--cfg", "docsrs"]  all-features = true @@ -32,6 +35,8 @@ glyphon.workspace = true  guillotiere.workspace = true  log.workspace = true  once_cell.workspace = true +rustc-hash.workspace = true +thiserror.workspace = true  wgpu.workspace = true  lyon.workspace = true @@ -39,6 +44,3 @@ lyon.optional = true  resvg.workspace = true  resvg.optional = true - -tracing.workspace = true -tracing.optional = true diff --git a/wgpu/src/backend.rs b/wgpu/src/backend.rs deleted file mode 100644 index 09ddbe4d..00000000 --- a/wgpu/src/backend.rs +++ /dev/null @@ -1,399 +0,0 @@ -use crate::core::{Color, Size, Transformation}; -use crate::graphics::backend; -use crate::graphics::color; -use crate::graphics::Viewport; -use crate::primitive::pipeline; -use crate::primitive::{self, Primitive}; -use crate::quad; -use crate::text; -use crate::triangle; -use crate::{Layer, Settings}; - -#[cfg(feature = "tracing")] -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 -#[allow(missing_debug_implementations)] -pub struct Backend { -    quad_pipeline: quad::Pipeline, -    text_pipeline: text::Pipeline, -    triangle_pipeline: triangle::Pipeline, -    pipeline_storage: pipeline::Storage, -    #[cfg(any(feature = "image", feature = "svg"))] -    image_pipeline: image::Pipeline, -} - -impl Backend { -    /// Creates a new [`Backend`]. -    pub fn new( -        _adapter: &wgpu::Adapter, -        device: &wgpu::Device, -        queue: &wgpu::Queue, -        settings: Settings, -        format: wgpu::TextureFormat, -    ) -> Self { -        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); - -        #[cfg(any(feature = "image", feature = "svg"))] -        let image_pipeline = { -            let backend = _adapter.get_info().backend; - -            image::Pipeline::new(device, format, backend) -        }; - -        Self { -            quad_pipeline, -            text_pipeline, -            triangle_pipeline, -            pipeline_storage: pipeline::Storage::default(), - -            #[cfg(any(feature = "image", feature = "svg"))] -            image_pipeline, -        } -    } - -    /// Draws the provided primitives in the given `TextureView`. -    /// -    /// The text provided as overlay will be rendered on top of the primitives. -    /// This is useful for rendering debug information. -    pub fn present<T: AsRef<str>>( -        &mut self, -        device: &wgpu::Device, -        queue: &wgpu::Queue, -        encoder: &mut wgpu::CommandEncoder, -        clear_color: Option<Color>, -        format: wgpu::TextureFormat, -        frame: &wgpu::TextureView, -        primitives: &[Primitive], -        viewport: &Viewport, -        overlay_text: &[T], -    ) { -        log::debug!("Drawing"); -        #[cfg(feature = "tracing")] -        let _ = info_span!("Wgpu::Backend", "PRESENT").entered(); - -        let target_size = viewport.physical_size(); -        let scale_factor = viewport.scale_factor() as f32; -        let transformation = viewport.projection(); - -        let mut layers = Layer::generate(primitives, viewport); - -        if !overlay_text.is_empty() { -            layers.push(Layer::overlay(overlay_text, viewport)); -        } - -        self.prepare( -            device, -            queue, -            format, -            encoder, -            scale_factor, -            target_size, -            transformation, -            &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( -        &mut self, -        device: &wgpu::Device, -        queue: &wgpu::Queue, -        format: wgpu::TextureFormat, -        _encoder: &mut wgpu::CommandEncoder, -        scale_factor: f32, -        target_size: Size<u32>, -        transformation: Transformation, -        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); - -                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); - -                    self.image_pipeline.prepare( -                        device, -                        queue, -                        _encoder, -                        &layer.images, -                        scaled, -                        scale_factor, -                    ); -                } -            } - -            if !layer.text.is_empty() { -                self.text_pipeline.prepare( -                    device, -                    queue, -                    &layer.text, -                    layer.bounds, -                    scale_factor, -                    target_size, -                ); -            } - -            if !layer.pipelines.is_empty() { -                for pipeline in &layer.pipelines { -                    pipeline.primitive.prepare( -                        format, -                        device, -                        queue, -                        pipeline.bounds, -                        target_size, -                        scale_factor, -                        &mut self.pipeline_storage, -                    ); -                } -            } -        } -    } - -    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<'_>], -    ) { -        use std::mem::ManuallyDrop; - -        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 render pass"), -                color_attachments: &[Some(wgpu::RenderPassColorAttachment { -                    view: target, -                    resolve_target: None, -                    ops: wgpu::Operations { -                        load: match clear_color { -                            Some(background_color) => wgpu::LoadOp::Clear({ -                                let [r, g, b, a] = -                                    color::pack(background_color).components(); - -                                wgpu::Color { -                                    r: f64::from(r), -                                    g: f64::from(g), -                                    b: f64::from(b), -                                    a: f64::from(a), -                                } -                            }), -                            None => wgpu::LoadOp::Load, -                        }, -                        store: wgpu::StoreOp::Store, -                    }, -                })], -                depth_stencil_attachment: None, -                timestamp_writes: None, -                occlusion_query_set: None, -            }, -        )); - -        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.render( -                    quad_layer, -                    bounds, -                    &layer.quads, -                    &mut render_pass, -                ); - -                quad_layer += 1; -            } - -            if !layer.meshes.is_empty() { -                let _ = ManuallyDrop::into_inner(render_pass); - -                self.triangle_pipeline.render( -                    device, -                    encoder, -                    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 render pass"), -                        color_attachments: &[Some( -                            wgpu::RenderPassColorAttachment { -                                view: target, -                                resolve_target: None, -                                ops: wgpu::Operations { -                                    load: wgpu::LoadOp::Load, -                                    store: wgpu::StoreOp::Store, -                                }, -                            }, -                        )], -                        depth_stencil_attachment: None, -                        timestamp_writes: None, -                        occlusion_query_set: None, -                    }, -                )); -            } - -            #[cfg(any(feature = "image", feature = "svg"))] -            { -                if !layer.images.is_empty() { -                    self.image_pipeline.render( -                        image_layer, -                        bounds, -                        &mut render_pass, -                    ); - -                    image_layer += 1; -                } -            } - -            if !layer.text.is_empty() { -                self.text_pipeline -                    .render(text_layer, bounds, &mut render_pass); - -                text_layer += 1; -            } - -            if !layer.pipelines.is_empty() { -                let _ = ManuallyDrop::into_inner(render_pass); - -                for pipeline in &layer.pipelines { -                    let viewport = (pipeline.viewport * scale_factor).snap(); - -                    if viewport.width < 1 || viewport.height < 1 { -                        continue; -                    } - -                    pipeline.primitive.render( -                        &self.pipeline_storage, -                        target, -                        target_size, -                        viewport, -                        encoder, -                    ); -                } - -                render_pass = ManuallyDrop::new(encoder.begin_render_pass( -                    &wgpu::RenderPassDescriptor { -                        label: Some("iced_wgpu render pass"), -                        color_attachments: &[Some( -                            wgpu::RenderPassColorAttachment { -                                view: target, -                                resolve_target: None, -                                ops: wgpu::Operations { -                                    load: wgpu::LoadOp::Load, -                                    store: wgpu::StoreOp::Store, -                                }, -                            }, -                        )], -                        depth_stencil_attachment: None, -                        timestamp_writes: None, -                        occlusion_query_set: None, -                    }, -                )); -            } -        } - -        let _ = ManuallyDrop::into_inner(render_pass); -    } -} - -impl crate::graphics::Backend for Backend { -    type Primitive = primitive::Custom; -} - -impl backend::Text for Backend { -    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: &crate::core::image::Handle) -> Size<u32> { -        self.image_pipeline.dimensions(handle) -    } -} - -#[cfg(feature = "svg")] -impl backend::Svg for Backend { -    fn viewport_dimensions( -        &self, -        handle: &crate::core::svg::Handle, -    ) -> Size<u32> { -        self.image_pipeline.viewport_dimensions(handle) -    } -} diff --git a/wgpu/src/buffer.rs b/wgpu/src/buffer.rs index ef00c58f..463ea24a 100644 --- a/wgpu/src/buffer.rs +++ b/wgpu/src/buffer.rs @@ -1,6 +1,13 @@  use std::marker::PhantomData; +use std::num::NonZeroU64;  use std::ops::RangeBounds; +pub const MAX_WRITE_SIZE: usize = 100 * 1024; + +#[allow(unsafe_code)] +const MAX_WRITE_SIZE_U64: NonZeroU64 = +    unsafe { NonZeroU64::new_unchecked(MAX_WRITE_SIZE as u64) }; +  #[derive(Debug)]  pub struct Buffer<T> {      label: &'static str, @@ -61,12 +68,46 @@ impl<T: bytemuck::Pod> Buffer<T> {      /// Returns the size of the written bytes.      pub fn write(          &mut self, -        queue: &wgpu::Queue, +        device: &wgpu::Device, +        encoder: &mut wgpu::CommandEncoder, +        belt: &mut wgpu::util::StagingBelt,          offset: usize,          contents: &[T],      ) -> usize {          let bytes: &[u8] = bytemuck::cast_slice(contents); -        queue.write_buffer(&self.raw, offset as u64, bytes); +        let mut bytes_written = 0; + +        // Split write into multiple chunks if necessary +        while bytes_written + MAX_WRITE_SIZE < bytes.len() { +            belt.write_buffer( +                encoder, +                &self.raw, +                (offset + bytes_written) as u64, +                MAX_WRITE_SIZE_U64, +                device, +            ) +            .copy_from_slice( +                &bytes[bytes_written..bytes_written + MAX_WRITE_SIZE], +            ); + +            bytes_written += MAX_WRITE_SIZE; +        } + +        // There will always be some bytes left, since the previous +        // loop guarantees `bytes_written < bytes.len()` +        let bytes_left = ((bytes.len() - bytes_written) as u64) +            .try_into() +            .expect("non-empty write"); + +        // Write them +        belt.write_buffer( +            encoder, +            &self.raw, +            (offset + bytes_written) as u64, +            bytes_left, +            device, +        ) +        .copy_from_slice(&bytes[bytes_written..]);          self.offsets.push(offset as u64); diff --git a/wgpu/src/color.rs b/wgpu/src/color.rs index 890f3f89..9d593d9c 100644 --- a/wgpu/src/color.rs +++ b/wgpu/src/color.rs @@ -1,5 +1,7 @@  use std::borrow::Cow; +use wgpu::util::DeviceExt; +  pub fn convert(      device: &wgpu::Device,      encoder: &mut wgpu::CommandEncoder, @@ -15,28 +17,58 @@ pub fn convert(          ..wgpu::SamplerDescriptor::default()      }); -    //sampler in 0 -    let sampler_layout = +    #[derive(Debug, Clone, Copy, bytemuck::Zeroable, bytemuck::Pod)] +    #[repr(C)] +    struct Ratio { +        u: f32, +        v: f32, +    } + +    let ratio = device.create_buffer_init(&wgpu::util::BufferInitDescriptor { +        label: Some("iced-wgpu::triangle::msaa ratio"), +        contents: bytemuck::bytes_of(&Ratio { u: 1.0, v: 1.0 }), +        usage: wgpu::BufferUsages::COPY_DST | wgpu::BufferUsages::UNIFORM, +    }); + +    let constant_layout =          device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {              label: Some("iced_wgpu.offscreen.blit.sampler_layout"), -            entries: &[wgpu::BindGroupLayoutEntry { -                binding: 0, -                visibility: wgpu::ShaderStages::FRAGMENT, -                ty: wgpu::BindingType::Sampler( -                    wgpu::SamplerBindingType::NonFiltering, -                ), -                count: None, -            }], +            entries: &[ +                wgpu::BindGroupLayoutEntry { +                    binding: 0, +                    visibility: wgpu::ShaderStages::FRAGMENT, +                    ty: wgpu::BindingType::Sampler( +                        wgpu::SamplerBindingType::NonFiltering, +                    ), +                    count: None, +                }, +                wgpu::BindGroupLayoutEntry { +                    binding: 1, +                    visibility: wgpu::ShaderStages::VERTEX, +                    ty: wgpu::BindingType::Buffer { +                        ty: wgpu::BufferBindingType::Uniform, +                        has_dynamic_offset: false, +                        min_binding_size: None, +                    }, +                    count: None, +                }, +            ],          }); -    let sampler_bind_group = +    let constant_bind_group =          device.create_bind_group(&wgpu::BindGroupDescriptor {              label: Some("iced_wgpu.offscreen.sampler.bind_group"), -            layout: &sampler_layout, -            entries: &[wgpu::BindGroupEntry { -                binding: 0, -                resource: wgpu::BindingResource::Sampler(&sampler), -            }], +            layout: &constant_layout, +            entries: &[ +                wgpu::BindGroupEntry { +                    binding: 0, +                    resource: wgpu::BindingResource::Sampler(&sampler), +                }, +                wgpu::BindGroupEntry { +                    binding: 1, +                    resource: ratio.as_entire_binding(), +                }, +            ],          });      let texture_layout = @@ -59,7 +91,7 @@ pub fn convert(      let pipeline_layout =          device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {              label: Some("iced_wgpu.offscreen.blit.pipeline_layout"), -            bind_group_layouts: &[&sampler_layout, &texture_layout], +            bind_group_layouts: &[&constant_layout, &texture_layout],              push_constant_ranges: &[],          }); @@ -152,7 +184,7 @@ pub fn convert(      });      pass.set_pipeline(&pipeline); -    pass.set_bind_group(0, &sampler_bind_group, &[]); +    pass.set_bind_group(0, &constant_bind_group, &[]);      pass.set_bind_group(1, &texture_bind_group, &[]);      pass.draw(0..6, 0..1); diff --git a/wgpu/src/engine.rs b/wgpu/src/engine.rs new file mode 100644 index 00000000..782fd58c --- /dev/null +++ b/wgpu/src/engine.rs @@ -0,0 +1,87 @@ +use crate::buffer; +use crate::graphics::Antialiasing; +use crate::primitive; +use crate::quad; +use crate::text; +use crate::triangle; + +#[allow(missing_debug_implementations)] +pub struct Engine { +    pub(crate) staging_belt: wgpu::util::StagingBelt, +    pub(crate) format: wgpu::TextureFormat, + +    pub(crate) quad_pipeline: quad::Pipeline, +    pub(crate) text_pipeline: text::Pipeline, +    pub(crate) triangle_pipeline: triangle::Pipeline, +    #[cfg(any(feature = "image", feature = "svg"))] +    pub(crate) image_pipeline: crate::image::Pipeline, +    pub(crate) primitive_storage: primitive::Storage, +} + +impl Engine { +    pub fn new( +        _adapter: &wgpu::Adapter, +        device: &wgpu::Device, +        queue: &wgpu::Queue, +        format: wgpu::TextureFormat, +        antialiasing: Option<Antialiasing>, // TODO: Initialize AA pipelines lazily +    ) -> Self { +        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, antialiasing); + +        #[cfg(any(feature = "image", feature = "svg"))] +        let image_pipeline = { +            let backend = _adapter.get_info().backend; + +            crate::image::Pipeline::new(device, format, backend) +        }; + +        Self { +            // TODO: Resize belt smartly (?) +            // It would be great if the `StagingBelt` API exposed methods +            // for introspection to detect when a resize may be worth it. +            staging_belt: wgpu::util::StagingBelt::new( +                buffer::MAX_WRITE_SIZE as u64, +            ), +            format, + +            quad_pipeline, +            text_pipeline, +            triangle_pipeline, + +            #[cfg(any(feature = "image", feature = "svg"))] +            image_pipeline, + +            primitive_storage: primitive::Storage::default(), +        } +    } + +    #[cfg(any(feature = "image", feature = "svg"))] +    pub fn create_image_cache( +        &self, +        device: &wgpu::Device, +    ) -> crate::image::Cache { +        self.image_pipeline.create_cache(device) +    } + +    pub fn submit( +        &mut self, +        queue: &wgpu::Queue, +        encoder: wgpu::CommandEncoder, +    ) -> wgpu::SubmissionIndex { +        self.staging_belt.finish(); +        let index = queue.submit(Some(encoder.finish())); +        self.staging_belt.recall(); + +        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(); + +        index +    } +} diff --git a/wgpu/src/geometry.rs b/wgpu/src/geometry.rs index f4e0fbda..f6213e1d 100644 --- a/wgpu/src/geometry.rs +++ b/wgpu/src/geometry.rs @@ -3,215 +3,136 @@ use crate::core::text::LineHeight;  use crate::core::{      Pixels, Point, Radians, Rectangle, Size, Transformation, Vector,  }; +use crate::graphics::cache::{self, Cached};  use crate::graphics::color;  use crate::graphics::geometry::fill::{self, Fill};  use crate::graphics::geometry::{ -    LineCap, LineDash, LineJoin, Path, Stroke, Style, Text, +    self, LineCap, LineDash, LineJoin, Path, Stroke, Style,  };  use crate::graphics::gradient::{self, Gradient};  use crate::graphics::mesh::{self, Mesh}; -use crate::primitive::{self, Primitive}; +use crate::graphics::{self, Text}; +use crate::text; +use crate::triangle;  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, +#[derive(Debug)] +pub enum Geometry { +    Live { meshes: Vec<Mesh>, text: Vec<Text> }, +    Cached(Cache),  } -enum Buffer { -    Solid(tessellation::VertexBuffers<mesh::SolidVertex2D, u32>), -    Gradient(tessellation::VertexBuffers<mesh::GradientVertex2D, u32>), +#[derive(Debug, Clone)] +pub struct Cache { +    pub meshes: Option<triangle::Cache>, +    pub text: Option<text::Cache>,  } -struct BufferStack { -    stack: Vec<Buffer>, -} +impl Cached for Geometry { +    type Cache = Cache; -impl BufferStack { -    fn new() -> Self { -        Self { stack: Vec::new() } +    fn load(cache: &Self::Cache) -> Self { +        Geometry::Cached(cache.clone())      } -    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(_) => match self.stack.last() { -                Some(Buffer::Gradient(_)) => {} -                _ => { -                    self.stack.push(Buffer::Gradient( -                        tessellation::VertexBuffers::new(), -                    )); -                } -            }, -        } - -        self.stack.last_mut().unwrap() -    } +    fn cache( +        self, +        group: cache::Group, +        previous: Option<Self::Cache>, +    ) -> Self::Cache { +        match self { +            Self::Live { meshes, text } => { +                if let Some(mut previous) = previous { +                    if let Some(cache) = &mut previous.meshes { +                        cache.update(meshes); +                    } else { +                        previous.meshes = triangle::Cache::new(meshes); +                    } -    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::pack(*color)), -                )) -            } -            (Style::Gradient(gradient), Buffer::Gradient(buffer)) => { -                Box::new(tessellation::BuffersBuilder::new( -                    buffer, -                    GradientVertex2DBuilder { -                        gradient: gradient.pack(), -                    }, -                )) -            } -            _ => unreachable!(), -        } -    } +                    if let Some(cache) = &mut previous.text { +                        cache.update(text); +                    } else { +                        previous.text = text::Cache::new(group, text); +                    } -    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::pack(*color)), -                )) -            } -            (Style::Gradient(gradient), Buffer::Gradient(buffer)) => { -                Box::new(tessellation::BuffersBuilder::new( -                    buffer, -                    GradientVertex2DBuilder { -                        gradient: gradient.pack(), -                    }, -                )) +                    previous +                } else { +                    Cache { +                        meshes: triangle::Cache::new(meshes), +                        text: text::Cache::new(group, text), +                    } +                }              } -            _ => unreachable!(), +            Self::Cached(cache) => cache,          }      }  } -#[derive(Debug)] -struct Transforms { -    previous: Vec<Transform>, -    current: Transform, -} - -#[derive(Debug, Clone, Copy)] -struct Transform(lyon::math::Transform); - -impl Transform { -    fn is_identity(&self) -> bool { -        self.0 == lyon::math::Transform::identity() -    } - -    fn is_scale_translation(&self) -> bool { -        self.0.m12.abs() < 2.0 * f32::EPSILON -            && self.0.m21.abs() < 2.0 * f32::EPSILON -    } - -    fn scale(&self) -> (f32, f32) { -        (self.0.m11, self.0.m22) -    } - -    fn transform_point(&self, point: Point) -> Point { -        let transformed = self -            .0 -            .transform_point(euclid::Point2D::new(point.x, point.y)); - -        Point { -            x: transformed.x, -            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 { -        match &mut gradient { -            Gradient::Linear(linear) => { -                linear.start = self.transform_point(linear.start); -                linear.end = self.transform_point(linear.end); -            } -        } - -        gradient -    } +/// A frame for drawing some geometry. +#[allow(missing_debug_implementations)] +pub struct Frame { +    clip_bounds: Rectangle, +    buffers: BufferStack, +    meshes: Vec<Mesh>, +    text: Vec<Text>, +    transforms: Transforms, +    fill_tessellator: tessellation::FillTessellator, +    stroke_tessellator: tessellation::StrokeTessellator,  }  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. +    /// Creates a new [`Frame`] with the given [`Size`].      pub fn new(size: Size) -> Frame { +        Self::with_clip(Rectangle::with_size(size)) +    } + +    /// Creates a new [`Frame`] with the given clip bounds. +    pub fn with_clip(bounds: Rectangle) -> Frame {          Frame { -            size, +            clip_bounds: bounds,              buffers: BufferStack::new(), -            primitives: Vec::new(), +            meshes: Vec::new(), +            text: Vec::new(),              transforms: Transforms {                  previous: Vec::new(), -                current: Transform(lyon::math::Transform::identity()), +                current: Transform(lyon::math::Transform::translation( +                    bounds.x, bounds.y, +                )),              },              fill_tessellator: tessellation::FillTessellator::new(),              stroke_tessellator: tessellation::StrokeTessellator::new(),          }      } +} + +impl geometry::frame::Backend for Frame { +    type Geometry = Geometry; -    /// Returns the width of the [`Frame`].      #[inline] -    pub fn width(&self) -> f32 { -        self.size.width +    fn width(&self) -> f32 { +        self.clip_bounds.width      } -    /// Returns the height of the [`Frame`].      #[inline] -    pub fn height(&self) -> f32 { -        self.size.height +    fn height(&self) -> f32 { +        self.clip_bounds.height      } -    /// Returns the dimensions of the [`Frame`].      #[inline] -    pub fn size(&self) -> Size { -        self.size +    fn size(&self) -> Size { +        self.clip_bounds.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) +    fn center(&self) -> Point { +        Point::new(self.clip_bounds.width / 2.0, self.clip_bounds.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>) { +    fn fill(&mut self, path: &Path, fill: impl Into<Fill>) {          let Fill { style, rule } = fill.into();          let mut buffer = self @@ -239,9 +160,7 @@ impl Frame {          .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( +    fn fill_rectangle(          &mut self,          top_left: Point,          size: Size, @@ -276,9 +195,7 @@ impl Frame {              .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>>) { +    fn stroke<'a>(&mut self, path: &Path, stroke: impl Into<Stroke<'a>>) {          let stroke = stroke.into();          let mut buffer = self @@ -315,20 +232,7 @@ impl Frame {          .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. -    pub fn fill_text(&mut self, text: impl Into<Text>) { +    fn fill_text(&mut self, text: impl Into<geometry::Text>) {          let text = text.into();          let (scale_x, scale_y) = self.transforms.current.scale(); @@ -366,105 +270,25 @@ impl Frame {                  height: f32::INFINITY,              }; -            // TODO: Honor layering! -            self.primitives.push(Primitive::Text { +            self.text.push(graphics::Text::Cached {                  content: text.content,                  bounds,                  color: text.color,                  size, -                line_height, +                line_height: line_height.to_absolute(size),                  font: text.font,                  horizontal_alignment: text.horizontal_alignment,                  vertical_alignment: text.vertical_alignment,                  shaping: text.shaping, -                clip_bounds: Rectangle::with_size(Size::INFINITY), +                clip_bounds: self.clip_bounds,              });          } else {              text.draw_with(|path, color| self.fill(&path, color));          }      } -    /// 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<R>(&mut self, f: impl FnOnce(&mut Frame) -> R) -> R { -        self.push_transform(); - -        let result = f(self); - -        self.pop_transform(); - -        result -    } - -    /// 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<R>( -        &mut self, -        region: Rectangle, -        f: impl FnOnce(&mut Frame) -> R, -    ) -> R { -        let mut frame = Frame::new(region.size()); - -        let result = f(&mut frame); - -        let origin = Point::new(region.x, region.y); - -        self.clip(frame, origin); - -        result -    } - -    /// 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 transformation = Transformation::translate(at.x, at.y); - -        let (text, meshes) = primitives -            .into_iter() -            .partition(|primitive| matches!(primitive, Primitive::Text { .. })); - -        self.primitives.push(Primitive::Group { -            primitives: vec![ -                Primitive::Transform { -                    transformation, -                    content: Box::new(Primitive::Group { primitives: meshes }), -                }, -                Primitive::Transform { -                    transformation, -                    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) { +    fn translate(&mut self, translation: Vector) {          self.transforms.current.0 =              self.transforms                  .current @@ -475,9 +299,8 @@ impl Frame {                  ));      } -    /// Applies a rotation in radians to the current transform of the [`Frame`].      #[inline] -    pub fn rotate(&mut self, angle: impl Into<Radians>) { +    fn rotate(&mut self, angle: impl Into<Radians>) {          self.transforms.current.0 = self              .transforms              .current @@ -485,66 +308,217 @@ impl Frame {              .pre_rotate(lyon::math::Angle::radians(angle.into().0));      } -    /// Applies a uniform scaling to the current transform of the [`Frame`].      #[inline] -    pub fn scale(&mut self, scale: impl Into<f32>) { +    fn scale(&mut self, scale: impl Into<f32>) {          let scale = scale.into();          self.scale_nonuniform(Vector { x: scale, y: scale });      } -    /// Applies a non-uniform scaling to the current transform of the [`Frame`].      #[inline] -    pub fn scale_nonuniform(&mut self, scale: impl Into<Vector>) { +    fn scale_nonuniform(&mut self, scale: impl Into<Vector>) {          let scale = scale.into();          self.transforms.current.0 =              self.transforms.current.0.pre_scale(scale.x, scale.y);      } -    /// Produces the [`Primitive`] representing everything drawn on the [`Frame`]. -    pub fn into_primitive(self) -> Primitive { -        Primitive::Group { -            primitives: self.into_primitives(), +    fn push_transform(&mut self) { +        self.transforms.previous.push(self.transforms.current); +    } + +    fn pop_transform(&mut self) { +        self.transforms.current = self.transforms.previous.pop().unwrap(); +    } + +    fn draft(&mut self, clip_bounds: Rectangle) -> Frame { +        Frame::with_clip(clip_bounds) +    } + +    fn paste(&mut self, frame: Frame, _at: Point) { +        self.meshes +            .extend(frame.buffers.into_meshes(frame.clip_bounds)); + +        self.text.extend(frame.text); +    } + +    fn into_geometry(mut self) -> Self::Geometry { +        self.meshes +            .extend(self.buffers.into_meshes(self.clip_bounds)); + +        Geometry::Live { +            meshes: self.meshes, +            text: self.text,          }      } +} -    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::Custom( -                            primitive::Custom::Mesh(Mesh::Solid { -                                buffers: mesh::Indexed { -                                    vertices: buffer.vertices, -                                    indices: buffer.indices, -                                }, -                                size: self.size, -                            }), -                        )); -                    } +enum Buffer { +    Solid(tessellation::VertexBuffers<mesh::SolidVertex2D, u32>), +    Gradient(tessellation::VertexBuffers<mesh::GradientVertex2D, u32>), +} + +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(), +                    ));                  } -                Buffer::Gradient(buffer) => { -                    if !buffer.indices.is_empty() { -                        self.primitives.push(Primitive::Custom( -                            primitive::Custom::Mesh(Mesh::Gradient { -                                buffers: mesh::Indexed { -                                    vertices: buffer.vertices, -                                    indices: buffer.indices, -                                }, -                                size: self.size, -                            }), -                        )); -                    } +            }, +            Style::Gradient(_) => match self.stack.last() { +                Some(Buffer::Gradient(_)) => {} +                _ => { +                    self.stack.push(Buffer::Gradient( +                        tessellation::VertexBuffers::new(), +                    ));                  } +            }, +        } + +        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::pack(*color)), +                )) +            } +            (Style::Gradient(gradient), Buffer::Gradient(buffer)) => { +                Box::new(tessellation::BuffersBuilder::new( +                    buffer, +                    GradientVertex2DBuilder { +                        gradient: gradient.pack(), +                    }, +                ))              } +            _ => unreachable!(),          } +    } -        self.primitives +    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::pack(*color)), +                )) +            } +            (Style::Gradient(gradient), Buffer::Gradient(buffer)) => { +                Box::new(tessellation::BuffersBuilder::new( +                    buffer, +                    GradientVertex2DBuilder { +                        gradient: gradient.pack(), +                    }, +                )) +            } +            _ => unreachable!(), +        } +    } + +    fn into_meshes(self, clip_bounds: Rectangle) -> impl Iterator<Item = Mesh> { +        self.stack +            .into_iter() +            .filter_map(move |buffer| match buffer { +                Buffer::Solid(buffer) if !buffer.indices.is_empty() => { +                    Some(Mesh::Solid { +                        buffers: mesh::Indexed { +                            vertices: buffer.vertices, +                            indices: buffer.indices, +                        }, +                        clip_bounds, +                        transformation: Transformation::IDENTITY, +                    }) +                } +                Buffer::Gradient(buffer) if !buffer.indices.is_empty() => { +                    Some(Mesh::Gradient { +                        buffers: mesh::Indexed { +                            vertices: buffer.vertices, +                            indices: buffer.indices, +                        }, +                        clip_bounds, +                        transformation: Transformation::IDENTITY, +                    }) +                } +                _ => None, +            })      }  } +#[derive(Debug)] +struct Transforms { +    previous: Vec<Transform>, +    current: Transform, +} + +#[derive(Debug, Clone, Copy)] +struct Transform(lyon::math::Transform); + +impl Transform { +    fn is_identity(&self) -> bool { +        self.0 == lyon::math::Transform::identity() +    } + +    fn is_scale_translation(&self) -> bool { +        self.0.m12.abs() < 2.0 * f32::EPSILON +            && self.0.m21.abs() < 2.0 * f32::EPSILON +    } + +    fn scale(&self) -> (f32, f32) { +        (self.0.m11, self.0.m22) +    } + +    fn transform_point(&self, point: Point) -> Point { +        let transformed = self +            .0 +            .transform_point(euclid::Point2D::new(point.x, point.y)); + +        Point { +            x: transformed.x, +            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 { +        match &mut gradient { +            Gradient::Linear(linear) => { +                linear.start = self.transform_point(linear.start); +                linear.end = self.transform_point(linear.end); +            } +        } + +        gradient +    } +}  struct GradientVertex2DBuilder {      gradient: gradient::Packed,  } @@ -651,7 +625,9 @@ pub(super) fn dashed(path: &Path, line_dash: LineDash<'_>) -> Path {          let mut draw_line = false;          walk_along_path( -            path.raw().iter().flattened(0.01), +            path.raw().iter().flattened( +                lyon::tessellation::StrokeOptions::DEFAULT_TOLERANCE, +            ),              0.0,              lyon::tessellation::StrokeOptions::DEFAULT_TOLERANCE,              &mut RepeatedPattern { diff --git a/wgpu/src/image/atlas.rs b/wgpu/src/image/atlas.rs index ea36e06d..a1ec0d7b 100644 --- a/wgpu/src/image/atlas.rs +++ b/wgpu/src/image/atlas.rs @@ -15,15 +15,23 @@ pub const SIZE: u32 = 2048;  use crate::core::Size;  use crate::graphics::color; +use std::sync::Arc; +  #[derive(Debug)]  pub struct Atlas {      texture: wgpu::Texture,      texture_view: wgpu::TextureView, +    texture_bind_group: wgpu::BindGroup, +    texture_layout: Arc<wgpu::BindGroupLayout>,      layers: Vec<Layer>,  }  impl Atlas { -    pub fn new(device: &wgpu::Device, backend: wgpu::Backend) -> Self { +    pub fn new( +        device: &wgpu::Device, +        backend: wgpu::Backend, +        texture_layout: Arc<wgpu::BindGroupLayout>, +    ) -> Self {          let layers = match backend {              // On the GL backend we start with 2 layers, to help wgpu figure              // out that this texture is `GL_TEXTURE_2D_ARRAY` rather than `GL_TEXTURE_2D` @@ -60,15 +68,27 @@ impl Atlas {              ..Default::default()          }); +        let texture_bind_group = +            device.create_bind_group(&wgpu::BindGroupDescriptor { +                label: Some("iced_wgpu::image texture atlas bind group"), +                layout: &texture_layout, +                entries: &[wgpu::BindGroupEntry { +                    binding: 0, +                    resource: wgpu::BindingResource::TextureView(&texture_view), +                }], +            }); +          Atlas {              texture,              texture_view, +            texture_bind_group, +            texture_layout,              layers,          }      } -    pub fn view(&self) -> &wgpu::TextureView { -        &self.texture_view +    pub fn bind_group(&self) -> &wgpu::BindGroup { +        &self.texture_bind_group      }      pub fn layer_count(&self) -> usize { @@ -94,7 +114,7 @@ impl Atlas {              entry          }; -        log::info!("Allocated atlas entry: {entry:?}"); +        log::debug!("Allocated atlas entry: {entry:?}");          // It is a webgpu requirement that:          //   BufferCopyView.layout.bytes_per_row % wgpu::COPY_BYTES_PER_ROW_ALIGNMENT == 0 @@ -147,13 +167,20 @@ impl Atlas {              }          } -        log::info!("Current atlas: {self:?}"); +        if log::log_enabled!(log::Level::Debug) { +            log::debug!( +                "Atlas layers: {} (busy: {}, allocations: {})", +                self.layer_count(), +                self.layers.iter().filter(|layer| !layer.is_empty()).count(), +                self.layers.iter().map(Layer::allocations).sum::<usize>(), +            ); +        }          Some(entry)      }      pub fn remove(&mut self, entry: &Entry) { -        log::info!("Removing atlas entry: {entry:?}"); +        log::debug!("Removing atlas entry: {entry:?}");          match entry {              Entry::Contiguous(allocation) => { @@ -266,7 +293,7 @@ impl Atlas {      }      fn deallocate(&mut self, allocation: &Allocation) { -        log::info!("Deallocating atlas: {allocation:?}"); +        log::debug!("Deallocating atlas: {allocation:?}");          match allocation {              Allocation::Full { layer } => { @@ -414,5 +441,17 @@ impl Atlas {                  dimension: Some(wgpu::TextureViewDimension::D2Array),                  ..Default::default()              }); + +        self.texture_bind_group = +            device.create_bind_group(&wgpu::BindGroupDescriptor { +                label: Some("iced_wgpu::image texture atlas bind group"), +                layout: &self.texture_layout, +                entries: &[wgpu::BindGroupEntry { +                    binding: 0, +                    resource: wgpu::BindingResource::TextureView( +                        &self.texture_view, +                    ), +                }], +            });      }  } diff --git a/wgpu/src/image/atlas/allocator.rs b/wgpu/src/image/atlas/allocator.rs index 204a5c26..a51ac1f5 100644 --- a/wgpu/src/image/atlas/allocator.rs +++ b/wgpu/src/image/atlas/allocator.rs @@ -33,6 +33,10 @@ impl Allocator {      pub fn is_empty(&self) -> bool {          self.allocations == 0      } + +    pub fn allocations(&self) -> usize { +        self.allocations +    }  }  pub struct Region { diff --git a/wgpu/src/image/atlas/layer.rs b/wgpu/src/image/atlas/layer.rs index cf089601..fd6788d9 100644 --- a/wgpu/src/image/atlas/layer.rs +++ b/wgpu/src/image/atlas/layer.rs @@ -11,4 +11,12 @@ impl Layer {      pub fn is_empty(&self) -> bool {          matches!(self, Layer::Empty)      } + +    pub fn allocations(&self) -> usize { +        match self { +            Layer::Empty => 0, +            Layer::Busy(allocator) => allocator.allocations(), +            Layer::Full => 1, +        } +    }  } diff --git a/wgpu/src/image/cache.rs b/wgpu/src/image/cache.rs new file mode 100644 index 00000000..94f7071d --- /dev/null +++ b/wgpu/src/image/cache.rs @@ -0,0 +1,86 @@ +use crate::core::{self, Size}; +use crate::image::atlas::{self, Atlas}; + +use std::sync::Arc; + +#[derive(Debug)] +pub struct Cache { +    atlas: Atlas, +    #[cfg(feature = "image")] +    raster: crate::image::raster::Cache, +    #[cfg(feature = "svg")] +    vector: crate::image::vector::Cache, +} + +impl Cache { +    pub fn new( +        device: &wgpu::Device, +        backend: wgpu::Backend, +        layout: Arc<wgpu::BindGroupLayout>, +    ) -> Self { +        Self { +            atlas: Atlas::new(device, backend, layout), +            #[cfg(feature = "image")] +            raster: crate::image::raster::Cache::default(), +            #[cfg(feature = "svg")] +            vector: crate::image::vector::Cache::default(), +        } +    } + +    pub fn bind_group(&self) -> &wgpu::BindGroup { +        self.atlas.bind_group() +    } + +    pub fn layer_count(&self) -> usize { +        self.atlas.layer_count() +    } + +    #[cfg(feature = "image")] +    pub fn measure_image(&mut self, handle: &core::image::Handle) -> Size<u32> { +        self.raster.load(handle).dimensions() +    } + +    #[cfg(feature = "svg")] +    pub fn measure_svg(&mut self, handle: &core::svg::Handle) -> Size<u32> { +        self.vector.load(handle).viewport_dimensions() +    } + +    #[cfg(feature = "image")] +    pub fn upload_raster( +        &mut self, +        device: &wgpu::Device, +        encoder: &mut wgpu::CommandEncoder, +        handle: &core::image::Handle, +    ) -> Option<&atlas::Entry> { +        self.raster.upload(device, encoder, handle, &mut self.atlas) +    } + +    #[cfg(feature = "svg")] +    pub fn upload_vector( +        &mut self, +        device: &wgpu::Device, +        encoder: &mut wgpu::CommandEncoder, +        handle: &core::svg::Handle, +        color: Option<core::Color>, +        size: [f32; 2], +        scale: f32, +    ) -> Option<&atlas::Entry> { +        self.vector.upload( +            device, +            encoder, +            handle, +            color, +            size, +            scale, +            &mut self.atlas, +        ) +    } + +    pub fn trim(&mut self) { +        #[cfg(feature = "image")] +        self.raster.trim(&mut self.atlas); + +        #[cfg(feature = "svg")] +        self.vector.trim(&mut self.atlas); +    } +} diff --git a/wgpu/src/image.rs b/wgpu/src/image/mod.rs index c8e4a4c2..daa2fe16 100644 --- a/wgpu/src/image.rs +++ b/wgpu/src/image/mod.rs @@ -1,3 +1,6 @@ +pub(crate) mod cache; +pub(crate) use cache::Cache; +  mod atlas;  #[cfg(feature = "image")] @@ -6,175 +9,30 @@ mod raster;  #[cfg(feature = "svg")]  mod vector; -use atlas::Atlas; -  use crate::core::{Rectangle, Size, Transformation}; -use crate::layer;  use crate::Buffer; -use std::cell::RefCell; -use std::mem; -  use bytemuck::{Pod, Zeroable}; -#[cfg(feature = "image")] -use crate::core::image; +use std::mem; +use std::sync::Arc; -#[cfg(feature = "svg")] -use crate::core::svg; +pub use crate::graphics::Image; -#[cfg(feature = "tracing")] -use tracing::info_span; +pub type Batch = Vec<Image>;  #[derive(Debug)]  pub struct Pipeline { -    #[cfg(feature = "image")] -    raster_cache: RefCell<raster::Cache>, -    #[cfg(feature = "svg")] -    vector_cache: RefCell<vector::Cache>, -      pipeline: wgpu::RenderPipeline, +    backend: wgpu::Backend,      nearest_sampler: wgpu::Sampler,      linear_sampler: wgpu::Sampler, -    texture: wgpu::BindGroup, -    texture_version: usize, -    texture_atlas: Atlas, -    texture_layout: wgpu::BindGroupLayout, +    texture_layout: Arc<wgpu::BindGroupLayout>,      constant_layout: wgpu::BindGroupLayout, -      layers: Vec<Layer>,      prepare_layer: usize,  } -#[derive(Debug)] -struct Layer { -    uniforms: wgpu::Buffer, -    nearest: Data, -    linear: Data, -} - -impl Layer { -    fn new( -        device: &wgpu::Device, -        constant_layout: &wgpu::BindGroupLayout, -        nearest_sampler: &wgpu::Sampler, -        linear_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 nearest = -            Data::new(device, constant_layout, nearest_sampler, &uniforms); - -        let linear = -            Data::new(device, constant_layout, linear_sampler, &uniforms); - -        Self { -            uniforms, -            nearest, -            linear, -        } -    } - -    fn prepare( -        &mut self, -        device: &wgpu::Device, -        queue: &wgpu::Queue, -        nearest_instances: &[Instance], -        linear_instances: &[Instance], -        transformation: Transformation, -    ) { -        queue.write_buffer( -            &self.uniforms, -            0, -            bytemuck::bytes_of(&Uniforms { -                transform: transformation.into(), -            }), -        ); - -        self.nearest.upload(device, queue, nearest_instances); -        self.linear.upload(device, queue, linear_instances); -    } - -    fn render<'a>(&'a self, render_pass: &mut wgpu::RenderPass<'a>) { -        self.nearest.render(render_pass); -        self.linear.render(render_pass); -    } -} - -#[derive(Debug)] -struct Data { -    constants: wgpu::BindGroup, -    instances: Buffer<Instance>, -    instance_count: usize, -} - -impl Data { -    pub fn new( -        device: &wgpu::Device, -        constant_layout: &wgpu::BindGroupLayout, -        sampler: &wgpu::Sampler, -        uniforms: &wgpu::Buffer, -    ) -> Self { -        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 { -            constants, -            instances, -            instance_count: 0, -        } -    } - -    fn upload( -        &mut self, -        device: &wgpu::Device, -        queue: &wgpu::Queue, -        instances: &[Instance], -    ) { -        let _ = self.instances.resize(device, instances.len()); -        let _ = self.instances.write(queue, 0, instances); - -        self.instance_count = instances.len(); -    } - -    fn render<'a>(&'a self, render_pass: &mut wgpu::RenderPass<'a>) { -        render_pass.set_bind_group(0, &self.constants, &[]); -        render_pass.set_vertex_buffer(0, self.instances.slice(..)); - -        render_pass.draw(0..6, 0..self.instance_count as u32); -    } -} -  impl Pipeline {      pub fn new(          device: &wgpu::Device, @@ -257,9 +115,9 @@ impl Pipeline {                  label: Some("iced_wgpu image shader"),                  source: wgpu::ShaderSource::Wgsl(std::borrow::Cow::Borrowed(                      concat!( -                        include_str!("shader/vertex.wgsl"), +                        include_str!("../shader/vertex.wgsl"),                          "\n", -                        include_str!("shader/image.wgsl"), +                        include_str!("../shader/image.wgsl"),                      ),                  )),              }); @@ -277,14 +135,20 @@ impl Pipeline {                          attributes: &wgpu::vertex_attr_array!(                              // Position                              0 => Float32x2, -                            // Scale +                            // Center                              1 => Float32x2, -                            // Atlas position +                            // Scale                              2 => Float32x2, +                            // Rotation +                            3 => Float32, +                            // Opacity +                            4 => Float32, +                            // Atlas position +                            5 => Float32x2,                              // Atlas scale -                            3 => Float32x2, +                            6 => Float32x2,                              // Layer -                            4 => Sint32, +                            7 => Sint32,                          ),                      }],                  }, @@ -322,137 +186,95 @@ impl Pipeline {                  multiview: None,              }); -        let texture_atlas = Atlas::new(device, backend); - -        let texture = device.create_bind_group(&wgpu::BindGroupDescriptor { -            label: Some("iced_wgpu::image texture atlas bind group"), -            layout: &texture_layout, -            entries: &[wgpu::BindGroupEntry { -                binding: 0, -                resource: wgpu::BindingResource::TextureView( -                    texture_atlas.view(), -                ), -            }], -        }); -          Pipeline { -            #[cfg(feature = "image")] -            raster_cache: RefCell::new(raster::Cache::default()), - -            #[cfg(feature = "svg")] -            vector_cache: RefCell::new(vector::Cache::default()), -              pipeline, +            backend,              nearest_sampler,              linear_sampler, -            texture, -            texture_version: texture_atlas.layer_count(), -            texture_atlas, -            texture_layout, +            texture_layout: Arc::new(texture_layout),              constant_layout, -              layers: Vec::new(),              prepare_layer: 0,          }      } -    #[cfg(feature = "image")] -    pub fn dimensions(&self, handle: &image::Handle) -> Size<u32> { -        let mut cache = self.raster_cache.borrow_mut(); -        let memory = cache.load(handle); - -        memory.dimensions() -    } - -    #[cfg(feature = "svg")] -    pub fn viewport_dimensions(&self, handle: &svg::Handle) -> Size<u32> { -        let mut cache = self.vector_cache.borrow_mut(); -        let svg = cache.load(handle); - -        svg.viewport_dimensions() +    pub fn create_cache(&self, device: &wgpu::Device) -> Cache { +        Cache::new(device, self.backend, self.texture_layout.clone())      }      pub fn prepare(          &mut self,          device: &wgpu::Device, -        queue: &wgpu::Queue,          encoder: &mut wgpu::CommandEncoder, -        images: &[layer::Image], +        belt: &mut wgpu::util::StagingBelt, +        cache: &mut Cache, +        images: &Batch,          transformation: Transformation, -        _scale: f32, +        scale: f32,      ) { -        #[cfg(feature = "tracing")] -        let _ = info_span!("Wgpu::Image", "PREPARE").entered(); - -        #[cfg(feature = "tracing")] -        let _ = info_span!("Wgpu::Image", "DRAW").entered(); +        let transformation = transformation * Transformation::scale(scale);          let nearest_instances: &mut Vec<Instance> = &mut Vec::new();          let linear_instances: &mut Vec<Instance> = &mut Vec::new(); -        #[cfg(feature = "image")] -        let mut raster_cache = self.raster_cache.borrow_mut(); - -        #[cfg(feature = "svg")] -        let mut vector_cache = self.vector_cache.borrow_mut(); -          for image in images {              match &image {                  #[cfg(feature = "image")] -                layer::Image::Raster { +                Image::Raster {                      handle,                      filter_method,                      bounds, +                    rotation, +                    opacity,                  } => { -                    if let Some(atlas_entry) = raster_cache.upload( -                        device, -                        encoder, -                        handle, -                        &mut self.texture_atlas, -                    ) { +                    if let Some(atlas_entry) = +                        cache.upload_raster(device, encoder, handle) +                    {                          add_instances(                              [bounds.x, bounds.y],                              [bounds.width, bounds.height], +                            f32::from(*rotation), +                            *opacity,                              atlas_entry,                              match filter_method { -                                image::FilterMethod::Nearest => { +                                crate::core::image::FilterMethod::Nearest => {                                      nearest_instances                                  } -                                image::FilterMethod::Linear => linear_instances, +                                crate::core::image::FilterMethod::Linear => { +                                    linear_instances +                                }                              },                          );                      }                  }                  #[cfg(not(feature = "image"))] -                layer::Image::Raster { .. } => {} +                Image::Raster { .. } => {}                  #[cfg(feature = "svg")] -                layer::Image::Vector { +                Image::Vector {                      handle,                      color,                      bounds, +                    rotation, +                    opacity,                  } => {                      let size = [bounds.width, bounds.height]; -                    if let Some(atlas_entry) = vector_cache.upload( -                        device, -                        encoder, -                        handle, -                        *color, -                        size, -                        _scale, -                        &mut self.texture_atlas, +                    if let Some(atlas_entry) = cache.upload_vector( +                        device, encoder, handle, *color, size, scale,                      ) {                          add_instances(                              [bounds.x, bounds.y],                              size, +                            f32::from(*rotation), +                            *opacity,                              atlas_entry,                              nearest_instances,                          );                      }                  }                  #[cfg(not(feature = "svg"))] -                layer::Image::Vector { .. } => {} +                Image::Vector { .. } => {}              }          } @@ -460,26 +282,6 @@ impl Pipeline {              return;          } -        let texture_version = self.texture_atlas.layer_count(); - -        if self.texture_version != texture_version { -            log::info!("Atlas has grown. Recreating bind group..."); - -            self.texture = -                device.create_bind_group(&wgpu::BindGroupDescriptor { -                    label: Some("iced_wgpu::image texture atlas bind group"), -                    layout: &self.texture_layout, -                    entries: &[wgpu::BindGroupEntry { -                        binding: 0, -                        resource: wgpu::BindingResource::TextureView( -                            self.texture_atlas.view(), -                        ), -                    }], -                }); - -            self.texture_version = texture_version; -        } -          if self.layers.len() <= self.prepare_layer {              self.layers.push(Layer::new(                  device, @@ -493,7 +295,8 @@ impl Pipeline {          layer.prepare(              device, -            queue, +            encoder, +            belt,              nearest_instances,              linear_instances,              transformation, @@ -504,6 +307,7 @@ impl Pipeline {      pub fn render<'a>(          &'a self, +        cache: &'a Cache,          layer: usize,          bounds: Rectangle<u32>,          render_pass: &mut wgpu::RenderPass<'a>, @@ -518,20 +322,162 @@ impl Pipeline {                  bounds.height,              ); -            render_pass.set_bind_group(1, &self.texture, &[]); +            render_pass.set_bind_group(1, cache.bind_group(), &[]);              layer.render(render_pass);          }      }      pub fn end_frame(&mut self) { -        #[cfg(feature = "image")] -        self.raster_cache.borrow_mut().trim(&mut self.texture_atlas); +        self.prepare_layer = 0; +    } +} + +#[derive(Debug)] +struct Layer { +    uniforms: wgpu::Buffer, +    nearest: Data, +    linear: Data, +} -        #[cfg(feature = "svg")] -        self.vector_cache.borrow_mut().trim(&mut self.texture_atlas); +impl Layer { +    fn new( +        device: &wgpu::Device, +        constant_layout: &wgpu::BindGroupLayout, +        nearest_sampler: &wgpu::Sampler, +        linear_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, +        }); -        self.prepare_layer = 0; +        let nearest = +            Data::new(device, constant_layout, nearest_sampler, &uniforms); + +        let linear = +            Data::new(device, constant_layout, linear_sampler, &uniforms); + +        Self { +            uniforms, +            nearest, +            linear, +        } +    } + +    fn prepare( +        &mut self, +        device: &wgpu::Device, +        encoder: &mut wgpu::CommandEncoder, +        belt: &mut wgpu::util::StagingBelt, +        nearest_instances: &[Instance], +        linear_instances: &[Instance], +        transformation: Transformation, +    ) { +        let uniforms = Uniforms { +            transform: transformation.into(), +        }; + +        let bytes = bytemuck::bytes_of(&uniforms); + +        belt.write_buffer( +            encoder, +            &self.uniforms, +            0, +            (bytes.len() as u64).try_into().expect("Sized uniforms"), +            device, +        ) +        .copy_from_slice(bytes); + +        self.nearest +            .upload(device, encoder, belt, nearest_instances); + +        self.linear.upload(device, encoder, belt, linear_instances); +    } + +    fn render<'a>(&'a self, render_pass: &mut wgpu::RenderPass<'a>) { +        self.nearest.render(render_pass); +        self.linear.render(render_pass); +    } +} + +#[derive(Debug)] +struct Data { +    constants: wgpu::BindGroup, +    instances: Buffer<Instance>, +    instance_count: usize, +} + +impl Data { +    pub fn new( +        device: &wgpu::Device, +        constant_layout: &wgpu::BindGroupLayout, +        sampler: &wgpu::Sampler, +        uniforms: &wgpu::Buffer, +    ) -> Self { +        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 { +            constants, +            instances, +            instance_count: 0, +        } +    } + +    fn upload( +        &mut self, +        device: &wgpu::Device, +        encoder: &mut wgpu::CommandEncoder, +        belt: &mut wgpu::util::StagingBelt, +        instances: &[Instance], +    ) { +        self.instance_count = instances.len(); + +        if self.instance_count == 0 { +            return; +        } + +        let _ = self.instances.resize(device, instances.len()); +        let _ = self.instances.write(device, encoder, belt, 0, instances); +    } + +    fn render<'a>(&'a self, render_pass: &mut wgpu::RenderPass<'a>) { +        if self.instance_count == 0 { +            return; +        } + +        render_pass.set_bind_group(0, &self.constants, &[]); +        render_pass.set_vertex_buffer(0, self.instances.slice(..)); + +        render_pass.draw(0..6, 0..self.instance_count as u32);      }  } @@ -539,7 +485,10 @@ impl Pipeline {  #[derive(Debug, Clone, Copy, Zeroable, Pod)]  struct Instance {      _position: [f32; 2], +    _center: [f32; 2],      _size: [f32; 2], +    _rotation: f32, +    _opacity: f32,      _position_in_atlas: [f32; 2],      _size_in_atlas: [f32; 2],      _layer: u32, @@ -558,12 +507,27 @@ struct Uniforms {  fn add_instances(      image_position: [f32; 2],      image_size: [f32; 2], +    rotation: f32, +    opacity: f32,      entry: &atlas::Entry,      instances: &mut Vec<Instance>,  ) { +    let center = [ +        image_position[0] + image_size[0] / 2.0, +        image_position[1] + image_size[1] / 2.0, +    ]; +      match entry {          atlas::Entry::Contiguous(allocation) => { -            add_instance(image_position, image_size, allocation, instances); +            add_instance( +                image_position, +                center, +                image_size, +                rotation, +                opacity, +                allocation, +                instances, +            );          }          atlas::Entry::Fragmented { fragments, size } => {              let scaling_x = image_size[0] / size.width as f32; @@ -589,7 +553,10 @@ fn add_instances(                      fragment_height as f32 * scaling_y,                  ]; -                add_instance(position, size, allocation, instances); +                add_instance( +                    position, center, size, rotation, opacity, allocation, +                    instances, +                );              }          }      } @@ -598,7 +565,10 @@ fn add_instances(  #[inline]  fn add_instance(      position: [f32; 2], +    center: [f32; 2],      size: [f32; 2], +    rotation: f32, +    opacity: f32,      allocation: &atlas::Allocation,      instances: &mut Vec<Instance>,  ) { @@ -608,7 +578,10 @@ fn add_instance(      let instance = Instance {          _position: position, +        _center: center,          _size: size, +        _rotation: rotation, +        _opacity: opacity,          _position_in_atlas: [              (x as f32 + 0.5) / atlas::SIZE as f32,              (y as f32 + 0.5) / atlas::SIZE as f32, diff --git a/wgpu/src/image/null.rs b/wgpu/src/image/null.rs new file mode 100644 index 00000000..c06d56be --- /dev/null +++ b/wgpu/src/image/null.rs @@ -0,0 +1,10 @@ +pub use crate::graphics::Image; + +#[derive(Debug, Default)] +pub struct Batch; + +impl Batch { +    pub fn push(&mut self, _image: Image) {} + +    pub fn clear(&mut self) {} +} diff --git a/wgpu/src/image/raster.rs b/wgpu/src/image/raster.rs index a6cba76a..4d3c3125 100644 --- a/wgpu/src/image/raster.rs +++ b/wgpu/src/image/raster.rs @@ -4,13 +4,13 @@ use crate::graphics;  use crate::graphics::image::image_rs;  use crate::image::atlas::{self, Atlas}; -use std::collections::{HashMap, HashSet}; +use rustc_hash::{FxHashMap, FxHashSet};  /// 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>>), +    Host(image_rs::ImageBuffer<image_rs::Rgba<u8>, image::Bytes>),      /// Storage entry      Device(atlas::Entry),      /// Image not found @@ -38,8 +38,9 @@ impl Memory {  /// Caches image raster data  #[derive(Debug, Default)]  pub struct Cache { -    map: HashMap<u64, Memory>, -    hits: HashSet<u64>, +    map: FxHashMap<image::Id, Memory>, +    hits: FxHashSet<image::Id>, +    should_trim: bool,  }  impl Cache { @@ -50,11 +51,13 @@ impl Cache {          }          let memory = match graphics::image::load(handle) { -            Ok(image) => Memory::Host(image.to_rgba8()), +            Ok(image) => Memory::Host(image),              Err(image_rs::error::ImageError::IoError(_)) => Memory::NotFound,              Err(_) => Memory::Invalid,          }; +        self.should_trim = true; +          self.insert(handle, memory);          self.get(handle).unwrap()      } @@ -86,6 +89,11 @@ impl Cache {      /// Trim cache misses from cache      pub fn trim(&mut self, atlas: &mut Atlas) { +        // Only trim if new entries have landed in the `Cache` +        if !self.should_trim { +            return; +        } +          let hits = &self.hits;          self.map.retain(|k, memory| { @@ -101,6 +109,7 @@ impl Cache {          });          self.hits.clear(); +        self.should_trim = false;      }      fn get(&mut self, handle: &image::Handle) -> Option<&mut Memory> { diff --git a/wgpu/src/image/vector.rs b/wgpu/src/image/vector.rs index d9be50d7..c6d829af 100644 --- a/wgpu/src/image/vector.rs +++ b/wgpu/src/image/vector.rs @@ -5,7 +5,7 @@ use crate::image::atlas::{self, Atlas};  use resvg::tiny_skia;  use resvg::usvg::{self, TreeTextToPath}; -use std::collections::{HashMap, HashSet}; +use rustc_hash::{FxHashMap, FxHashSet};  use std::fs;  /// Entry in cache corresponding to an svg handle @@ -33,10 +33,11 @@ impl Svg {  /// 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)>, +    svgs: FxHashMap<u64, Svg>, +    rasterized: FxHashMap<(u64, u32, u32, ColorFilter), atlas::Entry>, +    svg_hits: FxHashSet<u64>, +    rasterized_hits: FxHashSet<(u64, u32, u32, ColorFilter)>, +    should_trim: bool,  }  type ColorFilter = Option<[u8; 4]>; @@ -76,6 +77,8 @@ impl Cache {              }          } +        self.should_trim = true; +          let _ = self.svgs.insert(handle.id(), svg);          self.svgs.get(&handle.id()).unwrap()      } @@ -176,6 +179,10 @@ impl Cache {      /// Load svg and upload raster data      pub fn trim(&mut self, atlas: &mut Atlas) { +        if !self.should_trim { +            return; +        } +          let svg_hits = &self.svg_hits;          let rasterized_hits = &self.rasterized_hits; @@ -191,6 +198,7 @@ impl Cache {          });          self.svg_hits.clear();          self.rasterized_hits.clear(); +        self.should_trim = false;      }  } diff --git a/wgpu/src/layer.rs b/wgpu/src/layer.rs index cc767c25..9551311d 100644 --- a/wgpu/src/layer.rs +++ b/wgpu/src/layer.rs @@ -1,343 +1,302 @@ -//! Organize rendering primitives into a flattened list of layers. -mod image; -mod pipeline; -mod text; - -pub mod mesh; - -pub use image::Image; -pub use mesh::Mesh; -pub use pipeline::Pipeline; -pub use text::Text; - -use crate::core; -use crate::core::alignment;  use crate::core::{ -    Color, Font, Pixels, Point, Rectangle, Size, Transformation, Vector, +    renderer, Background, Color, Point, Radians, Rectangle, Transformation,  };  use crate::graphics;  use crate::graphics::color; -use crate::graphics::Viewport; +use crate::graphics::layer; +use crate::graphics::text::{Editor, Paragraph}; +use crate::graphics::Mesh; +use crate::image::{self, Image};  use crate::primitive::{self, Primitive};  use crate::quad::{self, Quad}; +use crate::text::{self, Text}; +use crate::triangle; + +pub type Stack = layer::Stack<Layer>; -/// A group of primitives that should be clipped together.  #[derive(Debug)] -pub struct Layer<'a> { -    /// The clipping bounds of the [`Layer`]. +pub struct Layer {      pub bounds: Rectangle, - -    /// The quads of the [`Layer`].      pub quads: quad::Batch, +    pub triangles: triangle::Batch, +    pub primitives: primitive::Batch, +    pub text: text::Batch, +    pub images: image::Batch, +    pending_meshes: Vec<Mesh>, +    pending_text: Vec<Text>, +} -    /// The triangle meshes of the [`Layer`]. -    pub meshes: Vec<Mesh<'a>>, +impl Layer { +    pub fn draw_quad( +        &mut self, +        quad: renderer::Quad, +        background: Background, +        transformation: Transformation, +    ) { +        let bounds = quad.bounds * transformation; + +        let quad = Quad { +            position: [bounds.x, bounds.y], +            size: [bounds.width, bounds.height], +            border_color: color::pack(quad.border.color), +            border_radius: quad.border.radius.into(), +            border_width: quad.border.width, +            shadow_color: color::pack(quad.shadow.color), +            shadow_offset: quad.shadow.offset.into(), +            shadow_blur_radius: quad.shadow.blur_radius, +        }; + +        self.quads.add(quad, &background); +    } -    /// The text of the [`Layer`]. -    pub text: Vec<Text<'a>>, +    pub fn draw_paragraph( +        &mut self, +        paragraph: &Paragraph, +        position: Point, +        color: Color, +        clip_bounds: Rectangle, +        transformation: Transformation, +    ) { +        let paragraph = Text::Paragraph { +            paragraph: paragraph.downgrade(), +            position, +            color, +            clip_bounds, +            transformation, +        }; + +        self.pending_text.push(paragraph); +    } -    /// The images of the [`Layer`]. -    pub images: Vec<Image>, +    pub fn draw_editor( +        &mut self, +        editor: &Editor, +        position: Point, +        color: Color, +        clip_bounds: Rectangle, +        transformation: Transformation, +    ) { +        let editor = Text::Editor { +            editor: editor.downgrade(), +            position, +            color, +            clip_bounds, +            transformation, +        }; + +        self.pending_text.push(editor); +    } -    /// The custom pipelines of this [`Layer`]. -    pub pipelines: Vec<Pipeline>, -} +    pub fn draw_text( +        &mut self, +        text: crate::core::Text, +        position: Point, +        color: Color, +        clip_bounds: Rectangle, +        transformation: Transformation, +    ) { +        let text = Text::Cached { +            content: text.content, +            bounds: Rectangle::new(position, text.bounds) * transformation, +            color, +            size: text.size * transformation.scale_factor(), +            line_height: text.line_height.to_absolute(text.size) +                * transformation.scale_factor(), +            font: text.font, +            horizontal_alignment: text.horizontal_alignment, +            vertical_alignment: text.vertical_alignment, +            shaping: text.shaping, +            clip_bounds: clip_bounds * transformation, +        }; + +        self.pending_text.push(text); +    } -impl<'a> Layer<'a> { -    /// Creates a new [`Layer`] with the given clipping bounds. -    pub fn new(bounds: Rectangle) -> Self { -        Self { -            bounds, -            quads: quad::Batch::default(), -            meshes: Vec::new(), -            text: Vec::new(), -            images: Vec::new(), -            pipelines: Vec::new(), -        } +    pub fn draw_image( +        &mut self, +        handle: crate::core::image::Handle, +        filter_method: crate::core::image::FilterMethod, +        bounds: Rectangle, +        transformation: Transformation, +        rotation: Radians, +        opacity: f32, +    ) { +        let image = Image::Raster { +            handle, +            filter_method, +            bounds: bounds * transformation, +            rotation, +            opacity, +        }; + +        self.images.push(image); +    } + +    pub fn draw_svg( +        &mut self, +        handle: crate::core::svg::Handle, +        color: Option<Color>, +        bounds: Rectangle, +        transformation: Transformation, +        rotation: Radians, +        opacity: f32, +    ) { +        let svg = Image::Vector { +            handle, +            color, +            bounds: bounds * transformation, +            rotation, +            opacity, +        }; + +        self.images.push(svg);      } -    /// 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::Cached { -                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: Pixels(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, -                clip_bounds: Rectangle::with_size(Size::INFINITY), -            }; - -            overlay.text.push(Text::Cached(text.clone())); - -            overlay.text.push(Text::Cached(text::Cached { -                bounds: text.bounds + Vector::new(-1.0, -1.0), -                color: Color::BLACK, -                ..text -            })); +    pub fn draw_mesh( +        &mut self, +        mut mesh: Mesh, +        transformation: Transformation, +    ) { +        match &mut mesh { +            Mesh::Solid { +                transformation: local_transformation, +                .. +            } +            | Mesh::Gradient { +                transformation: local_transformation, +                .. +            } => { +                *local_transformation = *local_transformation * transformation; +            }          } -        overlay +        self.pending_meshes.push(mesh);      } -    /// 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, -                Transformation::IDENTITY, -                primitive, -                0, -            ); -        } +    pub fn draw_mesh_group( +        &mut self, +        meshes: Vec<Mesh>, +        transformation: Transformation, +    ) { +        self.flush_meshes(); -        layers +        self.triangles.push(triangle::Item::Group { +            meshes, +            transformation, +        });      } -    fn process_primitive( -        layers: &mut Vec<Self>, +    pub fn draw_mesh_cache( +        &mut self, +        cache: triangle::Cache,          transformation: Transformation, -        primitive: &'a Primitive, -        current_layer: usize,      ) { -        match primitive { -            Primitive::Paragraph { -                paragraph, -                position, -                color, -                clip_bounds, -            } => { -                let layer = &mut layers[current_layer]; - -                layer.text.push(Text::Paragraph { -                    paragraph: paragraph.clone(), -                    position: *position, -                    color: *color, -                    clip_bounds: *clip_bounds, -                    transformation, -                }); -            } -            Primitive::Editor { -                editor, -                position, -                color, -                clip_bounds, -            } => { -                let layer = &mut layers[current_layer]; - -                layer.text.push(Text::Editor { -                    editor: editor.clone(), -                    position: *position, -                    color: *color, -                    clip_bounds: *clip_bounds, -                    transformation, -                }); -            } -            Primitive::Text { -                content, -                bounds, -                size, -                line_height, -                color, -                font, -                horizontal_alignment, -                vertical_alignment, -                shaping, -                clip_bounds, -            } => { -                let layer = &mut layers[current_layer]; - -                layer.text.push(Text::Cached(text::Cached { -                    content, -                    bounds: *bounds + transformation.translation(), -                    size: *size * transformation.scale_factor(), -                    line_height: *line_height, -                    color: *color, -                    font: *font, -                    horizontal_alignment: *horizontal_alignment, -                    vertical_alignment: *vertical_alignment, -                    shaping: *shaping, -                    clip_bounds: *clip_bounds * transformation, -                })); -            } -            graphics::Primitive::RawText(raw) => { -                let layer = &mut layers[current_layer]; +        self.flush_meshes(); -                layer.text.push(Text::Raw { -                    raw: raw.clone(), -                    transformation, -                }); -            } -            Primitive::Quad { -                bounds, -                background, -                border, -                shadow, -            } => { -                let layer = &mut layers[current_layer]; -                let bounds = *bounds * transformation; - -                let quad = Quad { -                    position: [bounds.x, bounds.y], -                    size: [bounds.width, bounds.height], -                    border_color: color::pack(border.color), -                    border_radius: border.radius.into(), -                    border_width: border.width, -                    shadow_color: shadow.color.into_linear(), -                    shadow_offset: shadow.offset.into(), -                    shadow_blur_radius: shadow.blur_radius, -                }; - -                layer.quads.add(quad, background); -            } -            Primitive::Image { -                handle, -                filter_method, -                bounds, -            } => { -                let layer = &mut layers[current_layer]; +        self.triangles.push(triangle::Item::Cached { +            cache, +            transformation, +        }); +    } -                layer.images.push(Image::Raster { -                    handle: handle.clone(), -                    filter_method: *filter_method, -                    bounds: *bounds * transformation, -                }); -            } -            Primitive::Svg { -                handle, -                color, -                bounds, -            } => { -                let layer = &mut layers[current_layer]; +    pub fn draw_text_group( +        &mut self, +        text: Vec<Text>, +        transformation: Transformation, +    ) { +        self.flush_text(); -                layer.images.push(Image::Vector { -                    handle: handle.clone(), -                    color: *color, -                    bounds: *bounds * transformation, -                }); -            } -            Primitive::Group { primitives } => { -                // TODO: Inspect a bit and regroup (?) -                for primitive in primitives { -                    Self::process_primitive( -                        layers, -                        transformation, -                        primitive, -                        current_layer, -                    ); -                } -            } -            Primitive::Clip { bounds, content } => { -                let layer = &mut layers[current_layer]; -                let translated_bounds = *bounds * transformation; - -                // 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, -                        transformation, -                        content, -                        layers.len() - 1, -                    ); -                } -            } -            Primitive::Transform { -                transformation: new_transformation, -                content, -            } => { -                Self::process_primitive( -                    layers, -                    transformation * *new_transformation, -                    content, -                    current_layer, -                ); -            } -            Primitive::Cache { content } => { -                Self::process_primitive( -                    layers, -                    transformation, -                    content, -                    current_layer, -                ); -            } -            Primitive::Custom(custom) => match custom { -                primitive::Custom::Mesh(mesh) => match mesh { -                    graphics::Mesh::Solid { buffers, size } => { -                        let layer = &mut layers[current_layer]; - -                        let bounds = -                            Rectangle::with_size(*size) * transformation; - -                        // Only draw visible content -                        if let Some(clip_bounds) = -                            layer.bounds.intersection(&bounds) -                        { -                            layer.meshes.push(Mesh::Solid { -                                transformation, -                                buffers, -                                clip_bounds, -                            }); -                        } -                    } -                    graphics::Mesh::Gradient { buffers, size } => { -                        let layer = &mut layers[current_layer]; - -                        let bounds = -                            Rectangle::with_size(*size) * transformation; - -                        // Only draw visible content -                        if let Some(clip_bounds) = -                            layer.bounds.intersection(&bounds) -                        { -                            layer.meshes.push(Mesh::Gradient { -                                transformation, -                                buffers, -                                clip_bounds, -                            }); -                        } -                    } -                }, -                primitive::Custom::Pipeline(pipeline) => { -                    let layer = &mut layers[current_layer]; -                    let bounds = pipeline.bounds * transformation; - -                    if let Some(clip_bounds) = -                        layer.bounds.intersection(&bounds) -                    { -                        layer.pipelines.push(Pipeline { -                            bounds, -                            viewport: clip_bounds, -                            primitive: pipeline.primitive.clone(), -                        }); -                    } -                } -            }, +        self.text.push(text::Item::Group { +            text, +            transformation, +        }); +    } + +    pub fn draw_text_cache( +        &mut self, +        cache: text::Cache, +        transformation: Transformation, +    ) { +        self.flush_text(); + +        self.text.push(text::Item::Cached { +            cache, +            transformation, +        }); +    } + +    pub fn draw_primitive( +        &mut self, +        bounds: Rectangle, +        primitive: Box<dyn Primitive>, +        transformation: Transformation, +    ) { +        let bounds = bounds * transformation; + +        self.primitives +            .push(primitive::Instance { bounds, primitive }); +    } + +    fn flush_meshes(&mut self) { +        if !self.pending_meshes.is_empty() { +            self.triangles.push(triangle::Item::Group { +                transformation: Transformation::IDENTITY, +                meshes: self.pending_meshes.drain(..).collect(), +            }); +        } +    } + +    fn flush_text(&mut self) { +        if !self.pending_text.is_empty() { +            self.text.push(text::Item::Group { +                transformation: Transformation::IDENTITY, +                text: self.pending_text.drain(..).collect(), +            }); +        } +    } +} + +impl graphics::Layer for Layer { +    fn with_bounds(bounds: Rectangle) -> Self { +        Self { +            bounds, +            ..Self::default() +        } +    } + +    fn flush(&mut self) { +        self.flush_meshes(); +        self.flush_text(); +    } + +    fn resize(&mut self, bounds: Rectangle) { +        self.bounds = bounds; +    } + +    fn reset(&mut self) { +        self.bounds = Rectangle::INFINITE; + +        self.quads.clear(); +        self.triangles.clear(); +        self.primitives.clear(); +        self.text.clear(); +        self.images.clear(); +        self.pending_meshes.clear(); +        self.pending_text.clear(); +    } +} + +impl Default for Layer { +    fn default() -> Self { +        Self { +            bounds: Rectangle::INFINITE, +            quads: quad::Batch::default(), +            triangles: triangle::Batch::default(), +            primitives: primitive::Batch::default(), +            text: text::Batch::default(), +            images: image::Batch::default(), +            pending_meshes: Vec::new(), +            pending_text: Vec::new(),          }      }  } diff --git a/wgpu/src/layer/image.rs b/wgpu/src/layer/image.rs deleted file mode 100644 index facbe192..00000000 --- a/wgpu/src/layer/image.rs +++ /dev/null @@ -1,30 +0,0 @@ -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 filter method of a raster image. -        filter_method: image::FilterMethod, - -        /// 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 deleted file mode 100644 index 5ed7c654..00000000 --- a/wgpu/src/layer/mesh.rs +++ /dev/null @@ -1,97 +0,0 @@ -//! A collection of triangle primitives. -use crate::core::{Rectangle, Transformation}; -use crate::graphics::mesh; - -/// A mesh of triangles. -#[derive(Debug, Clone, Copy)] -pub enum Mesh<'a> { -    /// A mesh of triangles with a solid color. -    Solid { -        /// The [`Transformation`] for the vertices of the [`Mesh`]. -        transformation: Transformation, - -        /// The vertex and index buffers of the [`Mesh`]. -        buffers: &'a mesh::Indexed<mesh::SolidVertex2D>, - -        /// The clipping bounds of the [`Mesh`]. -        clip_bounds: Rectangle<f32>, -    }, -    /// A mesh of triangles with a gradient color. -    Gradient { -        /// The [`Transformation`] for the vertices of the [`Mesh`]. -        transformation: Transformation, - -        /// The vertex and index buffers of the [`Mesh`]. -        buffers: &'a mesh::Indexed<mesh::GradientVertex2D>, - -        /// The clipping bounds of the [`Mesh`]. -        clip_bounds: Rectangle<f32>, -    }, -} - -impl Mesh<'_> { -    /// Returns the origin of the [`Mesh`]. -    pub fn transformation(&self) -> Transformation { -        match self { -            Self::Solid { transformation, .. } -            | Self::Gradient { transformation, .. } => *transformation, -        } -    } - -    /// 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 solid meshes. -    pub solids: usize, - -    /// The total amount of gradient vertices. -    pub gradient_vertices: usize, - -    /// The total amount of gradient meshes. -    pub gradients: usize, - -    /// The total amount of indices. -    pub indices: usize, -} - -/// 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.solids += 1; -                    count.solid_vertices += buffers.vertices.len(); -                    count.indices += buffers.indices.len(); -                } -                Mesh::Gradient { buffers, .. } => { -                    count.gradients += 1; -                    count.gradient_vertices += buffers.vertices.len(); -                    count.indices += buffers.indices.len(); -                } -            } - -            count -        }) -} diff --git a/wgpu/src/layer/pipeline.rs b/wgpu/src/layer/pipeline.rs deleted file mode 100644 index 6dfe6750..00000000 --- a/wgpu/src/layer/pipeline.rs +++ /dev/null @@ -1,17 +0,0 @@ -use crate::core::Rectangle; -use crate::primitive::pipeline::Primitive; - -use std::sync::Arc; - -#[derive(Clone, Debug)] -/// A custom primitive which can be used to render primitives associated with a custom pipeline. -pub struct Pipeline { -    /// The bounds of the [`Pipeline`]. -    pub bounds: Rectangle, - -    /// The viewport of the [`Pipeline`]. -    pub viewport: Rectangle, - -    /// The [`Primitive`] to render. -    pub primitive: Arc<dyn Primitive>, -} diff --git a/wgpu/src/layer/text.rs b/wgpu/src/layer/text.rs deleted file mode 100644 index b3a00130..00000000 --- a/wgpu/src/layer/text.rs +++ /dev/null @@ -1,70 +0,0 @@ -use crate::core::alignment; -use crate::core::text; -use crate::core::{Color, Font, Pixels, Point, Rectangle, Transformation}; -use crate::graphics; -use crate::graphics::text::editor; -use crate::graphics::text::paragraph; - -/// A text primitive. -#[derive(Debug, Clone)] -pub enum Text<'a> { -    /// A paragraph. -    #[allow(missing_docs)] -    Paragraph { -        paragraph: paragraph::Weak, -        position: Point, -        color: Color, -        clip_bounds: Rectangle, -        transformation: Transformation, -    }, -    /// An editor. -    #[allow(missing_docs)] -    Editor { -        editor: editor::Weak, -        position: Point, -        color: Color, -        clip_bounds: Rectangle, -        transformation: Transformation, -    }, -    /// Some cached text. -    Cached(Cached<'a>), -    /// Some raw text. -    #[allow(missing_docs)] -    Raw { -        raw: graphics::text::Raw, -        transformation: Transformation, -    }, -} - -#[derive(Debug, Clone)] -pub struct Cached<'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: Pixels, - -    /// 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, - -    /// The clip bounds of the text. -    pub clip_bounds: Rectangle, -} diff --git a/wgpu/src/lib.rs b/wgpu/src/lib.rs index b00e5c3c..ad88ce3e 100644 --- a/wgpu/src/lib.rs +++ b/wgpu/src/lib.rs @@ -20,15 +20,8 @@  #![doc(      html_logo_url = "https://raw.githubusercontent.com/iced-rs/iced/9ab6923e943f784985e9ef9ca28b10278297225d/docs/logo.svg"  )] -#![forbid(rust_2018_idioms)] -#![deny( -    missing_debug_implementations, -    missing_docs, -    unsafe_code, -    unused_results, -    rustdoc::broken_intra_doc_links -)]  #![cfg_attr(docsrs, feature(doc_auto_cfg))] +#![allow(missing_docs)]  pub mod layer;  pub mod primitive;  pub mod settings; @@ -37,13 +30,21 @@ pub mod window;  #[cfg(feature = "geometry")]  pub mod geometry; -mod backend;  mod buffer;  mod color; +mod engine;  mod quad;  mod text;  mod triangle; +#[cfg(any(feature = "image", feature = "svg"))] +#[path = "image/mod.rs"] +mod image; + +#[cfg(not(any(feature = "image", feature = "svg")))] +#[path = "image/null.rs"] +mod image; +  use buffer::Buffer;  pub use iced_graphics as graphics; @@ -51,16 +52,571 @@ pub use iced_graphics::core;  pub use wgpu; -pub use backend::Backend; +pub use engine::Engine;  pub use layer::Layer;  pub use primitive::Primitive;  pub use settings::Settings; -#[cfg(any(feature = "image", feature = "svg"))] -mod image; +#[cfg(feature = "geometry")] +pub use geometry::Geometry; + +use crate::core::{ +    Background, Color, Font, Pixels, Point, Rectangle, Size, Transformation, +    Vector, +}; +use crate::graphics::text::{Editor, Paragraph}; +use crate::graphics::Viewport;  /// A [`wgpu`] graphics renderer for [`iced`].  ///  /// [`wgpu`]: https://github.com/gfx-rs/wgpu-rs  /// [`iced`]: https://github.com/iced-rs/iced -pub type Renderer = iced_graphics::Renderer<Backend>; +#[allow(missing_debug_implementations)] +pub struct Renderer { +    default_font: Font, +    default_text_size: Pixels, +    layers: layer::Stack, + +    triangle_storage: triangle::Storage, +    text_storage: text::Storage, +    text_viewport: text::Viewport, + +    // TODO: Centralize all the image feature handling +    #[cfg(any(feature = "svg", feature = "image"))] +    image_cache: std::cell::RefCell<image::Cache>, +} + +impl Renderer { +    pub fn new( +        device: &wgpu::Device, +        engine: &Engine, +        default_font: Font, +        default_text_size: Pixels, +    ) -> Self { +        Self { +            default_font, +            default_text_size, +            layers: layer::Stack::new(), + +            triangle_storage: triangle::Storage::new(), +            text_storage: text::Storage::new(), +            text_viewport: engine.text_pipeline.create_viewport(device), + +            #[cfg(any(feature = "svg", feature = "image"))] +            image_cache: std::cell::RefCell::new( +                engine.create_image_cache(device), +            ), +        } +    } + +    pub fn present<T: AsRef<str>>( +        &mut self, +        engine: &mut Engine, +        device: &wgpu::Device, +        queue: &wgpu::Queue, +        encoder: &mut wgpu::CommandEncoder, +        clear_color: Option<Color>, +        format: wgpu::TextureFormat, +        frame: &wgpu::TextureView, +        viewport: &Viewport, +        overlay: &[T], +    ) { +        self.draw_overlay(overlay, viewport); +        self.prepare(engine, device, queue, format, encoder, viewport); +        self.render(engine, encoder, frame, clear_color, viewport); + +        self.triangle_storage.trim(); +        self.text_storage.trim(); + +        #[cfg(any(feature = "svg", feature = "image"))] +        self.image_cache.borrow_mut().trim(); +    } + +    fn prepare( +        &mut self, +        engine: &mut Engine, +        device: &wgpu::Device, +        queue: &wgpu::Queue, +        _format: wgpu::TextureFormat, +        encoder: &mut wgpu::CommandEncoder, +        viewport: &Viewport, +    ) { +        let scale_factor = viewport.scale_factor() as f32; + +        self.text_viewport.update(queue, viewport.physical_size()); + +        for layer in self.layers.iter_mut() { +            if !layer.quads.is_empty() { +                engine.quad_pipeline.prepare( +                    device, +                    encoder, +                    &mut engine.staging_belt, +                    &layer.quads, +                    viewport.projection(), +                    scale_factor, +                ); +            } + +            if !layer.triangles.is_empty() { +                engine.triangle_pipeline.prepare( +                    device, +                    encoder, +                    &mut engine.staging_belt, +                    &mut self.triangle_storage, +                    &layer.triangles, +                    Transformation::scale(scale_factor), +                    viewport.physical_size(), +                ); +            } + +            if !layer.primitives.is_empty() { +                for instance in &layer.primitives { +                    instance.primitive.prepare( +                        device, +                        queue, +                        engine.format, +                        &mut engine.primitive_storage, +                        &instance.bounds, +                        viewport, +                    ); +                } +            } + +            if !layer.text.is_empty() { +                engine.text_pipeline.prepare( +                    device, +                    queue, +                    &self.text_viewport, +                    encoder, +                    &mut self.text_storage, +                    &layer.text, +                    layer.bounds, +                    Transformation::scale(scale_factor), +                ); +            } + +            #[cfg(any(feature = "svg", feature = "image"))] +            if !layer.images.is_empty() { +                engine.image_pipeline.prepare( +                    device, +                    encoder, +                    &mut engine.staging_belt, +                    &mut self.image_cache.borrow_mut(), +                    &layer.images, +                    viewport.projection(), +                    scale_factor, +                ); +            } +        } +    } + +    fn render( +        &mut self, +        engine: &mut Engine, +        encoder: &mut wgpu::CommandEncoder, +        frame: &wgpu::TextureView, +        clear_color: Option<Color>, +        viewport: &Viewport, +    ) { +        use std::mem::ManuallyDrop; + +        let mut render_pass = ManuallyDrop::new(encoder.begin_render_pass( +            &wgpu::RenderPassDescriptor { +                label: Some("iced_wgpu render pass"), +                color_attachments: &[Some(wgpu::RenderPassColorAttachment { +                    view: frame, +                    resolve_target: None, +                    ops: wgpu::Operations { +                        load: match clear_color { +                            Some(background_color) => wgpu::LoadOp::Clear({ +                                let [r, g, b, a] = +                                    graphics::color::pack(background_color) +                                        .components(); + +                                wgpu::Color { +                                    r: f64::from(r), +                                    g: f64::from(g), +                                    b: f64::from(b), +                                    a: f64::from(a), +                                } +                            }), +                            None => wgpu::LoadOp::Load, +                        }, +                        store: wgpu::StoreOp::Store, +                    }, +                })], +                depth_stencil_attachment: None, +                timestamp_writes: None, +                occlusion_query_set: None, +            }, +        )); + +        let mut quad_layer = 0; +        let mut mesh_layer = 0; +        let mut text_layer = 0; + +        #[cfg(any(feature = "svg", feature = "image"))] +        let mut image_layer = 0; +        #[cfg(any(feature = "svg", feature = "image"))] +        let image_cache = self.image_cache.borrow(); + +        let scale_factor = viewport.scale_factor() as f32; +        let physical_bounds = Rectangle::<f32>::from(Rectangle::with_size( +            viewport.physical_size(), +        )); + +        let scale = Transformation::scale(scale_factor); + +        for layer in self.layers.iter() { +            let Some(physical_bounds) = +                physical_bounds.intersection(&(layer.bounds * scale)) +            else { +                continue; +            }; + +            let Some(scissor_rect) = physical_bounds.snap() else { +                continue; +            }; + +            if !layer.quads.is_empty() { +                engine.quad_pipeline.render( +                    quad_layer, +                    scissor_rect, +                    &layer.quads, +                    &mut render_pass, +                ); + +                quad_layer += 1; +            } + +            if !layer.triangles.is_empty() { +                let _ = ManuallyDrop::into_inner(render_pass); + +                mesh_layer += engine.triangle_pipeline.render( +                    encoder, +                    frame, +                    &self.triangle_storage, +                    mesh_layer, +                    &layer.triangles, +                    physical_bounds, +                    scale, +                ); + +                render_pass = ManuallyDrop::new(encoder.begin_render_pass( +                    &wgpu::RenderPassDescriptor { +                        label: Some("iced_wgpu render pass"), +                        color_attachments: &[Some( +                            wgpu::RenderPassColorAttachment { +                                view: frame, +                                resolve_target: None, +                                ops: wgpu::Operations { +                                    load: wgpu::LoadOp::Load, +                                    store: wgpu::StoreOp::Store, +                                }, +                            }, +                        )], +                        depth_stencil_attachment: None, +                        timestamp_writes: None, +                        occlusion_query_set: None, +                    }, +                )); +            } + +            if !layer.primitives.is_empty() { +                let _ = ManuallyDrop::into_inner(render_pass); + +                for instance in &layer.primitives { +                    if let Some(clip_bounds) = (instance.bounds * scale) +                        .intersection(&physical_bounds) +                        .and_then(Rectangle::snap) +                    { +                        instance.primitive.render( +                            encoder, +                            &engine.primitive_storage, +                            frame, +                            &clip_bounds, +                        ); +                    } +                } + +                render_pass = ManuallyDrop::new(encoder.begin_render_pass( +                    &wgpu::RenderPassDescriptor { +                        label: Some("iced_wgpu render pass"), +                        color_attachments: &[Some( +                            wgpu::RenderPassColorAttachment { +                                view: frame, +                                resolve_target: None, +                                ops: wgpu::Operations { +                                    load: wgpu::LoadOp::Load, +                                    store: wgpu::StoreOp::Store, +                                }, +                            }, +                        )], +                        depth_stencil_attachment: None, +                        timestamp_writes: None, +                        occlusion_query_set: None, +                    }, +                )); +            } + +            if !layer.text.is_empty() { +                text_layer += engine.text_pipeline.render( +                    &self.text_viewport, +                    &self.text_storage, +                    text_layer, +                    &layer.text, +                    scissor_rect, +                    &mut render_pass, +                ); +            } + +            #[cfg(any(feature = "svg", feature = "image"))] +            if !layer.images.is_empty() { +                engine.image_pipeline.render( +                    &image_cache, +                    image_layer, +                    scissor_rect, +                    &mut render_pass, +                ); + +                image_layer += 1; +            } +        } + +        let _ = ManuallyDrop::into_inner(render_pass); +    } + +    fn draw_overlay( +        &mut self, +        overlay: &[impl AsRef<str>], +        viewport: &Viewport, +    ) { +        use crate::core::alignment; +        use crate::core::text::Renderer as _; +        use crate::core::Renderer as _; + +        self.with_layer( +            Rectangle::with_size(viewport.logical_size()), +            |renderer| { +                for (i, line) in overlay.iter().enumerate() { +                    let text = crate::core::Text { +                        content: line.as_ref().to_owned(), +                        bounds: viewport.logical_size(), +                        size: Pixels(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, +                    }; + +                    renderer.fill_text( +                        text.clone(), +                        Point::new(11.0, 11.0 + 25.0 * i as f32), +                        Color::new(0.9, 0.9, 0.9, 1.0), +                        Rectangle::with_size(Size::INFINITY), +                    ); + +                    renderer.fill_text( +                        text, +                        Point::new(11.0, 11.0 + 25.0 * i as f32) +                            + Vector::new(-1.0, -1.0), +                        Color::BLACK, +                        Rectangle::with_size(Size::INFINITY), +                    ); +                } +            }, +        ); +    } +} + +impl core::Renderer for Renderer { +    fn start_layer(&mut self, bounds: Rectangle) { +        self.layers.push_clip(bounds); +    } + +    fn end_layer(&mut self) { +        self.layers.pop_clip(); +    } + +    fn start_transformation(&mut self, transformation: Transformation) { +        self.layers.push_transformation(transformation); +    } + +    fn end_transformation(&mut self) { +        self.layers.pop_transformation(); +    } + +    fn fill_quad( +        &mut self, +        quad: core::renderer::Quad, +        background: impl Into<Background>, +    ) { +        let (layer, transformation) = self.layers.current_mut(); +        layer.draw_quad(quad, background.into(), transformation); +    } + +    fn clear(&mut self) { +        self.layers.clear(); +    } +} + +impl core::text::Renderer for Renderer { +    type Font = Font; +    type Paragraph = Paragraph; +    type Editor = Editor; + +    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) -> Self::Font { +        self.default_font +    } + +    fn default_size(&self) -> Pixels { +        self.default_text_size +    } + +    fn fill_paragraph( +        &mut self, +        text: &Self::Paragraph, +        position: Point, +        color: Color, +        clip_bounds: Rectangle, +    ) { +        let (layer, transformation) = self.layers.current_mut(); + +        layer.draw_paragraph( +            text, +            position, +            color, +            clip_bounds, +            transformation, +        ); +    } + +    fn fill_editor( +        &mut self, +        editor: &Self::Editor, +        position: Point, +        color: Color, +        clip_bounds: Rectangle, +    ) { +        let (layer, transformation) = self.layers.current_mut(); +        layer.draw_editor(editor, position, color, clip_bounds, transformation); +    } + +    fn fill_text( +        &mut self, +        text: core::Text, +        position: Point, +        color: Color, +        clip_bounds: Rectangle, +    ) { +        let (layer, transformation) = self.layers.current_mut(); +        layer.draw_text(text, position, color, clip_bounds, transformation); +    } +} + +#[cfg(feature = "image")] +impl core::image::Renderer for Renderer { +    type Handle = core::image::Handle; + +    fn measure_image(&self, handle: &Self::Handle) -> Size<u32> { +        self.image_cache.borrow_mut().measure_image(handle) +    } + +    fn draw_image( +        &mut self, +        handle: Self::Handle, +        filter_method: core::image::FilterMethod, +        bounds: Rectangle, +        rotation: core::Radians, +        opacity: f32, +    ) { +        let (layer, transformation) = self.layers.current_mut(); +        layer.draw_image( +            handle, +            filter_method, +            bounds, +            transformation, +            rotation, +            opacity, +        ); +    } +} + +#[cfg(feature = "svg")] +impl core::svg::Renderer for Renderer { +    fn measure_svg(&self, handle: &core::svg::Handle) -> Size<u32> { +        self.image_cache.borrow_mut().measure_svg(handle) +    } + +    fn draw_svg( +        &mut self, +        handle: core::svg::Handle, +        color_filter: Option<Color>, +        bounds: Rectangle, +        rotation: core::Radians, +        opacity: f32, +    ) { +        let (layer, transformation) = self.layers.current_mut(); +        layer.draw_svg( +            handle, +            color_filter, +            bounds, +            transformation, +            rotation, +            opacity, +        ); +    } +} + +impl graphics::mesh::Renderer for Renderer { +    fn draw_mesh(&mut self, mesh: graphics::Mesh) { +        let (layer, transformation) = self.layers.current_mut(); +        layer.draw_mesh(mesh, transformation); +    } +} + +#[cfg(feature = "geometry")] +impl graphics::geometry::Renderer for Renderer { +    type Geometry = Geometry; +    type Frame = geometry::Frame; + +    fn new_frame(&self, size: Size) -> Self::Frame { +        geometry::Frame::new(size) +    } + +    fn draw_geometry(&mut self, geometry: Self::Geometry) { +        let (layer, transformation) = self.layers.current_mut(); + +        match geometry { +            Geometry::Live { meshes, text } => { +                layer.draw_mesh_group(meshes, transformation); +                layer.draw_text_group(text, transformation); +            } +            Geometry::Cached(cache) => { +                if let Some(meshes) = cache.meshes { +                    layer.draw_mesh_cache(meshes, transformation); +                } + +                if let Some(text) = cache.text { +                    layer.draw_text_cache(text, transformation); +                } +            } +        } +    } +} + +impl primitive::Renderer for Renderer { +    fn draw_primitive(&mut self, bounds: Rectangle, primitive: impl Primitive) { +        let (layer, transformation) = self.layers.current_mut(); +        layer.draw_primitive(bounds, Box::new(primitive), transformation); +    } +} + +impl graphics::compositor::Default for crate::Renderer { +    type Compositor = window::Compositor; +} diff --git a/wgpu/src/primitive.rs b/wgpu/src/primitive.rs index fff927ea..8641f27a 100644 --- a/wgpu/src/primitive.rs +++ b/wgpu/src/primitive.rs @@ -1,30 +1,95 @@ -//! Draw using different graphical primitives. -pub mod pipeline; +//! Draw custom primitives. +use crate::core::{self, Rectangle}; +use crate::graphics::Viewport; -pub use pipeline::Pipeline; +use rustc_hash::FxHashMap; +use std::any::{Any, TypeId}; +use std::fmt::Debug; -use crate::core::Rectangle; -use crate::graphics::{Damage, Mesh}; +/// A batch of primitives. +pub type Batch = Vec<Instance>; -use std::fmt::Debug; +/// A set of methods which allows a [`Primitive`] to be rendered. +pub trait Primitive: Debug + Send + Sync + 'static { +    /// Processes the [`Primitive`], allowing for GPU buffer allocation. +    fn prepare( +        &self, +        device: &wgpu::Device, +        queue: &wgpu::Queue, +        format: wgpu::TextureFormat, +        storage: &mut Storage, +        bounds: &Rectangle, +        viewport: &Viewport, +    ); -/// The graphical primitives supported by `iced_wgpu`. -pub type Primitive = crate::graphics::Primitive<Custom>; +    /// Renders the [`Primitive`]. +    fn render( +        &self, +        encoder: &mut wgpu::CommandEncoder, +        storage: &Storage, +        target: &wgpu::TextureView, +        clip_bounds: &Rectangle<u32>, +    ); +} -/// The custom primitives supported by `iced_wgpu`. -#[derive(Debug, Clone, PartialEq)] -pub enum Custom { -    /// A mesh primitive. -    Mesh(Mesh), -    /// A custom pipeline primitive. -    Pipeline(Pipeline), +#[derive(Debug)] +/// An instance of a specific [`Primitive`]. +pub struct Instance { +    /// The bounds of the [`Instance`]. +    pub bounds: Rectangle, + +    /// The [`Primitive`] to render. +    pub primitive: Box<dyn Primitive>,  } -impl Damage for Custom { -    fn bounds(&self) -> Rectangle { -        match self { -            Self::Mesh(mesh) => mesh.bounds(), -            Self::Pipeline(pipeline) => pipeline.bounds, +impl Instance { +    /// Creates a new [`Instance`] with the given [`Primitive`]. +    pub fn new(bounds: Rectangle, primitive: impl Primitive) -> Self { +        Instance { +            bounds, +            primitive: Box::new(primitive),          }      }  } + +/// A renderer than can draw custom primitives. +pub trait Renderer: core::Renderer { +    /// Draws a custom primitive. +    fn draw_primitive(&mut self, bounds: Rectangle, primitive: impl Primitive); +} + +/// Stores custom, user-provided types. +#[derive(Default, Debug)] +pub struct Storage { +    pipelines: FxHashMap<TypeId, Box<dyn Any + Send>>, +} + +impl Storage { +    /// Returns `true` if `Storage` contains a type `T`. +    pub fn has<T: 'static>(&self) -> bool { +        self.pipelines.contains_key(&TypeId::of::<T>()) +    } + +    /// Inserts the data `T` in to [`Storage`]. +    pub fn store<T: 'static + Send>(&mut self, data: T) { +        let _ = self.pipelines.insert(TypeId::of::<T>(), Box::new(data)); +    } + +    /// Returns a reference to the data with type `T` if it exists in [`Storage`]. +    pub fn get<T: 'static>(&self) -> Option<&T> { +        self.pipelines.get(&TypeId::of::<T>()).map(|pipeline| { +            pipeline +                .downcast_ref::<T>() +                .expect("Value with this type does not exist in Storage.") +        }) +    } + +    /// Returns a mutable reference to the data with type `T` if it exists in [`Storage`]. +    pub fn get_mut<T: 'static>(&mut self) -> Option<&mut T> { +        self.pipelines.get_mut(&TypeId::of::<T>()).map(|pipeline| { +            pipeline +                .downcast_mut::<T>() +                .expect("Value with this type does not exist in Storage.") +        }) +    } +} diff --git a/wgpu/src/primitive/pipeline.rs b/wgpu/src/primitive/pipeline.rs deleted file mode 100644 index c6b7c5e2..00000000 --- a/wgpu/src/primitive/pipeline.rs +++ /dev/null @@ -1,116 +0,0 @@ -//! Draw primitives using custom pipelines. -use crate::core::{Rectangle, Size}; - -use std::any::{Any, TypeId}; -use std::collections::HashMap; -use std::fmt::Debug; -use std::sync::Arc; - -#[derive(Clone, Debug)] -/// A custom primitive which can be used to render primitives associated with a custom pipeline. -pub struct Pipeline { -    /// The bounds of the [`Pipeline`]. -    pub bounds: Rectangle, - -    /// The [`Primitive`] to render. -    pub primitive: Arc<dyn Primitive>, -} - -impl Pipeline { -    /// Creates a new [`Pipeline`] with the given [`Primitive`]. -    pub fn new(bounds: Rectangle, primitive: impl Primitive) -> Self { -        Pipeline { -            bounds, -            primitive: Arc::new(primitive), -        } -    } -} - -impl PartialEq for Pipeline { -    fn eq(&self, other: &Self) -> bool { -        self.primitive.type_id() == other.primitive.type_id() -    } -} - -/// A set of methods which allows a [`Primitive`] to be rendered. -pub trait Primitive: Debug + Send + Sync + 'static { -    /// Processes the [`Primitive`], allowing for GPU buffer allocation. -    fn prepare( -        &self, -        format: wgpu::TextureFormat, -        device: &wgpu::Device, -        queue: &wgpu::Queue, -        bounds: Rectangle, -        target_size: Size<u32>, -        scale_factor: f32, -        storage: &mut Storage, -    ); - -    /// Renders the [`Primitive`]. -    fn render( -        &self, -        storage: &Storage, -        target: &wgpu::TextureView, -        target_size: Size<u32>, -        viewport: Rectangle<u32>, -        encoder: &mut wgpu::CommandEncoder, -    ); -} - -/// A renderer than can draw custom pipeline primitives. -pub trait Renderer: crate::core::Renderer { -    /// Draws a custom pipeline primitive. -    fn draw_pipeline_primitive( -        &mut self, -        bounds: Rectangle, -        primitive: impl Primitive, -    ); -} - -impl Renderer for crate::Renderer { -    fn draw_pipeline_primitive( -        &mut self, -        bounds: Rectangle, -        primitive: impl Primitive, -    ) { -        self.draw_primitive(super::Primitive::Custom(super::Custom::Pipeline( -            Pipeline::new(bounds, primitive), -        ))); -    } -} - -/// Stores custom, user-provided pipelines. -#[derive(Default, Debug)] -pub struct Storage { -    pipelines: HashMap<TypeId, Box<dyn Any + Send>>, -} - -impl Storage { -    /// Returns `true` if `Storage` contains a pipeline with type `T`. -    pub fn has<T: 'static>(&self) -> bool { -        self.pipelines.get(&TypeId::of::<T>()).is_some() -    } - -    /// Inserts the pipeline `T` in to [`Storage`]. -    pub fn store<T: 'static + Send>(&mut self, pipeline: T) { -        let _ = self.pipelines.insert(TypeId::of::<T>(), Box::new(pipeline)); -    } - -    /// Returns a reference to pipeline with type `T` if it exists in [`Storage`]. -    pub fn get<T: 'static>(&self) -> Option<&T> { -        self.pipelines.get(&TypeId::of::<T>()).map(|pipeline| { -            pipeline -                .downcast_ref::<T>() -                .expect("Pipeline with this type does not exist in Storage.") -        }) -    } - -    /// Returns a mutable reference to pipeline `T` if it exists in [`Storage`]. -    pub fn get_mut<T: 'static>(&mut self) -> Option<&mut T> { -        self.pipelines.get_mut(&TypeId::of::<T>()).map(|pipeline| { -            pipeline -                .downcast_mut::<T>() -                .expect("Pipeline with this type does not exist in Storage.") -        }) -    } -} diff --git a/wgpu/src/quad.rs b/wgpu/src/quad.rs index b932f54f..de432d2f 100644 --- a/wgpu/src/quad.rs +++ b/wgpu/src/quad.rs @@ -12,11 +12,37 @@ use bytemuck::{Pod, Zeroable};  use std::mem; -#[cfg(feature = "tracing")] -use tracing::info_span; -  const INITIAL_INSTANCES: usize = 2_000; +/// The properties of a quad. +#[derive(Clone, Copy, Debug, Pod, Zeroable)] +#[repr(C)] +pub struct Quad { +    /// The position of the [`Quad`]. +    pub position: [f32; 2], + +    /// The size of the [`Quad`]. +    pub size: [f32; 2], + +    /// The border color of the [`Quad`], in __linear RGB__. +    pub border_color: color::Packed, + +    /// The border radii of the [`Quad`]. +    pub border_radius: [f32; 4], + +    /// The border width of the [`Quad`]. +    pub border_width: f32, + +    /// The shadow color of the [`Quad`]. +    pub shadow_color: color::Packed, + +    /// The shadow offset of the [`Quad`]. +    pub shadow_offset: [f32; 2], + +    /// The shadow blur radius of the [`Quad`]. +    pub shadow_blur_radius: f32, +} +  #[derive(Debug)]  pub struct Pipeline {      solid: solid::Pipeline, @@ -57,7 +83,8 @@ impl Pipeline {      pub fn prepare(          &mut self,          device: &wgpu::Device, -        queue: &wgpu::Queue, +        encoder: &mut wgpu::CommandEncoder, +        belt: &mut wgpu::util::StagingBelt,          quads: &Batch,          transformation: Transformation,          scale: f32, @@ -67,7 +94,7 @@ impl Pipeline {          }          let layer = &mut self.layers[self.prepare_layer]; -        layer.prepare(device, queue, quads, transformation, scale); +        layer.prepare(device, encoder, belt, quads, transformation, scale);          self.prepare_layer += 1;      } @@ -123,7 +150,7 @@ impl Pipeline {  }  #[derive(Debug)] -struct Layer { +pub struct Layer {      constants: wgpu::BindGroup,      constants_buffer: wgpu::Buffer,      solid: solid::Layer, @@ -162,56 +189,46 @@ impl Layer {      pub fn prepare(          &mut self,          device: &wgpu::Device, -        queue: &wgpu::Queue, +        encoder: &mut wgpu::CommandEncoder, +        belt: &mut wgpu::util::StagingBelt,          quads: &Batch,          transformation: Transformation,          scale: f32,      ) { -        #[cfg(feature = "tracing")] -        let _ = info_span!("Wgpu::Quad", "PREPARE").entered(); +        self.update(device, encoder, belt, transformation, scale); +        if !quads.solids.is_empty() { +            self.solid.prepare(device, encoder, belt, &quads.solids); +        } + +        if !quads.gradients.is_empty() { +            self.gradient +                .prepare(device, encoder, belt, &quads.gradients); +        } +    } + +    pub fn update( +        &mut self, +        device: &wgpu::Device, +        encoder: &mut wgpu::CommandEncoder, +        belt: &mut wgpu::util::StagingBelt, +        transformation: Transformation, +        scale: f32, +    ) {          let uniforms = Uniforms::new(transformation, scale); +        let bytes = bytemuck::bytes_of(&uniforms); -        queue.write_buffer( +        belt.write_buffer( +            encoder,              &self.constants_buffer,              0, -            bytemuck::bytes_of(&uniforms), -        ); - -        self.solid.prepare(device, queue, &quads.solids); -        self.gradient.prepare(device, queue, &quads.gradients); +            (bytes.len() as u64).try_into().expect("Sized uniforms"), +            device, +        ) +        .copy_from_slice(bytes);      }  } -/// The properties of a quad. -#[derive(Clone, Copy, Debug, Pod, Zeroable)] -#[repr(C)] -pub struct Quad { -    /// The position of the [`Quad`]. -    pub position: [f32; 2], - -    /// The size of the [`Quad`]. -    pub size: [f32; 2], - -    /// The border color of the [`Quad`], in __linear RGB__. -    pub border_color: color::Packed, - -    /// The border radii of the [`Quad`]. -    pub border_radius: [f32; 4], - -    /// The border width of the [`Quad`]. -    pub border_width: f32, - -    /// The shadow color of the [`Quad`]. -    pub shadow_color: [f32; 4], - -    /// The shadow offset of the [`Quad`]. -    pub shadow_offset: [f32; 2], - -    /// The shadow blur radius of the [`Quad`]. -    pub shadow_blur_radius: f32, -} -  /// A group of [`Quad`]s rendered together.  #[derive(Default, Debug)]  pub struct Batch { @@ -221,10 +238,13 @@ pub struct Batch {      /// The gradient quads of the [`Layer`].      gradients: Vec<Gradient>, -    /// The quad order of the [`Layer`]; stored as a tuple of the quad type & its count. -    order: Vec<(Kind, usize)>, +    /// The quad order of the [`Layer`]. +    order: Order,  } +/// The quad order of a [`Layer`]; stored as a tuple of the quad type & its count. +type Order = Vec<(Kind, usize)>; +  impl Batch {      /// Returns true if there are no quads of any type in [`Quads`].      pub fn is_empty(&self) -> bool { @@ -264,6 +284,12 @@ impl Batch {              }          }      } + +    pub fn clear(&mut self) { +        self.solids.clear(); +        self.gradients.clear(); +        self.order.clear(); +    }  }  #[derive(Debug, Copy, Clone, PartialEq, Eq)] diff --git a/wgpu/src/quad/gradient.rs b/wgpu/src/quad/gradient.rs index 560fcad2..5b32c52a 100644 --- a/wgpu/src/quad/gradient.rs +++ b/wgpu/src/quad/gradient.rs @@ -46,11 +46,12 @@ impl Layer {      pub fn prepare(          &mut self,          device: &wgpu::Device, -        queue: &wgpu::Queue, +        encoder: &mut wgpu::CommandEncoder, +        belt: &mut wgpu::util::StagingBelt,          instances: &[Gradient],      ) {          let _ = self.instances.resize(device, instances.len()); -        let _ = self.instances.write(queue, 0, instances); +        let _ = self.instances.write(device, encoder, belt, 0, instances);          self.instance_count = instances.len();      } diff --git a/wgpu/src/quad/solid.rs b/wgpu/src/quad/solid.rs index 771eee34..1cead367 100644 --- a/wgpu/src/quad/solid.rs +++ b/wgpu/src/quad/solid.rs @@ -40,11 +40,12 @@ impl Layer {      pub fn prepare(          &mut self,          device: &wgpu::Device, -        queue: &wgpu::Queue, +        encoder: &mut wgpu::CommandEncoder, +        belt: &mut wgpu::util::StagingBelt,          instances: &[Solid],      ) {          let _ = self.instances.resize(device, instances.len()); -        let _ = self.instances.write(queue, 0, instances); +        let _ = self.instances.write(device, encoder, belt, 0, instances);          self.instance_count = instances.len();      } diff --git a/wgpu/src/settings.rs b/wgpu/src/settings.rs index c9338fec..b3c3cf6a 100644 --- a/wgpu/src/settings.rs +++ b/wgpu/src/settings.rs @@ -1,19 +1,19 @@  //! Configure a renderer.  use crate::core::{Font, Pixels}; -use crate::graphics::Antialiasing; +use crate::graphics::{self, Antialiasing}; -/// The settings of a [`Backend`]. +/// The settings of a [`Renderer`].  /// -/// [`Backend`]: crate::Backend +/// [`Renderer`]: crate::Renderer  #[derive(Debug, Clone, Copy, PartialEq)]  pub struct Settings { -    /// The present mode of the [`Backend`]. +    /// The present mode of the [`Renderer`].      /// -    /// [`Backend`]: crate::Backend +    /// [`Renderer`]: crate::Renderer      pub present_mode: wgpu::PresentMode, -    /// The internal graphics backend to use. -    pub internal_backend: wgpu::Backends, +    /// The graphics backends to use. +    pub backends: wgpu::Backends,      /// The default [`Font`] to use.      pub default_font: Font, @@ -29,38 +29,51 @@ pub struct Settings {      pub antialiasing: Option<Antialiasing>,  } -impl Settings { -    /// Creates new [`Settings`] using environment configuration. -    /// -    /// Specifically: -    /// -    /// - The `internal_backend` can be configured using the `WGPU_BACKEND` -    /// environment variable. If the variable is not set, the primary backend -    /// will be used. The following values are allowed: -    ///     - `vulkan` -    ///     - `metal` -    ///     - `dx12` -    ///     - `dx11` -    ///     - `gl` -    ///     - `webgpu` -    ///     - `primary` -    pub fn from_env() -> Self { -        Settings { -            internal_backend: wgpu::util::backend_bits_from_env() -                .unwrap_or(wgpu::Backends::all()), -            ..Self::default() -        } -    } -} -  impl Default for Settings {      fn default() -> Settings {          Settings {              present_mode: wgpu::PresentMode::AutoVsync, -            internal_backend: wgpu::Backends::all(), +            backends: wgpu::Backends::all(),              default_font: Font::default(),              default_text_size: Pixels(16.0),              antialiasing: None,          }      }  } + +impl From<graphics::Settings> for Settings { +    fn from(settings: graphics::Settings) -> Self { +        Self { +            default_font: settings.default_font, +            default_text_size: settings.default_text_size, +            antialiasing: settings.antialiasing, +            ..Settings::default() +        } +    } +} + +/// Obtains a [`wgpu::PresentMode`] from the current environment +/// configuration, if set. +/// +/// The value returned by this function can be changed by setting +/// the `ICED_PRESENT_MODE` env variable. The possible values are: +/// +/// - `vsync` → [`wgpu::PresentMode::AutoVsync`] +/// - `no_vsync` → [`wgpu::PresentMode::AutoNoVsync`] +/// - `immediate` → [`wgpu::PresentMode::Immediate`] +/// - `fifo` → [`wgpu::PresentMode::Fifo`] +/// - `fifo_relaxed` → [`wgpu::PresentMode::FifoRelaxed`] +/// - `mailbox` → [`wgpu::PresentMode::Mailbox`] +pub fn present_mode_from_env() -> Option<wgpu::PresentMode> { +    let present_mode = std::env::var("ICED_PRESENT_MODE").ok()?; + +    match present_mode.to_lowercase().as_str() { +        "vsync" => Some(wgpu::PresentMode::AutoVsync), +        "no_vsync" => Some(wgpu::PresentMode::AutoNoVsync), +        "immediate" => Some(wgpu::PresentMode::Immediate), +        "fifo" => Some(wgpu::PresentMode::Fifo), +        "fifo_relaxed" => Some(wgpu::PresentMode::FifoRelaxed), +        "mailbox" => Some(wgpu::PresentMode::Mailbox), +        _ => None, +    } +} diff --git a/wgpu/src/shader/blit.wgsl b/wgpu/src/shader/blit.wgsl index c2ea223f..d7633808 100644 --- a/wgpu/src/shader/blit.wgsl +++ b/wgpu/src/shader/blit.wgsl @@ -1,22 +1,14 @@ -var<private> positions: array<vec2<f32>, 6> = array<vec2<f32>, 6>( -    vec2<f32>(-1.0, 1.0), -    vec2<f32>(-1.0, -1.0), -    vec2<f32>(1.0, -1.0), -    vec2<f32>(-1.0, 1.0), -    vec2<f32>(1.0, 1.0), -    vec2<f32>(1.0, -1.0) -); -  var<private> uvs: array<vec2<f32>, 6> = array<vec2<f32>, 6>(      vec2<f32>(0.0, 0.0), -    vec2<f32>(0.0, 1.0), +    vec2<f32>(1.0, 0.0),      vec2<f32>(1.0, 1.0),      vec2<f32>(0.0, 0.0), -    vec2<f32>(1.0, 0.0), +    vec2<f32>(0.0, 1.0),      vec2<f32>(1.0, 1.0)  );  @group(0) @binding(0) var u_sampler: sampler; +@group(0) @binding(1) var<uniform> u_ratio: vec2<f32>;  @group(1) @binding(0) var u_texture: texture_2d<f32>;  struct VertexInput { @@ -30,9 +22,11 @@ struct VertexOutput {  @vertex  fn vs_main(input: VertexInput) -> VertexOutput { +    let uv = uvs[input.vertex_index]; +      var out: VertexOutput; -    out.uv = uvs[input.vertex_index]; -    out.position = vec4<f32>(positions[input.vertex_index], 0.0, 1.0); +    out.uv = uv * u_ratio; +    out.position = vec4<f32>(uv * vec2(2.0, -2.0) + vec2(-1.0, 1.0), 0.0, 1.0);      return out;  } diff --git a/wgpu/src/shader/image.wgsl b/wgpu/src/shader/image.wgsl index 7b2e5238..0eeb100f 100644 --- a/wgpu/src/shader/image.wgsl +++ b/wgpu/src/shader/image.wgsl @@ -9,40 +9,55 @@ struct Globals {  struct VertexInput {      @builtin(vertex_index) vertex_index: u32,      @location(0) pos: vec2<f32>, -    @location(1) scale: vec2<f32>, -    @location(2) atlas_pos: vec2<f32>, -    @location(3) atlas_scale: vec2<f32>, -    @location(4) layer: i32, +    @location(1) center: vec2<f32>, +    @location(2) scale: vec2<f32>, +    @location(3) rotation: f32, +    @location(4) opacity: f32, +    @location(5) atlas_pos: vec2<f32>, +    @location(6) atlas_scale: vec2<f32>, +    @location(7) layer: i32,  }  struct VertexOutput {      @builtin(position) position: vec4<f32>,      @location(0) uv: vec2<f32>,      @location(1) layer: f32, // this should be an i32, but naga currently reads that as requiring interpolation. +    @location(2) opacity: f32,  }  @vertex  fn vs_main(input: VertexInput) -> VertexOutput {      var out: VertexOutput; -    let v_pos = vertex_position(input.vertex_index); +    // Generate a vertex position in the range [0, 1] from the vertex index. +    var v_pos = vertex_position(input.vertex_index); +    // Map the vertex position to the atlas texture.      out.uv = vec2<f32>(v_pos * input.atlas_scale + input.atlas_pos);      out.layer = f32(input.layer); +    out.opacity = input.opacity; -    var transform: mat4x4<f32> = mat4x4<f32>( -        vec4<f32>(input.scale.x, 0.0, 0.0, 0.0), -        vec4<f32>(0.0, input.scale.y, 0.0, 0.0), +    // Calculate the vertex position and move the center to the origin +    v_pos = round(input.pos) + v_pos * input.scale - input.center; + +    // Apply the rotation around the center of the image +    let cos_rot = cos(input.rotation); +    let sin_rot = sin(input.rotation); +    let rotate = mat4x4<f32>( +        vec4<f32>(cos_rot, sin_rot, 0.0, 0.0), +        vec4<f32>(-sin_rot, cos_rot, 0.0, 0.0),          vec4<f32>(0.0, 0.0, 1.0, 0.0), -        vec4<f32>(input.pos, 0.0, 1.0) +        vec4<f32>(0.0, 0.0, 0.0, 1.0)      ); -    out.position = globals.transform * transform * vec4<f32>(v_pos, 0.0, 1.0); +    // Calculate the final position of the vertex +    out.position = globals.transform * (vec4<f32>(input.center, 0.0, 0.0) + rotate * vec4<f32>(v_pos, 0.0, 1.0));      return out;  }  @fragment  fn fs_main(input: VertexOutput) -> @location(0) vec4<f32> { -    return textureSample(u_texture, u_sampler, input.uv, i32(input.layer)); +    // Sample the texture at the given UV coordinate and layer. +    return textureSample(u_texture, u_sampler, input.uv, i32(input.layer)) * vec4<f32>(1.0, 1.0, 1.0, input.opacity);  } diff --git a/wgpu/src/shader/quad/solid.wgsl b/wgpu/src/shader/quad/solid.wgsl index 1274f814..d908afbc 100644 --- a/wgpu/src/shader/quad/solid.wgsl +++ b/wgpu/src/shader/quad/solid.wgsl @@ -107,13 +107,19 @@ fn solid_fs_main(      let quad_color = vec4<f32>(mixed_color.x, mixed_color.y, mixed_color.z, mixed_color.w * radius_alpha);      if input.shadow_color.a > 0.0 { -        let shadow_distance = rounded_box_sdf(input.position.xy - input.pos - input.shadow_offset - (input.scale / 2.0), input.scale / 2.0, border_radius); +        let shadow_radius = select_border_radius( +            input.border_radius, +            input.position.xy - input.shadow_offset, +            (input.pos + input.scale * 0.5).xy +        ); +        let shadow_distance = max(rounded_box_sdf(input.position.xy - input.pos - input.shadow_offset - (input.scale / 2.0), input.scale / 2.0, shadow_radius), 0.); +                  let shadow_alpha = 1.0 - smoothstep(-input.shadow_blur_radius, input.shadow_blur_radius, shadow_distance);          let shadow_color = input.shadow_color; -        let base_color = select( +        let base_color = mix(              vec4<f32>(shadow_color.x, shadow_color.y, shadow_color.z, 0.0),              quad_color, -            quad_color.a > 0.0 +            quad_color.a          );          return mix(base_color, shadow_color, (1.0 - radius_alpha) * shadow_alpha); diff --git a/wgpu/src/text.rs b/wgpu/src/text.rs index 6fa1922d..05db5f80 100644 --- a/wgpu/src/text.rs +++ b/wgpu/src/text.rs @@ -1,296 +1,389 @@  use crate::core::alignment;  use crate::core::{Rectangle, Size, Transformation}; +use crate::graphics::cache;  use crate::graphics::color; -use crate::graphics::text::cache::{self, Cache}; +use crate::graphics::text::cache::{self as text_cache, Cache as BufferCache};  use crate::graphics::text::{font_system, to_color, Editor, Paragraph}; -use crate::layer::Text; -use std::borrow::Cow; -use std::cell::RefCell; +use rustc_hash::FxHashMap; +use std::collections::hash_map; +use std::rc::{self, Rc}; +use std::sync::atomic::{self, AtomicU64};  use std::sync::Arc; -#[allow(missing_debug_implementations)] -pub struct Pipeline { -    renderers: Vec<glyphon::TextRenderer>, -    atlas: glyphon::TextAtlas, -    prepare_layer: usize, -    cache: RefCell<Cache>, +pub use crate::graphics::Text; + +const COLOR_MODE: glyphon::ColorMode = if color::GAMMA_CORRECTION { +    glyphon::ColorMode::Accurate +} else { +    glyphon::ColorMode::Web +}; + +pub type Batch = Vec<Item>; + +#[derive(Debug)] +pub enum Item { +    Group { +        transformation: Transformation, +        text: Vec<Text>, +    }, +    Cached { +        transformation: Transformation, +        cache: Cache, +    },  } -impl Pipeline { -    pub fn new( -        device: &wgpu::Device, -        queue: &wgpu::Queue, -        format: wgpu::TextureFormat, -    ) -> Self { -        Pipeline { -            renderers: Vec::new(), -            atlas: glyphon::TextAtlas::with_color_mode( -                device, -                queue, -                format, -                if color::GAMMA_CORRECTION { -                    glyphon::ColorMode::Accurate -                } else { -                    glyphon::ColorMode::Web -                }, -            ), -            prepare_layer: 0, -            cache: RefCell::new(Cache::new()), +#[derive(Debug, Clone)] +pub struct Cache { +    id: Id, +    group: cache::Group, +    text: Rc<[Text]>, +    version: usize, +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub struct Id(u64); + +impl Cache { +    pub fn new(group: cache::Group, text: Vec<Text>) -> Option<Self> { +        static NEXT_ID: AtomicU64 = AtomicU64::new(0); + +        if text.is_empty() { +            return None; +        } + +        Some(Self { +            id: Id(NEXT_ID.fetch_add(1, atomic::Ordering::Relaxed)), +            group, +            text: Rc::from(text), +            version: 0, +        }) +    } + +    pub fn update(&mut self, text: Vec<Text>) { +        if self.text.is_empty() && text.is_empty() { +            return;          } + +        self.text = Rc::from(text); +        self.version += 1; +    } +} + +struct Upload { +    renderer: glyphon::TextRenderer, +    buffer_cache: BufferCache, +    transformation: Transformation, +    version: usize, +    group_version: usize, +    text: rc::Weak<[Text]>, +    _atlas: rc::Weak<()>, +} + +#[derive(Default)] +pub struct Storage { +    groups: FxHashMap<cache::Group, Group>, +    uploads: FxHashMap<Id, Upload>, +} + +struct Group { +    atlas: glyphon::TextAtlas, +    version: usize, +    should_trim: bool, +    handle: Rc<()>, // Keeps track of active uploads +} + +impl Storage { +    pub fn new() -> Self { +        Self::default()      } -    pub fn load_font(&mut self, bytes: Cow<'static, [u8]>) { -        font_system() -            .write() -            .expect("Write font system") -            .load_font(bytes); +    fn get(&self, cache: &Cache) -> Option<(&glyphon::TextAtlas, &Upload)> { +        if cache.text.is_empty() { +            return None; +        } -        self.cache = RefCell::new(Cache::new()); +        self.groups +            .get(&cache.group) +            .map(|group| &group.atlas) +            .zip(self.uploads.get(&cache.id))      } -    pub fn prepare( +    fn prepare(          &mut self,          device: &wgpu::Device,          queue: &wgpu::Queue, -        sections: &[Text<'_>], -        layer_bounds: Rectangle, -        scale_factor: f32, -        target_size: Size<u32>, +        viewport: &glyphon::Viewport, +        encoder: &mut wgpu::CommandEncoder, +        format: wgpu::TextureFormat, +        state: &glyphon::Cache, +        cache: &Cache, +        new_transformation: Transformation, +        bounds: Rectangle,      ) { -        if self.renderers.len() <= self.prepare_layer { -            self.renderers.push(glyphon::TextRenderer::new( -                &mut self.atlas, -                device, -                wgpu::MultisampleState::default(), -                None, -            )); -        } - -        let mut font_system = font_system().write().expect("Write font system"); -        let font_system = font_system.raw(); +        let group_count = self.groups.len(); + +        let group = self.groups.entry(cache.group).or_insert_with(|| { +            log::debug!( +                "New text atlas: {:?} (total: {})", +                cache.group, +                group_count + 1 +            ); + +            Group { +                atlas: glyphon::TextAtlas::with_color_mode( +                    device, queue, state, format, COLOR_MODE, +                ), +                version: 0, +                should_trim: false, +                handle: Rc::new(()), +            } +        }); + +        match self.uploads.entry(cache.id) { +            hash_map::Entry::Occupied(entry) => { +                let upload = entry.into_mut(); + +                if upload.version != cache.version +                    || upload.group_version != group.version +                    || upload.transformation != new_transformation +                { +                    if !cache.text.is_empty() { +                        let _ = prepare( +                            device, +                            queue, +                            viewport, +                            encoder, +                            &mut upload.renderer, +                            &mut group.atlas, +                            &mut upload.buffer_cache, +                            &cache.text, +                            bounds, +                            new_transformation, +                        ); +                    } -        let renderer = &mut self.renderers[self.prepare_layer]; -        let cache = self.cache.get_mut(); +                    // Only trim if glyphs have changed +                    group.should_trim = +                        group.should_trim || upload.version != cache.version; -        enum Allocation { -            Paragraph(Paragraph), -            Editor(Editor), -            Cache(cache::KeyHash), -            Raw(Arc<glyphon::Buffer>), -        } +                    upload.text = Rc::downgrade(&cache.text); +                    upload.version = cache.version; +                    upload.group_version = group.version; +                    upload.transformation = new_transformation; -        let allocations: Vec<_> = sections -            .iter() -            .map(|section| match section { -                Text::Paragraph { paragraph, .. } => { -                    paragraph.upgrade().map(Allocation::Paragraph) +                    upload.buffer_cache.trim();                  } -                Text::Editor { editor, .. } => { -                    editor.upgrade().map(Allocation::Editor) -                } -                Text::Cached(text) => { -                    let (key, _) = cache.allocate( -                        font_system, -                        cache::Key { -                            content: text.content, -                            size: text.size.into(), -                            line_height: f32::from( -                                text.line_height.to_absolute(text.size), -                            ), -                            font: text.font, -                            bounds: Size { -                                width: text.bounds.width, -                                height: text.bounds.height, -                            }, -                            shaping: text.shaping, -                        }, +            } +            hash_map::Entry::Vacant(entry) => { +                let mut renderer = glyphon::TextRenderer::new( +                    &mut group.atlas, +                    device, +                    wgpu::MultisampleState::default(), +                    None, +                ); + +                let mut buffer_cache = BufferCache::new(); + +                if !cache.text.is_empty() { +                    let _ = prepare( +                        device, +                        queue, +                        viewport, +                        encoder, +                        &mut renderer, +                        &mut group.atlas, +                        &mut buffer_cache, +                        &cache.text, +                        bounds, +                        new_transformation,                      ); - -                    Some(Allocation::Cache(key)) -                } -                Text::Raw { raw, .. } => { -                    raw.buffer.upgrade().map(Allocation::Raw)                  } -            }) -            .collect(); -        let layer_bounds = layer_bounds * scale_factor; +                let _ = entry.insert(Upload { +                    renderer, +                    buffer_cache, +                    transformation: new_transformation, +                    version: 0, +                    group_version: group.version, +                    text: Rc::downgrade(&cache.text), +                    _atlas: Rc::downgrade(&group.handle), +                }); + +                group.should_trim = cache.group.is_singleton(); + +                log::debug!( +                    "New text upload: {} (total: {})", +                    cache.id.0, +                    self.uploads.len() +                ); +            } +        } +    } -        let text_areas = sections.iter().zip(allocations.iter()).filter_map( -            |(section, allocation)| { -                let ( -                    buffer, -                    bounds, -                    horizontal_alignment, -                    vertical_alignment, -                    color, -                    clip_bounds, -                    transformation, -                ) = match section { -                    Text::Paragraph { -                        position, -                        color, -                        clip_bounds, -                        transformation, -                        .. -                    } => { -                        use crate::core::text::Paragraph as _; - -                        let Some(Allocation::Paragraph(paragraph)) = allocation -                        else { -                            return None; -                        }; - -                        ( -                            paragraph.buffer(), -                            Rectangle::new(*position, paragraph.min_bounds()), -                            paragraph.horizontal_alignment(), -                            paragraph.vertical_alignment(), -                            *color, -                            *clip_bounds, -                            *transformation, -                        ) -                    } -                    Text::Editor { -                        position, -                        color, -                        clip_bounds, -                        transformation, -                        .. -                    } => { -                        use crate::core::text::Editor as _; - -                        let Some(Allocation::Editor(editor)) = allocation -                        else { -                            return None; -                        }; - -                        ( -                            editor.buffer(), -                            Rectangle::new(*position, editor.bounds()), -                            alignment::Horizontal::Left, -                            alignment::Vertical::Top, -                            *color, -                            *clip_bounds, -                            *transformation, -                        ) -                    } -                    Text::Cached(text) => { -                        let Some(Allocation::Cache(key)) = allocation else { -                            return None; -                        }; - -                        let entry = cache.get(key).expect("Get cached buffer"); - -                        ( -                            &entry.buffer, -                            Rectangle::new( -                                text.bounds.position(), -                                entry.min_bounds, -                            ), -                            text.horizontal_alignment, -                            text.vertical_alignment, -                            text.color, -                            text.clip_bounds, -                            Transformation::IDENTITY, -                        ) -                    } -                    Text::Raw { -                        raw, -                        transformation, -                    } => { -                        let Some(Allocation::Raw(buffer)) = allocation else { -                            return None; -                        }; - -                        let (width, height) = buffer.size(); - -                        ( -                            buffer.as_ref(), -                            Rectangle::new( -                                raw.position, -                                Size::new(width, height), -                            ), -                            alignment::Horizontal::Left, -                            alignment::Vertical::Top, -                            raw.color, -                            raw.clip_bounds, -                            *transformation, -                        ) -                    } -                }; +    pub fn trim(&mut self) { +        self.uploads +            .retain(|_id, upload| upload.text.strong_count() > 0); -                let bounds = bounds * transformation * scale_factor; +        self.groups.retain(|id, group| { +            let active_uploads = Rc::weak_count(&group.handle); -                let left = match horizontal_alignment { -                    alignment::Horizontal::Left => bounds.x, -                    alignment::Horizontal::Center => { -                        bounds.x - bounds.width / 2.0 -                    } -                    alignment::Horizontal::Right => bounds.x - bounds.width, -                }; +            if active_uploads == 0 { +                log::debug!("Dropping text atlas: {id:?}"); -                let top = match vertical_alignment { -                    alignment::Vertical::Top => bounds.y, -                    alignment::Vertical::Center => { -                        bounds.y - bounds.height / 2.0 -                    } -                    alignment::Vertical::Bottom => bounds.y - bounds.height, -                }; - -                let clip_bounds = layer_bounds.intersection( -                    &(clip_bounds * transformation * scale_factor), -                )?; - -                Some(glyphon::TextArea { -                    buffer, -                    left, -                    top, -                    scale: scale_factor * transformation.scale_factor(), -                    bounds: glyphon::TextBounds { -                        left: clip_bounds.x as i32, -                        top: clip_bounds.y as i32, -                        right: (clip_bounds.x + clip_bounds.width) as i32, -                        bottom: (clip_bounds.y + clip_bounds.height) as i32, -                    }, -                    default_color: to_color(color), -                }) -            }, -        ); +                return false; +            } + +            if group.should_trim { +                log::trace!("Trimming text atlas: {id:?}"); + +                group.atlas.trim(); +                group.should_trim = false; + +                // We only need to worry about glyph fighting +                // when the atlas may be shared by multiple +                // uploads. +                if !id.is_singleton() { +                    log::debug!( +                        "Invalidating text atlas: {id:?} \ +                        (uploads: {active_uploads})" +                    ); + +                    group.version += 1; +                } +            } + +            true +        }); +    } +} + +pub struct Viewport(glyphon::Viewport); -        let result = renderer.prepare( -            device, +impl Viewport { +    pub fn update(&mut self, queue: &wgpu::Queue, resolution: Size<u32>) { +        self.0.update(              queue, -            font_system, -            &mut self.atlas,              glyphon::Resolution { -                width: target_size.width, -                height: target_size.height, +                width: resolution.width, +                height: resolution.height,              }, -            text_areas, -            &mut glyphon::SwashCache::new(),          ); +    } +} -        match result { -            Ok(()) => { -                self.prepare_layer += 1; -            } -            Err(glyphon::PrepareError::AtlasFull) => { -                // If the atlas cannot grow, then all bets are off. -                // Instead of panicking, we will just pray that the result -                // will be somewhat readable... +#[allow(missing_debug_implementations)] +pub struct Pipeline { +    state: glyphon::Cache, +    format: wgpu::TextureFormat, +    atlas: glyphon::TextAtlas, +    renderers: Vec<glyphon::TextRenderer>, +    prepare_layer: usize, +    cache: BufferCache, +} + +impl Pipeline { +    pub fn new( +        device: &wgpu::Device, +        queue: &wgpu::Queue, +        format: wgpu::TextureFormat, +    ) -> Self { +        let state = glyphon::Cache::new(device); +        let atlas = glyphon::TextAtlas::with_color_mode( +            device, queue, &state, format, COLOR_MODE, +        ); + +        Pipeline { +            state, +            format, +            renderers: Vec::new(), +            atlas, +            prepare_layer: 0, +            cache: BufferCache::new(), +        } +    } + +    pub fn prepare( +        &mut self, +        device: &wgpu::Device, +        queue: &wgpu::Queue, +        viewport: &Viewport, +        encoder: &mut wgpu::CommandEncoder, +        storage: &mut Storage, +        batch: &Batch, +        layer_bounds: Rectangle, +        layer_transformation: Transformation, +    ) { +        for item in batch { +            match item { +                Item::Group { +                    transformation, +                    text, +                } => { +                    if self.renderers.len() <= self.prepare_layer { +                        self.renderers.push(glyphon::TextRenderer::new( +                            &mut self.atlas, +                            device, +                            wgpu::MultisampleState::default(), +                            None, +                        )); +                    } + +                    let renderer = &mut self.renderers[self.prepare_layer]; +                    let result = prepare( +                        device, +                        queue, +                        &viewport.0, +                        encoder, +                        renderer, +                        &mut self.atlas, +                        &mut self.cache, +                        text, +                        layer_bounds * layer_transformation, +                        layer_transformation * *transformation, +                    ); + +                    match result { +                        Ok(()) => { +                            self.prepare_layer += 1; +                        } +                        Err(glyphon::PrepareError::AtlasFull) => { +                            // If the atlas cannot grow, then all bets are off. +                            // Instead of panicking, we will just pray that the result +                            // will be somewhat readable... +                        } +                    } +                } +                Item::Cached { +                    transformation, +                    cache, +                } => { +                    storage.prepare( +                        device, +                        queue, +                        &viewport.0, +                        encoder, +                        self.format, +                        &self.state, +                        cache, +                        layer_transformation * *transformation, +                        layer_bounds * layer_transformation, +                    ); +                }              }          }      }      pub fn render<'a>(          &'a self, -        layer: usize, +        viewport: &'a Viewport, +        storage: &'a Storage, +        start: usize, +        batch: &'a Batch,          bounds: Rectangle<u32>,          render_pass: &mut wgpu::RenderPass<'a>, -    ) { -        let renderer = &self.renderers[layer]; +    ) -> usize { +        let mut layer_count = 0;          render_pass.set_scissor_rect(              bounds.x, @@ -299,15 +392,252 @@ impl Pipeline {              bounds.height,          ); -        renderer -            .render(&self.atlas, render_pass) -            .expect("Render text"); +        for item in batch { +            match item { +                Item::Group { .. } => { +                    let renderer = &self.renderers[start + layer_count]; + +                    renderer +                        .render(&self.atlas, &viewport.0, render_pass) +                        .expect("Render text"); + +                    layer_count += 1; +                } +                Item::Cached { cache, .. } => { +                    if let Some((atlas, upload)) = storage.get(cache) { +                        upload +                            .renderer +                            .render(atlas, &viewport.0, render_pass) +                            .expect("Render cached text"); +                    } +                } +            } +        } + +        layer_count +    } + +    pub fn create_viewport(&self, device: &wgpu::Device) -> Viewport { +        Viewport(glyphon::Viewport::new(device, &self.state))      }      pub fn end_frame(&mut self) {          self.atlas.trim(); -        self.cache.get_mut().trim(); +        self.cache.trim();          self.prepare_layer = 0;      }  } + +fn prepare( +    device: &wgpu::Device, +    queue: &wgpu::Queue, +    viewport: &glyphon::Viewport, +    encoder: &mut wgpu::CommandEncoder, +    renderer: &mut glyphon::TextRenderer, +    atlas: &mut glyphon::TextAtlas, +    buffer_cache: &mut BufferCache, +    sections: &[Text], +    layer_bounds: Rectangle, +    layer_transformation: Transformation, +) -> Result<(), glyphon::PrepareError> { +    let mut font_system = font_system().write().expect("Write font system"); +    let font_system = font_system.raw(); + +    enum Allocation { +        Paragraph(Paragraph), +        Editor(Editor), +        Cache(text_cache::KeyHash), +        Raw(Arc<glyphon::Buffer>), +    } + +    let allocations: Vec<_> = sections +        .iter() +        .map(|section| match section { +            Text::Paragraph { paragraph, .. } => { +                paragraph.upgrade().map(Allocation::Paragraph) +            } +            Text::Editor { editor, .. } => { +                editor.upgrade().map(Allocation::Editor) +            } +            Text::Cached { +                content, +                bounds, +                size, +                line_height, +                font, +                shaping, +                .. +            } => { +                let (key, _) = buffer_cache.allocate( +                    font_system, +                    text_cache::Key { +                        content, +                        size: f32::from(*size), +                        line_height: f32::from(*line_height), +                        font: *font, +                        bounds: Size { +                            width: bounds.width, +                            height: bounds.height, +                        }, +                        shaping: *shaping, +                    }, +                ); + +                Some(Allocation::Cache(key)) +            } +            Text::Raw { raw, .. } => raw.buffer.upgrade().map(Allocation::Raw), +        }) +        .collect(); + +    let text_areas = sections.iter().zip(allocations.iter()).filter_map( +        |(section, allocation)| { +            let ( +                buffer, +                bounds, +                horizontal_alignment, +                vertical_alignment, +                color, +                clip_bounds, +                transformation, +            ) = match section { +                Text::Paragraph { +                    position, +                    color, +                    clip_bounds, +                    transformation, +                    .. +                } => { +                    use crate::core::text::Paragraph as _; + +                    let Some(Allocation::Paragraph(paragraph)) = allocation +                    else { +                        return None; +                    }; + +                    ( +                        paragraph.buffer(), +                        Rectangle::new(*position, paragraph.min_bounds()), +                        paragraph.horizontal_alignment(), +                        paragraph.vertical_alignment(), +                        *color, +                        *clip_bounds, +                        *transformation, +                    ) +                } +                Text::Editor { +                    position, +                    color, +                    clip_bounds, +                    transformation, +                    .. +                } => { +                    use crate::core::text::Editor as _; + +                    let Some(Allocation::Editor(editor)) = allocation else { +                        return None; +                    }; + +                    ( +                        editor.buffer(), +                        Rectangle::new(*position, editor.bounds()), +                        alignment::Horizontal::Left, +                        alignment::Vertical::Top, +                        *color, +                        *clip_bounds, +                        *transformation, +                    ) +                } +                Text::Cached { +                    bounds, +                    horizontal_alignment, +                    vertical_alignment, +                    color, +                    clip_bounds, +                    .. +                } => { +                    let Some(Allocation::Cache(key)) = allocation else { +                        return None; +                    }; + +                    let entry = +                        buffer_cache.get(key).expect("Get cached buffer"); + +                    ( +                        &entry.buffer, +                        Rectangle::new(bounds.position(), entry.min_bounds), +                        *horizontal_alignment, +                        *vertical_alignment, +                        *color, +                        *clip_bounds, +                        Transformation::IDENTITY, +                    ) +                } +                Text::Raw { +                    raw, +                    transformation, +                } => { +                    let Some(Allocation::Raw(buffer)) = allocation else { +                        return None; +                    }; + +                    let (width, height) = buffer.size(); + +                    ( +                        buffer.as_ref(), +                        Rectangle::new(raw.position, Size::new(width, height)), +                        alignment::Horizontal::Left, +                        alignment::Vertical::Top, +                        raw.color, +                        raw.clip_bounds, +                        *transformation, +                    ) +                } +            }; + +            let bounds = bounds * transformation * layer_transformation; + +            let left = match horizontal_alignment { +                alignment::Horizontal::Left => bounds.x, +                alignment::Horizontal::Center => bounds.x - bounds.width / 2.0, +                alignment::Horizontal::Right => bounds.x - bounds.width, +            }; + +            let top = match vertical_alignment { +                alignment::Vertical::Top => bounds.y, +                alignment::Vertical::Center => bounds.y - bounds.height / 2.0, +                alignment::Vertical::Bottom => bounds.y - bounds.height, +            }; + +            let clip_bounds = layer_bounds.intersection( +                &(clip_bounds * transformation * layer_transformation), +            )?; + +            Some(glyphon::TextArea { +                buffer, +                left, +                top, +                scale: transformation.scale_factor() +                    * layer_transformation.scale_factor(), +                bounds: glyphon::TextBounds { +                    left: clip_bounds.x as i32, +                    top: clip_bounds.y as i32, +                    right: (clip_bounds.x + clip_bounds.width) as i32, +                    bottom: (clip_bounds.y + clip_bounds.height) as i32, +                }, +                default_color: to_color(color), +            }) +        }, +    ); + +    renderer.prepare( +        device, +        queue, +        encoder, +        font_system, +        atlas, +        viewport, +        text_areas, +        &mut glyphon::SwashCache::new(), +    ) +} diff --git a/wgpu/src/triangle.rs b/wgpu/src/triangle.rs index 2bb6f307..b0551f55 100644 --- a/wgpu/src/triangle.rs +++ b/wgpu/src/triangle.rs @@ -1,14 +1,158 @@  //! Draw meshes of triangles.  mod msaa; -use crate::core::{Size, Transformation}; +use crate::core::{Rectangle, Size, Transformation}; +use crate::graphics::mesh::{self, Mesh};  use crate::graphics::Antialiasing; -use crate::layer::mesh::{self, Mesh};  use crate::Buffer; +use rustc_hash::FxHashMap; +use std::collections::hash_map; +use std::rc::{self, Rc}; +use std::sync::atomic::{self, AtomicU64}; +  const INITIAL_INDEX_COUNT: usize = 1_000;  const INITIAL_VERTEX_COUNT: usize = 1_000; +pub type Batch = Vec<Item>; + +#[derive(Debug)] +pub enum Item { +    Group { +        transformation: Transformation, +        meshes: Vec<Mesh>, +    }, +    Cached { +        transformation: Transformation, +        cache: Cache, +    }, +} + +#[derive(Debug, Clone)] +pub struct Cache { +    id: Id, +    batch: Rc<[Mesh]>, +    version: usize, +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub struct Id(u64); + +impl Cache { +    pub fn new(meshes: Vec<Mesh>) -> Option<Self> { +        static NEXT_ID: AtomicU64 = AtomicU64::new(0); + +        if meshes.is_empty() { +            return None; +        } + +        Some(Self { +            id: Id(NEXT_ID.fetch_add(1, atomic::Ordering::Relaxed)), +            batch: Rc::from(meshes), +            version: 0, +        }) +    } + +    pub fn update(&mut self, meshes: Vec<Mesh>) { +        self.batch = Rc::from(meshes); +        self.version += 1; +    } +} + +#[derive(Debug)] +struct Upload { +    layer: Layer, +    transformation: Transformation, +    version: usize, +    batch: rc::Weak<[Mesh]>, +} + +#[derive(Debug, Default)] +pub struct Storage { +    uploads: FxHashMap<Id, Upload>, +} + +impl Storage { +    pub fn new() -> Self { +        Self::default() +    } + +    fn get(&self, cache: &Cache) -> Option<&Upload> { +        if cache.batch.is_empty() { +            return None; +        } + +        self.uploads.get(&cache.id) +    } + +    fn prepare( +        &mut self, +        device: &wgpu::Device, +        encoder: &mut wgpu::CommandEncoder, +        belt: &mut wgpu::util::StagingBelt, +        solid: &solid::Pipeline, +        gradient: &gradient::Pipeline, +        cache: &Cache, +        new_transformation: Transformation, +    ) { +        match self.uploads.entry(cache.id) { +            hash_map::Entry::Occupied(entry) => { +                let upload = entry.into_mut(); + +                if !cache.batch.is_empty() +                    && (upload.version != cache.version +                        || upload.transformation != new_transformation) +                { +                    upload.layer.prepare( +                        device, +                        encoder, +                        belt, +                        solid, +                        gradient, +                        &cache.batch, +                        new_transformation, +                    ); + +                    upload.batch = Rc::downgrade(&cache.batch); +                    upload.version = cache.version; +                    upload.transformation = new_transformation; +                } +            } +            hash_map::Entry::Vacant(entry) => { +                let mut layer = Layer::new(device, solid, gradient); + +                layer.prepare( +                    device, +                    encoder, +                    belt, +                    solid, +                    gradient, +                    &cache.batch, +                    new_transformation, +                ); + +                let _ = entry.insert(Upload { +                    layer, +                    transformation: new_transformation, +                    version: 0, +                    batch: Rc::downgrade(&cache.batch), +                }); + +                log::debug!( +                    "New mesh upload: {} (total: {})", +                    cache.id.0, +                    self.uploads.len() +                ); +            } +        } +    } + +    pub fn trim(&mut self) { +        self.uploads +            .retain(|_id, upload| upload.batch.strong_count() > 0); +    } +} +  #[derive(Debug)]  pub struct Pipeline {      blit: Option<msaa::Blit>, @@ -18,8 +162,198 @@ pub struct Pipeline {      prepare_layer: usize,  } +impl Pipeline { +    pub fn new( +        device: &wgpu::Device, +        format: wgpu::TextureFormat, +        antialiasing: Option<Antialiasing>, +    ) -> Pipeline { +        Pipeline { +            blit: antialiasing.map(|a| msaa::Blit::new(device, format, a)), +            solid: solid::Pipeline::new(device, format, antialiasing), +            gradient: gradient::Pipeline::new(device, format, antialiasing), +            layers: Vec::new(), +            prepare_layer: 0, +        } +    } + +    pub fn prepare( +        &mut self, +        device: &wgpu::Device, +        encoder: &mut wgpu::CommandEncoder, +        belt: &mut wgpu::util::StagingBelt, +        storage: &mut Storage, +        items: &[Item], +        scale: Transformation, +        target_size: Size<u32>, +    ) { +        let projection = if let Some(blit) = &mut self.blit { +            blit.prepare(device, encoder, belt, target_size) * scale +        } else { +            Transformation::orthographic(target_size.width, target_size.height) +                * scale +        }; + +        for item in items { +            match item { +                Item::Group { +                    transformation, +                    meshes, +                } => { +                    if self.layers.len() <= self.prepare_layer { +                        self.layers.push(Layer::new( +                            device, +                            &self.solid, +                            &self.gradient, +                        )); +                    } + +                    let layer = &mut self.layers[self.prepare_layer]; +                    layer.prepare( +                        device, +                        encoder, +                        belt, +                        &self.solid, +                        &self.gradient, +                        meshes, +                        projection * *transformation, +                    ); + +                    self.prepare_layer += 1; +                } +                Item::Cached { +                    transformation, +                    cache, +                } => { +                    storage.prepare( +                        device, +                        encoder, +                        belt, +                        &self.solid, +                        &self.gradient, +                        cache, +                        projection * *transformation, +                    ); +                } +            } +        } +    } + +    pub fn render( +        &mut self, +        encoder: &mut wgpu::CommandEncoder, +        target: &wgpu::TextureView, +        storage: &Storage, +        start: usize, +        batch: &Batch, +        bounds: Rectangle, +        screen_transformation: Transformation, +    ) -> usize { +        let mut layer_count = 0; + +        let items = batch.iter().filter_map(|item| match item { +            Item::Group { +                transformation, +                meshes, +            } => { +                let layer = &self.layers[start + layer_count]; +                layer_count += 1; + +                Some(( +                    layer, +                    meshes.as_slice(), +                    screen_transformation * *transformation, +                )) +            } +            Item::Cached { +                transformation, +                cache, +            } => { +                let upload = storage.get(cache)?; + +                Some(( +                    &upload.layer, +                    &cache.batch, +                    screen_transformation * *transformation, +                )) +            } +        }); + +        render( +            encoder, +            target, +            self.blit.as_mut(), +            &self.solid, +            &self.gradient, +            bounds, +            items, +        ); + +        layer_count +    } + +    pub fn end_frame(&mut self) { +        self.prepare_layer = 0; +    } +} + +fn render<'a>( +    encoder: &mut wgpu::CommandEncoder, +    target: &wgpu::TextureView, +    mut blit: Option<&mut msaa::Blit>, +    solid: &solid::Pipeline, +    gradient: &gradient::Pipeline, +    bounds: Rectangle, +    group: impl Iterator<Item = (&'a Layer, &'a [Mesh], Transformation)>, +) { +    { +        let (attachment, resolve_target, load) = if let Some(blit) = &mut blit { +            let (attachment, resolve_target) = blit.targets(); + +            ( +                attachment, +                Some(resolve_target), +                wgpu::LoadOp::Clear(wgpu::Color::TRANSPARENT), +            ) +        } else { +            (target, None, wgpu::LoadOp::Load) +        }; + +        let mut render_pass = +            encoder.begin_render_pass(&wgpu::RenderPassDescriptor { +                label: Some("iced_wgpu.triangle.render_pass"), +                color_attachments: &[Some(wgpu::RenderPassColorAttachment { +                    view: attachment, +                    resolve_target, +                    ops: wgpu::Operations { +                        load, +                        store: wgpu::StoreOp::Store, +                    }, +                })], +                depth_stencil_attachment: None, +                timestamp_writes: None, +                occlusion_query_set: None, +            }); + +        for (layer, meshes, transformation) in group { +            layer.render( +                solid, +                gradient, +                meshes, +                bounds, +                transformation, +                &mut render_pass, +            ); +        } +    } + +    if let Some(blit) = blit { +        blit.draw(encoder, target); +    } +} +  #[derive(Debug)] -struct Layer { +pub struct Layer {      index_buffer: Buffer<u32>,      index_strides: Vec<u32>,      solid: solid::Layer, @@ -48,10 +382,11 @@ impl Layer {      fn prepare(          &mut self,          device: &wgpu::Device, -        queue: &wgpu::Queue, +        encoder: &mut wgpu::CommandEncoder, +        belt: &mut wgpu::util::StagingBelt,          solid: &solid::Pipeline,          gradient: &gradient::Pipeline, -        meshes: &[Mesh<'_>], +        meshes: &[Mesh],          transformation: Transformation,      ) {          // Count the total amount of vertices & indices we need to handle @@ -103,33 +438,47 @@ impl Layer {              let uniforms =                  Uniforms::new(transformation * mesh.transformation()); -            index_offset += -                self.index_buffer.write(queue, index_offset, indices); +            index_offset += self.index_buffer.write( +                device, +                encoder, +                belt, +                index_offset, +                indices, +            ); +              self.index_strides.push(indices.len() as u32);              match mesh {                  Mesh::Solid { buffers, .. } => {                      solid_vertex_offset += self.solid.vertices.write( -                        queue, +                        device, +                        encoder, +                        belt,                          solid_vertex_offset,                          &buffers.vertices,                      );                      solid_uniform_offset += self.solid.uniforms.write( -                        queue, +                        device, +                        encoder, +                        belt,                          solid_uniform_offset,                          &[uniforms],                      );                  }                  Mesh::Gradient { buffers, .. } => {                      gradient_vertex_offset += self.gradient.vertices.write( -                        queue, +                        device, +                        encoder, +                        belt,                          gradient_vertex_offset,                          &buffers.vertices,                      );                      gradient_uniform_offset += self.gradient.uniforms.write( -                        queue, +                        device, +                        encoder, +                        belt,                          gradient_uniform_offset,                          &[uniforms],                      ); @@ -142,8 +491,9 @@ impl Layer {          &'a self,          solid: &'a solid::Pipeline,          gradient: &'a gradient::Pipeline, -        meshes: &[Mesh<'_>], -        scale_factor: f32, +        meshes: &[Mesh], +        bounds: Rectangle, +        transformation: Transformation,          render_pass: &mut wgpu::RenderPass<'a>,      ) {          let mut num_solids = 0; @@ -151,11 +501,12 @@ impl Layer {          let mut last_is_solid = None;          for (index, mesh) in meshes.iter().enumerate() { -            let clip_bounds = (mesh.clip_bounds() * scale_factor).snap(); - -            if clip_bounds.width < 1 || clip_bounds.height < 1 { +            let Some(clip_bounds) = bounds +                .intersection(&(mesh.clip_bounds() * transformation)) +                .and_then(Rectangle::snap) +            else {                  continue; -            } +            };              render_pass.set_scissor_rect(                  clip_bounds.x, @@ -219,117 +570,6 @@ impl Layer {      }  } -impl Pipeline { -    pub fn new( -        device: &wgpu::Device, -        format: wgpu::TextureFormat, -        antialiasing: Option<Antialiasing>, -    ) -> Pipeline { -        Pipeline { -            blit: antialiasing.map(|a| msaa::Blit::new(device, format, a)), -            solid: solid::Pipeline::new(device, format, antialiasing), -            gradient: gradient::Pipeline::new(device, format, antialiasing), -            layers: Vec::new(), -            prepare_layer: 0, -        } -    } - -    pub fn prepare( -        &mut self, -        device: &wgpu::Device, -        queue: &wgpu::Queue, -        meshes: &[Mesh<'_>], -        transformation: Transformation, -    ) { -        #[cfg(feature = "tracing")] -        let _ = tracing::info_span!("Wgpu::Triangle", "PREPARE").entered(); - -        if self.layers.len() <= self.prepare_layer { -            self.layers -                .push(Layer::new(device, &self.solid, &self.gradient)); -        } - -        let layer = &mut self.layers[self.prepare_layer]; -        layer.prepare( -            device, -            queue, -            &self.solid, -            &self.gradient, -            meshes, -            transformation, -        ); - -        self.prepare_layer += 1; -    } - -    pub fn render( -        &mut self, -        device: &wgpu::Device, -        encoder: &mut wgpu::CommandEncoder, -        target: &wgpu::TextureView, -        layer: usize, -        target_size: Size<u32>, -        meshes: &[Mesh<'_>], -        scale_factor: f32, -    ) { -        #[cfg(feature = "tracing")] -        let _ = tracing::info_span!("Wgpu::Triangle", "DRAW").entered(); - -        { -            let (attachment, resolve_target, load) = if let Some(blit) = -                &mut self.blit -            { -                let (attachment, resolve_target) = -                    blit.targets(device, target_size.width, target_size.height); - -                ( -                    attachment, -                    Some(resolve_target), -                    wgpu::LoadOp::Clear(wgpu::Color::TRANSPARENT), -                ) -            } else { -                (target, None, wgpu::LoadOp::Load) -            }; - -            let mut render_pass = -                encoder.begin_render_pass(&wgpu::RenderPassDescriptor { -                    label: Some("iced_wgpu.triangle.render_pass"), -                    color_attachments: &[Some( -                        wgpu::RenderPassColorAttachment { -                            view: attachment, -                            resolve_target, -                            ops: wgpu::Operations { -                                load, -                                store: wgpu::StoreOp::Store, -                            }, -                        }, -                    )], -                    depth_stencil_attachment: None, -                    timestamp_writes: None, -                    occlusion_query_set: None, -                }); - -            let layer = &mut self.layers[layer]; - -            layer.render( -                &self.solid, -                &self.gradient, -                meshes, -                scale_factor, -                &mut render_pass, -            ); -        } - -        if let Some(blit) = &mut self.blit { -            blit.draw(encoder, target); -        } -    } - -    pub fn end_frame(&mut self) { -        self.prepare_layer = 0; -    } -} -  fn fragment_target(      texture_format: wgpu::TextureFormat,  ) -> wgpu::ColorTargetState { diff --git a/wgpu/src/triangle/msaa.rs b/wgpu/src/triangle/msaa.rs index 14abd20b..71c16925 100644 --- a/wgpu/src/triangle/msaa.rs +++ b/wgpu/src/triangle/msaa.rs @@ -1,13 +1,18 @@ +use crate::core::{Size, Transformation};  use crate::graphics; +use std::num::NonZeroU64; +  #[derive(Debug)]  pub struct Blit {      format: wgpu::TextureFormat,      pipeline: wgpu::RenderPipeline,      constants: wgpu::BindGroup, +    ratio: wgpu::Buffer,      texture_layout: wgpu::BindGroupLayout,      sample_count: u32,      targets: Option<Targets>, +    last_region: Option<Size<u32>>,  }  impl Blit { @@ -19,27 +24,52 @@ impl Blit {          let sampler =              device.create_sampler(&wgpu::SamplerDescriptor::default()); +        let ratio = device.create_buffer(&wgpu::BufferDescriptor { +            label: Some("iced-wgpu::triangle::msaa ratio"), +            size: std::mem::size_of::<Ratio>() as u64, +            usage: wgpu::BufferUsages::COPY_DST | wgpu::BufferUsages::UNIFORM, +            mapped_at_creation: false, +        }); +          let constant_layout =              device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {                  label: Some("iced_wgpu::triangle:msaa uniforms layout"), -                entries: &[wgpu::BindGroupLayoutEntry { -                    binding: 0, -                    visibility: wgpu::ShaderStages::FRAGMENT, -                    ty: wgpu::BindingType::Sampler( -                        wgpu::SamplerBindingType::NonFiltering, -                    ), -                    count: None, -                }], +                entries: &[ +                    wgpu::BindGroupLayoutEntry { +                        binding: 0, +                        visibility: wgpu::ShaderStages::FRAGMENT, +                        ty: wgpu::BindingType::Sampler( +                            wgpu::SamplerBindingType::NonFiltering, +                        ), +                        count: None, +                    }, +                    wgpu::BindGroupLayoutEntry { +                        binding: 1, +                        visibility: wgpu::ShaderStages::VERTEX, +                        ty: wgpu::BindingType::Buffer { +                            ty: wgpu::BufferBindingType::Uniform, +                            has_dynamic_offset: false, +                            min_binding_size: None, +                        }, +                        count: None, +                    }, +                ],              });          let constant_bind_group =              device.create_bind_group(&wgpu::BindGroupDescriptor {                  label: Some("iced_wgpu::triangle::msaa uniforms bind group"),                  layout: &constant_layout, -                entries: &[wgpu::BindGroupEntry { -                    binding: 0, -                    resource: wgpu::BindingResource::Sampler(&sampler), -                }], +                entries: &[ +                    wgpu::BindGroupEntry { +                        binding: 0, +                        resource: wgpu::BindingResource::Sampler(&sampler), +                    }, +                    wgpu::BindGroupEntry { +                        binding: 1, +                        resource: ratio.as_entire_binding(), +                    }, +                ],              });          let texture_layout = @@ -112,43 +142,61 @@ impl Blit {              format,              pipeline,              constants: constant_bind_group, +            ratio,              texture_layout,              sample_count: antialiasing.sample_count(),              targets: None, +            last_region: None,          }      } -    pub fn targets( +    pub fn prepare(          &mut self,          device: &wgpu::Device, -        width: u32, -        height: u32, -    ) -> (&wgpu::TextureView, &wgpu::TextureView) { +        encoder: &mut wgpu::CommandEncoder, +        belt: &mut wgpu::util::StagingBelt, +        region_size: Size<u32>, +    ) -> Transformation {          match &mut self.targets { -            None => { +            Some(targets) +                if region_size.width <= targets.size.width +                    && region_size.height <= targets.size.height => {} +            _ => {                  self.targets = Some(Targets::new(                      device,                      self.format,                      &self.texture_layout,                      self.sample_count, -                    width, -                    height, +                    region_size,                  ));              } -            Some(targets) => { -                if targets.width != width || targets.height != height { -                    self.targets = Some(Targets::new( -                        device, -                        self.format, -                        &self.texture_layout, -                        self.sample_count, -                        width, -                        height, -                    )); -                } -            }          } +        let targets = self.targets.as_mut().unwrap(); + +        if Some(region_size) != self.last_region { +            let ratio = Ratio { +                u: region_size.width as f32 / targets.size.width as f32, +                v: region_size.height as f32 / targets.size.height as f32, +            }; + +            belt.write_buffer( +                encoder, +                &self.ratio, +                0, +                NonZeroU64::new(std::mem::size_of::<Ratio>() as u64) +                    .expect("non-empty ratio"), +                device, +            ) +            .copy_from_slice(bytemuck::bytes_of(&ratio)); + +            self.last_region = Some(region_size); +        } + +        Transformation::orthographic(targets.size.width, targets.size.height) +    } + +    pub fn targets(&self) -> (&wgpu::TextureView, &wgpu::TextureView) {          let targets = self.targets.as_ref().unwrap();          (&targets.attachment, &targets.resolve) @@ -191,8 +239,7 @@ struct Targets {      attachment: wgpu::TextureView,      resolve: wgpu::TextureView,      bind_group: wgpu::BindGroup, -    width: u32, -    height: u32, +    size: Size<u32>,  }  impl Targets { @@ -201,12 +248,11 @@ impl Targets {          format: wgpu::TextureFormat,          texture_layout: &wgpu::BindGroupLayout,          sample_count: u32, -        width: u32, -        height: u32, +        size: Size<u32>,      ) -> Targets {          let extent = wgpu::Extent3d { -            width, -            height, +            width: size.width, +            height: size.height,              depth_or_array_layers: 1,          }; @@ -252,8 +298,14 @@ impl Targets {              attachment,              resolve,              bind_group, -            width, -            height, +            size,          }      }  } + +#[derive(Debug, Clone, Copy, bytemuck::Pod, bytemuck::Zeroable)] +#[repr(C)] +struct Ratio { +    u: f32, +    v: f32, +} diff --git a/wgpu/src/window/compositor.rs b/wgpu/src/window/compositor.rs index 328ad781..2e938c77 100644 --- a/wgpu/src/window/compositor.rs +++ b/wgpu/src/window/compositor.rs @@ -1,23 +1,49 @@  //! Connect a window with a renderer.  use crate::core::{Color, Size}; -use crate::graphics;  use crate::graphics::color;  use crate::graphics::compositor; -use crate::graphics::{Error, Viewport}; -use crate::{Backend, Primitive, Renderer, Settings}; - -use std::future::Future; +use crate::graphics::error; +use crate::graphics::{self, Viewport}; +use crate::settings::{self, Settings}; +use crate::{Engine, Renderer};  /// A window graphics backend for iced powered by `wgpu`.  #[allow(missing_debug_implementations)]  pub struct Compositor { -    settings: Settings,      instance: wgpu::Instance,      adapter: wgpu::Adapter,      device: wgpu::Device,      queue: wgpu::Queue,      format: wgpu::TextureFormat,      alpha_mode: wgpu::CompositeAlphaMode, +    engine: Engine, +    settings: Settings, +} + +/// A compositor error. +#[derive(Debug, Clone, thiserror::Error)] +pub enum Error { +    /// The surface creation failed. +    #[error("the surface creation failed: {0}")] +    SurfaceCreationFailed(#[from] wgpu::CreateSurfaceError), +    /// The surface is not compatible. +    #[error("the surface is not compatible")] +    IncompatibleSurface, +    /// No adapter was found for the options requested. +    #[error("no adapter was found for the options requested: {0:?}")] +    NoAdapterFound(String), +    /// No device request succeeded. +    #[error("no device request succeeded: {0:?}")] +    RequestDeviceFailed(Vec<(wgpu::Limits, wgpu::RequestDeviceError)>), +} + +impl From<Error> for graphics::Error { +    fn from(error: Error) -> Self { +        Self::GraphicsAdapterNotFound { +            backend: "wgpu", +            reason: error::Reason::RequestFailed(error.to_string()), +        } +    }  }  impl Compositor { @@ -27,9 +53,9 @@ impl Compositor {      pub async fn request<W: compositor::Window>(          settings: Settings,          compatible_window: Option<W>, -    ) -> Option<Self> { +    ) -> Result<Self, Error> {          let instance = wgpu::Instance::new(wgpu::InstanceDescriptor { -            backends: settings.internal_backend, +            backends: settings.backends,              ..Default::default()          }); @@ -38,7 +64,7 @@ impl Compositor {          #[cfg(not(target_arch = "wasm32"))]          if log::max_level() >= log::LevelFilter::Info {              let available_adapters: Vec<_> = instance -                .enumerate_adapters(settings.internal_backend) +                .enumerate_adapters(settings.backends)                  .iter()                  .map(wgpu::Adapter::get_info)                  .collect(); @@ -49,23 +75,27 @@ impl Compositor {          let compatible_surface = compatible_window              .and_then(|window| instance.create_surface(window).ok()); +        let adapter_options = wgpu::RequestAdapterOptions { +            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, +        }; +          let adapter = instance -            .request_adapter(&wgpu::RequestAdapterOptions { -                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, -            }) -            .await?; +            .request_adapter(&adapter_options) +            .await +            .ok_or(Error::NoAdapterFound(format!("{:?}", adapter_options)))?;          log::info!("Selected: {:#?}", adapter.get_info()); -        let (format, alpha_mode) = -            compatible_surface.as_ref().and_then(|surface| { +        let (format, alpha_mode) = compatible_surface +            .as_ref() +            .and_then(|surface| {                  let capabilities = surface.get_capabilities(&adapter);                  let mut formats = capabilities.formats.iter().copied(); @@ -92,12 +122,17 @@ impl Compositor {                      .contains(&wgpu::CompositeAlphaMode::PostMultiplied)                  {                      wgpu::CompositeAlphaMode::PostMultiplied +                } else if alpha_modes +                    .contains(&wgpu::CompositeAlphaMode::PreMultiplied) +                { +                    wgpu::CompositeAlphaMode::PreMultiplied                  } else {                      wgpu::CompositeAlphaMode::Auto                  };                  format.zip(Some(preferred_alpha)) -            })?; +            }) +            .ok_or(Error::IncompatibleSurface)?;          log::info!(              "Selected format: {format:?} with alpha mode: {alpha_mode:?}" @@ -111,70 +146,71 @@ impl Compositor {          let limits =              [wgpu::Limits::default(), wgpu::Limits::downlevel_defaults()]; -        let mut limits = limits.into_iter().map(|limits| wgpu::Limits { +        let limits = limits.into_iter().map(|limits| wgpu::Limits {              max_bind_groups: 2,              ..limits          }); -        let (device, queue) = -            loop { -                let required_limits = limits.next()?; -                let device = adapter.request_device( +        let mut errors = Vec::new(); + +        for required_limits in limits { +            let result = adapter +                .request_device(                      &wgpu::DeviceDescriptor {                          label: Some(                              "iced_wgpu::window::compositor device descriptor",                          ),                          required_features: wgpu::Features::empty(), -                        required_limits, +                        required_limits: required_limits.clone(),                      },                      None, -                ).await.ok(); - -                if let Some(device) = device { -                    break Some(device); +                ) +                .await; + +            match result { +                Ok((device, queue)) => { +                    let engine = Engine::new( +                        &adapter, +                        &device, +                        &queue, +                        format, +                        settings.antialiasing, +                    ); + +                    return Ok(Compositor { +                        instance, +                        adapter, +                        device, +                        queue, +                        format, +                        alpha_mode, +                        engine, +                        settings, +                    });                  } -            }?; - -        Some(Compositor { -            instance, -            settings, -            adapter, -            device, -            queue, -            format, -            alpha_mode, -        }) -    } +                Err(error) => { +                    errors.push((required_limits, error)); +                } +            } +        } -    /// Creates a new rendering [`Backend`] for this [`Compositor`]. -    pub fn create_backend(&self) -> Backend { -        Backend::new( -            &self.adapter, -            &self.device, -            &self.queue, -            self.settings, -            self.format, -        ) +        Err(Error::RequestDeviceFailed(errors))      }  } -/// Creates a [`Compositor`] and its [`Backend`] for the given [`Settings`] and -/// window. +/// Creates a [`Compositor`] with the given [`Settings`] and window.  pub async fn new<W: compositor::Window>(      settings: Settings,      compatible_window: W,  ) -> Result<Compositor, Error> { -    Compositor::request(settings, Some(compatible_window)) -        .await -        .ok_or(Error::GraphicsAdapterNotFound) +    Compositor::request(settings, Some(compatible_window)).await  } -/// Presents the given primitives with the given [`Compositor`] and [`Backend`]. +/// Presents the given primitives with the given [`Compositor`].  pub fn present<T: AsRef<str>>(      compositor: &mut Compositor, -    backend: &mut Backend, +    renderer: &mut Renderer,      surface: &mut wgpu::Surface<'static>, -    primitives: &[Primitive],      viewport: &Viewport,      background_color: Color,      overlay: &[T], @@ -191,20 +227,21 @@ pub fn present<T: AsRef<str>>(                  .texture                  .create_view(&wgpu::TextureViewDescriptor::default()); -            backend.present( +            renderer.present( +                &mut compositor.engine,                  &compositor.device,                  &compositor.queue,                  &mut encoder,                  Some(background_color),                  frame.texture.format(),                  view, -                primitives,                  viewport,                  overlay,              ); -            // Submit work -            let _submission = compositor.queue.submit(Some(encoder.finish())); +            let _ = compositor.engine.submit(&compositor.queue, encoder); + +            // Present the frame              frame.present();              Ok(()) @@ -225,20 +262,41 @@ pub fn present<T: AsRef<str>>(  }  impl graphics::Compositor for Compositor { -    type Settings = Settings;      type Renderer = Renderer;      type Surface = wgpu::Surface<'static>; -    fn new<W: compositor::Window>( -        settings: Self::Settings, +    async fn with_backend<W: compositor::Window>( +        settings: graphics::Settings,          compatible_window: W, -    ) -> impl Future<Output = Result<Self, Error>> { -        new(settings, compatible_window) +        backend: Option<&str>, +    ) -> Result<Self, graphics::Error> { +        match backend { +            None | Some("wgpu") => { +                let mut settings = Settings::from(settings); + +                if let Some(backends) = wgpu::util::backend_bits_from_env() { +                    settings.backends = backends; +                } + +                if let Some(present_mode) = settings::present_mode_from_env() { +                    settings.present_mode = present_mode; +                } + +                Ok(new(settings, compatible_window).await?) +            } +            Some(backend) => Err(graphics::Error::GraphicsAdapterNotFound { +                backend: "wgpu", +                reason: error::Reason::DidNotMatch { +                    preferred_backend: backend.to_owned(), +                }, +            }), +        }      }      fn create_renderer(&self) -> Self::Renderer {          Renderer::new( -            self.create_backend(), +            &self.device, +            &self.engine,              self.settings.default_font,              self.settings.default_text_size,          ) @@ -278,7 +336,7 @@ impl graphics::Compositor for Compositor {                  height,                  alpha_mode: self.alpha_mode,                  view_formats: vec![], -                desired_maximum_frame_latency: 2, +                desired_maximum_frame_latency: 1,              },          );      } @@ -300,17 +358,7 @@ impl graphics::Compositor for Compositor {          background_color: Color,          overlay: &[T],      ) -> Result<(), compositor::SurfaceError> { -        renderer.with_primitives(|backend, primitives| { -            present( -                self, -                backend, -                surface, -                primitives, -                viewport, -                background_color, -                overlay, -            ) -        }) +        present(self, renderer, surface, viewport, background_color, overlay)      }      fn screenshot<T: AsRef<str>>( @@ -321,16 +369,7 @@ impl graphics::Compositor for Compositor {          background_color: Color,          overlay: &[T],      ) -> Vec<u8> { -        renderer.with_primitives(|backend, primitives| { -            screenshot( -                self, -                backend, -                primitives, -                viewport, -                background_color, -                overlay, -            ) -        }) +        screenshot(self, renderer, viewport, background_color, overlay)      }  } @@ -338,19 +377,12 @@ impl graphics::Compositor for Compositor {  ///  /// Returns RGBA bytes of the texture data.  pub fn screenshot<T: AsRef<str>>( -    compositor: &Compositor, -    backend: &mut Backend, -    primitives: &[Primitive], +    compositor: &mut Compositor, +    renderer: &mut Renderer,      viewport: &Viewport,      background_color: Color,      overlay: &[T],  ) -> Vec<u8> { -    let mut encoder = compositor.device.create_command_encoder( -        &wgpu::CommandEncoderDescriptor { -            label: Some("iced_wgpu.offscreen.encoder"), -        }, -    ); -      let dimensions = BufferDimensions::new(viewport.physical_size());      let texture_extent = wgpu::Extent3d { @@ -374,14 +406,20 @@ pub fn screenshot<T: AsRef<str>>(      let view = texture.create_view(&wgpu::TextureViewDescriptor::default()); -    backend.present( +    let mut encoder = compositor.device.create_command_encoder( +        &wgpu::CommandEncoderDescriptor { +            label: Some("iced_wgpu.offscreen.encoder"), +        }, +    ); + +    renderer.present( +        &mut compositor.engine,          &compositor.device,          &compositor.queue,          &mut encoder,          Some(background_color),          texture.format(),          &view, -        primitives,          viewport,          overlay,      ); @@ -419,7 +457,7 @@ pub fn screenshot<T: AsRef<str>>(          texture_extent,      ); -    let index = compositor.queue.submit(Some(encoder.finish())); +    let index = compositor.engine.submit(&compositor.queue, encoder);      let slice = output_buffer.slice(..);      slice.map_async(wgpu::MapMode::Read, |_| {});  | 
