From 80324284282f173e4d26e1f297daaf71a93f51a6 Mon Sep 17 00:00:00 2001 From: Malte Veerman Date: Fri, 6 Dec 2019 16:47:40 +0100 Subject: Implemented SVG support in iced_wgpu. --- wgpu/src/lib.rs | 2 + wgpu/src/primitive.rs | 9 + wgpu/src/renderer.rs | 34 ++- wgpu/src/svg.rs | 595 ++++++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 639 insertions(+), 1 deletion(-) create mode 100644 wgpu/src/svg.rs (limited to 'wgpu/src') diff --git a/wgpu/src/lib.rs b/wgpu/src/lib.rs index 9f9ed8db..1f5794ee 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 04264e5d..c637626b 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 fa52bd96..9895e1c4 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 00000000..87394799 --- /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 -- cgit From a88aae5e04e0a92457e5dd617a86af823e90af6c Mon Sep 17 00:00:00 2001 From: Malte Veerman Date: Fri, 6 Dec 2019 19:37:56 +0100 Subject: Added an `Icon` widget to native. --- wgpu/src/primitive.rs | 6 ++---- wgpu/src/renderer/widget.rs | 1 + wgpu/src/renderer/widget/icon.rs | 21 +++++++++++++++++++++ wgpu/src/svg.rs | 3 ++- 4 files changed, 26 insertions(+), 5 deletions(-) create mode 100644 wgpu/src/renderer/widget/icon.rs (limited to 'wgpu/src') diff --git a/wgpu/src/primitive.rs b/wgpu/src/primitive.rs index c637626b..1b0729cf 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 52410bee..3e1b8d92 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 00000000..a271bb47 --- /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 87394799..1b8f14b0 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)] -- cgit From 5696afcadd5b3b89a532f4205efac30d8a24d558 Mon Sep 17 00:00:00 2001 From: Malte Veerman Date: Wed, 11 Dec 2019 22:13:29 +0100 Subject: Ran cargo_fmt over changed files. --- wgpu/src/renderer.rs | 12 ++++----- wgpu/src/renderer/widget/icon.rs | 12 +++------ wgpu/src/svg.rs | 54 +++++++++++++++++++++++----------------- wgpu/src/text/font.rs | 5 ++-- 4 files changed, 42 insertions(+), 41 deletions(-) (limited to 'wgpu/src') diff --git a/wgpu/src/renderer.rs b/wgpu/src/renderer.rs index 9895e1c4..92e24933 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 a271bb47..652e57a3 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 1b8f14b0..755303a5 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 31df5bf4..7346ccdb 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, -- cgit From f737c6da24d5c75e3efa92c0fd9d0d11fbd715c1 Mon Sep 17 00:00:00 2001 From: Malte Veerman Date: Thu, 12 Dec 2019 00:20:06 +0100 Subject: Improved dpi handling --- wgpu/src/renderer.rs | 3 +-- wgpu/src/svg.rs | 59 +++++++--------------------------------------------- 2 files changed, 8 insertions(+), 54 deletions(-) (limited to 'wgpu/src') diff --git a/wgpu/src/renderer.rs b/wgpu/src/renderer.rs index 92e24933..3eb8c5ca 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 755303a5..f2161489 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, -- cgit From 895eaef99b52c24e6f3d804897ad850c1f1de960 Mon Sep 17 00:00:00 2001 From: Malte Veerman Date: Thu, 12 Dec 2019 01:14:54 +0100 Subject: Merged svg pipeline into image --- 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 --------------------------------------- 7 files changed, 87 insertions(+), 634 deletions(-) delete mode 100644 wgpu/src/renderer/widget/icon.rs delete mode 100644 wgpu/src/svg.rs (limited to 'wgpu/src') diff --git a/wgpu/src/image.rs b/wgpu/src/image.rs index 7e4e2670..e0e093e0 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 1f5794ee..9f9ed8db 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 1b0729cf..04264e5d 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 3eb8c5ca..d1d4de14 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 3e1b8d92..52410bee 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 652e57a3..00000000 --- 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 f2161489..00000000 --- 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], -} -- cgit From 09707f29fcf7fbd71570a43db214921043427c3f Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Sun, 15 Dec 2019 06:19:07 +0100 Subject: Rerasterize SVGs when resized and refactor a bit --- 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 +++ 7 files changed, 455 insertions(+), 262 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 (limited to 'wgpu/src') diff --git a/wgpu/src/image.rs b/wgpu/src/image.rs index e0e093e0..01059d2d 100644 --- a/wgpu/src/image.rs +++ b/wgpu/src/image.rs @@ -1,20 +1,15 @@ +mod raster; +mod vector; + use crate::Transformation; -use iced_native::{ - image::{Data, Handle}, - Rectangle, -}; - -use std::{ - cell::RefCell, - collections::{HashMap, HashSet}, - fmt, - mem, - rc::Rc, -}; +use iced_native::{image, svg, Rectangle}; + +use std::{cell::RefCell, mem}; #[derive(Debug)] pub struct Pipeline { - cache: RefCell, + 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 00000000..fa107879 --- /dev/null +++ b/wgpu/src/image/raster.rs @@ -0,0 +1,176 @@ +use iced_native::image; +use std::{ + collections::{HashMap, HashSet}, + rc::Rc, +}; + +#[derive(Debug)] +pub enum Memory { + Host(::image::ImageBuffer<::image::Bgra, 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 00000000..0894c6bc --- /dev/null +++ b/wgpu/src/image/vector.rs @@ -0,0 +1,187 @@ +use iced_native::svg; +use std::{ + collections::{HashMap, HashSet}, + rc::Rc, +}; + +pub struct Svg { + tree: resvg::usvg::Tree, +} + +impl Svg { + pub fn viewport_dimensions(&self) -> (u32, u32) { + let size = self.tree.svg_node().size; + + (size.width() as u32, size.height() as u32) + } +} + +impl std::fmt::Debug for Svg { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "Svg") + } +} + +#[derive(Debug)] +pub struct Cache { + svgs: HashMap, + 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 04264e5d..958cc17f 100644 --- a/wgpu/src/primitive.rs +++ b/wgpu/src/primitive.rs @@ -1,6 +1,6 @@ use iced_native::{ - image, Background, Color, Font, HorizontalAlignment, Rectangle, Vector, - VerticalAlignment, + image, svg, Background, Color, Font, HorizontalAlignment, Rectangle, + Vector, VerticalAlignment, }; /// A rendering primitive. @@ -46,6 +46,14 @@ pub enum Primitive { /// The bounds of the image bounds: Rectangle, }, + /// An SVG primitive + Svg { + /// The path of the SVG file + handle: svg::Handle, + + /// The bounds of the viewport + bounds: Rectangle, + }, /// A clip primitive Clip { /// The bounds of the clip diff --git a/wgpu/src/renderer.rs b/wgpu/src/renderer.rs index d1d4de14..365ef1ef 100644 --- a/wgpu/src/renderer.rs +++ b/wgpu/src/renderer.rs @@ -1,4 +1,4 @@ -use crate::{quad, text, Image, Primitive, Quad, Transformation}; +use crate::{image, quad, text, Image, Primitive, Quad, Transformation}; use iced_native::{ renderer::{Debugger, Windowed}, Background, Color, Layout, MouseCursor, Point, Rectangle, Vector, Widget, @@ -232,7 +232,14 @@ impl Renderer { } Primitive::Image { handle, bounds } => { layer.images.push(Image { - handle: handle.clone(), + handle: image::Handle::Raster(handle.clone()), + position: [bounds.x, bounds.y], + scale: [bounds.width, bounds.height], + }); + } + Primitive::Svg { handle, bounds } => { + layer.images.push(Image { + handle: image::Handle::Vector(handle.clone()), position: [bounds.x, bounds.y], scale: [bounds.width, bounds.height], }); diff --git a/wgpu/src/renderer/widget.rs b/wgpu/src/renderer/widget.rs index 52410bee..65bb3bcd 100644 --- a/wgpu/src/renderer/widget.rs +++ b/wgpu/src/renderer/widget.rs @@ -6,5 +6,6 @@ mod radio; mod row; mod scrollable; mod slider; +mod svg; mod text; mod text_input; diff --git a/wgpu/src/renderer/widget/svg.rs b/wgpu/src/renderer/widget/svg.rs new file mode 100644 index 00000000..67bc3fe1 --- /dev/null +++ b/wgpu/src/renderer/widget/svg.rs @@ -0,0 +1,22 @@ +use crate::{Primitive, Renderer}; +use iced_native::{svg, Layout, MouseCursor}; + +impl svg::Renderer for Renderer { + fn dimensions(&self, handle: &svg::Handle) -> (u32, u32) { + self.image_pipeline.viewport_dimensions(handle) + } + + fn draw( + &mut self, + handle: svg::Handle, + layout: Layout<'_>, + ) -> Self::Output { + ( + Primitive::Svg { + handle, + bounds: layout.bounds(), + }, + MouseCursor::OutOfBounds, + ) + } +} -- cgit From aa298499768bb50129cc3bd0dca6f3f858e5802e Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Sun, 15 Dec 2019 06:28:55 +0100 Subject: Add `svg` example --- wgpu/src/image/vector.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'wgpu/src') diff --git a/wgpu/src/image/vector.rs b/wgpu/src/image/vector.rs index 0894c6bc..097223a5 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)); -- cgit From 232d4873ba0fb9b87d08c8d70b117e81aa7489b5 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Sun, 15 Dec 2019 06:45:20 +0100 Subject: Put `svg` rendering behind a feature gate This reduces binary size when SVG supoprt is not needed. --- wgpu/src/image.rs | 36 ++++++++++++++++++++++++------------ wgpu/src/renderer/widget.rs | 4 +++- 2 files changed, 27 insertions(+), 13 deletions(-) (limited to 'wgpu/src') diff --git a/wgpu/src/image.rs b/wgpu/src/image.rs index 01059d2d..e30f70a7 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 65bb3bcd..91f107e8 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; -- cgit From 514ccf8a72d660d77f26e085b545e5104389c138 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Sun, 15 Dec 2019 07:03:54 +0100 Subject: 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(-) (limited to 'wgpu/src') diff --git a/wgpu/src/image.rs b/wgpu/src/image.rs index e30f70a7..4558ffb0 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 097223a5..aa712372 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, } } -- cgit