From 09707f29fcf7fbd71570a43db214921043427c3f Mon Sep 17 00:00:00 2001
From: Héctor Ramón Jiménez <hector0193@gmail.com>
Date: Sun, 15 Dec 2019 06:19:07 +0100
Subject: Rerasterize SVGs when resized and refactor a bit

---
 native/src/widget/svg.rs        | 101 +++++++++++--
 wgpu/src/image.rs               | 308 +++++++---------------------------------
 wgpu/src/image/raster.rs        | 176 +++++++++++++++++++++++
 wgpu/src/image/vector.rs        | 187 ++++++++++++++++++++++++
 wgpu/src/primitive.rs           |  12 +-
 wgpu/src/renderer.rs            |  11 +-
 wgpu/src/renderer/widget.rs     |   1 +
 wgpu/src/renderer/widget/svg.rs |  22 +++
 8 files changed, 544 insertions(+), 274 deletions(-)
 create mode 100644 wgpu/src/image/raster.rs
 create mode 100644 wgpu/src/image/vector.rs
 create mode 100644 wgpu/src/renderer/widget/svg.rs

diff --git a/native/src/widget/svg.rs b/native/src/widget/svg.rs
index 097c1d86..42f2ebdf 100644
--- a/native/src/widget/svg.rs
+++ b/native/src/widget/svg.rs
@@ -1,28 +1,27 @@
 //! Display an icon.
