diff options
| author | 2022-11-05 04:37:59 +0100 | |
|---|---|---|
| committer | 2022-11-05 04:37:59 +0100 | |
| commit | 370fa14efb8d41bcf81e4a0ead6a9ab7ab750bee (patch) | |
| tree | 6a434e6bc8c3a95065faafa533cf1d5e2318cc06 | |
| parent | 7b129917281baaa6688158c303922f94341ab69f (diff) | |
| parent | 078cadfed0e67560d9047d84435e87b8671c5992 (diff) | |
| download | iced-370fa14efb8d41bcf81e4a0ead6a9ab7ab750bee.tar.gz iced-370fa14efb8d41bcf81e4a0ead6a9ab7ab750bee.tar.bz2 iced-370fa14efb8d41bcf81e4a0ead6a9ab7ab750bee.zip  | |
Merge pull request #1485 from ids1024/glow-image
Glow image rendering support; move image/svg code to iced_graphics
Diffstat (limited to '')
29 files changed, 750 insertions, 267 deletions
@@ -15,9 +15,9 @@ resolver = "2"  [features]  default = ["wgpu"]  # Enables the `Image` widget -image = ["iced_wgpu/image", "image_rs"] +image = ["iced_wgpu?/image", "iced_glow?/image", "image_rs"]  # Enables the `Svg` widget -svg = ["iced_wgpu/svg"] +svg = ["iced_wgpu?/svg", "iced_glow?/svg"]  # Enables the `Canvas` widget  canvas = ["iced_graphics/canvas"]  # Enables the `QRCode` widget @@ -104,7 +104,7 @@ iced_glow = { version = "0.3", path = "glow", optional = true }  thiserror = "1.0"  [dependencies.image_rs] -version = "0.23" +version = "0.24"  package = "image"  optional = true diff --git a/glow/Cargo.toml b/glow/Cargo.toml index 18215e9b..a50fd375 100644 --- a/glow/Cargo.toml +++ b/glow/Cargo.toml @@ -8,12 +8,22 @@ license = "MIT AND OFL-1.1"  repository = "https://github.com/iced-rs/iced"  [features] +svg = ["iced_graphics/svg"] +image = ["iced_graphics/image"] +png = ["iced_graphics/png"] +jpeg = ["iced_graphics/jpeg"] +jpeg_rayon = ["iced_graphics/jpeg_rayon"] +gif = ["iced_graphics/gif"] +webp = ["iced_graphics/webp"] +pnm = ["iced_graphics/pnm"] +ico = ["iced_graphics/ico"] +bmp = ["iced_graphics/bmp"] +hdr = ["iced_graphics/hdr"] +dds = ["iced_graphics/dds"] +farbfeld = ["iced_graphics/farbfeld"]  canvas = ["iced_graphics/canvas"]  qr_code = ["iced_graphics/qr_code"]  default_system_font = ["iced_graphics/font-source"] -# Not supported yet! -image = [] -svg = []  [dependencies]  glow = "0.11.1" diff --git a/glow/src/backend.rs b/glow/src/backend.rs index 21af2ecf..1a41d540 100644 --- a/glow/src/backend.rs +++ b/glow/src/backend.rs @@ -1,3 +1,5 @@ +#[cfg(any(feature = "image", feature = "svg"))] +use crate::image;  use crate::quad;  use crate::text;  use crate::{program, triangle}; @@ -15,6 +17,8 @@ use iced_native::{Font, Size};  /// [`iced`]: https://github.com/iced-rs/iced  #[derive(Debug)]  pub struct Backend { +    #[cfg(any(feature = "image", feature = "svg"))] +    image_pipeline: image::Pipeline,      quad_pipeline: quad::Pipeline,      text_pipeline: text::Pipeline,      triangle_pipeline: triangle::Pipeline, @@ -32,10 +36,14 @@ impl Backend {          let shader_version = program::Version::new(gl); +        #[cfg(any(feature = "image", feature = "svg"))] +        let image_pipeline = image::Pipeline::new(gl, &shader_version);          let quad_pipeline = quad::Pipeline::new(gl, &shader_version);          let triangle_pipeline = triangle::Pipeline::new(gl, &shader_version);          Self { +            #[cfg(any(feature = "image", feature = "svg"))] +            image_pipeline,              quad_pipeline,              text_pipeline,              triangle_pipeline, @@ -70,6 +78,9 @@ impl Backend {                  viewport_size.height,              );          } + +        #[cfg(any(feature = "image", feature = "svg"))] +        self.image_pipeline.trim_cache(gl);      }      fn flush( @@ -112,6 +123,15 @@ impl Backend {              );          } +        #[cfg(any(feature = "image", feature = "svg"))] +        if !layer.images.is_empty() { +            let scaled = transformation +                * Transformation::scale(scale_factor, scale_factor); + +            self.image_pipeline +                .draw(gl, scaled, scale_factor, &layer.images); +        } +          if !layer.text.is_empty() {              for text in layer.text.iter() {                  // Target physical coordinates directly to avoid blurry text @@ -238,8 +258,8 @@ impl backend::Text for Backend {  #[cfg(feature = "image")]  impl backend::Image for Backend { -    fn dimensions(&self, _handle: &iced_native::image::Handle) -> (u32, u32) { -        (50, 50) +    fn dimensions(&self, handle: &iced_native::image::Handle) -> Size<u32> { +        self.image_pipeline.dimensions(handle)      }  } @@ -247,8 +267,8 @@ impl backend::Image for Backend {  impl backend::Svg for Backend {      fn viewport_dimensions(          &self, -        _handle: &iced_native::svg::Handle, -    ) -> (u32, u32) { -        (50, 50) +        handle: &iced_native::svg::Handle, +    ) -> Size<u32> { +        self.image_pipeline.viewport_dimensions(handle)      }  } diff --git a/glow/src/image.rs b/glow/src/image.rs new file mode 100644 index 00000000..f906cd4c --- /dev/null +++ b/glow/src/image.rs @@ -0,0 +1,230 @@ +mod storage; + +use storage::Storage; + +pub use iced_graphics::triangle::{Mesh2D, Vertex2D}; + +use crate::program::{self, Shader}; +use crate::Transformation; + +#[cfg(feature = "image")] +use iced_graphics::image::raster; + +#[cfg(feature = "svg")] +use iced_graphics::image::vector; + +use iced_graphics::layer; +use iced_graphics::Size; + +use glow::HasContext; + +use std::cell::RefCell; + +#[derive(Debug)] +pub(crate) struct Pipeline { +    program: <glow::Context as HasContext>::Program, +    vertex_array: <glow::Context as HasContext>::VertexArray, +    vertex_buffer: <glow::Context as HasContext>::Buffer, +    transform_location: <glow::Context as HasContext>::UniformLocation, +    storage: Storage, +    #[cfg(feature = "image")] +    raster_cache: RefCell<raster::Cache<Storage>>, +    #[cfg(feature = "svg")] +    vector_cache: RefCell<vector::Cache<Storage>>, +} + +impl Pipeline { +    pub fn new( +        gl: &glow::Context, +        shader_version: &program::Version, +    ) -> Pipeline { +        let program = unsafe { +            let vertex_shader = Shader::vertex( +                gl, +                shader_version, +                include_str!("shader/common/image.vert"), +            ); +            let fragment_shader = Shader::fragment( +                gl, +                shader_version, +                include_str!("shader/common/image.frag"), +            ); + +            program::create( +                gl, +                &[vertex_shader, fragment_shader], +                &[(0, "i_Position")], +            ) +        }; + +        let transform_location = +            unsafe { gl.get_uniform_location(program, "u_Transform") } +                .expect("Get transform location"); + +        unsafe { +            gl.use_program(Some(program)); + +            let transform: [f32; 16] = Transformation::identity().into(); +            gl.uniform_matrix_4_f32_slice( +                Some(&transform_location), +                false, +                &transform, +            ); + +            gl.use_program(None); +        } + +        let vertex_buffer = +            unsafe { gl.create_buffer().expect("Create vertex buffer") }; +        let vertex_array = +            unsafe { gl.create_vertex_array().expect("Create vertex array") }; + +        unsafe { +            gl.bind_vertex_array(Some(vertex_array)); +            gl.bind_buffer(glow::ARRAY_BUFFER, Some(vertex_buffer)); + +            let vertices = &[0u8, 0, 1, 0, 0, 1, 1, 1]; +            gl.buffer_data_size( +                glow::ARRAY_BUFFER, +                vertices.len() as i32, +                glow::STATIC_DRAW, +            ); +            gl.buffer_sub_data_u8_slice( +                glow::ARRAY_BUFFER, +                0, +                bytemuck::cast_slice(vertices), +            ); + +            gl.enable_vertex_attrib_array(0); +            gl.vertex_attrib_pointer_f32( +                0, +                2, +                glow::UNSIGNED_BYTE, +                false, +                0, +                0, +            ); + +            gl.bind_buffer(glow::ARRAY_BUFFER, None); +            gl.bind_vertex_array(None); +        } + +        Pipeline { +            program, +            vertex_array, +            vertex_buffer, +            transform_location, +            storage: Storage::default(), +            #[cfg(feature = "image")] +            raster_cache: RefCell::new(raster::Cache::default()), +            #[cfg(feature = "svg")] +            vector_cache: RefCell::new(vector::Cache::default()), +        } +    } + +    #[cfg(feature = "image")] +    pub fn dimensions(&self, handle: &iced_native::image::Handle) -> Size<u32> { +        self.raster_cache.borrow_mut().load(handle).dimensions() +    } + +    #[cfg(feature = "svg")] +    pub fn viewport_dimensions( +        &self, +        handle: &iced_native::svg::Handle, +    ) -> Size<u32> { +        let mut cache = self.vector_cache.borrow_mut(); +        let svg = cache.load(handle); + +        svg.viewport_dimensions() +    } + +    pub fn draw( +        &mut self, +        mut gl: &glow::Context, +        transformation: Transformation, +        _scale_factor: f32, +        images: &[layer::Image], +    ) { +        unsafe { +            gl.use_program(Some(self.program)); +            gl.bind_vertex_array(Some(self.vertex_array)); +            gl.bind_buffer(glow::ARRAY_BUFFER, Some(self.vertex_buffer)); +        } + +        #[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 { +            let (entry, bounds) = match &image { +                #[cfg(feature = "image")] +                layer::Image::Raster { handle, bounds } => ( +                    raster_cache.upload(handle, &mut gl, &mut self.storage), +                    bounds, +                ), +                #[cfg(not(feature = "image"))] +                layer::Image::Raster { handle: _, bounds } => (None, bounds), + +                #[cfg(feature = "svg")] +                layer::Image::Vector { handle, bounds } => { +                    let size = [bounds.width, bounds.height]; +                    ( +                        vector_cache.upload( +                            handle, +                            size, +                            _scale_factor, +                            &mut gl, +                            &mut self.storage, +                        ), +                        bounds, +                    ) +                } + +                #[cfg(not(feature = "svg"))] +                layer::Image::Vector { handle: _, bounds } => (None, bounds), +            }; + +            unsafe { +                if let Some(storage::Entry { texture, .. }) = entry { +                    gl.bind_texture(glow::TEXTURE_2D, Some(*texture)) +                } else { +                    continue; +                } + +                let translate = Transformation::translate(bounds.x, bounds.y); +                let scale = Transformation::scale(bounds.width, bounds.height); +                let transformation = transformation * translate * scale; +                let matrix: [f32; 16] = transformation.into(); +                gl.uniform_matrix_4_f32_slice( +                    Some(&self.transform_location), +                    false, +                    &matrix, +                ); + +                gl.draw_arrays(glow::TRIANGLE_STRIP, 0, 4); + +                gl.bind_texture(glow::TEXTURE_2D, None); +            } +        } + +        unsafe { +            gl.bind_buffer(glow::ARRAY_BUFFER, None); +            gl.bind_vertex_array(None); +            gl.use_program(None); +        } +    } + +    pub fn trim_cache(&mut self, mut gl: &glow::Context) { +        #[cfg(feature = "image")] +        self.raster_cache +            .borrow_mut() +            .trim(&mut self.storage, &mut gl); + +        #[cfg(feature = "svg")] +        self.vector_cache +            .borrow_mut() +            .trim(&mut self.storage, &mut gl); +    } +} diff --git a/glow/src/image/storage.rs b/glow/src/image/storage.rs new file mode 100644 index 00000000..9bc20641 --- /dev/null +++ b/glow/src/image/storage.rs @@ -0,0 +1,78 @@ +use iced_graphics::image; +use iced_graphics::Size; + +use glow::HasContext; + +#[derive(Debug, Default)] +pub struct Storage; + +impl image::Storage for Storage { +    type Entry = Entry; +    type State<'a> = &'a glow::Context; + +    fn upload( +        &mut self, +        width: u32, +        height: u32, +        data: &[u8], +        gl: &mut &glow::Context, +    ) -> Option<Self::Entry> { +        unsafe { +            let texture = gl.create_texture().expect("create texture"); +            gl.bind_texture(glow::TEXTURE_2D, Some(texture)); +            gl.tex_image_2d( +                glow::TEXTURE_2D, +                0, +                glow::SRGB8_ALPHA8 as i32, +                width as i32, +                height as i32, +                0, +                glow::RGBA, +                glow::UNSIGNED_BYTE, +                Some(data), +            ); +            gl.tex_parameter_i32( +                glow::TEXTURE_2D, +                glow::TEXTURE_WRAP_S, +                glow::CLAMP_TO_EDGE as _, +            ); +            gl.tex_parameter_i32( +                glow::TEXTURE_2D, +                glow::TEXTURE_WRAP_T, +                glow::CLAMP_TO_EDGE as _, +            ); +            gl.tex_parameter_i32( +                glow::TEXTURE_2D, +                glow::TEXTURE_MIN_FILTER, +                glow::LINEAR as _, +            ); +            gl.tex_parameter_i32( +                glow::TEXTURE_2D, +                glow::TEXTURE_MAG_FILTER, +                glow::LINEAR as _, +            ); +            gl.bind_texture(glow::TEXTURE_2D, None); + +            Some(Entry { +                size: Size::new(width, height), +                texture, +            }) +        } +    } + +    fn remove(&mut self, entry: &Entry, gl: &mut &glow::Context) { +        unsafe { gl.delete_texture(entry.texture) } +    } +} + +#[derive(Debug)] +pub struct Entry { +    size: Size<u32>, +    pub(super) texture: glow::NativeTexture, +} + +impl image::storage::Entry for Entry { +    fn size(&self) -> Size<u32> { +        self.size +    } +} diff --git a/glow/src/lib.rs b/glow/src/lib.rs index de9c0002..e3690a69 100644 --- a/glow/src/lib.rs +++ b/glow/src/lib.rs @@ -24,6 +24,8 @@  pub use glow;  mod backend; +#[cfg(any(feature = "image", feature = "svg"))] +mod image;  mod program;  mod quad;  mod text; diff --git a/glow/src/shader/common/image.frag b/glow/src/shader/common/image.frag new file mode 100644 index 00000000..5e05abdf --- /dev/null +++ b/glow/src/shader/common/image.frag @@ -0,0 +1,22 @@ +#ifdef GL_ES +#ifdef GL_FRAGMENT_PRECISION_HIGH +precision highp float; +#else +precision mediump float; +#endif +#endif + +uniform sampler2D tex; +in vec2 tex_pos; + +#ifdef HIGHER_THAN_300 +out vec4 fragColor; +#define gl_FragColor fragColor +#endif +#ifdef GL_ES +#define texture texture2D +#endif + +void main() { +    gl_FragColor = texture(tex, tex_pos); +} diff --git a/glow/src/shader/common/image.vert b/glow/src/shader/common/image.vert new file mode 100644 index 00000000..93e541f2 --- /dev/null +++ b/glow/src/shader/common/image.vert @@ -0,0 +1,9 @@ +uniform mat4 u_Transform; + +in vec2 i_Position; +out vec2 tex_pos; + +void main() { +    gl_Position = u_Transform * vec4(i_Position, 0.0, 1.0); +    tex_pos = i_Position; +} diff --git a/graphics/Cargo.toml b/graphics/Cargo.toml index 3b0e5236..57079b95 100644 --- a/graphics/Cargo.toml +++ b/graphics/Cargo.toml @@ -11,17 +11,33 @@ keywords = ["gui", "ui", "graphics", "interface", "widgets"]  categories = ["gui"]  [features] +svg = ["resvg", "usvg", "tiny-skia"] +image = ["png", "jpeg", "jpeg_rayon", "gif", "webp", "bmp"] +png = ["image_rs/png"] +jpeg = ["image_rs/jpeg"] +jpeg_rayon = ["image_rs/jpeg_rayon"] +gif = ["image_rs/gif"] +webp = ["image_rs/webp"] +pnm = ["image_rs/pnm"] +ico = ["image_rs/ico"] +bmp = ["image_rs/bmp"] +hdr = ["image_rs/hdr"] +dds = ["image_rs/dds"] +farbfeld = ["image_rs/farbfeld"]  canvas = ["lyon"]  qr_code = ["qrcode", "canvas"]  font-source = ["font-kit"]  font-fallback = []  font-icons = []  opengl = [] +image_rs = ["kamadak-exif"]  [dependencies]  glam = "0.21.3" +log = "0.4"  raw-window-handle = "0.5"  thiserror = "1.0" +bitflags = "1.2"  [dependencies.bytemuck]  version = "1.4" @@ -48,6 +64,28 @@ default-features = false  version = "0.10"  optional = true +[dependencies.image_rs] +version = "0.24" +package = "image" +default-features = false +optional = true + +[dependencies.resvg] +version = "0.18" +optional = true + +[dependencies.usvg] +version = "0.18" +optional = true + +[dependencies.tiny-skia] +version = "0.6" +optional = true + +[dependencies.kamadak-exif] +version = "0.5" +optional = true +  [package.metadata.docs.rs]  rustdoc-args = ["--cfg", "docsrs"]  all-features = true diff --git a/graphics/src/backend.rs b/graphics/src/backend.rs index 7e0af2cc..2f8e9fc3 100644 --- a/graphics/src/backend.rs +++ b/graphics/src/backend.rs @@ -66,11 +66,11 @@ pub trait Text {  /// A graphics backend that supports image rendering.  pub trait Image {      /// Returns the dimensions of the provided image. -    fn dimensions(&self, handle: &image::Handle) -> (u32, u32); +    fn dimensions(&self, handle: &image::Handle) -> Size<u32>;  }  /// A graphics backend that supports SVG rendering.  pub trait Svg {      /// Returns the viewport dimensions of the provided SVG. -    fn viewport_dimensions(&self, handle: &svg::Handle) -> (u32, u32); +    fn viewport_dimensions(&self, handle: &svg::Handle) -> Size<u32>;  } diff --git a/graphics/src/image.rs b/graphics/src/image.rs new file mode 100644 index 00000000..04f4ff9d --- /dev/null +++ b/graphics/src/image.rs @@ -0,0 +1,10 @@ +//! Render images. +#[cfg(feature = "image_rs")] +pub mod raster; + +#[cfg(feature = "svg")] +pub mod vector; + +pub mod storage; + +pub use storage::Storage; diff --git a/wgpu/src/image/raster.rs b/graphics/src/image/raster.rs index 2b4d4af3..da46c30f 100644 --- a/wgpu/src/image/raster.rs +++ b/graphics/src/image/raster.rs @@ -1,43 +1,53 @@ -use crate::image::atlas::{self, Atlas}; +//! Raster image loading and caching. +use crate::image::Storage; +use crate::Size; +  use iced_native::image; -use std::collections::{HashMap, HashSet};  use bitflags::bitflags; +use std::collections::{HashMap, HashSet}; +/// Entry in cache corresponding to an image handle  #[derive(Debug)] -pub enum Memory { -    Host(::image_rs::ImageBuffer<::image_rs::Bgra<u8>, Vec<u8>>), -    Device(atlas::Entry), +pub enum Memory<T: Storage> { +    /// Image data on host +    Host(::image_rs::ImageBuffer<::image_rs::Rgba<u8>, Vec<u8>>), +    /// Storage entry +    Device(T::Entry), +    /// Image not found      NotFound, +    /// Invalid image data      Invalid,  } -impl Memory { -    pub fn dimensions(&self) -> (u32, u32) { +impl<T: Storage> Memory<T> { +    /// Width and height of image +    pub fn dimensions(&self) -> Size<u32> { +        use crate::image::storage::Entry; +          match self { -            Memory::Host(image) => image.dimensions(), +            Memory::Host(image) => { +                let (width, height) = image.dimensions(); + +                Size::new(width, height) +            }              Memory::Device(entry) => entry.size(), -            Memory::NotFound => (1, 1), -            Memory::Invalid => (1, 1), +            Memory::NotFound => Size::new(1, 1), +            Memory::Invalid => Size::new(1, 1),          }      }  } +/// Caches image raster data  #[derive(Debug)] -pub struct Cache { -    map: HashMap<u64, Memory>, +pub struct Cache<T: Storage> { +    map: HashMap<u64, Memory<T>>,      hits: HashSet<u64>,  } -impl Cache { -    pub fn new() -> Self { -        Self { -            map: HashMap::new(), -            hits: HashSet::new(), -        } -    } - -    pub fn load(&mut self, handle: &image::Handle) -> &mut Memory { +impl<T: Storage> Cache<T> { +    /// Load image +    pub fn load(&mut self, handle: &image::Handle) -> &mut Memory<T> {          if self.contains(handle) {              return self.get(handle).unwrap();          } @@ -53,7 +63,7 @@ impl Cache {                          })                          .unwrap_or_else(Operation::empty); -                    Memory::Host(operation.perform(image.to_bgra8())) +                    Memory::Host(operation.perform(image.to_rgba8()))                  } else {                      Memory::NotFound                  } @@ -65,12 +75,12 @@ impl Cache {                              .ok()                              .unwrap_or_else(Operation::empty); -                    Memory::Host(operation.perform(image.to_bgra8())) +                    Memory::Host(operation.perform(image.to_rgba8()))                  } else {                      Memory::Invalid                  }              } -            image::Data::Pixels { +            image::Data::Rgba {                  width,                  height,                  pixels, @@ -91,19 +101,19 @@ impl Cache {          self.get(handle).unwrap()      } +    /// Load image and upload raster data      pub fn upload(          &mut self,          handle: &image::Handle, -        device: &wgpu::Device, -        encoder: &mut wgpu::CommandEncoder, -        atlas: &mut Atlas, -    ) -> Option<&atlas::Entry> { +        state: &mut T::State<'_>, +        storage: &mut T, +    ) -> Option<&T::Entry> {          let memory = self.load(handle);          if let Memory::Host(image) = memory {              let (width, height) = image.dimensions(); -            let entry = atlas.upload(width, height, image, device, encoder)?; +            let entry = storage.upload(width, height, image, state)?;              *memory = Memory::Device(entry);          } @@ -115,7 +125,8 @@ impl Cache {          }      } -    pub fn trim(&mut self, atlas: &mut Atlas) { +    /// Trim cache misses from cache +    pub fn trim(&mut self, storage: &mut T, state: &mut T::State<'_>) {          let hits = &self.hits;          self.map.retain(|k, memory| { @@ -123,7 +134,7 @@ impl Cache {              if !retain {                  if let Memory::Device(entry) = memory { -                    atlas.remove(entry); +                    storage.remove(entry, state);                  }              } @@ -133,13 +144,13 @@ impl Cache {          self.hits.clear();      } -    fn get(&mut self, handle: &image::Handle) -> Option<&mut Memory> { +    fn get(&mut self, handle: &image::Handle) -> Option<&mut Memory<T>> {          let _ = self.hits.insert(handle.id());          self.map.get_mut(&handle.id())      } -    fn insert(&mut self, handle: &image::Handle, memory: Memory) { +    fn insert(&mut self, handle: &image::Handle, memory: Memory<T>) {          let _ = self.map.insert(handle.id(), memory);      } @@ -148,6 +159,15 @@ impl Cache {      }  } +impl<T: Storage> Default for Cache<T> { +    fn default() -> Self { +        Self { +            map: HashMap::new(), +            hits: HashSet::new(), +        } +    } +} +  bitflags! {      struct Operation: u8 {          const FLIP_HORIZONTALLY = 0b001; diff --git a/graphics/src/image/storage.rs b/graphics/src/image/storage.rs new file mode 100644 index 00000000..2098c7b2 --- /dev/null +++ b/graphics/src/image/storage.rs @@ -0,0 +1,31 @@ +//! Store images. +use crate::Size; + +use std::fmt::Debug; + +/// Stores cached image data for use in rendering +pub trait Storage { +    /// The type of an [`Entry`] in the [`Storage`]. +    type Entry: Entry; + +    /// State provided to upload or remove a [`Self::Entry`]. +    type State<'a>; + +    /// Upload the image data of a [`Self::Entry`]. +    fn upload( +        &mut self, +        width: u32, +        height: u32, +        data: &[u8], +        state: &mut Self::State<'_>, +    ) -> Option<Self::Entry>; + +    /// Romve a [`Self::Entry`] from the [`Storage`]. +    fn remove(&mut self, entry: &Self::Entry, state: &mut Self::State<'_>); +} + +/// An entry in some [`Storage`], +pub trait Entry: Debug { +    /// The [`Size`] of the [`Entry`]. +    fn size(&self) -> Size<u32>; +} diff --git a/wgpu/src/image/vector.rs b/graphics/src/image/vector.rs index b08a0aa2..dc271c9e 100644 --- a/wgpu/src/image/vector.rs +++ b/graphics/src/image/vector.rs @@ -1,46 +1,45 @@ -use crate::image::atlas::{self, Atlas}; +//! Vector image loading and caching +use crate::image::Storage;  use iced_native::svg; +use iced_native::Size;  use std::collections::{HashMap, HashSet};  use std::fs; +/// Entry in cache corresponding to an svg handle  pub enum Svg { +    /// Parsed svg      Loaded(usvg::Tree), +    /// Svg not found or failed to parse      NotFound,  }  impl Svg { -    pub fn viewport_dimensions(&self) -> (u32, u32) { +    /// Viewport width and height +    pub fn viewport_dimensions(&self) -> Size<u32> {          match self {              Svg::Loaded(tree) => {                  let size = tree.svg_node().size; -                (size.width() as u32, size.height() as u32) +                Size::new(size.width() as u32, size.height() as u32)              } -            Svg::NotFound => (1, 1), +            Svg::NotFound => Size::new(1, 1),          }      }  } +/// Caches svg vector and raster data  #[derive(Debug)] -pub struct Cache { +pub struct Cache<T: Storage> {      svgs: HashMap<u64, Svg>, -    rasterized: HashMap<(u64, u32, u32), atlas::Entry>, +    rasterized: HashMap<(u64, u32, u32), T::Entry>,      svg_hits: HashSet<u64>,      rasterized_hits: HashSet<(u64, u32, u32)>,  } -impl Cache { -    pub fn new() -> Self { -        Self { -            svgs: HashMap::new(), -            rasterized: HashMap::new(), -            svg_hits: HashSet::new(), -            rasterized_hits: HashSet::new(), -        } -    } - +impl<T: Storage> Cache<T> { +    /// Load svg      pub fn load(&mut self, handle: &svg::Handle) -> &Svg {          if self.svgs.contains_key(&handle.id()) {              return self.svgs.get(&handle.id()).unwrap(); @@ -73,15 +72,15 @@ impl Cache {          self.svgs.get(&handle.id()).unwrap()      } +    /// Load svg and upload raster data      pub fn upload(          &mut self,          handle: &svg::Handle,          [width, height]: [f32; 2],          scale: f32, -        device: &wgpu::Device, -        encoder: &mut wgpu::CommandEncoder, -        texture_atlas: &mut Atlas, -    ) -> Option<&atlas::Entry> { +        state: &mut T::State<'_>, +        storage: &mut T, +    ) -> Option<&T::Entry> {          let id = handle.id();          let (width, height) = ( @@ -125,12 +124,11 @@ impl Cache {                  let mut rgba = img.take();                  rgba.chunks_exact_mut(4).for_each(|rgba| rgba.swap(0, 2)); -                let allocation = texture_atlas.upload( +                let allocation = storage.upload(                      width,                      height,                      bytemuck::cast_slice(rgba.as_slice()), -                    device, -                    encoder, +                    state,                  )?;                  log::debug!("allocating {} {}x{}", id, width, height); @@ -144,7 +142,8 @@ impl Cache {          }      } -    pub fn trim(&mut self, atlas: &mut Atlas) { +    /// Load svg and upload raster data +    pub fn trim(&mut self, storage: &mut T, state: &mut T::State<'_>) {          let svg_hits = &self.svg_hits;          let rasterized_hits = &self.rasterized_hits; @@ -153,7 +152,7 @@ impl Cache {              let retain = rasterized_hits.contains(k);              if !retain { -                atlas.remove(entry); +                storage.remove(entry, state);              }              retain @@ -163,6 +162,17 @@ impl Cache {      }  } +impl<T: Storage> Default for Cache<T> { +    fn default() -> Self { +        Self { +            svgs: HashMap::new(), +            rasterized: HashMap::new(), +            svg_hits: HashSet::new(), +            rasterized_hits: HashSet::new(), +        } +    } +} +  impl std::fmt::Debug for Svg {      fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {          match self { diff --git a/graphics/src/lib.rs b/graphics/src/lib.rs index ec28ee58..d39dd90c 100644 --- a/graphics/src/lib.rs +++ b/graphics/src/lib.rs @@ -30,6 +30,7 @@ mod viewport;  pub mod backend;  pub mod font;  pub mod gradient; +pub mod image;  pub mod layer;  pub mod overlay;  pub mod renderer; diff --git a/graphics/src/renderer.rs b/graphics/src/renderer.rs index cdbc4f40..036b398c 100644 --- a/graphics/src/renderer.rs +++ b/graphics/src/renderer.rs @@ -183,7 +183,7 @@ where  {      type Handle = image::Handle; -    fn dimensions(&self, handle: &image::Handle) -> (u32, u32) { +    fn dimensions(&self, handle: &image::Handle) -> Size<u32> {          self.backend().dimensions(handle)      } @@ -196,7 +196,7 @@ impl<B, T> svg::Renderer for Renderer<B, T>  where      B: Backend + backend::Svg,  { -    fn dimensions(&self, handle: &svg::Handle) -> (u32, u32) { +    fn dimensions(&self, handle: &svg::Handle) -> Size<u32> {          self.backend().viewport_dimensions(handle)      } diff --git a/native/src/image.rs b/native/src/image.rs index b849ef84..06fd7ae6 100644 --- a/native/src/image.rs +++ b/native/src/image.rs @@ -1,5 +1,5 @@  //! Load and draw raster graphics. -use crate::{Hasher, Rectangle}; +use crate::{Hasher, Rectangle, Size};  use std::borrow::Cow;  use std::hash::{Hash, Hasher as _}; @@ -22,7 +22,7 @@ impl Handle {      }      /// Creates an image [`Handle`] containing the image pixels directly. This -    /// function expects the input data to be provided as a `Vec<u8>` of BGRA +    /// function expects the input data to be provided as a `Vec<u8>` of RGBA      /// pixels.      ///      /// This is useful if you have already decoded your image. @@ -31,7 +31,7 @@ impl Handle {          height: u32,          pixels: impl Into<Cow<'static, [u8]>>,      ) -> Handle { -        Self::from_data(Data::Pixels { +        Self::from_data(Data::Rgba {              width,              height,              pixels: pixels.into(), @@ -93,8 +93,8 @@ pub enum Data {      /// In-memory data      Bytes(Cow<'static, [u8]>), -    /// Decoded image pixels in BGRA format. -    Pixels { +    /// Decoded image pixels in RGBA format. +    Rgba {          /// The width of the image.          width: u32,          /// The height of the image. @@ -109,7 +109,7 @@ impl std::fmt::Debug for Data {          match self {              Data::Path(path) => write!(f, "Path({:?})", path),              Data::Bytes(_) => write!(f, "Bytes(...)"), -            Data::Pixels { width, height, .. } => { +            Data::Rgba { width, height, .. } => {                  write!(f, "Pixels({} * {})", width, height)              }          } @@ -126,7 +126,7 @@ pub trait Renderer: crate::Renderer {      type Handle: Clone + Hash;      /// Returns the dimensions of an image for the given [`Handle`]. -    fn dimensions(&self, handle: &Self::Handle) -> (u32, u32); +    fn dimensions(&self, handle: &Self::Handle) -> Size<u32>;      /// Draws an image with the given [`Handle`] and inside the provided      /// `bounds`. diff --git a/native/src/svg.rs b/native/src/svg.rs index d4d20182..a8e481d2 100644 --- a/native/src/svg.rs +++ b/native/src/svg.rs @@ -1,5 +1,5 @@  //! Load and draw vector graphics. -use crate::{Hasher, Rectangle}; +use crate::{Hasher, Rectangle, Size};  use std::borrow::Cow;  use std::hash::{Hash, Hasher as _}; @@ -82,7 +82,7 @@ impl std::fmt::Debug for Data {  /// [renderer]: crate::renderer  pub trait Renderer: crate::Renderer {      /// Returns the default dimensions of an SVG for the given [`Handle`]. -    fn dimensions(&self, handle: &Handle) -> (u32, u32); +    fn dimensions(&self, handle: &Handle) -> Size<u32>;      /// Draws an SVG with the given [`Handle`] and inside the provided `bounds`.      fn draw(&mut self, handle: Handle, bounds: Rectangle); diff --git a/native/src/widget/image.rs b/native/src/widget/image.rs index 91d68e34..8bd8ca1e 100644 --- a/native/src/widget/image.rs +++ b/native/src/widget/image.rs @@ -85,7 +85,7 @@ where  {      // The raw w/h of the underlying image      let image_size = { -        let (width, height) = renderer.dimensions(handle); +        let Size { width, height } = renderer.dimensions(handle);          Size::new(width as f32, height as f32)      }; @@ -149,7 +149,7 @@ where          _cursor_position: Point,          _viewport: &Rectangle,      ) { -        let (width, height) = renderer.dimensions(&self.handle); +        let Size { width, height } = renderer.dimensions(&self.handle);          let image_size = Size::new(width as f32, height as f32);          let bounds = layout.bounds(); diff --git a/native/src/widget/image/viewer.rs b/native/src/widget/image/viewer.rs index b1fe596c..9c83287e 100644 --- a/native/src/widget/image/viewer.rs +++ b/native/src/widget/image/viewer.rs @@ -108,7 +108,7 @@ where          renderer: &Renderer,          limits: &layout::Limits,      ) -> layout::Node { -        let (width, height) = renderer.dimensions(&self.handle); +        let Size { width, height } = renderer.dimensions(&self.handle);          let mut size = limits              .width(self.width) @@ -409,7 +409,7 @@ pub fn image_size<Renderer>(  where      Renderer: image::Renderer,  { -    let (width, height) = renderer.dimensions(handle); +    let Size { width, height } = renderer.dimensions(handle);      let (width, height) = {          let dimensions = (width as f32, height as f32); diff --git a/native/src/widget/svg.rs b/native/src/widget/svg.rs index aa68bfb8..1015ed0a 100644 --- a/native/src/widget/svg.rs +++ b/native/src/widget/svg.rs @@ -83,7 +83,7 @@ where          limits: &layout::Limits,      ) -> layout::Node {          // The raw w/h of the underlying image -        let (width, height) = renderer.dimensions(&self.handle); +        let Size { width, height } = renderer.dimensions(&self.handle);          let image_size = Size::new(width as f32, height as f32);          // The size to be available to the widget prior to `Shrink`ing @@ -120,7 +120,7 @@ where          _cursor_position: Point,          _viewport: &Rectangle,      ) { -        let (width, height) = renderer.dimensions(&self.handle); +        let Size { width, height } = renderer.dimensions(&self.handle);          let image_size = Size::new(width as f32, height as f32);          let bounds = layout.bounds(); diff --git a/wgpu/Cargo.toml b/wgpu/Cargo.toml index 9a57e58b..e9509db2 100644 --- a/wgpu/Cargo.toml +++ b/wgpu/Cargo.toml @@ -8,19 +8,19 @@ license = "MIT AND OFL-1.1"  repository = "https://github.com/iced-rs/iced"  [features] -svg = ["resvg", "usvg", "tiny-skia"] -image = ["png", "jpeg", "jpeg_rayon", "gif", "webp", "bmp"] -png = ["image_rs/png"] -jpeg = ["image_rs/jpeg"] -jpeg_rayon = ["image_rs/jpeg_rayon"] -gif = ["image_rs/gif"] -webp = ["image_rs/webp"] -pnm = ["image_rs/pnm"] -ico = ["image_rs/ico"] -bmp = ["image_rs/bmp"] -hdr = ["image_rs/hdr"] -dds = ["image_rs/dds"] -farbfeld = ["image_rs/farbfeld"] +svg = ["iced_graphics/svg"] +image = ["iced_graphics/image"] +png = ["iced_graphics/png"] +jpeg = ["iced_graphics/jpeg"] +jpeg_rayon = ["iced_graphics/jpeg_rayon"] +gif = ["iced_graphics/gif"] +webp = ["iced_graphics/webp"] +pnm = ["iced_graphics/pnm"] +ico = ["iced_graphics/ico"] +bmp = ["iced_graphics/bmp"] +hdr = ["iced_graphics/hdr"] +dds = ["iced_graphics/dds"] +farbfeld = ["iced_graphics/farbfeld"]  canvas = ["iced_graphics/canvas"]  qr_code = ["iced_graphics/qr_code"]  default_system_font = ["iced_graphics/font-source"] @@ -35,7 +35,6 @@ raw-window-handle = "0.5"  log = "0.4"  guillotiere = "0.6"  futures = "0.3" -kamadak-exif = "0.5"  bitflags = "1.2"  [dependencies.bytemuck] @@ -51,24 +50,6 @@ version = "0.3"  path = "../graphics"  features = ["font-fallback", "font-icons"] -[dependencies.image_rs] -version = "0.23" -package = "image" -default-features = false -optional = true - -[dependencies.resvg] -version = "0.18" -optional = true - -[dependencies.usvg] -version = "0.18" -optional = true - -[dependencies.tiny-skia] -version = "0.6" -optional = true -  [dependencies.encase]  version = "0.3.0"  features = ["glam"] diff --git a/wgpu/src/backend.rs b/wgpu/src/backend.rs index 80026673..946eb712 100644 --- a/wgpu/src/backend.rs +++ b/wgpu/src/backend.rs @@ -10,7 +10,7 @@ use iced_graphics::{Primitive, Viewport};  use iced_native::alignment;  use iced_native::{Font, Size}; -#[cfg(any(feature = "image_rs", feature = "svg"))] +#[cfg(any(feature = "image", feature = "svg"))]  use crate::image;  /// A [`wgpu`] graphics backend for [`iced`]. @@ -23,7 +23,7 @@ pub struct Backend {      text_pipeline: text::Pipeline,      triangle_pipeline: triangle::Pipeline, -    #[cfg(any(feature = "image_rs", feature = "svg"))] +    #[cfg(any(feature = "image", feature = "svg"))]      image_pipeline: image::Pipeline,      default_text_size: u16, @@ -47,7 +47,7 @@ impl Backend {          let triangle_pipeline =              triangle::Pipeline::new(device, format, settings.antialiasing); -        #[cfg(any(feature = "image_rs", feature = "svg"))] +        #[cfg(any(feature = "image", feature = "svg"))]          let image_pipeline = image::Pipeline::new(device, format);          Self { @@ -55,7 +55,7 @@ impl Backend {              text_pipeline,              triangle_pipeline, -            #[cfg(any(feature = "image_rs", feature = "svg"))] +            #[cfg(any(feature = "image", feature = "svg"))]              image_pipeline,              default_text_size: settings.default_text_size, @@ -98,8 +98,8 @@ impl Backend {              );          } -        #[cfg(any(feature = "image_rs", feature = "svg"))] -        self.image_pipeline.trim_cache(); +        #[cfg(any(feature = "image", feature = "svg"))] +        self.image_pipeline.trim_cache(device, encoder);      }      fn flush( @@ -148,7 +148,7 @@ impl Backend {              );          } -        #[cfg(any(feature = "image_rs", feature = "svg"))] +        #[cfg(any(feature = "image", feature = "svg"))]          {              if !layer.images.is_empty() {                  let scaled = transformation @@ -294,9 +294,9 @@ impl backend::Text for Backend {      }  } -#[cfg(feature = "image_rs")] +#[cfg(feature = "image")]  impl backend::Image for Backend { -    fn dimensions(&self, handle: &iced_native::image::Handle) -> (u32, u32) { +    fn dimensions(&self, handle: &iced_native::image::Handle) -> Size<u32> {          self.image_pipeline.dimensions(handle)      }  } @@ -306,7 +306,7 @@ impl backend::Svg for Backend {      fn viewport_dimensions(          &self,          handle: &iced_native::svg::Handle, -    ) -> (u32, u32) { +    ) -> Size<u32> {          self.image_pipeline.viewport_dimensions(handle)      }  } diff --git a/wgpu/src/image.rs b/wgpu/src/image.rs index d964aed7..d06815bb 100644 --- a/wgpu/src/image.rs +++ b/wgpu/src/image.rs @@ -1,22 +1,23 @@  mod atlas; -#[cfg(feature = "image_rs")] -mod raster; +#[cfg(feature = "image")] +use iced_graphics::image::raster;  #[cfg(feature = "svg")] -mod vector; +use iced_graphics::image::vector;  use crate::Transformation;  use atlas::Atlas;  use iced_graphics::layer; -use iced_native::Rectangle; +use iced_native::{Rectangle, Size}; +  use std::cell::RefCell;  use std::mem;  use bytemuck::{Pod, Zeroable}; -#[cfg(feature = "image_rs")] +#[cfg(feature = "image")]  use iced_native::image;  #[cfg(feature = "svg")] @@ -24,10 +25,10 @@ use iced_native::svg;  #[derive(Debug)]  pub struct Pipeline { -    #[cfg(feature = "image_rs")] -    raster_cache: RefCell<raster::Cache>, +    #[cfg(feature = "image")] +    raster_cache: RefCell<raster::Cache<Atlas>>,      #[cfg(feature = "svg")] -    vector_cache: RefCell<vector::Cache>, +    vector_cache: RefCell<vector::Cache<Atlas>>,      pipeline: wgpu::RenderPipeline,      uniforms: wgpu::Buffer, @@ -242,11 +243,11 @@ impl Pipeline {          });          Pipeline { -            #[cfg(feature = "image_rs")] -            raster_cache: RefCell::new(raster::Cache::new()), +            #[cfg(feature = "image")] +            raster_cache: RefCell::new(raster::Cache::default()),              #[cfg(feature = "svg")] -            vector_cache: RefCell::new(vector::Cache::new()), +            vector_cache: RefCell::new(vector::Cache::default()),              pipeline,              uniforms: uniforms_buffer, @@ -261,8 +262,8 @@ impl Pipeline {          }      } -    #[cfg(feature = "image_rs")] -    pub fn dimensions(&self, handle: &image::Handle) -> (u32, u32) { +    #[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); @@ -270,7 +271,7 @@ impl Pipeline {      }      #[cfg(feature = "svg")] -    pub fn viewport_dimensions(&self, handle: &svg::Handle) -> (u32, u32) { +    pub fn viewport_dimensions(&self, handle: &svg::Handle) -> Size<u32> {          let mut cache = self.vector_cache.borrow_mut();          let svg = cache.load(handle); @@ -290,7 +291,7 @@ impl Pipeline {      ) {          let instances: &mut Vec<Instance> = &mut Vec::new(); -        #[cfg(feature = "image_rs")] +        #[cfg(feature = "image")]          let mut raster_cache = self.raster_cache.borrow_mut();          #[cfg(feature = "svg")] @@ -298,12 +299,11 @@ impl Pipeline {          for image in images {              match &image { -                #[cfg(feature = "image_rs")] +                #[cfg(feature = "image")]                  layer::Image::Raster { handle, bounds } => {                      if let Some(atlas_entry) = raster_cache.upload(                          handle, -                        device, -                        encoder, +                        &mut (device, encoder),                          &mut self.texture_atlas,                      ) {                          add_instances( @@ -314,7 +314,7 @@ impl Pipeline {                          );                      }                  } -                #[cfg(not(feature = "image_rs"))] +                #[cfg(not(feature = "image"))]                  layer::Image::Raster { .. } => {}                  #[cfg(feature = "svg")] @@ -325,8 +325,7 @@ impl Pipeline {                          handle,                          size,                          _scale, -                        device, -                        encoder, +                        &mut (device, encoder),                          &mut self.texture_atlas,                      ) {                          add_instances( @@ -446,12 +445,20 @@ impl Pipeline {          }      } -    pub fn trim_cache(&mut self) { -        #[cfg(feature = "image_rs")] -        self.raster_cache.borrow_mut().trim(&mut self.texture_atlas); +    pub fn trim_cache( +        &mut self, +        device: &wgpu::Device, +        encoder: &mut wgpu::CommandEncoder, +    ) { +        #[cfg(feature = "image")] +        self.raster_cache +            .borrow_mut() +            .trim(&mut self.texture_atlas, &mut (device, encoder));          #[cfg(feature = "svg")] -        self.vector_cache.borrow_mut().trim(&mut self.texture_atlas); +        self.vector_cache +            .borrow_mut() +            .trim(&mut self.texture_atlas, &mut (device, encoder));      }  } @@ -509,15 +516,18 @@ fn add_instances(              add_instance(image_position, image_size, allocation, instances);          }          atlas::Entry::Fragmented { fragments, size } => { -            let scaling_x = image_size[0] / size.0 as f32; -            let scaling_y = image_size[1] / size.1 as f32; +            let scaling_x = image_size[0] / size.width as f32; +            let scaling_y = image_size[1] / size.height as f32;              for fragment in fragments {                  let allocation = &fragment.allocation;                  let [x, y] = image_position;                  let (fragment_x, fragment_y) = fragment.position; -                let (fragment_width, fragment_height) = allocation.size(); +                let Size { +                    width: fragment_width, +                    height: fragment_height, +                } = allocation.size();                  let position = [                      x + fragment_x as f32 * scaling_x, @@ -543,7 +553,7 @@ fn add_instance(      instances: &mut Vec<Instance>,  ) {      let (x, y) = allocation.position(); -    let (width, height) = allocation.size(); +    let Size { width, height } = allocation.size();      let layer = allocation.layer();      let instance = Instance { diff --git a/wgpu/src/image/atlas.rs b/wgpu/src/image/atlas.rs index 953dd4e2..eafe2f96 100644 --- a/wgpu/src/image/atlas.rs +++ b/wgpu/src/image/atlas.rs @@ -4,8 +4,6 @@ mod allocation;  mod allocator;  mod layer; -use std::num::NonZeroU32; -  pub use allocation::Allocation;  pub use entry::Entry;  pub use layer::Layer; @@ -14,6 +12,11 @@ use allocator::Allocator;  pub const SIZE: u32 = 2048; +use iced_graphics::image; +use iced_graphics::Size; + +use std::num::NonZeroU32; +  #[derive(Debug)]  pub struct Atlas {      texture: wgpu::Texture, @@ -35,7 +38,7 @@ impl Atlas {              mip_level_count: 1,              sample_count: 1,              dimension: wgpu::TextureDimension::D2, -            format: wgpu::TextureFormat::Bgra8UnormSrgb, +            format: wgpu::TextureFormat::Rgba8UnormSrgb,              usage: wgpu::TextureUsages::COPY_DST                  | wgpu::TextureUsages::COPY_SRC                  | wgpu::TextureUsages::TEXTURE_BINDING, @@ -61,99 +64,6 @@ impl Atlas {          self.layers.len()      } -    pub fn upload( -        &mut self, -        width: u32, -        height: u32, -        data: &[u8], -        device: &wgpu::Device, -        encoder: &mut wgpu::CommandEncoder, -    ) -> Option<Entry> { -        use wgpu::util::DeviceExt; - -        let entry = { -            let current_size = self.layers.len(); -            let entry = self.allocate(width, height)?; - -            // We grow the internal texture after allocating if necessary -            let new_layers = self.layers.len() - current_size; -            self.grow(new_layers, device, encoder); - -            entry -        }; - -        log::info!("Allocated atlas entry: {:?}", entry); - -        // It is a webgpu requirement that: -        //   BufferCopyView.layout.bytes_per_row % wgpu::COPY_BYTES_PER_ROW_ALIGNMENT == 0 -        // So we calculate padded_width by rounding width up to the next -        // multiple of wgpu::COPY_BYTES_PER_ROW_ALIGNMENT. -        let align = wgpu::COPY_BYTES_PER_ROW_ALIGNMENT; -        let padding = (align - (4 * width) % align) % align; -        let padded_width = (4 * width + padding) as usize; -        let padded_data_size = padded_width * height as usize; - -        let mut padded_data = vec![0; padded_data_size]; - -        for row in 0..height as usize { -            let offset = row * padded_width; - -            padded_data[offset..offset + 4 * width as usize].copy_from_slice( -                &data[row * 4 * width as usize..(row + 1) * 4 * width as usize], -            ) -        } - -        let buffer = -            device.create_buffer_init(&wgpu::util::BufferInitDescriptor { -                label: Some("iced_wgpu::image staging buffer"), -                contents: &padded_data, -                usage: wgpu::BufferUsages::COPY_SRC, -            }); - -        match &entry { -            Entry::Contiguous(allocation) => { -                self.upload_allocation( -                    &buffer, width, height, padding, 0, allocation, encoder, -                ); -            } -            Entry::Fragmented { fragments, .. } => { -                for fragment in fragments { -                    let (x, y) = fragment.position; -                    let offset = (y * padded_width as u32 + 4 * x) as usize; - -                    self.upload_allocation( -                        &buffer, -                        width, -                        height, -                        padding, -                        offset, -                        &fragment.allocation, -                        encoder, -                    ); -                } -            } -        } - -        log::info!("Current atlas: {:?}", self); - -        Some(entry) -    } - -    pub fn remove(&mut self, entry: &Entry) { -        log::info!("Removing atlas entry: {:?}", entry); - -        match entry { -            Entry::Contiguous(allocation) => { -                self.deallocate(allocation); -            } -            Entry::Fragmented { fragments, .. } => { -                for fragment in fragments { -                    self.deallocate(&fragment.allocation); -                } -            } -        } -    } -      fn allocate(&mut self, width: u32, height: u32) -> Option<Entry> {          // Allocate one layer if texture fits perfectly          if width == SIZE && height == SIZE { @@ -204,7 +114,7 @@ impl Atlas {              }              return Some(Entry::Fragmented { -                size: (width, height), +                size: Size::new(width, height),                  fragments,              });          } @@ -284,7 +194,7 @@ impl Atlas {          encoder: &mut wgpu::CommandEncoder,      ) {          let (x, y) = allocation.position(); -        let (width, height) = allocation.size(); +        let Size { width, height } = allocation.size();          let layer = allocation.layer();          let extent = wgpu::Extent3d { @@ -336,7 +246,7 @@ impl Atlas {              mip_level_count: 1,              sample_count: 1,              dimension: wgpu::TextureDimension::D2, -            format: wgpu::TextureFormat::Bgra8UnormSrgb, +            format: wgpu::TextureFormat::Rgba8UnormSrgb,              usage: wgpu::TextureUsages::COPY_DST                  | wgpu::TextureUsages::COPY_SRC                  | wgpu::TextureUsages::TEXTURE_BINDING, @@ -388,3 +298,100 @@ impl Atlas {              });      }  } + +impl image::Storage for Atlas { +    type Entry = Entry; +    type State<'a> = (&'a wgpu::Device, &'a mut wgpu::CommandEncoder); + +    fn upload( +        &mut self, +        width: u32, +        height: u32, +        data: &[u8], +        (device, encoder): &mut Self::State<'_>, +    ) -> Option<Self::Entry> { +        use wgpu::util::DeviceExt; + +        let entry = { +            let current_size = self.layers.len(); +            let entry = self.allocate(width, height)?; + +            // We grow the internal texture after allocating if necessary +            let new_layers = self.layers.len() - current_size; +            self.grow(new_layers, device, encoder); + +            entry +        }; + +        log::info!("Allocated atlas entry: {:?}", entry); + +        // It is a webgpu requirement that: +        //   BufferCopyView.layout.bytes_per_row % wgpu::COPY_BYTES_PER_ROW_ALIGNMENT == 0 +        // So we calculate padded_width by rounding width up to the next +        // multiple of wgpu::COPY_BYTES_PER_ROW_ALIGNMENT. +        let align = wgpu::COPY_BYTES_PER_ROW_ALIGNMENT; +        let padding = (align - (4 * width) % align) % align; +        let padded_width = (4 * width + padding) as usize; +        let padded_data_size = padded_width * height as usize; + +        let mut padded_data = vec![0; padded_data_size]; + +        for row in 0..height as usize { +            let offset = row * padded_width; + +            padded_data[offset..offset + 4 * width as usize].copy_from_slice( +                &data[row * 4 * width as usize..(row + 1) * 4 * width as usize], +            ) +        } + +        let buffer = +            device.create_buffer_init(&wgpu::util::BufferInitDescriptor { +                label: Some("iced_wgpu::image staging buffer"), +                contents: &padded_data, +                usage: wgpu::BufferUsages::COPY_SRC, +            }); + +        match &entry { +            Entry::Contiguous(allocation) => { +                self.upload_allocation( +                    &buffer, width, height, padding, 0, allocation, encoder, +                ); +            } +            Entry::Fragmented { fragments, .. } => { +                for fragment in fragments { +                    let (x, y) = fragment.position; +                    let offset = (y * padded_width as u32 + 4 * x) as usize; + +                    self.upload_allocation( +                        &buffer, +                        width, +                        height, +                        padding, +                        offset, +                        &fragment.allocation, +                        encoder, +                    ); +                } +            } +        } + +        log::info!("Current atlas: {:?}", self); + +        Some(entry) +    } + +    fn remove(&mut self, entry: &Entry, _: &mut Self::State<'_>) { +        log::info!("Removing atlas entry: {:?}", entry); + +        match entry { +            Entry::Contiguous(allocation) => { +                self.deallocate(allocation); +            } +            Entry::Fragmented { fragments, .. } => { +                for fragment in fragments { +                    self.deallocate(&fragment.allocation); +                } +            } +        } +    } +} diff --git a/wgpu/src/image/atlas/allocation.rs b/wgpu/src/image/atlas/allocation.rs index 59b7239f..43aba875 100644 --- a/wgpu/src/image/atlas/allocation.rs +++ b/wgpu/src/image/atlas/allocation.rs @@ -1,5 +1,7 @@  use crate::image::atlas::{self, allocator}; +use iced_graphics::Size; +  #[derive(Debug)]  pub enum Allocation {      Partial { @@ -19,10 +21,10 @@ impl Allocation {          }      } -    pub fn size(&self) -> (u32, u32) { +    pub fn size(&self) -> Size<u32> {          match self {              Allocation::Partial { region, .. } => region.size(), -            Allocation::Full { .. } => (atlas::SIZE, atlas::SIZE), +            Allocation::Full { .. } => Size::new(atlas::SIZE, atlas::SIZE),          }      } diff --git a/wgpu/src/image/atlas/allocator.rs b/wgpu/src/image/atlas/allocator.rs index 7a4ff5b1..03effdcb 100644 --- a/wgpu/src/image/atlas/allocator.rs +++ b/wgpu/src/image/atlas/allocator.rs @@ -46,10 +46,10 @@ impl Region {          (rectangle.min.x as u32, rectangle.min.y as u32)      } -    pub fn size(&self) -> (u32, u32) { +    pub fn size(&self) -> iced_graphics::Size<u32> {          let size = self.allocation.rectangle.size(); -        (size.width as u32, size.height as u32) +        iced_graphics::Size::new(size.width as u32, size.height as u32)      }  } diff --git a/wgpu/src/image/atlas/entry.rs b/wgpu/src/image/atlas/entry.rs index 9b3f16df..69c05a50 100644 --- a/wgpu/src/image/atlas/entry.rs +++ b/wgpu/src/image/atlas/entry.rs @@ -1,17 +1,19 @@  use crate::image::atlas; +use iced_graphics::image; +use iced_graphics::Size; +  #[derive(Debug)]  pub enum Entry {      Contiguous(atlas::Allocation),      Fragmented { -        size: (u32, u32), +        size: Size<u32>,          fragments: Vec<Fragment>,      },  } -impl Entry { -    #[cfg(feature = "image_rs")] -    pub fn size(&self) -> (u32, u32) { +impl image::storage::Entry for Entry { +    fn size(&self) -> Size<u32> {          match self {              Entry::Contiguous(allocation) => allocation.size(),              Entry::Fragmented { size, .. } => *size, diff --git a/wgpu/src/lib.rs b/wgpu/src/lib.rs index 1295516b..dcb699e8 100644 --- a/wgpu/src/lib.rs +++ b/wgpu/src/lib.rs @@ -56,7 +56,7 @@ pub use settings::Settings;  pub(crate) use iced_graphics::Transformation; -#[cfg(any(feature = "image_rs", feature = "svg"))] +#[cfg(any(feature = "image", feature = "svg"))]  mod image;  /// A [`wgpu`] graphics renderer for [`iced`].  | 
