diff options
Diffstat (limited to 'wgpu')
-rw-r--r-- | wgpu/Cargo.toml | 2 | ||||
-rw-r--r-- | wgpu/src/geometry.rs | 110 | ||||
-rw-r--r-- | wgpu/src/image/mod.rs | 58 | ||||
-rw-r--r-- | wgpu/src/image/vector.rs | 35 | ||||
-rw-r--r-- | wgpu/src/layer.rs | 43 | ||||
-rw-r--r-- | wgpu/src/lib.rs | 102 | ||||
-rw-r--r-- | wgpu/src/shader/image.wgsl | 12 | ||||
-rw-r--r-- | wgpu/src/shader/quad/solid.wgsl | 13 | ||||
-rw-r--r-- | wgpu/src/text.rs | 8 |
9 files changed, 243 insertions, 140 deletions
diff --git a/wgpu/Cargo.toml b/wgpu/Cargo.toml index 30545fa2..b13ecb36 100644 --- a/wgpu/Cargo.toml +++ b/wgpu/Cargo.toml @@ -20,7 +20,7 @@ all-features = true [features] geometry = ["iced_graphics/geometry", "lyon"] image = ["iced_graphics/image"] -svg = ["resvg/text"] +svg = ["iced_graphics/svg", "resvg/text"] web-colors = ["iced_graphics/web-colors"] webgl = ["wgpu/webgl"] diff --git a/wgpu/src/geometry.rs b/wgpu/src/geometry.rs index f6213e1d..8e6f77d7 100644 --- a/wgpu/src/geometry.rs +++ b/wgpu/src/geometry.rs @@ -1,7 +1,7 @@ //! Build and draw geometry. use crate::core::text::LineHeight; use crate::core::{ - Pixels, Point, Radians, Rectangle, Size, Transformation, Vector, + self, Pixels, Point, Radians, Rectangle, Size, Svg, Transformation, Vector, }; use crate::graphics::cache::{self, Cached}; use crate::graphics::color; @@ -11,7 +11,7 @@ use crate::graphics::geometry::{ }; use crate::graphics::gradient::{self, Gradient}; use crate::graphics::mesh::{self, Mesh}; -use crate::graphics::{self, Text}; +use crate::graphics::{Image, Text}; use crate::text; use crate::triangle; @@ -19,16 +19,22 @@ use lyon::geom::euclid; use lyon::tessellation; use std::borrow::Cow; +use std::sync::Arc; #[derive(Debug)] pub enum Geometry { - Live { meshes: Vec<Mesh>, text: Vec<Text> }, + Live { + meshes: Vec<Mesh>, + images: Vec<Image>, + text: Vec<Text>, + }, Cached(Cache), } #[derive(Debug, Clone)] pub struct Cache { pub meshes: Option<triangle::Cache>, + pub images: Option<Arc<[Image]>>, pub text: Option<text::Cache>, } @@ -45,7 +51,17 @@ impl Cached for Geometry { previous: Option<Self::Cache>, ) -> Self::Cache { match self { - Self::Live { meshes, text } => { + Self::Live { + meshes, + images, + text, + } => { + let images = if images.is_empty() { + None + } else { + Some(Arc::from(images)) + }; + if let Some(mut previous) = previous { if let Some(cache) = &mut previous.meshes { cache.update(meshes); @@ -59,10 +75,13 @@ impl Cached for Geometry { previous.text = text::Cache::new(group, text); } + previous.images = images; + previous } else { Cache { meshes: triangle::Cache::new(meshes), + images, text: text::Cache::new(group, text), } } @@ -78,6 +97,7 @@ pub struct Frame { clip_bounds: Rectangle, buffers: BufferStack, meshes: Vec<Mesh>, + images: Vec<Image>, text: Vec<Text>, transforms: Transforms, fill_tessellator: tessellation::FillTessellator, @@ -96,6 +116,7 @@ impl Frame { clip_bounds: bounds, buffers: BufferStack::new(), meshes: Vec::new(), + images: Vec::new(), text: Vec::new(), transforms: Transforms { previous: Vec::new(), @@ -232,6 +253,44 @@ impl geometry::frame::Backend for Frame { .expect("Stroke path"); } + fn stroke_rectangle<'a>( + &mut self, + top_left: Point, + size: Size, + stroke: impl Into<Stroke<'a>>, + ) { + let stroke = stroke.into(); + + let mut buffer = self + .buffers + .get_stroke(&self.transforms.current.transform_style(stroke.style)); + + let top_left = self + .transforms + .current + .0 + .transform_point(lyon::math::Point::new(top_left.x, top_left.y)); + + let size = + self.transforms.current.0.transform_vector( + lyon::math::Vector::new(size.width, size.height), + ); + + let mut options = tessellation::StrokeOptions::default(); + options.line_width = stroke.width; + options.start_cap = into_line_cap(stroke.line_cap); + options.end_cap = into_line_cap(stroke.line_cap); + options.line_join = into_line_join(stroke.line_join); + + self.stroke_tessellator + .tessellate_rectangle( + &lyon::math::Box2D::new(top_left, top_left + size), + &options, + buffer.as_mut(), + ) + .expect("Stroke rectangle"); + } + fn fill_text(&mut self, text: impl Into<geometry::Text>) { let text = text.into(); @@ -270,7 +329,7 @@ impl geometry::frame::Backend for Frame { height: f32::INFINITY, }; - self.text.push(graphics::Text::Cached { + self.text.push(Text::Cached { content: text.content, bounds, color: text.color, @@ -335,10 +394,11 @@ impl geometry::frame::Backend for Frame { Frame::with_clip(clip_bounds) } - fn paste(&mut self, frame: Frame, _at: Point) { + fn paste(&mut self, frame: Frame) { self.meshes .extend(frame.buffers.into_meshes(frame.clip_bounds)); + self.images.extend(frame.images); self.text.extend(frame.text); } @@ -348,9 +408,32 @@ impl geometry::frame::Backend for Frame { Geometry::Live { meshes: self.meshes, + images: self.images, text: self.text, } } + + fn draw_image(&mut self, bounds: Rectangle, image: impl Into<core::Image>) { + let mut image = image.into(); + + let (bounds, external_rotation) = + self.transforms.current.transform_rectangle(bounds); + + image.rotation += external_rotation; + + self.images.push(Image::Raster(image, bounds)); + } + + fn draw_svg(&mut self, bounds: Rectangle, svg: impl Into<Svg>) { + let mut svg = svg.into(); + + let (bounds, external_rotation) = + self.transforms.current.transform_rectangle(bounds); + + svg.rotation += external_rotation; + + self.images.push(Image::Vector(svg, bounds)); + } } enum Buffer { @@ -518,6 +601,21 @@ impl Transform { gradient } + + fn transform_rectangle( + &self, + rectangle: Rectangle, + ) -> (Rectangle, Radians) { + let top_left = self.transform_point(rectangle.position()); + let top_right = self.transform_point( + rectangle.position() + Vector::new(rectangle.width, 0.0), + ); + let bottom_left = self.transform_point( + rectangle.position() + Vector::new(0.0, rectangle.height), + ); + + Rectangle::with_vertices(top_left, top_right, bottom_left) + } } struct GradientVertex2DBuilder { gradient: gradient::Packed, diff --git a/wgpu/src/image/mod.rs b/wgpu/src/image/mod.rs index daa2fe16..1b16022a 100644 --- a/wgpu/src/image/mod.rs +++ b/wgpu/src/image/mod.rs @@ -149,6 +149,8 @@ impl Pipeline { 6 => Float32x2, // Layer 7 => Sint32, + // Snap + 8 => Uint32, ), }], }, @@ -212,31 +214,24 @@ impl Pipeline { transformation: Transformation, scale: f32, ) { - let transformation = transformation * Transformation::scale(scale); - let nearest_instances: &mut Vec<Instance> = &mut Vec::new(); let linear_instances: &mut Vec<Instance> = &mut Vec::new(); for image in images { match &image { #[cfg(feature = "image")] - Image::Raster { - handle, - filter_method, - bounds, - rotation, - opacity, - } => { + Image::Raster(image, bounds) => { if let Some(atlas_entry) = - cache.upload_raster(device, encoder, handle) + cache.upload_raster(device, encoder, &image.handle) { add_instances( [bounds.x, bounds.y], [bounds.width, bounds.height], - f32::from(*rotation), - *opacity, + f32::from(image.rotation), + image.opacity, + image.snap, atlas_entry, - match filter_method { + match image.filter_method { crate::core::image::FilterMethod::Nearest => { nearest_instances } @@ -251,23 +246,23 @@ impl Pipeline { Image::Raster { .. } => {} #[cfg(feature = "svg")] - Image::Vector { - handle, - color, - bounds, - rotation, - opacity, - } => { + Image::Vector(svg, bounds) => { let size = [bounds.width, bounds.height]; if let Some(atlas_entry) = cache.upload_vector( - device, encoder, handle, *color, size, scale, + device, + encoder, + &svg.handle, + svg.color, + size, + scale, ) { add_instances( [bounds.x, bounds.y], size, - f32::from(*rotation), - *opacity, + f32::from(svg.rotation), + svg.opacity, + true, atlas_entry, nearest_instances, ); @@ -300,6 +295,7 @@ impl Pipeline { nearest_instances, linear_instances, transformation, + scale, ); self.prepare_layer += 1; @@ -375,9 +371,12 @@ impl Layer { nearest_instances: &[Instance], linear_instances: &[Instance], transformation: Transformation, + scale_factor: f32, ) { let uniforms = Uniforms { transform: transformation.into(), + scale_factor, + _padding: [0.0; 3], }; let bytes = bytemuck::bytes_of(&uniforms); @@ -492,6 +491,7 @@ struct Instance { _position_in_atlas: [f32; 2], _size_in_atlas: [f32; 2], _layer: u32, + _snap: u32, } impl Instance { @@ -502,6 +502,10 @@ impl Instance { #[derive(Debug, Clone, Copy, Zeroable, Pod)] struct Uniforms { transform: [f32; 16], + scale_factor: f32, + // Uniforms must be aligned to their largest member, + // this uses a mat4x4<f32> which aligns to 16, so align to that + _padding: [f32; 3], } fn add_instances( @@ -509,6 +513,7 @@ fn add_instances( image_size: [f32; 2], rotation: f32, opacity: f32, + snap: bool, entry: &atlas::Entry, instances: &mut Vec<Instance>, ) { @@ -525,6 +530,7 @@ fn add_instances( image_size, rotation, opacity, + snap, allocation, instances, ); @@ -554,8 +560,8 @@ fn add_instances( ]; add_instance( - position, center, size, rotation, opacity, allocation, - instances, + position, center, size, rotation, opacity, snap, + allocation, instances, ); } } @@ -569,6 +575,7 @@ fn add_instance( size: [f32; 2], rotation: f32, opacity: f32, + snap: bool, allocation: &atlas::Allocation, instances: &mut Vec<Instance>, ) { @@ -591,6 +598,7 @@ fn add_instance( (height as f32 - 1.0) / atlas::SIZE as f32, ], _layer: layer as u32, + _snap: snap as u32, }; instances.push(instance); diff --git a/wgpu/src/image/vector.rs b/wgpu/src/image/vector.rs index c6d829af..74e9924d 100644 --- a/wgpu/src/image/vector.rs +++ b/wgpu/src/image/vector.rs @@ -1,10 +1,9 @@ use crate::core::svg; use crate::core::{Color, Size}; -use crate::graphics::text; use crate::image::atlas::{self, Atlas}; use resvg::tiny_skia; -use resvg::usvg::{self, TreeTextToPath}; +use resvg::usvg; use rustc_hash::{FxHashMap, FxHashSet}; use std::fs; @@ -21,7 +20,7 @@ impl Svg { pub fn viewport_dimensions(&self) -> Size<u32> { match self { Svg::Loaded(tree) => { - let size = tree.size; + let size = tree.size(); Size::new(size.width() as u32, size.height() as u32) } @@ -45,38 +44,33 @@ type ColorFilter = Option<[u8; 4]>; impl Cache { /// Load svg pub fn load(&mut self, handle: &svg::Handle) -> &Svg { - use usvg::TreeParsing; - if self.svgs.contains_key(&handle.id()) { return self.svgs.get(&handle.id()).unwrap(); } - let mut svg = match handle.data() { + let svg = match handle.data() { svg::Data::Path(path) => fs::read_to_string(path) .ok() .and_then(|contents| { - usvg::Tree::from_str(&contents, &usvg::Options::default()) - .ok() + usvg::Tree::from_str( + &contents, + &usvg::Options::default(), // TODO: Set usvg::Options::fontdb + ) + .ok() }) .map(Svg::Loaded) .unwrap_or(Svg::NotFound), svg::Data::Bytes(bytes) => { - match usvg::Tree::from_data(bytes, &usvg::Options::default()) { + match usvg::Tree::from_data( + bytes, + &usvg::Options::default(), // TODO: Set usvg::Options::fontdb + ) { Ok(tree) => Svg::Loaded(tree), Err(_) => Svg::NotFound, } } }; - if let Svg::Loaded(svg) = &mut svg { - if svg.has_text_nodes() { - let mut font_system = - text::font_system().write().expect("Write font system"); - - svg.convert_text(font_system.raw().db_mut()); - } - } - self.should_trim = true; let _ = self.svgs.insert(handle.id(), svg); @@ -127,7 +121,7 @@ impl Cache { // It would be cool to be able to smooth resize the `svg` example. let mut img = tiny_skia::Pixmap::new(width, height)?; - let tree_size = tree.size.to_int_size(); + let tree_size = tree.size().to_int_size(); let target_size = if width > height { tree_size.scale_to_width(width) @@ -147,8 +141,7 @@ impl Cache { tiny_skia::Transform::default() }; - resvg::Tree::from_usvg(tree) - .render(transform, &mut img.as_mut()); + resvg::render(tree, transform, &mut img.as_mut()); let mut rgba = img.take(); diff --git a/wgpu/src/layer.rs b/wgpu/src/layer.rs index 9551311d..68d5a015 100644 --- a/wgpu/src/layer.rs +++ b/wgpu/src/layer.rs @@ -1,5 +1,5 @@ use crate::core::{ - renderer, Background, Color, Point, Radians, Rectangle, Transformation, + self, renderer, Background, Color, Point, Rectangle, Svg, Transformation, }; use crate::graphics; use crate::graphics::color; @@ -20,8 +20,8 @@ pub struct Layer { pub quads: quad::Batch, pub triangles: triangle::Batch, pub primitives: primitive::Batch, - pub text: text::Batch, pub images: image::Batch, + pub text: text::Batch, pending_meshes: Vec<Mesh>, pending_text: Vec<Text>, } @@ -112,42 +112,35 @@ impl Layer { self.pending_text.push(text); } - pub fn draw_image( + pub fn draw_image(&mut self, image: Image, transformation: Transformation) { + match image { + Image::Raster(image, bounds) => { + self.draw_raster(image, bounds, transformation); + } + Image::Vector(svg, bounds) => { + self.draw_svg(svg, bounds, transformation); + } + } + } + + pub fn draw_raster( &mut self, - handle: crate::core::image::Handle, - filter_method: crate::core::image::FilterMethod, + image: core::Image, bounds: Rectangle, transformation: Transformation, - rotation: Radians, - opacity: f32, ) { - let image = Image::Raster { - handle, - filter_method, - bounds: bounds * transformation, - rotation, - opacity, - }; + let image = Image::Raster(image, bounds * transformation); self.images.push(image); } pub fn draw_svg( &mut self, - handle: crate::core::svg::Handle, - color: Option<Color>, + svg: Svg, bounds: Rectangle, transformation: Transformation, - rotation: Radians, - opacity: f32, ) { - let svg = Image::Vector { - handle, - color, - bounds: bounds * transformation, - rotation, - opacity, - }; + let svg = Image::Vector(svg, bounds * transformation); self.images.push(svg); } diff --git a/wgpu/src/lib.rs b/wgpu/src/lib.rs index ad88ce3e..d79f0dc8 100644 --- a/wgpu/src/lib.rs +++ b/wgpu/src/lib.rs @@ -182,19 +182,6 @@ impl Renderer { } } - if !layer.text.is_empty() { - engine.text_pipeline.prepare( - device, - queue, - &self.text_viewport, - encoder, - &mut self.text_storage, - &layer.text, - layer.bounds, - Transformation::scale(scale_factor), - ); - } - #[cfg(any(feature = "svg", feature = "image"))] if !layer.images.is_empty() { engine.image_pipeline.prepare( @@ -207,6 +194,19 @@ impl Renderer { scale_factor, ); } + + if !layer.text.is_empty() { + engine.text_pipeline.prepare( + device, + queue, + &self.text_viewport, + encoder, + &mut self.text_storage, + &layer.text, + layer.bounds, + Transformation::scale(scale_factor), + ); + } } } @@ -359,17 +359,6 @@ impl Renderer { )); } - if !layer.text.is_empty() { - text_layer += engine.text_pipeline.render( - &self.text_viewport, - &self.text_storage, - text_layer, - &layer.text, - scissor_rect, - &mut render_pass, - ); - } - #[cfg(any(feature = "svg", feature = "image"))] if !layer.images.is_empty() { engine.image_pipeline.render( @@ -381,6 +370,17 @@ impl Renderer { image_layer += 1; } + + if !layer.text.is_empty() { + text_layer += engine.text_pipeline.render( + &self.text_viewport, + &self.text_storage, + text_layer, + &layer.text, + scissor_rect, + &mut render_pass, + ); + } } let _ = ManuallyDrop::into_inner(render_pass); @@ -408,6 +408,7 @@ impl Renderer { horizontal_alignment: alignment::Horizontal::Left, vertical_alignment: alignment::Vertical::Top, shaping: core::text::Shaping::Basic, + wrapping: core::text::Wrapping::Word, }; renderer.fill_text( @@ -527,23 +528,9 @@ impl core::image::Renderer for Renderer { self.image_cache.borrow_mut().measure_image(handle) } - fn draw_image( - &mut self, - handle: Self::Handle, - filter_method: core::image::FilterMethod, - bounds: Rectangle, - rotation: core::Radians, - opacity: f32, - ) { + fn draw_image(&mut self, image: core::Image, bounds: Rectangle) { let (layer, transformation) = self.layers.current_mut(); - layer.draw_image( - handle, - filter_method, - bounds, - transformation, - rotation, - opacity, - ); + layer.draw_raster(image, bounds, transformation); } } @@ -553,23 +540,9 @@ impl core::svg::Renderer for Renderer { self.image_cache.borrow_mut().measure_svg(handle) } - fn draw_svg( - &mut self, - handle: core::svg::Handle, - color_filter: Option<Color>, - bounds: Rectangle, - rotation: core::Radians, - opacity: f32, - ) { + fn draw_svg(&mut self, svg: core::Svg, bounds: Rectangle) { let (layer, transformation) = self.layers.current_mut(); - layer.draw_svg( - handle, - color_filter, - bounds, - transformation, - rotation, - opacity, - ); + layer.draw_svg(svg, bounds, transformation); } } @@ -593,8 +566,17 @@ impl graphics::geometry::Renderer for Renderer { let (layer, transformation) = self.layers.current_mut(); match geometry { - Geometry::Live { meshes, text } => { + Geometry::Live { + meshes, + images, + text, + } => { layer.draw_mesh_group(meshes, transformation); + + for image in images { + layer.draw_image(image, transformation); + } + layer.draw_text_group(text, transformation); } Geometry::Cached(cache) => { @@ -602,6 +584,12 @@ impl graphics::geometry::Renderer for Renderer { layer.draw_mesh_cache(meshes, transformation); } + if let Some(images) = cache.images { + for image in images.iter().cloned() { + layer.draw_image(image, transformation); + } + } + if let Some(text) = cache.text { layer.draw_text_cache(text, transformation); } diff --git a/wgpu/src/shader/image.wgsl b/wgpu/src/shader/image.wgsl index 0eeb100f..bc922838 100644 --- a/wgpu/src/shader/image.wgsl +++ b/wgpu/src/shader/image.wgsl @@ -1,5 +1,6 @@ struct Globals { transform: mat4x4<f32>, + scale_factor: f32, } @group(0) @binding(0) var<uniform> globals: Globals; @@ -16,6 +17,7 @@ struct VertexInput { @location(5) atlas_pos: vec2<f32>, @location(6) atlas_scale: vec2<f32>, @location(7) layer: i32, + @location(8) snap: u32, } struct VertexOutput { @@ -38,7 +40,7 @@ fn vs_main(input: VertexInput) -> VertexOutput { out.opacity = input.opacity; // Calculate the vertex position and move the center to the origin - v_pos = round(input.pos) + v_pos * input.scale - input.center; + v_pos = input.pos + v_pos * input.scale - input.center; // Apply the rotation around the center of the image let cos_rot = cos(input.rotation); @@ -51,7 +53,13 @@ fn vs_main(input: VertexInput) -> VertexOutput { ); // Calculate the final position of the vertex - out.position = globals.transform * (vec4<f32>(input.center, 0.0, 0.0) + rotate * vec4<f32>(v_pos, 0.0, 1.0)); + out.position = vec4(vec2(globals.scale_factor), 1.0, 1.0) * (vec4<f32>(input.center, 0.0, 0.0) + rotate * vec4<f32>(v_pos, 0.0, 1.0)); + + if bool(input.snap) { + out.position = round(out.position); + } + + out.position = globals.transform * out.position; return out; } diff --git a/wgpu/src/shader/quad/solid.wgsl b/wgpu/src/shader/quad/solid.wgsl index d908afbc..8eee16bb 100644 --- a/wgpu/src/shader/quad/solid.wgsl +++ b/wgpu/src/shader/quad/solid.wgsl @@ -30,6 +30,15 @@ fn solid_vs_main(input: SolidVertexInput) -> SolidVertexOutput { var pos: vec2<f32> = (input.pos + min(input.shadow_offset, vec2<f32>(0.0, 0.0)) - input.shadow_blur_radius) * globals.scale; var scale: vec2<f32> = (input.scale + vec2<f32>(abs(input.shadow_offset.x), abs(input.shadow_offset.y)) + input.shadow_blur_radius * 2.0) * globals.scale; + var snap: vec2<f32> = vec2<f32>(0.0, 0.0); + + if input.scale.x == 1.0 { + snap.x = round(pos.x) - pos.x; + } + + if input.scale.y == 1.0 { + snap.y = round(pos.y) - pos.y; + } var min_border_radius = min(input.scale.x, input.scale.y) * 0.5; var border_radius: vec4<f32> = vec4<f32>( @@ -43,13 +52,13 @@ fn solid_vs_main(input: SolidVertexInput) -> SolidVertexOutput { vec4<f32>(scale.x + 1.0, 0.0, 0.0, 0.0), vec4<f32>(0.0, scale.y + 1.0, 0.0, 0.0), vec4<f32>(0.0, 0.0, 1.0, 0.0), - vec4<f32>(pos - vec2<f32>(0.5, 0.5), 0.0, 1.0) + vec4<f32>(pos - vec2<f32>(0.5, 0.5) + snap, 0.0, 1.0) ); out.position = globals.transform * transform * vec4<f32>(vertex_position(input.vertex_index), 0.0, 1.0); out.color = input.color; out.border_color = input.border_color; - out.pos = input.pos * globals.scale; + out.pos = input.pos * globals.scale + snap; out.scale = input.scale * globals.scale; out.border_radius = border_radius * globals.scale; out.border_width = input.border_width * globals.scale; diff --git a/wgpu/src/text.rs b/wgpu/src/text.rs index 05db5f80..bf7eae18 100644 --- a/wgpu/src/text.rs +++ b/wgpu/src/text.rs @@ -585,7 +585,13 @@ fn prepare( ( buffer.as_ref(), - Rectangle::new(raw.position, Size::new(width, height)), + Rectangle::new( + raw.position, + Size::new( + width.unwrap_or(layer_bounds.width), + height.unwrap_or(layer_bounds.height), + ), + ), alignment::Horizontal::Left, alignment::Vertical::Top, raw.color, |