-use crate::{
-    image, layout, Element, Hasher, Layout, Length, Point, Size, Widget,
-};
+use crate::{layout, Element, Hasher, Layout, Length, Point, Size, Widget};
 
 use std::{
     hash::Hash,
-    path::PathBuf,
+    path::{Path, PathBuf},
 };
 
 /// A simple icon_loader widget.
 #[derive(Debug, Clone)]
 pub struct Svg {
-    handle: image::Handle,
+    handle: Handle,
     width: Length,
     height: Length,
 }
 
 impl Svg {
-    /// Create a new [`Svg`] from the file at `path`.
+    /// Creates a new [`Svg`] from the given [`Handle`].
     ///
     /// [`Svg`]: struct.Svg.html
-    pub fn new(path: impl Into<PathBuf>) -> Self {
+    /// [`Handle`]: struct.Handle.html
+    pub fn new(handle: impl Into<Handle>) -> Self {
         Svg {
-            handle: image::Handle::from_path(path),
+            handle: handle.into(),
             width: Length::Fill,
             height: Length::Fill,
         }
@@ -47,7 +46,7 @@ impl Svg {
 
 impl<Message, Renderer> Widget<Message, Renderer> for Svg
 where
-    Renderer: image::Renderer,
+    Renderer: self::Renderer,
 {
     fn width(&self) -> Length {
         self.width
@@ -57,7 +56,11 @@ where
         self.height
     }
 
-    fn layout(&self, renderer: &Renderer, limits: &layout::Limits) -> layout::Node {
+    fn layout(
+        &self,
+        renderer: &Renderer,
+        limits: &layout::Limits,
+    ) -> layout::Node {
         let (width, height) = renderer.dimensions(&self.handle);
 
         let aspect_ratio = width as f32 / height as f32;
@@ -65,7 +68,7 @@ where
         let mut size = limits
             .width(self.width)
             .height(self.height)
-            .max();
+            .resolve(Size::new(width as f32, height as f32));
 
         let viewport_aspect_ratio = size.width / size.height;
 
@@ -93,9 +96,83 @@ where
     }
 }
 
+/// An [`Svg`] handle.
+///
+/// [`Svg`]: struct.Svg.html
+#[derive(Debug, Clone)]
+pub struct Handle {
+    id: u64,
+    path: PathBuf,
+}
+
+impl Handle {
+    /// Creates an SVG [`Handle`] pointing to the vector image of the given
+    /// path.
+    ///
+    /// [`Handle`]: struct.Handle.html
+    pub fn from_path<T: Into<PathBuf>>(path: T) -> Handle {
+        use std::hash::Hasher as _;
+
+        let path = path.into();
+
+        let mut hasher = Hasher::default();
+        path.hash(&mut hasher);
+
+        Handle {
+            id: hasher.finish(),
+            path,
+        }
+    }
+
+    /// Returns the unique identifier of the [`Handle`].
+    ///
+    /// [`Handle`]: struct.Handle.html
+    pub fn id(&self) -> u64 {
+        self.id
+    }
+
+    /// Returns a reference to the path of the [`Handle`].
+    ///
+    /// [`Handle`]: enum.Handle.html
+    pub fn path(&self) -> &Path {
+        &self.path
+    }
+}
+
+impl From<String> for Handle {
+    fn from(path: String) -> Handle {
+        Handle::from_path(path)
+    }
+}
+
+impl From<&str> for Handle {
+    fn from(path: &str) -> Handle {
+        Handle::from_path(path)
+    }
+}
+
+/// The renderer of an [`Svg`].
+///
+/// Your [renderer] will need to implement this trait before being able to use
+/// an [`Svg`] in your user interface.
+///
+/// [`Svg`]: struct.Svg.html
+/// [renderer]: ../../renderer/index.html
+pub trait Renderer: crate::Renderer {
+    /// Returns the default dimensions of an [`Svg`] located on the given path.
+    ///
+    /// [`Svg`]: struct.Svg.html
+    fn dimensions(&self, handle: &Handle) -> (u32, u32);
+
+    /// Draws an [`Svg`].
+    ///
+    /// [`Svg`]: struct.Svg.html
+    fn draw(&mut self, handle: Handle, layout: Layout<'_>) -> Self::Output;
+}
+
 impl<'a, Message, Renderer> From<Svg> for Element<'a, Message, Renderer>
 where
-    Renderer: image::Renderer,
+    Renderer: self::Renderer,
 {
     fn from(icon: Svg) -> Element<'a, Message, Renderer> {
         Element::new(icon)
diff --git a/wgpu/src/image.rs b/wgpu/src/image.rs
index e0e093e0..01059d2d 100644
--- a/wgpu/src/image.rs
+++ b/wgpu/src/image.rs
@@ -1,20 +1,15 @@
+mod raster;
+mod vector;
+
 use crate::Transformation;
-use iced_native::{
-    image::{Data, Handle},
-    Rectangle,
-};
-
-use std::{
-    cell::RefCell,
-    collections::{HashMap, HashSet},
-    fmt,
-    mem,
-    rc::Rc,
-};
+use iced_native::{image, svg, Rectangle};
+
+use std::{cell::RefCell, mem};
 
 #[derive(Debug)]
 pub struct Pipeline {
-    cache: RefCell<Cache>,
+    raster_cache: RefCell<raster::Cache>,
+    vector_cache: RefCell<vector::Cache>,
 
     pipeline: wgpu::RenderPipeline,
     uniforms: wgpu::Buffer,
@@ -194,7 +189,8 @@ impl Pipeline {
         });
 
         Pipeline {
-            cache: RefCell::new(Cache::new()),
+            raster_cache: RefCell::new(raster::Cache::new()),
+            vector_cache: RefCell::new(vector::Cache::new()),
 
             pipeline,
             uniforms: uniforms_buffer,
@@ -206,44 +202,20 @@ impl Pipeline {
         }
     }
 
-    pub fn dimensions(&self, handle: &Handle) -> (u32, u32) {
-        self.load(handle);
+    pub fn dimensions(&self, handle: &image::Handle) -> (u32, u32) {
+        let mut cache = self.raster_cache.borrow_mut();
+        let memory = cache.load(&handle);
 
-        self.cache.borrow_mut().get(handle).unwrap().dimensions()
+        memory.dimensions()
     }
 
-    fn load(&self, handle: &Handle) {
-        if !self.cache.borrow().contains(&handle) {
-            let memory = match handle.data() {
-                Data::Path(path) => {
-                    if let Some(ext) = path.extension() {
-                        if ext == "svg" || ext == "svgz" || ext == "SVG" || ext == "SVGZ" {
-                            let opt = resvg::Options::default();
-                            match resvg::usvg::Tree::from_file(path, &opt.usvg) {
-                                Ok(tree) => Memory::Host(HostMemory::Svg(tree)),
-                                Err(_) => Memory::Invalid,
-                            }
-                        } else if let Ok(image) = image::open(path) {
-                            Memory::Host(HostMemory::Image(image.to_bgra()))
-                        } else {
-                            Memory::NotFound
-                        }
-                    } else if let Ok(image) = image::open(path) {
-                        Memory::Host(HostMemory::Image(image.to_bgra()))
-                    } else {
-                        Memory::NotFound
-                    }
-                }
-                Data::Bytes(bytes) => {
-                    if let Ok(image) = image::load_from_memory(&bytes) {
-                        Memory::Host(HostMemory::Image(image.to_bgra()))
-                    } else {
-                        Memory::Invalid
-                    }
-                }
-            };
+    pub fn viewport_dimensions(&self, handle: &svg::Handle) -> (u32, u32) {
+        let mut cache = self.vector_cache.borrow_mut();
 
-            let _ = self.cache.borrow_mut().insert(&handle, memory);
+        if let Some(svg) = cache.load(&handle) {
+            svg.viewport_dimensions()
+        } else {
+            (1, 1)
         }
     }
 
@@ -255,7 +227,7 @@ impl Pipeline {
         transformation: Transformation,
         bounds: Rectangle<u32>,
         target: &wgpu::TextureView,
-        dpi: f32,
+        scale: f32,
     ) {
         let uniforms_buffer = device
             .create_buffer_mapped(1, wgpu::BufferUsage::COPY_SRC)
@@ -276,21 +248,28 @@ impl Pipeline {
         //
         // [1]: https://github.com/nical/guillotiere
         for image in instances {
-            self.load(&image.handle);
-
-            if let Some(texture) = self
-                .cache
-                .borrow_mut()
-                .get(&image.handle)
-                .unwrap()
-                .upload(
-                    device,
-                    encoder,
-                    &self.texture_layout,
-                    (image.scale[0] * dpi) as u32,
-                    (image.scale[1] * dpi) as u32,
-                )
-            {
+            let uploaded_texture = match &image.handle {
+                Handle::Raster(handle) => {
+                    let mut cache = self.raster_cache.borrow_mut();
+                    let memory = cache.load(&handle);
+
+                    memory.upload(device, encoder, &self.texture_layout)
+                }
+                Handle::Vector(handle) => {
+                    let mut cache = self.vector_cache.borrow_mut();
+
+                    cache.upload(
+                        handle,
+                        image.scale,
+                        scale,
+                        device,
+                        encoder,
+                        &self.texture_layout,
+                    )
+                }
+            };
+
+            if let Some(texture) = uploaded_texture {
                 let instance_buffer = device
                     .create_buffer_mapped(1, wgpu::BufferUsage::COPY_SRC)
                     .fill_from_slice(&[Instance {
@@ -353,200 +332,8 @@ impl Pipeline {
     }
 
     pub fn trim_cache(&mut self) {
-        self.cache.borrow_mut().trim();
-    }
-}
-
-enum HostMemory {
-    Image(image::ImageBuffer<image::Bgra<u8>, Vec<u8>>),
-    Svg(resvg::usvg::Tree),
-}
-
-impl fmt::Debug for HostMemory {
-    fn fmt(
-        &self,
-        f: &mut fmt::Formatter<'_>,
-    ) -> Result<(), fmt::Error> {
-        match self {
-            HostMemory::Image(_) => write!(f, "HostMemory::Image"),
-            HostMemory::Svg(_) => write!(f, "HostMemory::Svg"),
-        }
-    }
-}
-
-#[derive(Debug)]
-enum Memory {
-    Host(HostMemory),
-    Device {
-        bind_group: Rc<wgpu::BindGroup>,
-        width: u32,
-        height: u32,
-    },
-    NotFound,
-    Invalid,
-}
-
-impl Memory {
-    fn dimensions(&self) -> (u32, u32) {
-        match self {
-            Memory::Host(host_memory) => match host_memory {
-                HostMemory::Image(image) => image.dimensions(),
-                HostMemory::Svg(tree) => {
-                    let size = tree.svg_node().size;
-                    (size.width() as u32, size.height() as u32)
-                }
-            }
-            Memory::Device { width, height, .. } => (*width, *height),
-            Memory::NotFound => (1, 1),
-            Memory::Invalid => (1, 1),
-        }
-    }
-
-    fn upload(
-        &mut self,
-        device: &wgpu::Device,
-        encoder: &mut wgpu::CommandEncoder,
-        texture_layout: &wgpu::BindGroupLayout,
-        svg_width: u32,
-        svg_height: u32,
-    ) -> Option<Rc<wgpu::BindGroup>> {
-        match self {
-            Memory::Host(host_memory) => {
-                let (width, height) = match host_memory {
-                    HostMemory::Image(image) => image.dimensions(),
-                    HostMemory::Svg(_) => (svg_width, svg_height),
-                };
-
-                let extent = wgpu::Extent3d {
-                    width,
-                    height,
-                    depth: 1,
-                };
-
-                let texture = device.create_texture(&wgpu::TextureDescriptor {
-                    size: extent,
-                    array_layer_count: 1,
-                    mip_level_count: 1,
-                    sample_count: 1,
-                    dimension: wgpu::TextureDimension::D2,
-                    format: wgpu::TextureFormat::Bgra8UnormSrgb,
-                    usage: wgpu::TextureUsage::COPY_DST
-                        | wgpu::TextureUsage::SAMPLED,
-                });
-
-                let temp_buf = match host_memory {
-                    HostMemory::Image(image) => {
-                        let flat_samples = image.as_flat_samples();
-                        let slice = flat_samples.as_slice();
-                        device.create_buffer_mapped(
-                            slice.len(),
-                            wgpu::BufferUsage::COPY_SRC,
-                        )
-                            .fill_from_slice(slice)
-                    },
-                    HostMemory::Svg(tree) => {
-                        let mut canvas =
-                            resvg::raqote::DrawTarget::new(width as i32, height as i32);
-                        let opt = resvg::Options::default();
-                        let screen_size =
-                            resvg::ScreenSize::new(width, height).unwrap();
-                        resvg::backend_raqote::render_to_canvas(
-                            tree,
-                            &opt,
-                            screen_size,
-                            &mut canvas,
-                        );
-                        let slice = canvas.get_data();
-
-                        device.create_buffer_mapped(
-                                slice.len(),
-                                wgpu::BufferUsage::COPY_SRC,
-                            )
-                            .fill_from_slice(slice)
-                    },
-                };
-
-                encoder.copy_buffer_to_texture(
-                    wgpu::BufferCopyView {
-                        buffer: &temp_buf,
-                        offset: 0,
-                        row_pitch: 4 * width as u32,
-                        image_height: height as u32,
-                    },
-                    wgpu::TextureCopyView {
-                        texture: &texture,
-                        array_layer: 0,
-                        mip_level: 0,
-                        origin: wgpu::Origin3d {
-                            x: 0.0,
-                            y: 0.0,
-                            z: 0.0,
-                        },
-                    },
-                    extent,
-                );
-
-                let bind_group =
-                    device.create_bind_group(&wgpu::BindGroupDescriptor {
-                        layout: texture_layout,
-                        bindings: &[wgpu::Binding {
-                            binding: 0,
-                            resource: wgpu::BindingResource::TextureView(
-                                &texture.create_default_view(),
-                            ),
-                        }],
-                    });
-
-                let bind_group = Rc::new(bind_group);
-
-                *self = Memory::Device {
-                    bind_group: bind_group.clone(),
-                    width,
-                    height,
-                };
-
-                Some(bind_group)
-            }
-            Memory::Device { bind_group, .. } => Some(bind_group.clone()),
-            Memory::NotFound => None,
-            Memory::Invalid => None,
-        }
-    }
-}
-
-#[derive(Debug)]
-struct Cache {
-    map: HashMap<u64, Memory>,
-    hits: HashSet<u64>,
-}
-
-impl Cache {
-    fn new() -> Self {
-        Self {
-            map: HashMap::new(),
-            hits: HashSet::new(),
-        }
-    }
-
-    fn contains(&self, handle: &Handle) -> bool {
-        self.map.contains_key(&handle.id())
-    }
-
-    fn get(&mut self, handle: &Handle) -> Option<&mut Memory> {
-        let _ = self.hits.insert(handle.id());
-
-        self.map.get_mut(&handle.id())
-    }
-
-    fn insert(&mut self, handle: &Handle, memory: Memory) {
-        let _ = self.map.insert(handle.id(), memory);
-    }
-
-    fn trim(&mut self) {
-        let hits = &self.hits;
-
-        self.map.retain(|k, _| hits.contains(k));
-        self.hits.clear();
+        self.raster_cache.borrow_mut().trim();
+        self.vector_cache.borrow_mut().trim();
     }
 }
 
@@ -556,6 +343,11 @@ pub struct Image {
     pub scale: [f32; 2],
 }
 
+pub enum Handle {
+    Raster(image::Handle),
+    Vector(svg::Handle),
+}
+
 #[repr(C)]
 #[derive(Clone, Copy)]
 pub struct Vertex {
diff --git a/wgpu/src/image/raster.rs b/wgpu/src/image/raster.rs
new file mode 100644
index 00000000..fa107879
--- /dev/null
+++ b/wgpu/src/image/raster.rs
@@ -0,0 +1,176 @@
+use iced_native::image;
+use std::{
+    collections::{HashMap, HashSet},
+    rc::Rc,
+};
+
+#[derive(Debug)]
+pub enum Memory {
+    Host(::image::ImageBuffer<::image::Bgra<u8>, Vec<u8>>),
+    Device {
+        bind_group: Rc<wgpu::BindGroup>,
+        width: u32,
+        height: u32,
+    },
+    NotFound,
+    Invalid,
+}
+
+impl Memory {
+    pub fn dimensions(&self) -> (u32, u32) {
+        match self {
+            Memory::Host(image) => image.dimensions(),
+            Memory::Device { width, height, .. } => (*width, *height),
+            Memory::NotFound => (1, 1),
+            Memory::Invalid => (1, 1),
+        }
+    }
+
+    pub fn upload(
+        &mut self,
+        device: &wgpu::Device,
+        encoder: &mut wgpu::CommandEncoder,
+        texture_layout: &wgpu::BindGroupLayout,
+    ) -> Option<Rc<wgpu::BindGroup>> {
+        match self {
+            Memory::Host(image) => {
+                let (width, height) = image.dimensions();
+
+                let extent = wgpu::Extent3d {
+                    width,
+                    height,
+                    depth: 1,
+                };
+
+                let texture = device.create_texture(&wgpu::TextureDescriptor {
+                    size: extent,
+                    array_layer_count: 1,
+                    mip_level_count: 1,
+                    sample_count: 1,
+                    dimension: wgpu::TextureDimension::D2,
+                    format: wgpu::TextureFormat::Bgra8UnormSrgb,
+                    usage: wgpu::TextureUsage::COPY_DST
+                        | wgpu::TextureUsage::SAMPLED,
+                });
+
+                let temp_buf = {
+                    let flat_samples = image.as_flat_samples();
+                    let slice = flat_samples.as_slice();
+
+                    device
+                        .create_buffer_mapped(
+                            slice.len(),
+                            wgpu::BufferUsage::COPY_SRC,
+                        )
+                        .fill_from_slice(slice)
+                };
+
+                encoder.copy_buffer_to_texture(
+                    wgpu::BufferCopyView {
+                        buffer: &temp_buf,
+                        offset: 0,
+                        row_pitch: 4 * width as u32,
+                        image_height: height as u32,
+                    },
+                    wgpu::TextureCopyView {
+                        texture: &texture,
+                        array_layer: 0,
+                        mip_level: 0,
+                        origin: wgpu::Origin3d {
+                            x: 0.0,
+                            y: 0.0,
+                            z: 0.0,
+                        },
+                    },
+                    extent,
+                );
+
+                let bind_group =
+                    device.create_bind_group(&wgpu::BindGroupDescriptor {
+                        layout: texture_layout,
+                        bindings: &[wgpu::Binding {
+                            binding: 0,
+                            resource: wgpu::BindingResource::TextureView(
+                                &texture.create_default_view(),
+                            ),
+                        }],
+                    });
+
+                let bind_group = Rc::new(bind_group);
+
+                *self = Memory::Device {
+                    bind_group: bind_group.clone(),
+                    width,
+                    height,
+                };
+
+                Some(bind_group)
+            }
+            Memory::Device { bind_group, .. } => Some(bind_group.clone()),
+            Memory::NotFound => None,
+            Memory::Invalid => None,
+        }
+    }
+}
+
+#[derive(Debug)]
+pub struct Cache {
+    map: HashMap<u64, Memory>,
+    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 {
+        if self.contains(handle) {
+            return self.get(handle).unwrap();
+        }
+
+        let memory = match handle.data() {
+            image::Data::Path(path) => {
+                if let Ok(image) = ::image::open(path) {
+                    Memory::Host(image.to_bgra())
+                } else {
+                    Memory::NotFound
+                }
+            }
+            image::Data::Bytes(bytes) => {
+                if let Ok(image) = ::image::load_from_memory(&bytes) {
+                    Memory::Host(image.to_bgra())
+                } else {
+                    Memory::Invalid
+                }
+            }
+        };
+
+        self.insert(handle, memory);
+        self.get(handle).unwrap()
+    }
+
+    pub fn trim(&mut self) {
+        let hits = &self.hits;
+
+        self.map.retain(|k, _| hits.contains(k));
+        self.hits.clear();
+    }
+
+    fn get(&mut self, handle: &image::Handle) -> Option<&mut Memory> {
+        let _ = self.hits.insert(handle.id());
+
+        self.map.get_mut(&handle.id())
+    }
+
+    fn insert(&mut self, handle: &image::Handle, memory: Memory) {
+        let _ = self.map.insert(handle.id(), memory);
+    }
+
+    fn contains(&self, handle: &image::Handle) -> bool {
+        self.map.contains_key(&handle.id())
+    }
+}
diff --git a/wgpu/src/image/vector.rs b/wgpu/src/image/vector.rs
new file mode 100644
index 00000000..0894c6bc
--- /dev/null
+++ b/wgpu/src/image/vector.rs
@@ -0,0 +1,187 @@
+use iced_native::svg;
+use std::{
+    collections::{HashMap, HashSet},
+    rc::Rc,
+};
+
+pub struct Svg {
+    tree: resvg::usvg::Tree,
+}
+
+impl Svg {
+    pub fn viewport_dimensions(&self) -> (u32, u32) {
+        let size = self.tree.svg_node().size;
+
+        (size.width() as u32, size.height() as u32)
+    }
+}
+
+impl std::fmt::Debug for Svg {
+    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+        write!(f, "Svg")
+    }
+}
+
+#[derive(Debug)]
+pub struct Cache {
+    svgs: HashMap<u64, Svg>,
+    rasterized: HashMap<(u64, u32, u32), Rc<wgpu::BindGroup>>,
+    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(),
+        }
+    }
+
+    pub fn load(&mut self, handle: &svg::Handle) -> Option<&Svg> {
+        if self.svgs.contains_key(&handle.id()) {
+            return self.svgs.get(&handle.id());
+        }
+
+        let opt = resvg::Options::default();
+
+        match resvg::usvg::Tree::from_file(handle.path(), &opt.usvg) {
+            Ok(tree) => {
+                let _ = self.svgs.insert(handle.id(), Svg { tree });
+            }
+            Err(_) => {}
+        };
+
+        self.svgs.get(&handle.id())
+    }
+
+    pub fn upload(
+        &mut self,
+        handle: &svg::Handle,
+        [width, height]: [f32; 2],
+        scale: f32,
+        device: &wgpu::Device,
+        encoder: &mut wgpu::CommandEncoder,
+        texture_layout: &wgpu::BindGroupLayout,
+    ) -> Option<Rc<wgpu::BindGroup>> {
+        let id = handle.id();
+
+        let (width, height) = (
+            (scale * width).round() as u32,
+            (scale * height).round() as u32,
+        );
+
+        // TODO: Optimize!
+        // We currently rerasterize the SVG when its size changes. This is slow
+        // as heck. A GPU rasterizer like `pathfinder` may perform better.
+        // It would be cool to be able to smooth resize the `tiger` example.
+        if let Some(bind_group) = self.rasterized.get(&(id, width, height)) {
+            let _ = self.svg_hits.insert(id);
+            let _ = self.rasterized_hits.insert((id, width, height));
+
+            return Some(bind_group.clone());
+        }
+
+        match self.load(handle) {
+            Some(svg) => {
+                let extent = wgpu::Extent3d {
+                    width,
+                    height,
+                    depth: 1,
+                };
+
+                let texture = device.create_texture(&wgpu::TextureDescriptor {
+                    size: extent,
+                    array_layer_count: 1,
+                    mip_level_count: 1,
+                    sample_count: 1,
+                    dimension: wgpu::TextureDimension::D2,
+                    format: wgpu::TextureFormat::Bgra8UnormSrgb,
+                    usage: wgpu::TextureUsage::COPY_DST
+                        | wgpu::TextureUsage::SAMPLED,
+                });
+
+                let temp_buf = {
+                    let screen_size =
+                        resvg::ScreenSize::new(width, height).unwrap();
+
+                    let mut canvas = resvg::raqote::DrawTarget::new(
+                        width as i32,
+                        height as i32,
+                    );
+
+                    resvg::backend_raqote::render_to_canvas(
+                        &svg.tree,
+                        &resvg::Options::default(),
+                        screen_size,
+                        &mut canvas,
+                    );
+
+                    let slice = canvas.get_data();
+
+                    device
+                        .create_buffer_mapped(
+                            slice.len(),
+                            wgpu::BufferUsage::COPY_SRC,
+                        )
+                        .fill_from_slice(slice)
+                };
+
+                encoder.copy_buffer_to_texture(
+                    wgpu::BufferCopyView {
+                        buffer: &temp_buf,
+                        offset: 0,
+                        row_pitch: 4 * width as u32,
+                        image_height: height as u32,
+                    },
+                    wgpu::TextureCopyView {
+                        texture: &texture,
+                        array_layer: 0,
+                        mip_level: 0,
+                        origin: wgpu::Origin3d {
+                            x: 0.0,
+                            y: 0.0,
+                            z: 0.0,
+                        },
+                    },
+                    extent,
+                );
+
+                let bind_group =
+                    device.create_bind_group(&wgpu::BindGroupDescriptor {
+                        layout: texture_layout,
+                        bindings: &[wgpu::Binding {
+                            binding: 0,
+                            resource: wgpu::BindingResource::TextureView(
+                                &texture.create_default_view(),
+                            ),
+                        }],
+                    });
+
+                let bind_group = Rc::new(bind_group);
+
+                let _ = self
+                    .rasterized
+                    .insert((id, width, height), bind_group.clone());
+
+                let _ = self.svg_hits.insert(id);
+                let _ = self.rasterized_hits.insert((id, width, height));
+
+                Some(bind_group)
+            }
+            None => None,
+        }
+    }
+
+    pub fn trim(&mut self) {
+        let svg_hits = &self.svg_hits;
+        let rasterized_hits = &self.rasterized_hits;
+
+        self.svgs.retain(|k, _| svg_hits.contains(k));
+        self.rasterized.retain(|k, _| rasterized_hits.contains(k));
+        self.svg_hits.clear();
+        self.rasterized_hits.clear();
+    }
+}
diff --git a/wgpu/src/primitive.rs b/wgpu/src/primitive.rs
index 04264e5d..958cc17f 100644
--- a/wgpu/src/primitive.rs
+++ b/wgpu/src/primitive.rs
@@ -1,6 +1,6 @@
 use iced_native::{
-    image, Background, Color, Font, HorizontalAlignment, Rectangle, Vector,
-    VerticalAlignment,
+    image, svg, Background, Color, Font, HorizontalAlignment, Rectangle,
+    Vector, VerticalAlignment,
 };
 
 /// A rendering primitive.
@@ -46,6 +46,14 @@ pub enum Primitive {
         /// The bounds of the image
         bounds: Rectangle,
     },
+    /// An SVG primitive
+    Svg {
+        /// The path of the SVG file
+        handle: svg::Handle,
+
+        /// The bounds of the viewport
+        bounds: Rectangle,
+    },
     /// A clip primitive
     Clip {
         /// The bounds of the clip
diff --git a/wgpu/src/renderer.rs b/wgpu/src/renderer.rs
index d1d4de14..365ef1ef 100644
--- a/wgpu/src/renderer.rs
+++ b/wgpu/src/renderer.rs
@@ -1,4 +1,4 @@
-use crate::{quad, text, Image, Primitive, Quad, Transformation};
+use crate::{image, quad, text, Image, Primitive, Quad, Transformation};
 use iced_native::{
     renderer::{Debugger, Windowed},
     Background, Color, Layout, MouseCursor, Point, Rectangle, Vector, Widget,
@@ -232,7 +232,14 @@ impl Renderer {
             }
             Primitive::Image { handle, bounds } => {
                 layer.images.push(Image {
-                    handle: handle.clone(),
+                    handle: image::Handle::Raster(handle.clone()),
+                    position: [bounds.x, bounds.y],
+                    scale: [bounds.width, bounds.height],
+                });
+            }
+            Primitive::Svg { handle, bounds } => {
+                layer.images.push(Image {
+                    handle: image::Handle::Vector(handle.clone()),
                     position: [bounds.x, bounds.y],
                     scale: [bounds.width, bounds.height],
                 });
diff --git a/wgpu/src/renderer/widget.rs b/wgpu/src/renderer/widget.rs
index 52410bee..65bb3bcd 100644
--- a/wgpu/src/renderer/widget.rs
+++ b/wgpu/src/renderer/widget.rs
@@ -6,5 +6,6 @@ mod radio;
 mod row;
 mod scrollable;
 mod slider;
+mod svg;
 mod text;
 mod text_input;
diff --git a/wgpu/src/renderer/widget/svg.rs b/wgpu/src/renderer/widget/svg.rs
new file mode 100644
index 00000000..67bc3fe1
--- /dev/null
+++ b/wgpu/src/renderer/widget/svg.rs
@@ -0,0 +1,22 @@
+use crate::{Primitive, Renderer};
+use iced_native::{svg, Layout, MouseCursor};
+
+impl svg::Renderer for Renderer {
+    fn dimensions(&self, handle: &svg::Handle) -> (u32, u32) {
+        self.image_pipeline.viewport_dimensions(handle)
+    }
+
+    fn draw(
+        &mut self,
+        handle: svg::Handle,
+        layout: Layout<'_>,
+    ) -> Self::Output {
+        (
+            Primitive::Svg {
+                handle,
+                bounds: layout.bounds(),
+            },
+            MouseCursor::OutOfBounds,
+        )
+    }
+}
-- 
cgit