From 80324284282f173e4d26e1f297daaf71a93f51a6 Mon Sep 17 00:00:00 2001 From: Malte Veerman Date: Fri, 6 Dec 2019 16:47:40 +0100 Subject: [PATCH 01/11] Implemented SVG support in iced_wgpu. --- wgpu/Cargo.toml | 1 + wgpu/src/lib.rs | 2 + wgpu/src/primitive.rs | 9 + wgpu/src/renderer.rs | 34 ++- wgpu/src/svg.rs | 595 ++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 640 insertions(+), 1 deletion(-) create mode 100644 wgpu/src/svg.rs diff --git a/wgpu/Cargo.toml b/wgpu/Cargo.toml index 47ec053717..e8e9c877ad 100644 --- a/wgpu/Cargo.toml +++ b/wgpu/Cargo.toml @@ -17,3 +17,4 @@ image = "0.22" glam = "0.8" font-kit = "0.4" log = "0.4" +resvg = { version = "0.8", features = ["raqote-backend"] } diff --git a/wgpu/src/lib.rs b/wgpu/src/lib.rs index 9f9ed8db81..1f5794ee6e 100644 --- a/wgpu/src/lib.rs +++ b/wgpu/src/lib.rs @@ -28,11 +28,13 @@ mod image; mod primitive; mod quad; mod renderer; +mod svg; mod text; mod transformation; pub(crate) use crate::image::Image; pub(crate) use quad::Quad; +pub(crate) use svg::Svg; pub(crate) use transformation::Transformation; pub use primitive::Primitive; diff --git a/wgpu/src/primitive.rs b/wgpu/src/primitive.rs index 04264e5d54..c637626baf 100644 --- a/wgpu/src/primitive.rs +++ b/wgpu/src/primitive.rs @@ -3,6 +3,8 @@ use iced_native::{ VerticalAlignment, }; +use crate::svg; + /// A rendering primitive. #[derive(Debug, Clone)] pub enum Primitive { @@ -46,6 +48,13 @@ pub enum Primitive { /// The bounds of the image bounds: Rectangle, }, + /// A svg icon primitive + Svg { + /// The handle of the icon + handle: svg::Handle, + /// The bounds of the icon + bounds: Rectangle, + }, /// A clip primitive Clip { /// The bounds of the clip diff --git a/wgpu/src/renderer.rs b/wgpu/src/renderer.rs index fa52bd96e8..9895e1c4f8 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::{quad, text, Image, Primitive, Quad, Svg, Transformation}; use iced_native::{ renderer::{Debugger, Windowed}, Background, Color, Layout, MouseCursor, Point, Rectangle, Vector, Widget, @@ -23,6 +23,7 @@ pub struct Renderer { queue: Queue, quad_pipeline: quad::Pipeline, image_pipeline: crate::image::Pipeline, + svg_pipeline: crate::svg::Pipeline, text_pipeline: text::Pipeline, } @@ -31,6 +32,7 @@ struct Layer<'a> { offset: Vector, quads: Vec, images: Vec, + svgs: Vec, text: Vec>, } @@ -41,6 +43,7 @@ impl<'a> Layer<'a> { offset, quads: Vec::new(), images: Vec::new(), + svgs: Vec::new(), text: Vec::new(), } } @@ -64,12 +67,14 @@ impl Renderer { let text_pipeline = text::Pipeline::new(&mut device); let quad_pipeline = quad::Pipeline::new(&mut device); let image_pipeline = crate::image::Pipeline::new(&mut device); + let svg_pipeline = crate::svg::Pipeline::new(&mut device); Self { device, queue, quad_pipeline, image_pipeline, + svg_pipeline, text_pipeline, } } @@ -128,6 +133,7 @@ impl Renderer { self.queue.submit(&[encoder.finish()]); self.image_pipeline.trim_cache(); + self.svg_pipeline.trim_cache(); *mouse_cursor } @@ -237,6 +243,13 @@ impl Renderer { scale: [bounds.width, bounds.height], }); } + Primitive::Svg { handle, bounds } => { + layer.svgs.push(Svg { + handle: handle.clone(), + position: [bounds.x, bounds.y], + scale: [bounds.width, bounds.height], + }) + }, Primitive::Clip { bounds, offset, @@ -345,6 +358,25 @@ impl Renderer { ); } + if layer.svgs.len() > 0 { + let translated = transformation + * Transformation::translate( + -(layer.offset.x as f32), + -(layer.offset.y as f32), + ); + + self.svg_pipeline.draw( + &mut self.device, + encoder, + &layer.svgs, + translated, + bounds, + target, + (dpi * 96.0) as u16, + (dpi * 20.0) as u16, + ); + } + if layer.text.len() > 0 { for text in layer.text.iter() { // Target physical coordinates directly to avoid blurry text diff --git a/wgpu/src/svg.rs b/wgpu/src/svg.rs new file mode 100644 index 0000000000..873947994d --- /dev/null +++ b/wgpu/src/svg.rs @@ -0,0 +1,595 @@ +use crate::Transformation; +use iced_native::{Hasher, Rectangle}; + +use std::{ + cell::RefCell, + collections::{HashMap, HashSet}, + hash::{Hash, Hasher as _}, + mem, + path::PathBuf, + rc::Rc, +}; +use std::fmt::Debug; + + +#[derive(Debug)] +pub struct Pipeline { + cache: RefCell, + + pipeline: wgpu::RenderPipeline, + uniforms: wgpu::Buffer, + vertices: wgpu::Buffer, + indices: wgpu::Buffer, + instances: wgpu::Buffer, + constants: wgpu::BindGroup, + texture_layout: wgpu::BindGroupLayout, +} + +impl Pipeline { + pub fn new(device: &wgpu::Device) -> Self { + let sampler = device.create_sampler(&wgpu::SamplerDescriptor { + address_mode_u: wgpu::AddressMode::ClampToEdge, + address_mode_v: wgpu::AddressMode::ClampToEdge, + address_mode_w: wgpu::AddressMode::ClampToEdge, + mag_filter: wgpu::FilterMode::Linear, + min_filter: wgpu::FilterMode::Linear, + mipmap_filter: wgpu::FilterMode::Linear, + lod_min_clamp: -100.0, + lod_max_clamp: 100.0, + compare_function: wgpu::CompareFunction::Always, + }); + + let constant_layout = + device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { + bindings: &[ + wgpu::BindGroupLayoutBinding { + binding: 0, + visibility: wgpu::ShaderStage::VERTEX, + ty: wgpu::BindingType::UniformBuffer { dynamic: false }, + }, + wgpu::BindGroupLayoutBinding { + binding: 1, + visibility: wgpu::ShaderStage::FRAGMENT, + ty: wgpu::BindingType::Sampler, + }, + ], + }); + + let uniforms = Uniforms { + transform: Transformation::identity().into(), + }; + + let uniforms_buffer = device + .create_buffer_mapped( + 1, + wgpu::BufferUsage::UNIFORM | wgpu::BufferUsage::COPY_DST, + ) + .fill_from_slice(&[uniforms]); + + let constant_bind_group = + device.create_bind_group(&wgpu::BindGroupDescriptor { + layout: &constant_layout, + bindings: &[ + wgpu::Binding { + binding: 0, + resource: wgpu::BindingResource::Buffer { + buffer: &uniforms_buffer, + range: 0..std::mem::size_of::() as u64, + }, + }, + wgpu::Binding { + binding: 1, + resource: wgpu::BindingResource::Sampler(&sampler), + }, + ], + }); + + let texture_layout = + device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { + bindings: &[wgpu::BindGroupLayoutBinding { + binding: 0, + visibility: wgpu::ShaderStage::FRAGMENT, + ty: wgpu::BindingType::SampledTexture { + multisampled: false, + dimension: wgpu::TextureViewDimension::D2, + }, + }], + }); + + let layout = + device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor { + bind_group_layouts: &[&constant_layout, &texture_layout], + }); + + let vs = include_bytes!("shader/image.vert.spv"); + let vs_module = device.create_shader_module( + &wgpu::read_spirv(std::io::Cursor::new(&vs[..])) + .expect("Read image vertex shader as SPIR-V"), + ); + + let fs = include_bytes!("shader/image.frag.spv"); + let fs_module = device.create_shader_module( + &wgpu::read_spirv(std::io::Cursor::new(&fs[..])) + .expect("Read image fragment shader as SPIR-V"), + ); + + let pipeline = + device.create_render_pipeline(&wgpu::RenderPipelineDescriptor { + layout: &layout, + vertex_stage: wgpu::ProgrammableStageDescriptor { + module: &vs_module, + entry_point: "main", + }, + fragment_stage: Some(wgpu::ProgrammableStageDescriptor { + module: &fs_module, + entry_point: "main", + }), + rasterization_state: Some(wgpu::RasterizationStateDescriptor { + front_face: wgpu::FrontFace::Cw, + cull_mode: wgpu::CullMode::None, + depth_bias: 0, + depth_bias_slope_scale: 0.0, + depth_bias_clamp: 0.0, + }), + primitive_topology: wgpu::PrimitiveTopology::TriangleList, + color_states: &[wgpu::ColorStateDescriptor { + format: wgpu::TextureFormat::Bgra8UnormSrgb, + color_blend: wgpu::BlendDescriptor { + src_factor: wgpu::BlendFactor::SrcAlpha, + dst_factor: wgpu::BlendFactor::OneMinusSrcAlpha, + operation: wgpu::BlendOperation::Add, + }, + alpha_blend: wgpu::BlendDescriptor { + src_factor: wgpu::BlendFactor::One, + dst_factor: wgpu::BlendFactor::OneMinusSrcAlpha, + operation: wgpu::BlendOperation::Add, + }, + write_mask: wgpu::ColorWrite::ALL, + }], + depth_stencil_state: None, + index_format: wgpu::IndexFormat::Uint16, + vertex_buffers: &[ + wgpu::VertexBufferDescriptor { + stride: mem::size_of::() as u64, + step_mode: wgpu::InputStepMode::Vertex, + attributes: &[wgpu::VertexAttributeDescriptor { + shader_location: 0, + format: wgpu::VertexFormat::Float2, + offset: 0, + }], + }, + wgpu::VertexBufferDescriptor { + stride: mem::size_of::() as u64, + step_mode: wgpu::InputStepMode::Instance, + attributes: &[ + wgpu::VertexAttributeDescriptor { + shader_location: 1, + format: wgpu::VertexFormat::Float2, + offset: 0, + }, + wgpu::VertexAttributeDescriptor { + shader_location: 2, + format: wgpu::VertexFormat::Float2, + offset: 4 * 2, + }, + ], + }, + ], + sample_count: 1, + sample_mask: !0, + alpha_to_coverage_enabled: false, + }); + + let vertices = device + .create_buffer_mapped(QUAD_VERTS.len(), wgpu::BufferUsage::VERTEX) + .fill_from_slice(&QUAD_VERTS); + + let indices = device + .create_buffer_mapped(QUAD_INDICES.len(), wgpu::BufferUsage::INDEX) + .fill_from_slice(&QUAD_INDICES); + + let instances = device.create_buffer(&wgpu::BufferDescriptor { + size: mem::size_of::() as u64, + usage: wgpu::BufferUsage::VERTEX | wgpu::BufferUsage::COPY_DST, + }); + + Pipeline { + cache: RefCell::new(Cache::new()), + + pipeline, + uniforms: uniforms_buffer, + vertices, + indices, + instances, + constants: constant_bind_group, + texture_layout, + } + } + + fn load(&self, handle: &Handle) { + if !self.cache.borrow().contains(&handle) { + if !handle.path.is_file() { + let mem = Memory::NotFound; + + let _ = self.cache.borrow_mut().insert(&handle, mem); + } + + let mut opt = resvg::Options::default(); + opt.usvg.path = Some(handle.path.clone()); + opt.usvg.dpi = handle.dpi as f64; + opt.usvg.font_size = handle.font_size as f64; + + let mem = match resvg::usvg::Tree::from_file(&handle.path, &opt.usvg) { + Ok(tree) => Memory::Host { tree }, + Err(_) => Memory::Invalid + }; + + let _ = self.cache.borrow_mut().insert(&handle, mem); + } + } + + pub fn draw( + &mut self, + device: &mut wgpu::Device, + encoder: &mut wgpu::CommandEncoder, + svgs: &[Svg], + transformation: Transformation, + bounds: Rectangle, + target: &wgpu::TextureView, + dpi: u16, + font_size: u16, + ) { + let uniforms_buffer = device + .create_buffer_mapped(1, wgpu::BufferUsage::COPY_SRC) + .fill_from_slice(&[Uniforms { + transform: transformation.into(), + }]); + + encoder.copy_buffer_to_buffer( + &uniforms_buffer, + 0, + &self.uniforms, + 0, + std::mem::size_of::() as u64, + ); + + // TODO: Batch draw calls using a texture atlas + // Guillotière[1] by @nical can help us a lot here. + // + // [1]: https://github.com/nical/guillotiere + for svg in svgs { + let mut handle = svg.handle.clone(); + handle.set_dpi(dpi); + handle.set_font_size(font_size); + + self.load(&handle); + + if let Some(texture) = self + .cache + .borrow_mut() + .get(&handle) + .unwrap() + .upload(device, encoder, &self.texture_layout, svg.scale[0] as u32, svg.scale[1] as u32) + { + let instance_buffer = device + .create_buffer_mapped(1, wgpu::BufferUsage::COPY_SRC) + .fill_from_slice(&[Instance { + _position: svg.position, + _scale: svg.scale, + }]); + + encoder.copy_buffer_to_buffer( + &instance_buffer, + 0, + &self.instances, + 0, + mem::size_of::() as u64, + ); + + { + let mut render_pass = encoder.begin_render_pass( + &wgpu::RenderPassDescriptor { + color_attachments: &[ + wgpu::RenderPassColorAttachmentDescriptor { + attachment: target, + resolve_target: None, + load_op: wgpu::LoadOp::Load, + store_op: wgpu::StoreOp::Store, + clear_color: wgpu::Color::TRANSPARENT, + }, + ], + depth_stencil_attachment: None, + }, + ); + + render_pass.set_pipeline(&self.pipeline); + render_pass.set_bind_group(0, &self.constants, &[]); + render_pass.set_bind_group(1, &texture, &[]); + render_pass.set_index_buffer(&self.indices, 0); + render_pass.set_vertex_buffers( + 0, + &[(&self.vertices, 0), (&self.instances, 0)], + ); + render_pass.set_scissor_rect( + bounds.x, + bounds.y, + bounds.width, + bounds.height, + ); + + render_pass.draw_indexed( + 0..QUAD_INDICES.len() as u32, + 0, + 0..1 as u32, + ); + } + } + } + } + + pub fn trim_cache(&mut self) { + self.cache.borrow_mut().trim(); + } +} + +#[derive(Debug, Clone, PartialEq)] +pub struct Handle { + id: u64, + path: PathBuf, + dpi: u16, + font_size: u16, +} + +impl Handle { + /// Returns the unique id of this [`Handle`] + /// + /// [`Handle`]: struct.Handle.html + pub fn id(&self) -> u64 { + self.id + } + + /// Creates a svg [`Handle`] pointing to the svg icon of the given path. + /// + /// [`Handle`]: struct.Handle.html + pub fn from_path>(path: T) -> Handle { + let path = path.into(); + let dpi = 96; + let font_size = 20; + let mut hasher = Hasher::default(); + path.hash(&mut hasher); + dpi.hash(&mut hasher); + font_size.hash(&mut hasher); + + Self { + id: hasher.finish(), + path, + dpi, + font_size, + } + } + + fn set_dpi(&mut self, dpi: u16) { + if self.dpi == dpi { + return; + } + + self.dpi = dpi; + + let mut hasher = Hasher::default(); + self.path.hash(&mut hasher); + self.dpi.hash(&mut hasher); + self.font_size.hash(&mut hasher); + + self.id = hasher.finish(); + } + + fn set_font_size(&mut self, font_size: u16) { + if self.font_size == font_size { + return; + } + + self.font_size = font_size; + + let mut hasher = Hasher::default(); + self.path.hash(&mut hasher); + self.dpi.hash(&mut hasher); + self.font_size.hash(&mut hasher); + + self.id = hasher.finish(); + } +} + +impl Hash for Handle { + fn hash(&self, state: &mut H) { + self.id.hash(state); + self.dpi.hash(state); + self.font_size.hash(state); + } +} + +enum Memory { + Host { + tree: resvg::usvg::Tree, + }, + Device { + bind_group: Rc, + }, + NotFound, + Invalid, +} + +impl Memory { + fn upload( + &mut self, + device: &wgpu::Device, + encoder: &mut wgpu::CommandEncoder, + texture_layout: &wgpu::BindGroupLayout, + width: u32, + height: u32 + ) -> Option> { + match self { + Memory::Host { tree } => { + 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 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(); + let temp_buf = 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: width * 4, + image_height: height, + }, + 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(), + }; + + Some(bind_group) + } + Memory::Device { bind_group, .. } => Some(bind_group.clone()), + Memory::NotFound => None, + Memory::Invalid => None, + } + } +} + +impl Debug for Memory { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> { + match self { + Memory::Host { .. } => write!(f, "Memory::Host"), + Memory::Device { .. } => write!(f, "Memory::Device"), + Memory::NotFound => write!(f, "Memory::NotFound"), + Memory::Invalid => write!(f, "Memory::Invalid"), + } + } +} + +#[derive(Debug)] +struct Cache { + map: HashMap, + hits: HashSet, +} + +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(); + } +} + +#[derive(Debug)] +pub struct Svg { + pub handle: Handle, + pub position: [f32; 2], + pub scale: [f32; 2], +} + +#[repr(C)] +#[derive(Clone, Copy)] +pub struct Vertex { + _position: [f32; 2], +} + +const QUAD_INDICES: [u16; 6] = [0, 1, 2, 0, 2, 3]; + +const QUAD_VERTS: [Vertex; 4] = [ + Vertex { + _position: [0.0, 0.0], + }, + Vertex { + _position: [1.0, 0.0], + }, + Vertex { + _position: [1.0, 1.0], + }, + Vertex { + _position: [0.0, 1.0], + }, +]; + +#[repr(C)] +#[derive(Clone, Copy)] +struct Instance { + _position: [f32; 2], + _scale: [f32; 2], +} + +#[repr(C)] +#[derive(Debug, Clone, Copy)] +struct Uniforms { + transform: [f32; 16], +} \ No newline at end of file From a88aae5e04e0a92457e5dd617a86af823e90af6c Mon Sep 17 00:00:00 2001 From: Malte Veerman Date: Fri, 6 Dec 2019 19:37:56 +0100 Subject: [PATCH 02/11] Added an `Icon` widget to native. --- native/src/widget.rs | 3 + native/src/widget/icon.rs | 103 +++++++++++++++++++++++++++++++ src/native.rs | 7 ++- wgpu/src/primitive.rs | 6 +- wgpu/src/renderer/widget.rs | 1 + wgpu/src/renderer/widget/icon.rs | 21 +++++++ wgpu/src/svg.rs | 3 +- 7 files changed, 138 insertions(+), 6 deletions(-) create mode 100644 native/src/widget/icon.rs create mode 100644 wgpu/src/renderer/widget/icon.rs diff --git a/native/src/widget.rs b/native/src/widget.rs index 71dcdc0d74..0cf6a6393b 100644 --- a/native/src/widget.rs +++ b/native/src/widget.rs @@ -24,6 +24,7 @@ pub mod button; pub mod checkbox; pub mod column; pub mod container; +pub mod icon; pub mod image; pub mod radio; pub mod row; @@ -41,6 +42,8 @@ pub use column::Column; #[doc(no_inline)] pub use container::Container; #[doc(no_inline)] +pub use icon::Icon; +#[doc(no_inline)] pub use image::Image; #[doc(no_inline)] pub use radio::Radio; diff --git a/native/src/widget/icon.rs b/native/src/widget/icon.rs new file mode 100644 index 0000000000..4687d7e623 --- /dev/null +++ b/native/src/widget/icon.rs @@ -0,0 +1,103 @@ +//! Display an icon. +use crate::{layout, Element, Hasher, Layout, Length, Point, Rectangle, Widget}; + +use std::hash::Hash; +use std::path::{Path, PathBuf}; + +/// A simple icon_loader widget. +#[derive(Debug, Clone)] +pub struct Icon { + path: PathBuf, + size: Length, +} + +impl Icon { + /// Create a new [`Icon`] from the file at `path`. + /// + /// [`Icon`]: struct.Icon.html + pub fn new(path: impl Into) -> Self { + Icon { + path: path.into(), + size: Length::Fill, + } + } + + /// Sets the size of the [`Icon`]. + /// + /// [`Icon`]: struct.Icon.html + pub fn size(mut self, size: Length) -> Self { + self.size = size; + self + } +} + +impl Widget for Icon +where + Renderer: self::Renderer, +{ + fn width(&self) -> Length { + self.size + } + + fn height(&self) -> Length { + self.size + } + + fn layout(&self, _: &Renderer, limits: &layout::Limits) -> layout::Node { + let mut size = limits.width(self.size).height(self.size).max(); + + if size.width > size.height { + size.width = size.height; + } else if size.width < size.height { + size.height = size.width; + } + + layout::Node::new(size) + } + + fn draw( + &self, + renderer: &mut Renderer, + layout: Layout<'_>, + _cursor_position: Point, + ) -> Renderer::Output { + let bounds = layout.bounds(); + + renderer.draw( + bounds, + self.path.as_path(), + ) + } + + fn hash_layout(&self, state: &mut Hasher) { + self.size.hash(state); + } +} + +/// The renderer of an [`Icon`]. +/// +/// Your [renderer] will need to implement this trait before being +/// able to use [`Icon`] in your [`UserInterface`]. +/// +/// [`Icon`]: struct.Icon.html +/// [renderer]: ../../renderer/index.html +/// [`UserInterface`]: ../../struct.UserInterface.html +pub trait Renderer: crate::Renderer { + /// Draws an [`Icon`]. + /// + /// [`Icon`]: struct.Icon.html + fn draw( + &mut self, + bounds: Rectangle, + path: &Path, + ) -> Self::Output; +} + +impl<'a, Message, Renderer> From for Element<'a, Message, Renderer> +where + Renderer: self::Renderer, +{ + fn from(icon: Icon) -> Element<'a, Message, Renderer> { + Element::new(icon) + } +} diff --git a/src/native.rs b/src/native.rs index 3537dd5215..b24518a2a6 100644 --- a/src/native.rs +++ b/src/native.rs @@ -80,11 +80,16 @@ pub mod widget { pub use iced_winit::image::{Handle, Image}; } + pub mod icon { + //! Display icons in your user interface. + pub use iced_winit::icon::Icon; + } + pub use iced_winit::{Checkbox, Radio, Text}; #[doc(no_inline)] pub use { - button::Button, image::Image, scrollable::Scrollable, slider::Slider, + button::Button, icon::Icon, image::Image, scrollable::Scrollable, slider::Slider, text_input::TextInput, }; diff --git a/wgpu/src/primitive.rs b/wgpu/src/primitive.rs index c637626baf..1b0729cf9f 100644 --- a/wgpu/src/primitive.rs +++ b/wgpu/src/primitive.rs @@ -3,8 +3,6 @@ use iced_native::{ VerticalAlignment, }; -use crate::svg; - /// A rendering primitive. #[derive(Debug, Clone)] pub enum Primitive { @@ -50,8 +48,8 @@ pub enum Primitive { }, /// A svg icon primitive Svg { - /// The handle of the icon - handle: svg::Handle, + /// The path of the icon + handle: crate::svg::Handle, /// The bounds of the icon bounds: Rectangle, }, diff --git a/wgpu/src/renderer/widget.rs b/wgpu/src/renderer/widget.rs index 52410bee43..3e1b8d9225 100644 --- a/wgpu/src/renderer/widget.rs +++ b/wgpu/src/renderer/widget.rs @@ -1,6 +1,7 @@ mod button; mod checkbox; mod column; +mod icon; mod image; mod radio; mod row; diff --git a/wgpu/src/renderer/widget/icon.rs b/wgpu/src/renderer/widget/icon.rs new file mode 100644 index 0000000000..a271bb4717 --- /dev/null +++ b/wgpu/src/renderer/widget/icon.rs @@ -0,0 +1,21 @@ +use crate::{svg::Handle, Primitive, Renderer}; +use iced_native::{ + icon, MouseCursor, Rectangle, +}; +use std::path::Path; + +impl icon::Renderer for Renderer { + fn draw( + &mut self, + bounds: Rectangle, + path: &Path, + ) -> Self::Output { + ( + Primitive::Svg { + handle: Handle::from_path(path), + bounds, + }, + MouseCursor::OutOfBounds, + ) + } +} \ No newline at end of file diff --git a/wgpu/src/svg.rs b/wgpu/src/svg.rs index 873947994d..1b8f14b074 100644 --- a/wgpu/src/svg.rs +++ b/wgpu/src/svg.rs @@ -4,12 +4,13 @@ use iced_native::{Hasher, Rectangle}; use std::{ cell::RefCell, collections::{HashMap, HashSet}, + fmt::Debug, hash::{Hash, Hasher as _}, mem, path::PathBuf, rc::Rc, + u32, }; -use std::fmt::Debug; #[derive(Debug)] From 5696afcadd5b3b89a532f4205efac30d8a24d558 Mon Sep 17 00:00:00 2001 From: Malte Veerman Date: Wed, 11 Dec 2019 22:13:29 +0100 Subject: [PATCH 03/11] Ran cargo_fmt over changed files. --- native/src/widget/icon.rs | 21 ++++++------- src/native.rs | 4 +-- wgpu/src/renderer.rs | 12 +++---- wgpu/src/renderer/widget/icon.rs | 12 ++----- wgpu/src/svg.rs | 54 ++++++++++++++++++-------------- wgpu/src/text/font.rs | 5 +-- 6 files changed, 53 insertions(+), 55 deletions(-) diff --git a/native/src/widget/icon.rs b/native/src/widget/icon.rs index 4687d7e623..b3906ccfc2 100644 --- a/native/src/widget/icon.rs +++ b/native/src/widget/icon.rs @@ -1,8 +1,12 @@ //! Display an icon. -use crate::{layout, Element, Hasher, Layout, Length, Point, Rectangle, Widget}; +use crate::{ + layout, Element, Hasher, Layout, Length, Point, Rectangle, Widget, +}; -use std::hash::Hash; -use std::path::{Path, PathBuf}; +use std::{ + hash::Hash, + path::{Path, PathBuf}, +}; /// A simple icon_loader widget. #[derive(Debug, Clone)] @@ -63,10 +67,7 @@ where ) -> Renderer::Output { let bounds = layout.bounds(); - renderer.draw( - bounds, - self.path.as_path(), - ) + renderer.draw(bounds, self.path.as_path()) } fn hash_layout(&self, state: &mut Hasher) { @@ -86,11 +87,7 @@ pub trait Renderer: crate::Renderer { /// Draws an [`Icon`]. /// /// [`Icon`]: struct.Icon.html - fn draw( - &mut self, - bounds: Rectangle, - path: &Path, - ) -> Self::Output; + fn draw(&mut self, bounds: Rectangle, path: &Path) -> Self::Output; } impl<'a, Message, Renderer> From for Element<'a, Message, Renderer> diff --git a/src/native.rs b/src/native.rs index b24518a2a6..e52b63d3e0 100644 --- a/src/native.rs +++ b/src/native.rs @@ -89,8 +89,8 @@ pub mod widget { #[doc(no_inline)] pub use { - button::Button, icon::Icon, image::Image, scrollable::Scrollable, slider::Slider, - text_input::TextInput, + button::Button, icon::Icon, image::Image, scrollable::Scrollable, + slider::Slider, text_input::TextInput, }; /// A container that distributes its contents vertically. diff --git a/wgpu/src/renderer.rs b/wgpu/src/renderer.rs index 9895e1c4f8..92e249337d 100644 --- a/wgpu/src/renderer.rs +++ b/wgpu/src/renderer.rs @@ -243,13 +243,11 @@ impl Renderer { scale: [bounds.width, bounds.height], }); } - Primitive::Svg { handle, bounds } => { - layer.svgs.push(Svg { - handle: handle.clone(), - position: [bounds.x, bounds.y], - scale: [bounds.width, bounds.height], - }) - }, + Primitive::Svg { handle, bounds } => layer.svgs.push(Svg { + handle: handle.clone(), + position: [bounds.x, bounds.y], + scale: [bounds.width, bounds.height], + }), Primitive::Clip { bounds, offset, diff --git a/wgpu/src/renderer/widget/icon.rs b/wgpu/src/renderer/widget/icon.rs index a271bb4717..652e57a372 100644 --- a/wgpu/src/renderer/widget/icon.rs +++ b/wgpu/src/renderer/widget/icon.rs @@ -1,15 +1,9 @@ use crate::{svg::Handle, Primitive, Renderer}; -use iced_native::{ - icon, MouseCursor, Rectangle, -}; +use iced_native::{icon, MouseCursor, Rectangle}; use std::path::Path; impl icon::Renderer for Renderer { - fn draw( - &mut self, - bounds: Rectangle, - path: &Path, - ) -> Self::Output { + fn draw(&mut self, bounds: Rectangle, path: &Path) -> Self::Output { ( Primitive::Svg { handle: Handle::from_path(path), @@ -18,4 +12,4 @@ impl icon::Renderer for Renderer { MouseCursor::OutOfBounds, ) } -} \ No newline at end of file +} diff --git a/wgpu/src/svg.rs b/wgpu/src/svg.rs index 1b8f14b074..755303a591 100644 --- a/wgpu/src/svg.rs +++ b/wgpu/src/svg.rs @@ -12,7 +12,6 @@ use std::{ u32, }; - #[derive(Debug)] pub struct Pipeline { cache: RefCell, @@ -220,10 +219,11 @@ impl Pipeline { opt.usvg.dpi = handle.dpi as f64; opt.usvg.font_size = handle.font_size as f64; - let mem = match resvg::usvg::Tree::from_file(&handle.path, &opt.usvg) { - Ok(tree) => Memory::Host { tree }, - Err(_) => Memory::Invalid - }; + let mem = + match resvg::usvg::Tree::from_file(&handle.path, &opt.usvg) { + Ok(tree) => Memory::Host { tree }, + Err(_) => Memory::Invalid, + }; let _ = self.cache.borrow_mut().insert(&handle, mem); } @@ -265,12 +265,14 @@ impl Pipeline { self.load(&handle); - if let Some(texture) = self - .cache - .borrow_mut() - .get(&handle) - .unwrap() - .upload(device, encoder, &self.texture_layout, svg.scale[0] as u32, svg.scale[1] as u32) + if let Some(texture) = + self.cache.borrow_mut().get(&handle).unwrap().upload( + device, + encoder, + &self.texture_layout, + svg.scale[0] as u32, + svg.scale[1] as u32, + ) { let instance_buffer = device .create_buffer_mapped(1, wgpu::BufferUsage::COPY_SRC) @@ -409,12 +411,8 @@ impl Hash for Handle { } enum Memory { - Host { - tree: resvg::usvg::Tree, - }, - Device { - bind_group: Rc, - }, + Host { tree: resvg::usvg::Tree }, + Device { bind_group: Rc }, NotFound, Invalid, } @@ -426,7 +424,7 @@ impl Memory { encoder: &mut wgpu::CommandEncoder, texture_layout: &wgpu::BindGroupLayout, width: u32, - height: u32 + height: u32, ) -> Option> { match self { Memory::Host { tree } => { @@ -447,10 +445,17 @@ impl Memory { | wgpu::TextureUsage::SAMPLED, }); - let mut canvas = resvg::raqote::DrawTarget::new(width as i32, height as i32); + 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 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(); let temp_buf = device .create_buffer_mapped( @@ -506,7 +511,10 @@ impl Memory { } impl Debug for Memory { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> { + fn fmt( + &self, + f: &mut std::fmt::Formatter<'_>, + ) -> Result<(), std::fmt::Error> { match self { Memory::Host { .. } => write!(f, "Memory::Host"), Memory::Device { .. } => write!(f, "Memory::Device"), @@ -593,4 +601,4 @@ struct Instance { #[derive(Debug, Clone, Copy)] struct Uniforms { transform: [f32; 16], -} \ No newline at end of file +} diff --git a/wgpu/src/text/font.rs b/wgpu/src/text/font.rs index 31df5bf404..7346ccdb45 100644 --- a/wgpu/src/text/font.rs +++ b/wgpu/src/text/font.rs @@ -1,5 +1,6 @@ -pub use font_kit::error::SelectionError as LoadError; -pub use font_kit::family_name::FamilyName as Family; +pub use font_kit::{ + error::SelectionError as LoadError, family_name::FamilyName as Family, +}; pub struct Source { raw: font_kit::source::SystemSource, From f737c6da24d5c75e3efa92c0fd9d0d11fbd715c1 Mon Sep 17 00:00:00 2001 From: Malte Veerman Date: Thu, 12 Dec 2019 00:20:06 +0100 Subject: [PATCH 04/11] Improved dpi handling --- wgpu/src/renderer.rs | 3 +-- wgpu/src/svg.rs | 59 ++++++-------------------------------------- 2 files changed, 8 insertions(+), 54 deletions(-) diff --git a/wgpu/src/renderer.rs b/wgpu/src/renderer.rs index 92e249337d..3eb8c5ca83 100644 --- a/wgpu/src/renderer.rs +++ b/wgpu/src/renderer.rs @@ -370,8 +370,7 @@ impl Renderer { translated, bounds, target, - (dpi * 96.0) as u16, - (dpi * 20.0) as u16, + dpi, ); } diff --git a/wgpu/src/svg.rs b/wgpu/src/svg.rs index 755303a591..f2161489d8 100644 --- a/wgpu/src/svg.rs +++ b/wgpu/src/svg.rs @@ -216,8 +216,6 @@ impl Pipeline { let mut opt = resvg::Options::default(); opt.usvg.path = Some(handle.path.clone()); - opt.usvg.dpi = handle.dpi as f64; - opt.usvg.font_size = handle.font_size as f64; let mem = match resvg::usvg::Tree::from_file(&handle.path, &opt.usvg) { @@ -237,8 +235,7 @@ impl Pipeline { transformation: Transformation, bounds: Rectangle, target: &wgpu::TextureView, - dpi: u16, - font_size: u16, + dpi: f32, ) { let uniforms_buffer = device .create_buffer_mapped(1, wgpu::BufferUsage::COPY_SRC) @@ -259,19 +256,15 @@ impl Pipeline { // // [1]: https://github.com/nical/guillotiere for svg in svgs { - let mut handle = svg.handle.clone(); - handle.set_dpi(dpi); - handle.set_font_size(font_size); - - self.load(&handle); + self.load(&svg.handle); if let Some(texture) = - self.cache.borrow_mut().get(&handle).unwrap().upload( + self.cache.borrow_mut().get(&svg.handle).unwrap().upload( device, encoder, &self.texture_layout, - svg.scale[0] as u32, - svg.scale[1] as u32, + (svg.scale[0] * dpi) as u32, + (svg.scale[1] * dpi) as u32, ) { let instance_buffer = device @@ -339,8 +332,6 @@ impl Pipeline { pub struct Handle { id: u64, path: PathBuf, - dpi: u16, - font_size: u16, } impl Handle { @@ -356,57 +347,19 @@ impl Handle { /// [`Handle`]: struct.Handle.html pub fn from_path>(path: T) -> Handle { let path = path.into(); - let dpi = 96; - let font_size = 20; let mut hasher = Hasher::default(); path.hash(&mut hasher); - dpi.hash(&mut hasher); - font_size.hash(&mut hasher); Self { id: hasher.finish(), path, - dpi, - font_size, - } - } - - fn set_dpi(&mut self, dpi: u16) { - if self.dpi == dpi { - return; - } - - self.dpi = dpi; - - let mut hasher = Hasher::default(); - self.path.hash(&mut hasher); - self.dpi.hash(&mut hasher); - self.font_size.hash(&mut hasher); - - self.id = hasher.finish(); - } - - fn set_font_size(&mut self, font_size: u16) { - if self.font_size == font_size { - return; } - - self.font_size = font_size; - - let mut hasher = Hasher::default(); - self.path.hash(&mut hasher); - self.dpi.hash(&mut hasher); - self.font_size.hash(&mut hasher); - - self.id = hasher.finish(); } } impl Hash for Handle { fn hash(&self, state: &mut H) { self.id.hash(state); - self.dpi.hash(state); - self.font_size.hash(state); } } @@ -428,6 +381,8 @@ impl Memory { ) -> Option> { match self { Memory::Host { tree } => { + println!("{} {}", width, height); + let extent = wgpu::Extent3d { width, height, From 895eaef99b52c24e6f3d804897ad850c1f1de960 Mon Sep 17 00:00:00 2001 From: Malte Veerman Date: Thu, 12 Dec 2019 01:14:54 +0100 Subject: [PATCH 05/11] Merged svg pipeline into image --- native/src/widget/icon.rs | 29 +- wgpu/src/image.rs | 107 ++++-- wgpu/src/lib.rs | 2 - wgpu/src/primitive.rs | 7 - wgpu/src/renderer.rs | 30 +- wgpu/src/renderer/widget.rs | 1 - wgpu/src/renderer/widget/icon.rs | 15 - wgpu/src/svg.rs | 559 ------------------------------- 8 files changed, 93 insertions(+), 657 deletions(-) delete mode 100644 wgpu/src/renderer/widget/icon.rs delete mode 100644 wgpu/src/svg.rs diff --git a/native/src/widget/icon.rs b/native/src/widget/icon.rs index b3906ccfc2..c324e7867b 100644 --- a/native/src/widget/icon.rs +++ b/native/src/widget/icon.rs @@ -1,6 +1,6 @@ //! Display an icon. use crate::{ - layout, Element, Hasher, Layout, Length, Point, Rectangle, Widget, + image, layout, Element, Hasher, Layout, Length, Point, Rectangle, Widget, }; use std::{ @@ -11,7 +11,7 @@ use std::{ /// A simple icon_loader widget. #[derive(Debug, Clone)] pub struct Icon { - path: PathBuf, + handle: image::Handle, size: Length, } @@ -21,7 +21,7 @@ impl Icon { /// [`Icon`]: struct.Icon.html pub fn new(path: impl Into) -> Self { Icon { - path: path.into(), + handle: image::Handle::from_path(path), size: Length::Fill, } } @@ -37,7 +37,7 @@ impl Icon { impl Widget for Icon where - Renderer: self::Renderer, + Renderer: image::Renderer, { fn width(&self) -> Length { self.size @@ -65,9 +65,7 @@ where layout: Layout<'_>, _cursor_position: Point, ) -> Renderer::Output { - let bounds = layout.bounds(); - - renderer.draw(bounds, self.path.as_path()) + renderer.draw(self.handle.clone(), layout) } fn hash_layout(&self, state: &mut Hasher) { @@ -75,24 +73,9 @@ where } } -/// The renderer of an [`Icon`]. -/// -/// Your [renderer] will need to implement this trait before being -/// able to use [`Icon`] in your [`UserInterface`]. -/// -/// [`Icon`]: struct.Icon.html -/// [renderer]: ../../renderer/index.html -/// [`UserInterface`]: ../../struct.UserInterface.html -pub trait Renderer: crate::Renderer { - /// Draws an [`Icon`]. - /// - /// [`Icon`]: struct.Icon.html - fn draw(&mut self, bounds: Rectangle, path: &Path) -> Self::Output; -} - impl<'a, Message, Renderer> From for Element<'a, Message, Renderer> where - Renderer: self::Renderer, + Renderer: image::Renderer, { fn from(icon: Icon) -> Element<'a, Message, Renderer> { Element::new(icon) diff --git a/wgpu/src/image.rs b/wgpu/src/image.rs index 7e4e267080..e0e093e0ba 100644 --- a/wgpu/src/image.rs +++ b/wgpu/src/image.rs @@ -7,6 +7,7 @@ use iced_native::{ use std::{ cell::RefCell, collections::{HashMap, HashSet}, + fmt, mem, rc::Rc, }; @@ -215,19 +216,27 @@ impl Pipeline { if !self.cache.borrow().contains(&handle) { let memory = match handle.data() { Data::Path(path) => { - if let Ok(image) = image::open(path) { - Memory::Host { - image: image.to_bgra(), + 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 { - image: image.to_bgra(), - } + Memory::Host(HostMemory::Image(image.to_bgra())) } else { Memory::Invalid } @@ -246,6 +255,7 @@ impl Pipeline { transformation: Transformation, bounds: Rectangle, target: &wgpu::TextureView, + dpi: f32, ) { let uniforms_buffer = device .create_buffer_mapped(1, wgpu::BufferUsage::COPY_SRC) @@ -273,7 +283,13 @@ impl Pipeline { .borrow_mut() .get(&image.handle) .unwrap() - .upload(device, encoder, &self.texture_layout) + .upload( + device, + encoder, + &self.texture_layout, + (image.scale[0] * dpi) as u32, + (image.scale[1] * dpi) as u32, + ) { let instance_buffer = device .create_buffer_mapped(1, wgpu::BufferUsage::COPY_SRC) @@ -341,11 +357,26 @@ impl Pipeline { } } +enum HostMemory { + Image(image::ImageBuffer, Vec>), + 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 { - image: image::ImageBuffer, Vec>, - }, + Host(HostMemory), Device { bind_group: Rc, width: u32, @@ -358,7 +389,13 @@ enum Memory { impl Memory { fn dimensions(&self) -> (u32, u32) { match self { - Memory::Host { image } => image.dimensions(), + 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), @@ -370,10 +407,15 @@ impl Memory { device: &wgpu::Device, encoder: &mut wgpu::CommandEncoder, texture_layout: &wgpu::BindGroupLayout, + svg_width: u32, + svg_height: u32, ) -> Option> { match self { - Memory::Host { image } => { - let (width, height) = image.dimensions(); + 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, @@ -392,14 +434,37 @@ impl Memory { | wgpu::TextureUsage::SAMPLED, }); - let slice = image.clone().into_raw(); - - let temp_buf = device - .create_buffer_mapped( - slice.len(), - wgpu::BufferUsage::COPY_SRC, - ) - .fill_from_slice(&slice[..]); + 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 { diff --git a/wgpu/src/lib.rs b/wgpu/src/lib.rs index 1f5794ee6e..9f9ed8db81 100644 --- a/wgpu/src/lib.rs +++ b/wgpu/src/lib.rs @@ -28,13 +28,11 @@ mod image; mod primitive; mod quad; mod renderer; -mod svg; mod text; mod transformation; pub(crate) use crate::image::Image; pub(crate) use quad::Quad; -pub(crate) use svg::Svg; pub(crate) use transformation::Transformation; pub use primitive::Primitive; diff --git a/wgpu/src/primitive.rs b/wgpu/src/primitive.rs index 1b0729cf9f..04264e5d54 100644 --- a/wgpu/src/primitive.rs +++ b/wgpu/src/primitive.rs @@ -46,13 +46,6 @@ pub enum Primitive { /// The bounds of the image bounds: Rectangle, }, - /// A svg icon primitive - Svg { - /// The path of the icon - handle: crate::svg::Handle, - /// The bounds of the icon - bounds: Rectangle, - }, /// A clip primitive Clip { /// The bounds of the clip diff --git a/wgpu/src/renderer.rs b/wgpu/src/renderer.rs index 3eb8c5ca83..d1d4de14f9 100644 --- a/wgpu/src/renderer.rs +++ b/wgpu/src/renderer.rs @@ -1,4 +1,4 @@ -use crate::{quad, text, Image, Primitive, Quad, Svg, Transformation}; +use crate::{quad, text, Image, Primitive, Quad, Transformation}; use iced_native::{ renderer::{Debugger, Windowed}, Background, Color, Layout, MouseCursor, Point, Rectangle, Vector, Widget, @@ -23,7 +23,6 @@ pub struct Renderer { queue: Queue, quad_pipeline: quad::Pipeline, image_pipeline: crate::image::Pipeline, - svg_pipeline: crate::svg::Pipeline, text_pipeline: text::Pipeline, } @@ -32,7 +31,6 @@ struct Layer<'a> { offset: Vector, quads: Vec, images: Vec, - svgs: Vec, text: Vec>, } @@ -43,7 +41,6 @@ impl<'a> Layer<'a> { offset, quads: Vec::new(), images: Vec::new(), - svgs: Vec::new(), text: Vec::new(), } } @@ -67,14 +64,12 @@ impl Renderer { let text_pipeline = text::Pipeline::new(&mut device); let quad_pipeline = quad::Pipeline::new(&mut device); let image_pipeline = crate::image::Pipeline::new(&mut device); - let svg_pipeline = crate::svg::Pipeline::new(&mut device); Self { device, queue, quad_pipeline, image_pipeline, - svg_pipeline, text_pipeline, } } @@ -133,7 +128,6 @@ impl Renderer { self.queue.submit(&[encoder.finish()]); self.image_pipeline.trim_cache(); - self.svg_pipeline.trim_cache(); *mouse_cursor } @@ -243,11 +237,6 @@ impl Renderer { scale: [bounds.width, bounds.height], }); } - Primitive::Svg { handle, bounds } => layer.svgs.push(Svg { - handle: handle.clone(), - position: [bounds.x, bounds.y], - scale: [bounds.width, bounds.height], - }), Primitive::Clip { bounds, offset, @@ -353,23 +342,6 @@ impl Renderer { translated_and_scaled, bounds, target, - ); - } - - if layer.svgs.len() > 0 { - let translated = transformation - * Transformation::translate( - -(layer.offset.x as f32), - -(layer.offset.y as f32), - ); - - self.svg_pipeline.draw( - &mut self.device, - encoder, - &layer.svgs, - translated, - bounds, - target, dpi, ); } diff --git a/wgpu/src/renderer/widget.rs b/wgpu/src/renderer/widget.rs index 3e1b8d9225..52410bee43 100644 --- a/wgpu/src/renderer/widget.rs +++ b/wgpu/src/renderer/widget.rs @@ -1,7 +1,6 @@ mod button; mod checkbox; mod column; -mod icon; mod image; mod radio; mod row; diff --git a/wgpu/src/renderer/widget/icon.rs b/wgpu/src/renderer/widget/icon.rs deleted file mode 100644 index 652e57a372..0000000000 --- a/wgpu/src/renderer/widget/icon.rs +++ /dev/null @@ -1,15 +0,0 @@ -use crate::{svg::Handle, Primitive, Renderer}; -use iced_native::{icon, MouseCursor, Rectangle}; -use std::path::Path; - -impl icon::Renderer for Renderer { - fn draw(&mut self, bounds: Rectangle, path: &Path) -> Self::Output { - ( - Primitive::Svg { - handle: Handle::from_path(path), - bounds, - }, - MouseCursor::OutOfBounds, - ) - } -} diff --git a/wgpu/src/svg.rs b/wgpu/src/svg.rs deleted file mode 100644 index f2161489d8..0000000000 --- a/wgpu/src/svg.rs +++ /dev/null @@ -1,559 +0,0 @@ -use crate::Transformation; -use iced_native::{Hasher, Rectangle}; - -use std::{ - cell::RefCell, - collections::{HashMap, HashSet}, - fmt::Debug, - hash::{Hash, Hasher as _}, - mem, - path::PathBuf, - rc::Rc, - u32, -}; - -#[derive(Debug)] -pub struct Pipeline { - cache: RefCell, - - pipeline: wgpu::RenderPipeline, - uniforms: wgpu::Buffer, - vertices: wgpu::Buffer, - indices: wgpu::Buffer, - instances: wgpu::Buffer, - constants: wgpu::BindGroup, - texture_layout: wgpu::BindGroupLayout, -} - -impl Pipeline { - pub fn new(device: &wgpu::Device) -> Self { - let sampler = device.create_sampler(&wgpu::SamplerDescriptor { - address_mode_u: wgpu::AddressMode::ClampToEdge, - address_mode_v: wgpu::AddressMode::ClampToEdge, - address_mode_w: wgpu::AddressMode::ClampToEdge, - mag_filter: wgpu::FilterMode::Linear, - min_filter: wgpu::FilterMode::Linear, - mipmap_filter: wgpu::FilterMode::Linear, - lod_min_clamp: -100.0, - lod_max_clamp: 100.0, - compare_function: wgpu::CompareFunction::Always, - }); - - let constant_layout = - device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { - bindings: &[ - wgpu::BindGroupLayoutBinding { - binding: 0, - visibility: wgpu::ShaderStage::VERTEX, - ty: wgpu::BindingType::UniformBuffer { dynamic: false }, - }, - wgpu::BindGroupLayoutBinding { - binding: 1, - visibility: wgpu::ShaderStage::FRAGMENT, - ty: wgpu::BindingType::Sampler, - }, - ], - }); - - let uniforms = Uniforms { - transform: Transformation::identity().into(), - }; - - let uniforms_buffer = device - .create_buffer_mapped( - 1, - wgpu::BufferUsage::UNIFORM | wgpu::BufferUsage::COPY_DST, - ) - .fill_from_slice(&[uniforms]); - - let constant_bind_group = - device.create_bind_group(&wgpu::BindGroupDescriptor { - layout: &constant_layout, - bindings: &[ - wgpu::Binding { - binding: 0, - resource: wgpu::BindingResource::Buffer { - buffer: &uniforms_buffer, - range: 0..std::mem::size_of::() as u64, - }, - }, - wgpu::Binding { - binding: 1, - resource: wgpu::BindingResource::Sampler(&sampler), - }, - ], - }); - - let texture_layout = - device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { - bindings: &[wgpu::BindGroupLayoutBinding { - binding: 0, - visibility: wgpu::ShaderStage::FRAGMENT, - ty: wgpu::BindingType::SampledTexture { - multisampled: false, - dimension: wgpu::TextureViewDimension::D2, - }, - }], - }); - - let layout = - device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor { - bind_group_layouts: &[&constant_layout, &texture_layout], - }); - - let vs = include_bytes!("shader/image.vert.spv"); - let vs_module = device.create_shader_module( - &wgpu::read_spirv(std::io::Cursor::new(&vs[..])) - .expect("Read image vertex shader as SPIR-V"), - ); - - let fs = include_bytes!("shader/image.frag.spv"); - let fs_module = device.create_shader_module( - &wgpu::read_spirv(std::io::Cursor::new(&fs[..])) - .expect("Read image fragment shader as SPIR-V"), - ); - - let pipeline = - device.create_render_pipeline(&wgpu::RenderPipelineDescriptor { - layout: &layout, - vertex_stage: wgpu::ProgrammableStageDescriptor { - module: &vs_module, - entry_point: "main", - }, - fragment_stage: Some(wgpu::ProgrammableStageDescriptor { - module: &fs_module, - entry_point: "main", - }), - rasterization_state: Some(wgpu::RasterizationStateDescriptor { - front_face: wgpu::FrontFace::Cw, - cull_mode: wgpu::CullMode::None, - depth_bias: 0, - depth_bias_slope_scale: 0.0, - depth_bias_clamp: 0.0, - }), - primitive_topology: wgpu::PrimitiveTopology::TriangleList, - color_states: &[wgpu::ColorStateDescriptor { - format: wgpu::TextureFormat::Bgra8UnormSrgb, - color_blend: wgpu::BlendDescriptor { - src_factor: wgpu::BlendFactor::SrcAlpha, - dst_factor: wgpu::BlendFactor::OneMinusSrcAlpha, - operation: wgpu::BlendOperation::Add, - }, - alpha_blend: wgpu::BlendDescriptor { - src_factor: wgpu::BlendFactor::One, - dst_factor: wgpu::BlendFactor::OneMinusSrcAlpha, - operation: wgpu::BlendOperation::Add, - }, - write_mask: wgpu::ColorWrite::ALL, - }], - depth_stencil_state: None, - index_format: wgpu::IndexFormat::Uint16, - vertex_buffers: &[ - wgpu::VertexBufferDescriptor { - stride: mem::size_of::() as u64, - step_mode: wgpu::InputStepMode::Vertex, - attributes: &[wgpu::VertexAttributeDescriptor { - shader_location: 0, - format: wgpu::VertexFormat::Float2, - offset: 0, - }], - }, - wgpu::VertexBufferDescriptor { - stride: mem::size_of::() as u64, - step_mode: wgpu::InputStepMode::Instance, - attributes: &[ - wgpu::VertexAttributeDescriptor { - shader_location: 1, - format: wgpu::VertexFormat::Float2, - offset: 0, - }, - wgpu::VertexAttributeDescriptor { - shader_location: 2, - format: wgpu::VertexFormat::Float2, - offset: 4 * 2, - }, - ], - }, - ], - sample_count: 1, - sample_mask: !0, - alpha_to_coverage_enabled: false, - }); - - let vertices = device - .create_buffer_mapped(QUAD_VERTS.len(), wgpu::BufferUsage::VERTEX) - .fill_from_slice(&QUAD_VERTS); - - let indices = device - .create_buffer_mapped(QUAD_INDICES.len(), wgpu::BufferUsage::INDEX) - .fill_from_slice(&QUAD_INDICES); - - let instances = device.create_buffer(&wgpu::BufferDescriptor { - size: mem::size_of::() as u64, - usage: wgpu::BufferUsage::VERTEX | wgpu::BufferUsage::COPY_DST, - }); - - Pipeline { - cache: RefCell::new(Cache::new()), - - pipeline, - uniforms: uniforms_buffer, - vertices, - indices, - instances, - constants: constant_bind_group, - texture_layout, - } - } - - fn load(&self, handle: &Handle) { - if !self.cache.borrow().contains(&handle) { - if !handle.path.is_file() { - let mem = Memory::NotFound; - - let _ = self.cache.borrow_mut().insert(&handle, mem); - } - - let mut opt = resvg::Options::default(); - opt.usvg.path = Some(handle.path.clone()); - - let mem = - match resvg::usvg::Tree::from_file(&handle.path, &opt.usvg) { - Ok(tree) => Memory::Host { tree }, - Err(_) => Memory::Invalid, - }; - - let _ = self.cache.borrow_mut().insert(&handle, mem); - } - } - - pub fn draw( - &mut self, - device: &mut wgpu::Device, - encoder: &mut wgpu::CommandEncoder, - svgs: &[Svg], - transformation: Transformation, - bounds: Rectangle, - target: &wgpu::TextureView, - dpi: f32, - ) { - let uniforms_buffer = device - .create_buffer_mapped(1, wgpu::BufferUsage::COPY_SRC) - .fill_from_slice(&[Uniforms { - transform: transformation.into(), - }]); - - encoder.copy_buffer_to_buffer( - &uniforms_buffer, - 0, - &self.uniforms, - 0, - std::mem::size_of::() as u64, - ); - - // TODO: Batch draw calls using a texture atlas - // Guillotière[1] by @nical can help us a lot here. - // - // [1]: https://github.com/nical/guillotiere - for svg in svgs { - self.load(&svg.handle); - - if let Some(texture) = - self.cache.borrow_mut().get(&svg.handle).unwrap().upload( - device, - encoder, - &self.texture_layout, - (svg.scale[0] * dpi) as u32, - (svg.scale[1] * dpi) as u32, - ) - { - let instance_buffer = device - .create_buffer_mapped(1, wgpu::BufferUsage::COPY_SRC) - .fill_from_slice(&[Instance { - _position: svg.position, - _scale: svg.scale, - }]); - - encoder.copy_buffer_to_buffer( - &instance_buffer, - 0, - &self.instances, - 0, - mem::size_of::() as u64, - ); - - { - let mut render_pass = encoder.begin_render_pass( - &wgpu::RenderPassDescriptor { - color_attachments: &[ - wgpu::RenderPassColorAttachmentDescriptor { - attachment: target, - resolve_target: None, - load_op: wgpu::LoadOp::Load, - store_op: wgpu::StoreOp::Store, - clear_color: wgpu::Color::TRANSPARENT, - }, - ], - depth_stencil_attachment: None, - }, - ); - - render_pass.set_pipeline(&self.pipeline); - render_pass.set_bind_group(0, &self.constants, &[]); - render_pass.set_bind_group(1, &texture, &[]); - render_pass.set_index_buffer(&self.indices, 0); - render_pass.set_vertex_buffers( - 0, - &[(&self.vertices, 0), (&self.instances, 0)], - ); - render_pass.set_scissor_rect( - bounds.x, - bounds.y, - bounds.width, - bounds.height, - ); - - render_pass.draw_indexed( - 0..QUAD_INDICES.len() as u32, - 0, - 0..1 as u32, - ); - } - } - } - } - - pub fn trim_cache(&mut self) { - self.cache.borrow_mut().trim(); - } -} - -#[derive(Debug, Clone, PartialEq)] -pub struct Handle { - id: u64, - path: PathBuf, -} - -impl Handle { - /// Returns the unique id of this [`Handle`] - /// - /// [`Handle`]: struct.Handle.html - pub fn id(&self) -> u64 { - self.id - } - - /// Creates a svg [`Handle`] pointing to the svg icon of the given path. - /// - /// [`Handle`]: struct.Handle.html - pub fn from_path>(path: T) -> Handle { - let path = path.into(); - let mut hasher = Hasher::default(); - path.hash(&mut hasher); - - Self { - id: hasher.finish(), - path, - } - } -} - -impl Hash for Handle { - fn hash(&self, state: &mut H) { - self.id.hash(state); - } -} - -enum Memory { - Host { tree: resvg::usvg::Tree }, - Device { bind_group: Rc }, - NotFound, - Invalid, -} - -impl Memory { - fn upload( - &mut self, - device: &wgpu::Device, - encoder: &mut wgpu::CommandEncoder, - texture_layout: &wgpu::BindGroupLayout, - width: u32, - height: u32, - ) -> Option> { - match self { - Memory::Host { tree } => { - println!("{} {}", width, 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 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(); - let temp_buf = 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: width * 4, - image_height: height, - }, - 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(), - }; - - Some(bind_group) - } - Memory::Device { bind_group, .. } => Some(bind_group.clone()), - Memory::NotFound => None, - Memory::Invalid => None, - } - } -} - -impl Debug for Memory { - fn fmt( - &self, - f: &mut std::fmt::Formatter<'_>, - ) -> Result<(), std::fmt::Error> { - match self { - Memory::Host { .. } => write!(f, "Memory::Host"), - Memory::Device { .. } => write!(f, "Memory::Device"), - Memory::NotFound => write!(f, "Memory::NotFound"), - Memory::Invalid => write!(f, "Memory::Invalid"), - } - } -} - -#[derive(Debug)] -struct Cache { - map: HashMap, - hits: HashSet, -} - -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(); - } -} - -#[derive(Debug)] -pub struct Svg { - pub handle: Handle, - pub position: [f32; 2], - pub scale: [f32; 2], -} - -#[repr(C)] -#[derive(Clone, Copy)] -pub struct Vertex { - _position: [f32; 2], -} - -const QUAD_INDICES: [u16; 6] = [0, 1, 2, 0, 2, 3]; - -const QUAD_VERTS: [Vertex; 4] = [ - Vertex { - _position: [0.0, 0.0], - }, - Vertex { - _position: [1.0, 0.0], - }, - Vertex { - _position: [1.0, 1.0], - }, - Vertex { - _position: [0.0, 1.0], - }, -]; - -#[repr(C)] -#[derive(Clone, Copy)] -struct Instance { - _position: [f32; 2], - _scale: [f32; 2], -} - -#[repr(C)] -#[derive(Debug, Clone, Copy)] -struct Uniforms { - transform: [f32; 16], -} From 27717bc70c3947f553a8b75da9789fe967994a31 Mon Sep 17 00:00:00 2001 From: Malte Veerman Date: Thu, 12 Dec 2019 01:25:18 +0100 Subject: [PATCH 06/11] Renamed `Icon` widget to `Svg` and gave it separate width and height. The aspect ratio is now preserved like in the `Image` widget. --- native/src/widget.rs | 6 +-- native/src/widget/icon.rs | 83 ------------------------------ native/src/widget/svg.rs | 103 ++++++++++++++++++++++++++++++++++++++ src/native.rs | 8 +-- 4 files changed, 110 insertions(+), 90 deletions(-) delete mode 100644 native/src/widget/icon.rs create mode 100644 native/src/widget/svg.rs diff --git a/native/src/widget.rs b/native/src/widget.rs index 0cf6a6393b..ee7232cb3a 100644 --- a/native/src/widget.rs +++ b/native/src/widget.rs @@ -24,7 +24,7 @@ pub mod button; pub mod checkbox; pub mod column; pub mod container; -pub mod icon; +pub mod svg; pub mod image; pub mod radio; pub mod row; @@ -42,8 +42,6 @@ pub use column::Column; #[doc(no_inline)] pub use container::Container; #[doc(no_inline)] -pub use icon::Icon; -#[doc(no_inline)] pub use image::Image; #[doc(no_inline)] pub use radio::Radio; @@ -54,6 +52,8 @@ pub use scrollable::Scrollable; #[doc(no_inline)] pub use slider::Slider; #[doc(no_inline)] +pub use svg::Svg; +#[doc(no_inline)] pub use text::Text; #[doc(no_inline)] pub use text_input::TextInput; diff --git a/native/src/widget/icon.rs b/native/src/widget/icon.rs deleted file mode 100644 index c324e7867b..0000000000 --- a/native/src/widget/icon.rs +++ /dev/null @@ -1,83 +0,0 @@ -//! Display an icon. -use crate::{ - image, layout, Element, Hasher, Layout, Length, Point, Rectangle, Widget, -}; - -use std::{ - hash::Hash, - path::{Path, PathBuf}, -}; - -/// A simple icon_loader widget. -#[derive(Debug, Clone)] -pub struct Icon { - handle: image::Handle, - size: Length, -} - -impl Icon { - /// Create a new [`Icon`] from the file at `path`. - /// - /// [`Icon`]: struct.Icon.html - pub fn new(path: impl Into) -> Self { - Icon { - handle: image::Handle::from_path(path), - size: Length::Fill, - } - } - - /// Sets the size of the [`Icon`]. - /// - /// [`Icon`]: struct.Icon.html - pub fn size(mut self, size: Length) -> Self { - self.size = size; - self - } -} - -impl Widget for Icon -where - Renderer: image::Renderer, -{ - fn width(&self) -> Length { - self.size - } - - fn height(&self) -> Length { - self.size - } - - fn layout(&self, _: &Renderer, limits: &layout::Limits) -> layout::Node { - let mut size = limits.width(self.size).height(self.size).max(); - - if size.width > size.height { - size.width = size.height; - } else if size.width < size.height { - size.height = size.width; - } - - layout::Node::new(size) - } - - fn draw( - &self, - renderer: &mut Renderer, - layout: Layout<'_>, - _cursor_position: Point, - ) -> Renderer::Output { - renderer.draw(self.handle.clone(), layout) - } - - fn hash_layout(&self, state: &mut Hasher) { - self.size.hash(state); - } -} - -impl<'a, Message, Renderer> From for Element<'a, Message, Renderer> -where - Renderer: image::Renderer, -{ - fn from(icon: Icon) -> Element<'a, Message, Renderer> { - Element::new(icon) - } -} diff --git a/native/src/widget/svg.rs b/native/src/widget/svg.rs new file mode 100644 index 0000000000..097c1d8682 --- /dev/null +++ b/native/src/widget/svg.rs @@ -0,0 +1,103 @@ +//! Display an icon. +use crate::{ + image, layout, Element, Hasher, Layout, Length, Point, Size, Widget, +}; + +use std::{ + hash::Hash, + path::PathBuf, +}; + +/// A simple icon_loader widget. +#[derive(Debug, Clone)] +pub struct Svg { + handle: image::Handle, + width: Length, + height: Length, +} + +impl Svg { + /// Create a new [`Svg`] from the file at `path`. + /// + /// [`Svg`]: struct.Svg.html + pub fn new(path: impl Into) -> Self { + Svg { + handle: image::Handle::from_path(path), + width: Length::Fill, + height: Length::Fill, + } + } + + /// Sets the width of the [`Svg`]. + /// + /// [`Svg`]: struct.Svg.html + pub fn width(mut self, width: Length) -> Self { + self.width = width; + self + } + + /// Sets the height of the [`Svg`]. + /// + /// [`Svg`]: struct.Svg.html + pub fn height(mut self, height: Length) -> Self { + self.height = height; + self + } +} + +impl Widget for Svg +where + Renderer: image::Renderer, +{ + fn width(&self) -> Length { + self.width + } + + fn height(&self) -> Length { + self.height + } + + 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; + + let mut size = limits + .width(self.width) + .height(self.height) + .max(); + + let viewport_aspect_ratio = size.width / size.height; + + if viewport_aspect_ratio > aspect_ratio { + size.width = width as f32 * size.height / height as f32; + } else { + size.height = height as f32 * size.width / width as f32; + } + + layout::Node::new(size) + } + + fn draw( + &self, + renderer: &mut Renderer, + layout: Layout<'_>, + _cursor_position: Point, + ) -> Renderer::Output { + renderer.draw(self.handle.clone(), layout) + } + + fn hash_layout(&self, state: &mut Hasher) { + self.width.hash(state); + self.height.hash(state); + } +} + +impl<'a, Message, Renderer> From for Element<'a, Message, Renderer> +where + Renderer: image::Renderer, +{ + fn from(icon: Svg) -> Element<'a, Message, Renderer> { + Element::new(icon) + } +} diff --git a/src/native.rs b/src/native.rs index e52b63d3e0..8c86ec7ec0 100644 --- a/src/native.rs +++ b/src/native.rs @@ -80,17 +80,17 @@ pub mod widget { pub use iced_winit::image::{Handle, Image}; } - pub mod icon { + pub mod svg { //! Display icons in your user interface. - pub use iced_winit::icon::Icon; + pub use iced_winit::svg::Svg; } pub use iced_winit::{Checkbox, Radio, Text}; #[doc(no_inline)] pub use { - button::Button, icon::Icon, image::Image, scrollable::Scrollable, - slider::Slider, text_input::TextInput, + button::Button, image::Image, scrollable::Scrollable, + slider::Slider, svg::Svg, text_input::TextInput, }; /// A container that distributes its contents vertically. From 09707f29fcf7fbd71570a43db214921043427c3f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Sun, 15 Dec 2019 06:19:07 +0100 Subject: [PATCH 07/11] 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 097c1d8682..42f2ebdf68 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) -> Self { + /// [`Handle`]: struct.Handle.html + pub fn new(handle: impl Into) -> Self { Svg { - handle: image::Handle::from_path(path), + handle: handle.into(), width: Length::Fill, height: Length::Fill, } @@ -47,7 +46,7 @@ impl Svg { impl Widget 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>(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 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 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 e0e093e0ba..01059d2dba 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, + raster_cache: RefCell, + vector_cache: RefCell, 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, 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, Vec>), - 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, - 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> { - 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, - hits: HashSet, -} - -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 0000000000..fa10787977 --- /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, Vec>), + Device { + bind_group: Rc, + 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> { + 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, + hits: HashSet, +} + +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 0000000000..0894c6bc78 --- /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, + rasterized: HashMap<(u64, u32, u32), Rc>, + svg_hits: HashSet, + 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> { + 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 04264e5d54..958cc17f65 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 d1d4de14f9..365ef1ef4c 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 52410bee43..65bb3bcdce 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 0000000000..67bc3fe1f0 --- /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, + ) + } +} From 6ba2461445e68127ef686d2b9d79eb7a09f42a86 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Sun, 15 Dec 2019 06:28:44 +0100 Subject: [PATCH 08/11] Update `Svg` documentation --- native/src/widget/svg.rs | 11 +++++++++-- src/native.rs | 8 ++++---- 2 files changed, 13 insertions(+), 6 deletions(-) diff --git a/native/src/widget/svg.rs b/native/src/widget/svg.rs index 42f2ebdf68..9580f19586 100644 --- a/native/src/widget/svg.rs +++ b/native/src/widget/svg.rs @@ -1,4 +1,4 @@ -//! Display an icon. +//! Display vector graphics in your application. use crate::{layout, Element, Hasher, Layout, Length, Point, Size, Widget}; use std::{ @@ -6,7 +6,14 @@ use std::{ path::{Path, PathBuf}, }; -/// A simple icon_loader widget. +/// A vector graphics image. +/// +/// An [`Svg`] image resizes smoothly without losing any quality. +/// +/// [`Svg`] images can have a considerable rendering cost when resized, +/// specially when they are complex. +/// +/// [`Svg`]: struct.Svg.html #[derive(Debug, Clone)] pub struct Svg { handle: Handle, diff --git a/src/native.rs b/src/native.rs index 8c86ec7ec0..c6ddf25b84 100644 --- a/src/native.rs +++ b/src/native.rs @@ -81,16 +81,16 @@ pub mod widget { } pub mod svg { - //! Display icons in your user interface. - pub use iced_winit::svg::Svg; + //! Display vector graphics in your user interface. + pub use iced_winit::svg::{Handle, Svg}; } pub use iced_winit::{Checkbox, Radio, Text}; #[doc(no_inline)] pub use { - button::Button, image::Image, scrollable::Scrollable, - slider::Slider, svg::Svg, text_input::TextInput, + button::Button, image::Image, scrollable::Scrollable, slider::Slider, + svg::Svg, text_input::TextInput, }; /// A container that distributes its contents vertically. From aa298499768bb50129cc3bd0dca6f3f858e5802e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Sun, 15 Dec 2019 06:28:55 +0100 Subject: [PATCH 09/11] Add `svg` example --- examples/resources/tiger.svg | 725 +++++++++++++++++++++++++++++++++++ examples/svg.rs | 40 ++ wgpu/src/image/vector.rs | 2 +- 3 files changed, 766 insertions(+), 1 deletion(-) create mode 100644 examples/resources/tiger.svg create mode 100644 examples/svg.rs diff --git a/examples/resources/tiger.svg b/examples/resources/tiger.svg new file mode 100644 index 0000000000..679edec2eb --- /dev/null +++ b/examples/resources/tiger.svg @@ -0,0 +1,725 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/examples/svg.rs b/examples/svg.rs new file mode 100644 index 0000000000..6dfd5202ad --- /dev/null +++ b/examples/svg.rs @@ -0,0 +1,40 @@ +use iced::{Column, Container, Element, Length, Sandbox, Settings, Svg}; + +pub fn main() { + Tiger::run(Settings::default()) +} + +#[derive(Default)] +struct Tiger; + +impl Sandbox for Tiger { + type Message = (); + + fn new() -> Self { + Self::default() + } + + fn title(&self) -> String { + String::from("SVG - Iced") + } + + fn update(&mut self, _message: ()) {} + + fn view(&mut self) -> Element<()> { + let content = + Column::new() + .width(Length::Shrink) + .padding(20) + .push(Svg::new(format!( + "{}/examples/resources/tiger.svg", + env!("CARGO_MANIFEST_DIR") + ))); + + Container::new(content) + .width(Length::Fill) + .height(Length::Fill) + .center_x() + .center_y() + .into() + } +} diff --git a/wgpu/src/image/vector.rs b/wgpu/src/image/vector.rs index 0894c6bc78..097223a5d3 100644 --- a/wgpu/src/image/vector.rs +++ b/wgpu/src/image/vector.rs @@ -76,7 +76,7 @@ impl Cache { // 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. + // It would be cool to be able to smooth resize the `svg` 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)); From 232d4873ba0fb9b87d08c8d70b117e81aa7489b5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Sun, 15 Dec 2019 06:45:20 +0100 Subject: [PATCH 10/11] Put `svg` rendering behind a feature gate This reduces binary size when SVG supoprt is not needed. --- Cargo.toml | 2 ++ examples/svg.rs | 20 +++++++++++++++++--- wgpu/Cargo.toml | 5 ++++- wgpu/src/image.rs | 36 ++++++++++++++++++++++++------------ wgpu/src/renderer/widget.rs | 4 +++- 5 files changed, 50 insertions(+), 17 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index f6f1158add..f3b16da1e2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,6 +14,8 @@ categories = ["gui"] [features] # Enables a debug view in native platforms (press F12) debug = ["iced_winit/debug"] +# Enables support for SVG rendering +svg = ["iced_wgpu/svg"] [badges] maintenance = { status = "actively-developed" } diff --git a/examples/svg.rs b/examples/svg.rs index 6dfd5202ad..cdf238f0df 100644 --- a/examples/svg.rs +++ b/examples/svg.rs @@ -1,4 +1,4 @@ -use iced::{Column, Container, Element, Length, Sandbox, Settings, Svg}; +use iced::{Container, Element, Length, Sandbox, Settings}; pub fn main() { Tiger::run(Settings::default()) @@ -21,14 +21,28 @@ impl Sandbox for Tiger { fn update(&mut self, _message: ()) {} fn view(&mut self) -> Element<()> { - let content = + #[cfg(feature = "svg")] + let content = { + use iced::{Column, Svg}; + Column::new() .width(Length::Shrink) .padding(20) .push(Svg::new(format!( "{}/examples/resources/tiger.svg", env!("CARGO_MANIFEST_DIR") - ))); + ))) + }; + + #[cfg(not(feature = "svg"))] + let content = { + use iced::{HorizontalAlignment, Text}; + + Text::new("You need to enable the `svg` feature!") + .width(Length::Shrink) + .horizontal_alignment(HorizontalAlignment::Center) + .size(30) + }; Container::new(content) .width(Length::Fill) diff --git a/wgpu/Cargo.toml b/wgpu/Cargo.toml index e8e9c877ad..bb24191466 100644 --- a/wgpu/Cargo.toml +++ b/wgpu/Cargo.toml @@ -7,6 +7,9 @@ description = "A wgpu renderer for Iced" license = "MIT AND OFL-1.1" repository = "https://github.com/hecrj/iced" +[features] +svg = ["resvg"] + [dependencies] iced_native = { version = "0.1.0", path = "../native" } wgpu = "0.4" @@ -17,4 +20,4 @@ image = "0.22" glam = "0.8" font-kit = "0.4" log = "0.4" -resvg = { version = "0.8", features = ["raqote-backend"] } +resvg = { version = "0.8", features = ["raqote-backend"], optional = true } diff --git a/wgpu/src/image.rs b/wgpu/src/image.rs index 01059d2dba..e30f70a7cf 100644 --- a/wgpu/src/image.rs +++ b/wgpu/src/image.rs @@ -1,4 +1,5 @@ mod raster; +#[cfg(feature = "svg")] mod vector; use crate::Transformation; @@ -9,6 +10,7 @@ use std::{cell::RefCell, mem}; #[derive(Debug)] pub struct Pipeline { raster_cache: RefCell, + #[cfg(feature = "svg")] vector_cache: RefCell, pipeline: wgpu::RenderPipeline, @@ -190,6 +192,7 @@ impl Pipeline { Pipeline { raster_cache: RefCell::new(raster::Cache::new()), + #[cfg(feature = "svg")] vector_cache: RefCell::new(vector::Cache::new()), pipeline, @@ -209,6 +212,7 @@ impl Pipeline { memory.dimensions() } + #[cfg(feature = "svg")] pub fn viewport_dimensions(&self, handle: &svg::Handle) -> (u32, u32) { let mut cache = self.vector_cache.borrow_mut(); @@ -227,7 +231,7 @@ impl Pipeline { transformation: Transformation, bounds: Rectangle, target: &wgpu::TextureView, - scale: f32, + _scale: f32, ) { let uniforms_buffer = device .create_buffer_mapped(1, wgpu::BufferUsage::COPY_SRC) @@ -255,17 +259,23 @@ impl Pipeline { 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, - ) + Handle::Vector(_handle) => { + #[cfg(feature = "svg")] + { + let mut cache = self.vector_cache.borrow_mut(); + + cache.upload( + _handle, + image.scale, + _scale, + device, + encoder, + &self.texture_layout, + ) + } + + #[cfg(not(feature = "svg"))] + None } }; @@ -333,6 +343,8 @@ impl Pipeline { pub fn trim_cache(&mut self) { self.raster_cache.borrow_mut().trim(); + + #[cfg(feature = "svg")] self.vector_cache.borrow_mut().trim(); } } diff --git a/wgpu/src/renderer/widget.rs b/wgpu/src/renderer/widget.rs index 65bb3bcdce..91f107e829 100644 --- a/wgpu/src/renderer/widget.rs +++ b/wgpu/src/renderer/widget.rs @@ -6,6 +6,8 @@ mod radio; mod row; mod scrollable; mod slider; -mod svg; mod text; mod text_input; + +#[cfg(feature = "svg")] +mod svg; From 514ccf8a72d660d77f26e085b545e5104389c138 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Sun, 15 Dec 2019 07:03:54 +0100 Subject: [PATCH 11/11] Cache `Svg` load result properly This avoids trying to reload the file constantly on every frame. --- wgpu/src/image.rs | 7 ++----- wgpu/src/image/vector.rs | 35 ++++++++++++++++++++--------------- 2 files changed, 22 insertions(+), 20 deletions(-) diff --git a/wgpu/src/image.rs b/wgpu/src/image.rs index e30f70a7cf..4558ffb060 100644 --- a/wgpu/src/image.rs +++ b/wgpu/src/image.rs @@ -215,12 +215,9 @@ impl Pipeline { #[cfg(feature = "svg")] pub fn viewport_dimensions(&self, handle: &svg::Handle) -> (u32, u32) { let mut cache = self.vector_cache.borrow_mut(); + let svg = cache.load(&handle); - if let Some(svg) = cache.load(&handle) { - svg.viewport_dimensions() - } else { - (1, 1) - } + svg.viewport_dimensions() } pub fn draw( diff --git a/wgpu/src/image/vector.rs b/wgpu/src/image/vector.rs index 097223a5d3..aa712372e2 100644 --- a/wgpu/src/image/vector.rs +++ b/wgpu/src/image/vector.rs @@ -4,15 +4,21 @@ use std::{ rc::Rc, }; -pub struct Svg { - tree: resvg::usvg::Tree, +pub enum Svg { + Loaded { tree: resvg::usvg::Tree }, + NotFound, } impl Svg { pub fn viewport_dimensions(&self) -> (u32, u32) { - let size = self.tree.svg_node().size; + match self { + Svg::Loaded { tree } => { + let size = tree.svg_node().size; - (size.width() as u32, size.height() as u32) + (size.width() as u32, size.height() as u32) + } + Svg::NotFound => (1, 1), + } } } @@ -40,21 +46,20 @@ impl Cache { } } - pub fn load(&mut self, handle: &svg::Handle) -> Option<&Svg> { + pub fn load(&mut self, handle: &svg::Handle) -> &Svg { if self.svgs.contains_key(&handle.id()) { - return self.svgs.get(&handle.id()); + return self.svgs.get(&handle.id()).unwrap(); } 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(_) => {} + let svg = match resvg::usvg::Tree::from_file(handle.path(), &opt.usvg) { + Ok(tree) => Svg::Loaded { tree }, + Err(_) => Svg::NotFound, }; - self.svgs.get(&handle.id()) + let _ = self.svgs.insert(handle.id(), svg); + self.svgs.get(&handle.id()).unwrap() } pub fn upload( @@ -85,7 +90,7 @@ impl Cache { } match self.load(handle) { - Some(svg) => { + Svg::Loaded { tree } => { let extent = wgpu::Extent3d { width, height, @@ -113,7 +118,7 @@ impl Cache { ); resvg::backend_raqote::render_to_canvas( - &svg.tree, + &tree, &resvg::Options::default(), screen_size, &mut canvas, @@ -171,7 +176,7 @@ impl Cache { Some(bind_group) } - None => None, + Svg::NotFound => None, } }