From b9a9576207ddfc7afd89da30b7cfc7ca0d7e335c Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Fri, 6 Jan 2023 23:29:38 +0100 Subject: Remove `iced_glow`, `glyph-brush`, and `wgpu_glyph` dependencies --- wgpu/Cargo.toml | 4 - wgpu/src/backend.rs | 86 ++--------------- wgpu/src/text.rs | 264 ++++------------------------------------------------ 3 files changed, 25 insertions(+), 329 deletions(-) (limited to 'wgpu') diff --git a/wgpu/Cargo.toml b/wgpu/Cargo.toml index f1e22cf6..5badeae6 100644 --- a/wgpu/Cargo.toml +++ b/wgpu/Cargo.toml @@ -23,14 +23,11 @@ dds = ["iced_graphics/dds"] farbfeld = ["iced_graphics/farbfeld"] canvas = ["iced_graphics/canvas"] qr_code = ["iced_graphics/qr_code"] -default_system_font = ["iced_graphics/font-source"] spirv = ["wgpu/spirv"] webgl = ["wgpu/webgl"] [dependencies] wgpu = "0.14" -wgpu_glyph = "0.18" -glyph_brush = "0.7" raw-window-handle = "0.5" log = "0.4" guillotiere = "0.6" @@ -48,7 +45,6 @@ path = "../native" [dependencies.iced_graphics] version = "0.7" path = "../graphics" -features = ["font-fallback", "font-icons"] [dependencies.tracing] version = "0.1.6" diff --git a/wgpu/src/backend.rs b/wgpu/src/backend.rs index 6a299425..e9e23e80 100644 --- a/wgpu/src/backend.rs +++ b/wgpu/src/backend.rs @@ -4,10 +4,8 @@ use crate::triangle; use crate::{Settings, Transformation}; use iced_graphics::backend; -use iced_graphics::font; use iced_graphics::layer::Layer; use iced_graphics::{Primitive, Viewport}; -use iced_native::alignment; use iced_native::{Font, Size}; #[cfg(feature = "tracing")] @@ -173,83 +171,11 @@ impl Backend { } if !layer.text.is_empty() { - for text in layer.text.iter() { - // Target physical coordinates directly to avoid blurry text - let text = wgpu_glyph::Section { - // TODO: We `round` here to avoid rerasterizing text when - // its position changes slightly. This can make text feel a - // bit "jumpy". We may be able to do better once we improve - // our text rendering/caching pipeline. - screen_position: ( - (text.bounds.x * scale_factor).round(), - (text.bounds.y * scale_factor).round(), - ), - // TODO: Fix precision issues with some scale factors. - // - // The `ceil` here can cause some words to render on the - // same line when they should not. - // - // Ideally, `wgpu_glyph` should be able to compute layout - // using logical positions, and then apply the proper - // scaling when rendering. This would ensure that both - // measuring and rendering follow the same layout rules. - bounds: ( - (text.bounds.width * scale_factor).ceil(), - (text.bounds.height * scale_factor).ceil(), - ), - text: vec![wgpu_glyph::Text { - text: text.content, - scale: wgpu_glyph::ab_glyph::PxScale { - x: text.size * scale_factor, - y: text.size * scale_factor, - }, - font_id: self.text_pipeline.find_font(text.font), - extra: wgpu_glyph::Extra { - color: text.color, - z: 0.0, - }, - }], - layout: wgpu_glyph::Layout::default() - .h_align(match text.horizontal_alignment { - alignment::Horizontal::Left => { - wgpu_glyph::HorizontalAlign::Left - } - alignment::Horizontal::Center => { - wgpu_glyph::HorizontalAlign::Center - } - alignment::Horizontal::Right => { - wgpu_glyph::HorizontalAlign::Right - } - }) - .v_align(match text.vertical_alignment { - alignment::Vertical::Top => { - wgpu_glyph::VerticalAlign::Top - } - alignment::Vertical::Center => { - wgpu_glyph::VerticalAlign::Center - } - alignment::Vertical::Bottom => { - wgpu_glyph::VerticalAlign::Bottom - } - }), - }; - - self.text_pipeline.queue(text); + for _text in layer.text.iter() { + // TODO: Queue text sections } - self.text_pipeline.draw_queued( - device, - staging_belt, - encoder, - target, - transformation, - wgpu_glyph::Region { - x: bounds.x, - y: bounds.y, - width: bounds.width, - height: bounds.height, - }, - ); + // TODO: Draw queued } } } @@ -261,9 +187,9 @@ impl iced_graphics::Backend for Backend { } impl backend::Text for Backend { - const ICON_FONT: Font = font::ICONS; - const CHECKMARK_ICON: char = font::CHECKMARK_ICON; - const ARROW_DOWN_ICON: char = font::ARROW_DOWN_ICON; + const ICON_FONT: Font = Font::Default; // TODO + const CHECKMARK_ICON: char = '✓'; + const ARROW_DOWN_ICON: char = '▼'; fn default_size(&self) -> f32 { self.default_text_size diff --git a/wgpu/src/text.rs b/wgpu/src/text.rs index e17b84c1..125e6be0 100644 --- a/wgpu/src/text.rs +++ b/wgpu/src/text.rs @@ -1,265 +1,39 @@ -use crate::Transformation; - -use iced_graphics::font; - -use std::{cell::RefCell, collections::HashMap}; -use wgpu_glyph::ab_glyph; - pub use iced_native::text::Hit; #[derive(Debug)] -pub struct Pipeline { - draw_brush: RefCell>, - draw_font_map: RefCell>, - measure_brush: RefCell>, -} +pub struct Pipeline; impl Pipeline { pub fn new( - device: &wgpu::Device, - format: wgpu::TextureFormat, - default_font: Option<&[u8]>, - multithreading: bool, + _device: &wgpu::Device, + _format: wgpu::TextureFormat, + _default_font: Option<&[u8]>, + _multithreading: bool, ) -> Self { - let default_font = default_font.map(|slice| slice.to_vec()); - - // TODO: Font customization - #[cfg(not(target_os = "ios"))] - #[cfg(feature = "default_system_font")] - let default_font = { - default_font.or_else(|| { - font::Source::new() - .load(&[font::Family::SansSerif, font::Family::Serif]) - .ok() - }) - }; - - let default_font = - default_font.unwrap_or_else(|| font::FALLBACK.to_vec()); - - let font = ab_glyph::FontArc::try_from_vec(default_font) - .unwrap_or_else(|_| { - log::warn!( - "System font failed to load. Falling back to \ - embedded font..." - ); - - ab_glyph::FontArc::try_from_slice(font::FALLBACK) - .expect("Load fallback font") - }); - - let draw_brush_builder = - wgpu_glyph::GlyphBrushBuilder::using_font(font.clone()) - .initial_cache_size((2048, 2048)) - .draw_cache_multithread(multithreading); - - #[cfg(target_arch = "wasm32")] - let draw_brush_builder = draw_brush_builder.draw_cache_align_4x4(true); - - let draw_brush = draw_brush_builder.build(device, format); - - let measure_brush = - glyph_brush::GlyphBrushBuilder::using_font(font).build(); - - Pipeline { - draw_brush: RefCell::new(draw_brush), - draw_font_map: RefCell::new(HashMap::new()), - measure_brush: RefCell::new(measure_brush), - } - } - - pub fn queue(&mut self, section: wgpu_glyph::Section<'_>) { - self.draw_brush.borrow_mut().queue(section); - } - - pub fn draw_queued( - &mut self, - device: &wgpu::Device, - staging_belt: &mut wgpu::util::StagingBelt, - encoder: &mut wgpu::CommandEncoder, - target: &wgpu::TextureView, - transformation: Transformation, - region: wgpu_glyph::Region, - ) { - self.draw_brush - .borrow_mut() - .draw_queued_with_transform_and_scissoring( - device, - staging_belt, - encoder, - target, - transformation.into(), - region, - ) - .expect("Draw text"); + Pipeline } pub fn measure( &self, - content: &str, - size: f32, - font: iced_native::Font, - bounds: iced_native::Size, + _content: &str, + _size: f32, + _font: iced_native::Font, + _bounds: iced_native::Size, ) -> (f32, f32) { - use wgpu_glyph::GlyphCruncher; - - let wgpu_glyph::FontId(font_id) = self.find_font(font); - - let section = wgpu_glyph::Section { - bounds: (bounds.width, bounds.height), - text: vec![wgpu_glyph::Text { - text: content, - scale: size.into(), - font_id: wgpu_glyph::FontId(font_id), - extra: wgpu_glyph::Extra::default(), - }], - ..Default::default() - }; - - if let Some(bounds) = - self.measure_brush.borrow_mut().glyph_bounds(section) - { - (bounds.width().ceil(), bounds.height().ceil()) - } else { - (0.0, 0.0) - } + (0.0, 0.0) } pub fn hit_test( &self, - content: &str, - size: f32, - font: iced_native::Font, - bounds: iced_native::Size, - point: iced_native::Point, - nearest_only: bool, + _content: &str, + _size: f32, + _font: iced_native::Font, + _bounds: iced_native::Size, + _point: iced_native::Point, + _nearest_only: bool, ) -> Option { - use wgpu_glyph::GlyphCruncher; - - let wgpu_glyph::FontId(font_id) = self.find_font(font); - - let section = wgpu_glyph::Section { - bounds: (bounds.width, bounds.height), - text: vec![wgpu_glyph::Text { - text: content, - scale: size.into(), - font_id: wgpu_glyph::FontId(font_id), - extra: wgpu_glyph::Extra::default(), - }], - ..Default::default() - }; - - let mut mb = self.measure_brush.borrow_mut(); - - // The underlying type is FontArc, so clones are cheap. - use wgpu_glyph::ab_glyph::{Font, ScaleFont}; - let font = mb.fonts()[font_id].clone().into_scaled(size); - - // Implements an iterator over the glyph bounding boxes. - let bounds = mb.glyphs(section).map( - |wgpu_glyph::SectionGlyph { - byte_index, glyph, .. - }| { - ( - *byte_index, - iced_native::Rectangle::new( - iced_native::Point::new( - glyph.position.x - font.h_side_bearing(glyph.id), - glyph.position.y - font.ascent(), - ), - iced_native::Size::new( - font.h_advance(glyph.id), - font.ascent() - font.descent(), - ), - ), - ) - }, - ); - - // Implements computation of the character index based on the byte index - // within the input string. - let char_index = |byte_index| { - let mut b_count = 0; - for (i, utf8_len) in - content.chars().map(|c| c.len_utf8()).enumerate() - { - if byte_index < (b_count + utf8_len) { - return i; - } - b_count += utf8_len; - } - - byte_index - }; - - if !nearest_only { - for (idx, bounds) in bounds.clone() { - if bounds.contains(point) { - return Some(Hit::CharOffset(char_index(idx))); - } - } - } - - let nearest = bounds - .map(|(index, bounds)| (index, bounds.center())) - .min_by(|(_, center_a), (_, center_b)| { - center_a - .distance(point) - .partial_cmp(¢er_b.distance(point)) - .unwrap_or(std::cmp::Ordering::Greater) - }); - - nearest.map(|(idx, center)| { - Hit::NearestCharOffset(char_index(idx), point - center) - }) + None } - pub fn trim_measurement_cache(&mut self) { - // TODO: We should probably use a `GlyphCalculator` for this. However, - // it uses a lifetimed `GlyphCalculatorGuard` with side-effects on drop. - // This makes stuff quite inconvenient. A manual method for trimming the - // cache would make our lives easier. - loop { - let action = self - .measure_brush - .borrow_mut() - .process_queued(|_, _| {}, |_| {}); - - match action { - Ok(_) => break, - Err(glyph_brush::BrushError::TextureTooSmall { suggested }) => { - let (width, height) = suggested; - - self.measure_brush - .borrow_mut() - .resize_texture(width, height); - } - } - } - } - - pub fn find_font(&self, font: iced_native::Font) -> wgpu_glyph::FontId { - match font { - iced_native::Font::Default => wgpu_glyph::FontId(0), - iced_native::Font::External { name, bytes } => { - if let Some(font_id) = self.draw_font_map.borrow().get(name) { - return *font_id; - } - - let font = ab_glyph::FontArc::try_from_slice(bytes) - .expect("Load font"); - - let _ = self.measure_brush.borrow_mut().add_font(font.clone()); - - let font_id = self.draw_brush.borrow_mut().add_font(font); - - let _ = self - .draw_font_map - .borrow_mut() - .insert(String::from(name), font_id); - - font_id - } - } - } + pub fn trim_measurement_cache(&mut self) {} } -- cgit From baf51a8fcffc78e4ca20f7dcbba18ca3655f2840 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Tue, 31 Jan 2023 06:29:21 +0100 Subject: Draft `glyphon` implementation of text pipeline for `iced_wgpu` --- wgpu/Cargo.toml | 5 ++ wgpu/src/backend.rs | 19 +++-- wgpu/src/text.rs | 160 +++++++++++++++++++++++++++++++++++++++--- wgpu/src/window/compositor.rs | 3 +- 4 files changed, 171 insertions(+), 16 deletions(-) (limited to 'wgpu') diff --git a/wgpu/Cargo.toml b/wgpu/Cargo.toml index 5badeae6..d29c1129 100644 --- a/wgpu/Cargo.toml +++ b/wgpu/Cargo.toml @@ -33,6 +33,7 @@ log = "0.4" guillotiere = "0.6" futures = "0.3" bitflags = "1.2" +once_cell = "1.0" [dependencies.bytemuck] version = "1.9" @@ -46,6 +47,10 @@ path = "../native" version = "0.7" path = "../graphics" +[dependencies.glyphon] +version = "0.2" +path = "../../glyphon" + [dependencies.tracing] version = "0.1.6" optional = true diff --git a/wgpu/src/backend.rs b/wgpu/src/backend.rs index e9e23e80..77785760 100644 --- a/wgpu/src/backend.rs +++ b/wgpu/src/backend.rs @@ -18,7 +18,7 @@ use crate::image; /// /// [`wgpu`]: https://github.com/gfx-rs/wgpu-rs /// [`iced`]: https://github.com/iced-rs/iced -#[derive(Debug)] +#[allow(missing_debug_implementations)] pub struct Backend { quad_pipeline: quad::Pipeline, text_pipeline: text::Pipeline, @@ -34,11 +34,13 @@ impl Backend { /// Creates a new [`Backend`]. pub fn new( device: &wgpu::Device, + queue: &wgpu::Queue, settings: Settings, format: wgpu::TextureFormat, ) -> Self { let text_pipeline = text::Pipeline::new( device, + queue, format, settings.default_font, settings.text_multithreading, @@ -70,6 +72,7 @@ impl Backend { pub fn present>( &mut self, device: &wgpu::Device, + queue: &wgpu::Queue, staging_belt: &mut wgpu::util::StagingBelt, encoder: &mut wgpu::CommandEncoder, frame: &wgpu::TextureView, @@ -91,6 +94,7 @@ impl Backend { for layer in layers { self.flush( device, + queue, scale_factor, transformation, &layer, @@ -108,6 +112,7 @@ impl Backend { fn flush( &mut self, device: &wgpu::Device, + queue: &wgpu::Queue, scale_factor: f32, transformation: Transformation, layer: &Layer<'_>, @@ -171,11 +176,15 @@ impl Backend { } if !layer.text.is_empty() { - for _text in layer.text.iter() { - // TODO: Queue text sections - } + self.text_pipeline.prepare( + device, + queue, + &layer.text, + scale_factor, + target_size, + ); - // TODO: Draw queued + self.text_pipeline.render(encoder, target); } } } diff --git a/wgpu/src/text.rs b/wgpu/src/text.rs index 125e6be0..ccb627cd 100644 --- a/wgpu/src/text.rs +++ b/wgpu/src/text.rs @@ -1,26 +1,166 @@ pub use iced_native::text::Hit; -#[derive(Debug)] -pub struct Pipeline; +use iced_graphics::layer::Text; +use iced_native::{Font, Size}; + +#[allow(missing_debug_implementations)] +pub struct Pipeline { + renderer: glyphon::TextRenderer, + atlas: glyphon::TextAtlas, + cache: glyphon::SwashCache<'static>, +} + +// TODO: Share with `iced_graphics` +static FONT_SYSTEM: once_cell::sync::Lazy = + once_cell::sync::Lazy::new(glyphon::FontSystem::new); impl Pipeline { pub fn new( - _device: &wgpu::Device, - _format: wgpu::TextureFormat, + device: &wgpu::Device, + queue: &wgpu::Queue, + format: wgpu::TextureFormat, _default_font: Option<&[u8]>, _multithreading: bool, ) -> Self { - Pipeline + Pipeline { + renderer: glyphon::TextRenderer::new(device, queue), + atlas: glyphon::TextAtlas::new(device, queue, format), + cache: glyphon::SwashCache::new(&FONT_SYSTEM), + } + } + + pub fn prepare( + &mut self, + device: &wgpu::Device, + queue: &wgpu::Queue, + sections: &[Text<'_>], + scale_factor: f32, + target_size: Size, + ) { + let buffers: Vec<_> = sections + .iter() + .map(|section| { + let metrics = glyphon::Metrics::new( + (section.size * scale_factor) as i32, + (section.size * 1.2 * scale_factor) as i32, + ); + + let mut buffer = glyphon::Buffer::new(&FONT_SYSTEM, metrics); + + buffer.set_size( + (section.bounds.width * scale_factor).ceil() as i32, + (section.bounds.height * scale_factor).ceil() as i32, + ); + + buffer.set_text( + section.content, + glyphon::Attrs::new() + .color({ + let [r, g, b, a] = section.color.into_rgba8(); + glyphon::Color::rgba(r, g, b, a) + }) + .family(match section.font { + Font::Default => glyphon::Family::SansSerif, + Font::External { name, .. } => { + glyphon::Family::Name(name) + } + }), + ); + + buffer.shape_until_scroll(); + + buffer + }) + .collect(); + + let text_areas: Vec<_> = sections + .iter() + .zip(buffers.iter()) + .map(|(section, buffer)| glyphon::TextArea { + buffer, + left: (section.bounds.x * scale_factor) as i32, + top: (section.bounds.y * scale_factor) as i32, + bounds: glyphon::TextBounds { + left: (section.bounds.x * scale_factor) as i32, + top: (section.bounds.y * scale_factor) as i32, + right: ((section.bounds.x + section.bounds.width) + * scale_factor) as i32, + bottom: ((section.bounds.y + section.bounds.height) + * scale_factor) as i32, + }, + }) + .collect(); + + self.renderer + .prepare( + device, + queue, + &mut self.atlas, + glyphon::Resolution { + width: target_size.width, + height: target_size.height, + }, + &text_areas, + glyphon::Color::rgb(0, 0, 0), + &mut self.cache, + ) + .expect("Prepare text sections"); + } + + pub fn render( + &mut self, + encoder: &mut wgpu::CommandEncoder, + target: &wgpu::TextureView, + ) { + let mut render_pass = + encoder.begin_render_pass(&wgpu::RenderPassDescriptor { + label: None, + color_attachments: &[Some(wgpu::RenderPassColorAttachment { + view: target, + resolve_target: None, + ops: wgpu::Operations { + load: wgpu::LoadOp::Load, + store: true, + }, + })], + depth_stencil_attachment: None, + }); + + self.renderer + .render(&self.atlas, &mut render_pass) + .expect("Render text"); } pub fn measure( &self, - _content: &str, - _size: f32, - _font: iced_native::Font, - _bounds: iced_native::Size, + content: &str, + size: f32, + font: Font, + bounds: Size, ) -> (f32, f32) { - (0.0, 0.0) + let attrs = match font { + Font::Default => glyphon::Attrs::new(), + Font::External { name, .. } => glyphon::Attrs { + family: glyphon::Family::Name(name), + ..glyphon::Attrs::new() + }, + }; + + let mut paragraph = + glyphon::BufferLine::new(content, glyphon::AttrsList::new(attrs)); + + // TODO: Cache layout + let layout = paragraph.layout( + &FONT_SYSTEM, + size as i32, + bounds.width as i32, + glyphon::Wrap::Word, + ); + + ( + layout.iter().fold(0.0, |max, line| line.w.max(max)), + size * 1.2 * layout.len() as f32, + ) } pub fn hit_test( diff --git a/wgpu/src/window/compositor.rs b/wgpu/src/window/compositor.rs index 6d0c36f6..50231f7c 100644 --- a/wgpu/src/window/compositor.rs +++ b/wgpu/src/window/compositor.rs @@ -114,7 +114,7 @@ impl Compositor { /// Creates a new rendering [`Backend`] for this [`Compositor`]. pub fn create_backend(&self) -> Backend { - Backend::new(&self.device, self.settings, self.format) + Backend::new(&self.device, &self.queue, self.settings, self.format) } } @@ -227,6 +227,7 @@ impl iced_graphics::window::Compositor for Compositor { renderer.with_primitives(|backend, primitives| { backend.present( &self.device, + &self.queue, &mut self.staging_belt, &mut encoder, view, -- cgit From ba258f8fbcf72eaabefe193b6fbee6484b44e569 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Wed, 1 Feb 2023 03:24:14 +0100 Subject: Implement support for multiple text layers in `iced_wgpu` --- wgpu/src/backend.rs | 3 +++ wgpu/src/text.rs | 45 ++++++++++++++++++++++++++++++++------------- 2 files changed, 35 insertions(+), 13 deletions(-) (limited to 'wgpu') diff --git a/wgpu/src/backend.rs b/wgpu/src/backend.rs index 77785760..9bf464b3 100644 --- a/wgpu/src/backend.rs +++ b/wgpu/src/backend.rs @@ -105,6 +105,8 @@ impl Backend { ); } + self.text_pipeline.end_frame(); + #[cfg(any(feature = "image", feature = "svg"))] self.image_pipeline.trim_cache(device, encoder); } @@ -180,6 +182,7 @@ impl Backend { device, queue, &layer.text, + layer.bounds, scale_factor, target_size, ); diff --git a/wgpu/src/text.rs b/wgpu/src/text.rs index ccb627cd..2b18299f 100644 --- a/wgpu/src/text.rs +++ b/wgpu/src/text.rs @@ -1,13 +1,14 @@ pub use iced_native::text::Hit; use iced_graphics::layer::Text; -use iced_native::{Font, Size}; +use iced_native::{Font, Rectangle, Size}; #[allow(missing_debug_implementations)] pub struct Pipeline { - renderer: glyphon::TextRenderer, + renderers: Vec, atlas: glyphon::TextAtlas, cache: glyphon::SwashCache<'static>, + layer: usize, } // TODO: Share with `iced_graphics` @@ -23,9 +24,10 @@ impl Pipeline { _multithreading: bool, ) -> Self { Pipeline { - renderer: glyphon::TextRenderer::new(device, queue), + renderers: Vec::new(), atlas: glyphon::TextAtlas::new(device, queue, format), cache: glyphon::SwashCache::new(&FONT_SYSTEM), + layer: 0, } } @@ -34,9 +36,17 @@ impl Pipeline { device: &wgpu::Device, queue: &wgpu::Queue, sections: &[Text<'_>], + bounds: Rectangle, scale_factor: f32, target_size: Size, ) { + if self.renderers.len() <= self.layer { + self.renderers + .push(glyphon::TextRenderer::new(device, queue)); + } + + let renderer = &mut self.renderers[self.layer]; + let buffers: Vec<_> = sections .iter() .map(|section| { @@ -73,6 +83,13 @@ impl Pipeline { }) .collect(); + let bounds = glyphon::TextBounds { + left: (bounds.x * scale_factor) as i32, + top: (bounds.y * scale_factor) as i32, + right: ((bounds.x + bounds.width) * scale_factor) as i32, + bottom: ((bounds.y + bounds.height) * scale_factor) as i32, + }; + let text_areas: Vec<_> = sections .iter() .zip(buffers.iter()) @@ -80,18 +97,11 @@ impl Pipeline { buffer, left: (section.bounds.x * scale_factor) as i32, top: (section.bounds.y * scale_factor) as i32, - bounds: glyphon::TextBounds { - left: (section.bounds.x * scale_factor) as i32, - top: (section.bounds.y * scale_factor) as i32, - right: ((section.bounds.x + section.bounds.width) - * scale_factor) as i32, - bottom: ((section.bounds.y + section.bounds.height) - * scale_factor) as i32, - }, + bounds, }) .collect(); - self.renderer + renderer .prepare( device, queue, @@ -126,9 +136,18 @@ impl Pipeline { depth_stencil_attachment: None, }); - self.renderer + let renderer = &mut self.renderers[self.layer]; + + renderer .render(&self.atlas, &mut render_pass) .expect("Render text"); + + self.layer += 1; + } + + pub fn end_frame(&mut self) { + self.renderers.truncate(self.layer); + self.layer = 0; } pub fn measure( -- cgit From 98a16fd670d501a0072fee13b023be686ff30cf8 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Wed, 1 Feb 2023 03:39:15 +0100 Subject: Implement proper text alignment support in `iced_wgpu` --- wgpu/src/text.rs | 37 ++++++++++++++++++++++++++++++++----- 1 file changed, 32 insertions(+), 5 deletions(-) (limited to 'wgpu') diff --git a/wgpu/src/text.rs b/wgpu/src/text.rs index 2b18299f..19d0782a 100644 --- a/wgpu/src/text.rs +++ b/wgpu/src/text.rs @@ -1,6 +1,7 @@ pub use iced_native::text::Hit; use iced_graphics::layer::Text; +use iced_native::alignment; use iced_native::{Font, Rectangle, Size}; #[allow(missing_debug_implementations)] @@ -93,11 +94,37 @@ impl Pipeline { let text_areas: Vec<_> = sections .iter() .zip(buffers.iter()) - .map(|(section, buffer)| glyphon::TextArea { - buffer, - left: (section.bounds.x * scale_factor) as i32, - top: (section.bounds.y * scale_factor) as i32, - bounds, + .map(|(section, buffer)| { + let x = section.bounds.x * scale_factor; + let y = section.bounds.y * scale_factor; + + let max_width = buffer + .layout_runs() + .fold(0.0f32, |max, run| max.max(run.line_w)); + + let total_height = buffer.visible_lines() as f32 + * section.size + * 1.2 + * scale_factor; + + let left = match section.horizontal_alignment { + alignment::Horizontal::Left => x, + alignment::Horizontal::Center => x - max_width / 2.0, + alignment::Horizontal::Right => x - max_width, + }; + + let top = match section.vertical_alignment { + alignment::Vertical::Top => y, + alignment::Vertical::Center => y - total_height / 2.0, + alignment::Vertical::Bottom => y - total_height, + }; + + glyphon::TextArea { + buffer, + left: left as i32, + top: top as i32, + bounds, + } }) .collect(); -- cgit From bb27982009d89a1cf4874697f7c17ff0af71d93d Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Wed, 1 Feb 2023 04:06:29 +0100 Subject: Convert sRGB to linear RGB for text in `iced_wgpu` --- wgpu/src/text.rs | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) (limited to 'wgpu') diff --git a/wgpu/src/text.rs b/wgpu/src/text.rs index 19d0782a..3f828da1 100644 --- a/wgpu/src/text.rs +++ b/wgpu/src/text.rs @@ -67,8 +67,14 @@ impl Pipeline { section.content, glyphon::Attrs::new() .color({ - let [r, g, b, a] = section.color.into_rgba8(); - glyphon::Color::rgba(r, g, b, a) + let [r, g, b, a] = section.color.into_linear(); + + glyphon::Color::rgba( + (r * 255.0) as u8, + (g * 255.0) as u8, + (b * 255.0) as u8, + (a * 255.0) as u8, + ) }) .family(match section.font { Font::Default => glyphon::Family::SansSerif, -- cgit From 1d0c44fb255f5fbf5a03e7c737e40ab66d39de9e Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Thu, 2 Feb 2023 01:24:27 +0100 Subject: Implement basic text caching in `iced_wgpu` --- wgpu/Cargo.toml | 2 + wgpu/src/text.rs | 201 ++++++++++++++++++++++++++++++++++++++----------------- 2 files changed, 143 insertions(+), 60 deletions(-) (limited to 'wgpu') diff --git a/wgpu/Cargo.toml b/wgpu/Cargo.toml index d29c1129..1a94c6a3 100644 --- a/wgpu/Cargo.toml +++ b/wgpu/Cargo.toml @@ -34,6 +34,8 @@ guillotiere = "0.6" futures = "0.3" bitflags = "1.2" once_cell = "1.0" +rustc-hash = "1.1" +twox-hash = "1.6" [dependencies.bytemuck] version = "1.9" diff --git a/wgpu/src/text.rs b/wgpu/src/text.rs index 3f828da1..a1d621d4 100644 --- a/wgpu/src/text.rs +++ b/wgpu/src/text.rs @@ -2,16 +2,107 @@ pub use iced_native::text::Hit; use iced_graphics::layer::Text; use iced_native::alignment; -use iced_native::{Font, Rectangle, Size}; +use iced_native::{Color, Font, Rectangle, Size}; + +use rustc_hash::{FxHashMap, FxHashSet}; +use std::cell::RefCell; +use std::hash::{BuildHasher, Hash, Hasher}; +use twox_hash::RandomXxHashBuilder64; #[allow(missing_debug_implementations)] pub struct Pipeline { renderers: Vec, atlas: glyphon::TextAtlas, cache: glyphon::SwashCache<'static>, + measurement_cache: RefCell, + render_cache: Cache, layer: usize, } +struct Cache { + entries: FxHashMap>, + recently_used: FxHashSet, + hasher: RandomXxHashBuilder64, +} + +impl Cache { + fn new() -> Self { + Self { + entries: FxHashMap::default(), + recently_used: FxHashSet::default(), + hasher: RandomXxHashBuilder64::default(), + } + } + + fn get(&self, key: &KeyHash) -> Option<&glyphon::Buffer<'static>> { + self.entries.get(key) + } + + fn allocate( + &mut self, + key: Key<'_>, + ) -> (KeyHash, &mut glyphon::Buffer<'static>) { + let hash = { + let mut hasher = self.hasher.build_hasher(); + + key.content.hash(&mut hasher); + (key.size as i32).hash(&mut hasher); + key.font.hash(&mut hasher); + (key.bounds.width as i32).hash(&mut hasher); + (key.bounds.height as i32).hash(&mut hasher); + key.color.into_rgba8().hash(&mut hasher); + + hasher.finish() + }; + + if !self.entries.contains_key(&hash) { + let metrics = + glyphon::Metrics::new(key.size as i32, (key.size * 1.2) as i32); + + let mut buffer = glyphon::Buffer::new(&FONT_SYSTEM, metrics); + + buffer.set_size(key.bounds.width as i32, key.bounds.height as i32); + buffer.set_text( + key.content, + glyphon::Attrs::new().family(to_family(key.font)).color({ + let [r, g, b, a] = key.color.into_linear(); + + glyphon::Color::rgba( + (r * 255.0) as u8, + (g * 255.0) as u8, + (b * 255.0) as u8, + (a * 255.0) as u8, + ) + }), + ); + + let _ = self.entries.insert(hash, buffer); + } + + let _ = self.recently_used.insert(hash); + + (hash, self.entries.get_mut(&hash).unwrap()) + } + + fn trim(&mut self) { + self.entries + .retain(|key, _| self.recently_used.contains(key)); + + self.recently_used.clear(); + } +} + +#[derive(Debug, Clone, Copy)] +struct Key<'a> { + content: &'a str, + size: f32, + font: Font, + bounds: Size, + color: Color, +} + +type KeyHash = u64; + // TODO: Share with `iced_graphics` static FONT_SYSTEM: once_cell::sync::Lazy = once_cell::sync::Lazy::new(glyphon::FontSystem::new); @@ -28,6 +119,8 @@ impl Pipeline { renderers: Vec::new(), atlas: glyphon::TextAtlas::new(device, queue, format), cache: glyphon::SwashCache::new(&FONT_SYSTEM), + measurement_cache: RefCell::new(Cache::new()), + render_cache: Cache::new(), layer: 0, } } @@ -48,48 +141,29 @@ impl Pipeline { let renderer = &mut self.renderers[self.layer]; - let buffers: Vec<_> = sections + let keys: Vec<_> = sections .iter() .map(|section| { - let metrics = glyphon::Metrics::new( - (section.size * scale_factor) as i32, - (section.size * 1.2 * scale_factor) as i32, - ); - - let mut buffer = glyphon::Buffer::new(&FONT_SYSTEM, metrics); - - buffer.set_size( - (section.bounds.width * scale_factor).ceil() as i32, - (section.bounds.height * scale_factor).ceil() as i32, - ); - - buffer.set_text( - section.content, - glyphon::Attrs::new() - .color({ - let [r, g, b, a] = section.color.into_linear(); - - glyphon::Color::rgba( - (r * 255.0) as u8, - (g * 255.0) as u8, - (b * 255.0) as u8, - (a * 255.0) as u8, - ) - }) - .family(match section.font { - Font::Default => glyphon::Family::SansSerif, - Font::External { name, .. } => { - glyphon::Family::Name(name) - } - }), - ); - - buffer.shape_until_scroll(); - - buffer + let (key, _) = self.render_cache.allocate(Key { + content: section.content, + size: section.size * scale_factor, + font: section.font, + bounds: Size { + width: section.bounds.width * scale_factor, + height: section.bounds.height * scale_factor, + }, + color: section.color, + }); + + key }) .collect(); + let buffers: Vec<_> = keys + .iter() + .map(|key| self.render_cache.get(key).expect("Get cached buffer")) + .collect(); + let bounds = glyphon::TextBounds { left: (bounds.x * scale_factor) as i32, top: (bounds.y * scale_factor) as i32, @@ -190,29 +264,27 @@ impl Pipeline { font: Font, bounds: Size, ) -> (f32, f32) { - let attrs = match font { - Font::Default => glyphon::Attrs::new(), - Font::External { name, .. } => glyphon::Attrs { - family: glyphon::Family::Name(name), - ..glyphon::Attrs::new() + let mut measurement_cache = self.measurement_cache.borrow_mut(); + + let (_, paragraph) = measurement_cache.allocate(Key { + content, + size: size, + font, + bounds: Size { + width: bounds.width, + height: f32::INFINITY, }, - }; + color: Color::BLACK, + }); + + let (total_lines, max_width) = paragraph + .layout_runs() + .enumerate() + .fold((0, 0.0), |(_, max), (i, buffer)| { + (i + 1, buffer.line_w.max(max)) + }); - let mut paragraph = - glyphon::BufferLine::new(content, glyphon::AttrsList::new(attrs)); - - // TODO: Cache layout - let layout = paragraph.layout( - &FONT_SYSTEM, - size as i32, - bounds.width as i32, - glyphon::Wrap::Word, - ); - - ( - layout.iter().fold(0.0, |max, line| line.w.max(max)), - size * 1.2 * layout.len() as f32, - ) + (max_width, size * 1.2 * total_lines as f32) } pub fn hit_test( @@ -227,5 +299,14 @@ impl Pipeline { None } - pub fn trim_measurement_cache(&mut self) {} + pub fn trim_measurement_cache(&mut self) { + self.measurement_cache.borrow_mut().trim(); + } +} + +fn to_family(font: Font) -> glyphon::Family<'static> { + match font { + Font::Default => glyphon::Family::SansSerif, + Font::External { name, .. } => glyphon::Family::Name(name), + } } -- cgit From 02fc7e6e89e0a098a2a8cae8490f36d3bdf8126c Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Thu, 2 Feb 2023 01:30:49 +0100 Subject: Trim text `render_cache` after rendering in `iced_wgpu` --- wgpu/src/text.rs | 2 ++ 1 file changed, 2 insertions(+) (limited to 'wgpu') diff --git a/wgpu/src/text.rs b/wgpu/src/text.rs index a1d621d4..cca00435 100644 --- a/wgpu/src/text.rs +++ b/wgpu/src/text.rs @@ -254,6 +254,8 @@ impl Pipeline { pub fn end_frame(&mut self) { self.renderers.truncate(self.layer); + self.render_cache.trim(); + self.layer = 0; } -- cgit From 6b707711469c7298bb363029a0c3d12a7834278c Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Thu, 2 Feb 2023 01:34:25 +0100 Subject: Avoid unnecessary `Vec` allocation in text pipeline --- wgpu/src/text.rs | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) (limited to 'wgpu') diff --git a/wgpu/src/text.rs b/wgpu/src/text.rs index cca00435..1e2bf5c2 100644 --- a/wgpu/src/text.rs +++ b/wgpu/src/text.rs @@ -159,11 +159,6 @@ impl Pipeline { }) .collect(); - let buffers: Vec<_> = keys - .iter() - .map(|key| self.render_cache.get(key).expect("Get cached buffer")) - .collect(); - let bounds = glyphon::TextBounds { left: (bounds.x * scale_factor) as i32, top: (bounds.y * scale_factor) as i32, @@ -173,8 +168,11 @@ impl Pipeline { let text_areas: Vec<_> = sections .iter() - .zip(buffers.iter()) - .map(|(section, buffer)| { + .zip(keys.iter()) + .map(|(section, key)| { + let buffer = + self.render_cache.get(key).expect("Get cached buffer"); + let x = section.bounds.x * scale_factor; let y = section.bounds.y * scale_factor; -- cgit From c8e8b1a7ba47ab13cef2dc7f300fbfcefe1e8a48 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Thu, 2 Feb 2023 01:51:08 +0100 Subject: Use `bounds` directly for `measure` in text pipeline --- wgpu/src/text.rs | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) (limited to 'wgpu') diff --git a/wgpu/src/text.rs b/wgpu/src/text.rs index 1e2bf5c2..026a6d27 100644 --- a/wgpu/src/text.rs +++ b/wgpu/src/text.rs @@ -270,10 +270,7 @@ impl Pipeline { content, size: size, font, - bounds: Size { - width: bounds.width, - height: f32::INFINITY, - }, + bounds, color: Color::BLACK, }); -- cgit From 0a324f0aebdee06c9cce8ef107b44b847dace05b Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Thu, 2 Feb 2023 02:02:41 +0100 Subject: Implement `hit_test` for `text::Pipeline` in `iced_wgpu` --- wgpu/src/text.rs | 24 ++++++++++++++++++------ 1 file changed, 18 insertions(+), 6 deletions(-) (limited to 'wgpu') diff --git a/wgpu/src/text.rs b/wgpu/src/text.rs index 026a6d27..41787136 100644 --- a/wgpu/src/text.rs +++ b/wgpu/src/text.rs @@ -286,14 +286,26 @@ impl Pipeline { pub fn hit_test( &self, - _content: &str, - _size: f32, - _font: iced_native::Font, - _bounds: iced_native::Size, - _point: iced_native::Point, + content: &str, + size: f32, + font: iced_native::Font, + bounds: iced_native::Size, + point: iced_native::Point, _nearest_only: bool, ) -> Option { - None + let mut measurement_cache = self.measurement_cache.borrow_mut(); + + let (_, paragraph) = measurement_cache.allocate(Key { + content, + size: size, + font, + bounds, + color: Color::BLACK, + }); + + let cursor = paragraph.hit(point.x as i32, point.y as i32)?; + + Some(Hit::CharOffset(cursor.index)) } pub fn trim_measurement_cache(&mut self) { -- cgit From a7580e0696a1a0ba76a89db3f78bc99ba3fbb361 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Sat, 4 Feb 2023 06:50:38 +0100 Subject: Count `layout_runs` instead of using `visible_lines` in `text::Pipeline::prepare` --- wgpu/src/text.rs | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) (limited to 'wgpu') diff --git a/wgpu/src/text.rs b/wgpu/src/text.rs index 41787136..fa7fd5df 100644 --- a/wgpu/src/text.rs +++ b/wgpu/src/text.rs @@ -176,14 +176,15 @@ impl Pipeline { let x = section.bounds.x * scale_factor; let y = section.bounds.y * scale_factor; - let max_width = buffer + let (total_lines, max_width) = buffer .layout_runs() - .fold(0.0f32, |max, run| max.max(run.line_w)); + .enumerate() + .fold((0, 0.0), |(_, max), (i, buffer)| { + (i + 1, buffer.line_w.max(max)) + }); - let total_height = buffer.visible_lines() as f32 - * section.size - * 1.2 - * scale_factor; + let total_height = + total_lines as f32 * section.size * 1.2 * scale_factor; let left = match section.horizontal_alignment { alignment::Horizontal::Left => x, -- cgit From b29de28d1f0f608f8029c93d154cfd1b0f8b8cbb Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Sat, 4 Feb 2023 07:33:33 +0100 Subject: Overhaul `Font` type to allow font family selection --- wgpu/fonts/Iced-Icons.ttf | Bin 0 -> 5108 bytes wgpu/src/backend.rs | 21 ++++++++++----------- wgpu/src/lib.rs | 4 +++- wgpu/src/settings.rs | 37 +++++++------------------------------ wgpu/src/text.rs | 10 ++++++---- 5 files changed, 26 insertions(+), 46 deletions(-) create mode 100644 wgpu/fonts/Iced-Icons.ttf (limited to 'wgpu') diff --git a/wgpu/fonts/Iced-Icons.ttf b/wgpu/fonts/Iced-Icons.ttf new file mode 100644 index 00000000..7112f086 Binary files /dev/null and b/wgpu/fonts/Iced-Icons.ttf differ diff --git a/wgpu/src/backend.rs b/wgpu/src/backend.rs index 9bf464b3..874edb96 100644 --- a/wgpu/src/backend.rs +++ b/wgpu/src/backend.rs @@ -27,6 +27,7 @@ pub struct Backend { #[cfg(any(feature = "image", feature = "svg"))] image_pipeline: image::Pipeline, + default_font: Font, default_text_size: f32, } @@ -38,14 +39,7 @@ impl Backend { settings: Settings, format: wgpu::TextureFormat, ) -> Self { - let text_pipeline = text::Pipeline::new( - device, - queue, - format, - settings.default_font, - settings.text_multithreading, - ); - + let text_pipeline = text::Pipeline::new(device, queue, format); let quad_pipeline = quad::Pipeline::new(device, format); let triangle_pipeline = triangle::Pipeline::new(device, format, settings.antialiasing); @@ -61,6 +55,7 @@ impl Backend { #[cfg(any(feature = "image", feature = "svg"))] image_pipeline, + default_font: settings.default_font, default_text_size: settings.default_text_size, } } @@ -199,9 +194,13 @@ impl iced_graphics::Backend for Backend { } impl backend::Text for Backend { - const ICON_FONT: Font = Font::Default; // TODO - const CHECKMARK_ICON: char = '✓'; - const ARROW_DOWN_ICON: char = '▼'; + const ICON_FONT: Font = Font::Name("Iced-Icons"); + const CHECKMARK_ICON: char = '\u{e800}'; + const ARROW_DOWN_ICON: char = '\u{f00c}'; + + fn default_font(&self) -> Font { + self.default_font + } fn default_size(&self) -> f32 { self.default_text_size diff --git a/wgpu/src/lib.rs b/wgpu/src/lib.rs index 1a293681..6d6d3fd6 100644 --- a/wgpu/src/lib.rs +++ b/wgpu/src/lib.rs @@ -47,7 +47,9 @@ mod quad; mod text; mod triangle; -pub use iced_graphics::{Antialiasing, Color, Error, Primitive, Viewport}; +pub use iced_graphics::{ + Antialiasing, Color, Error, Font, Primitive, Viewport, +}; pub use iced_native::Theme; pub use wgpu; diff --git a/wgpu/src/settings.rs b/wgpu/src/settings.rs index 5ef79499..bd9cf473 100644 --- a/wgpu/src/settings.rs +++ b/wgpu/src/settings.rs @@ -1,12 +1,12 @@ //! Configure a renderer. -use std::fmt; - pub use crate::Antialiasing; +use crate::Font; + /// The settings of a [`Backend`]. /// /// [`Backend`]: crate::Backend -#[derive(Clone, Copy, PartialEq)] +#[derive(Debug, Clone, Copy, PartialEq)] pub struct Settings { /// The present mode of the [`Backend`]. /// @@ -16,42 +16,20 @@ pub struct Settings { /// The internal graphics backend to use. pub internal_backend: wgpu::Backends, - /// The bytes of the font that will be used by default. - /// - /// If `None` is provided, a default system font will be chosen. - pub default_font: Option<&'static [u8]>, + /// The default [`Font`] to use. + pub default_font: Font, /// The default size of text. /// /// By default, it will be set to `16.0`. pub default_text_size: f32, - /// If enabled, spread text workload in multiple threads when multiple cores - /// are available. - /// - /// By default, it is disabled. - pub text_multithreading: bool, - /// The antialiasing strategy that will be used for triangle primitives. /// /// By default, it is `None`. pub antialiasing: Option, } -impl fmt::Debug for Settings { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.debug_struct("Settings") - .field("present_mode", &self.present_mode) - .field("internal_backend", &self.internal_backend) - // Instead of printing the font bytes, we simply show a `bool` indicating if using a default font or not. - .field("default_font", &self.default_font.is_some()) - .field("default_text_size", &self.default_text_size) - .field("text_multithreading", &self.text_multithreading) - .field("antialiasing", &self.antialiasing) - .finish() - } -} - impl Settings { /// Creates new [`Settings`] using environment configuration. /// @@ -81,9 +59,8 @@ impl Default for Settings { Settings { present_mode: wgpu::PresentMode::AutoVsync, internal_backend: wgpu::Backends::all(), - default_font: None, - default_text_size: 20.0, - text_multithreading: false, + default_font: Font::SansSerif, + default_text_size: 16.0, antialiasing: None, } } diff --git a/wgpu/src/text.rs b/wgpu/src/text.rs index fa7fd5df..083d9d2b 100644 --- a/wgpu/src/text.rs +++ b/wgpu/src/text.rs @@ -112,8 +112,6 @@ impl Pipeline { device: &wgpu::Device, queue: &wgpu::Queue, format: wgpu::TextureFormat, - _default_font: Option<&[u8]>, - _multithreading: bool, ) -> Self { Pipeline { renderers: Vec::new(), @@ -316,7 +314,11 @@ impl Pipeline { fn to_family(font: Font) -> glyphon::Family<'static> { match font { - Font::Default => glyphon::Family::SansSerif, - Font::External { name, .. } => glyphon::Family::Name(name), + Font::Name(name) => glyphon::Family::Name(name), + Font::SansSerif => glyphon::Family::SansSerif, + Font::Serif => glyphon::Family::Serif, + Font::Cursive => glyphon::Family::Cursive, + Font::Fantasy => glyphon::Family::Fantasy, + Font::Monospace => glyphon::Family::Monospace, } } -- cgit From 238154af4ac8dda7f12dd90aa7be106e933bcb30 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Sat, 4 Feb 2023 11:12:15 +0100 Subject: Implement `font::load` command in `iced_native` --- wgpu/Cargo.toml | 1 + wgpu/src/backend.rs | 6 + wgpu/src/text.rs | 470 +++++++++++++++++++++++++++++----------------------- 3 files changed, 273 insertions(+), 204 deletions(-) (limited to 'wgpu') diff --git a/wgpu/Cargo.toml b/wgpu/Cargo.toml index 1a94c6a3..dffbbab0 100644 --- a/wgpu/Cargo.toml +++ b/wgpu/Cargo.toml @@ -36,6 +36,7 @@ bitflags = "1.2" once_cell = "1.0" rustc-hash = "1.1" twox-hash = "1.6" +ouroboros = "0.15" [dependencies.bytemuck] version = "1.9" diff --git a/wgpu/src/backend.rs b/wgpu/src/backend.rs index 874edb96..5a275c8a 100644 --- a/wgpu/src/backend.rs +++ b/wgpu/src/backend.rs @@ -14,6 +14,8 @@ use tracing::info_span; #[cfg(any(feature = "image", feature = "svg"))] use crate::image; +use std::borrow::Cow; + /// A [`wgpu`] graphics backend for [`iced`]. /// /// [`wgpu`]: https://github.com/gfx-rs/wgpu-rs @@ -234,6 +236,10 @@ impl backend::Text for Backend { nearest_only, ) } + + fn load_font(&mut self, font: Cow<'static, [u8]>) { + self.text_pipeline.load_font(font); + } } #[cfg(feature = "image")] diff --git a/wgpu/src/text.rs b/wgpu/src/text.rs index 083d9d2b..cdfcd576 100644 --- a/wgpu/src/text.rs +++ b/wgpu/src/text.rs @@ -5,108 +5,37 @@ use iced_native::alignment; use iced_native::{Color, Font, Rectangle, Size}; use rustc_hash::{FxHashMap, FxHashSet}; +use std::borrow::Cow; use std::cell::RefCell; use std::hash::{BuildHasher, Hash, Hasher}; +use std::sync::Arc; use twox_hash::RandomXxHashBuilder64; #[allow(missing_debug_implementations)] pub struct Pipeline { + system: Option, renderers: Vec, atlas: glyphon::TextAtlas, - cache: glyphon::SwashCache<'static>, - measurement_cache: RefCell, - render_cache: Cache, layer: usize, } -struct Cache { - entries: FxHashMap>, - recently_used: FxHashSet, - hasher: RandomXxHashBuilder64, -} - -impl Cache { - fn new() -> Self { - Self { - entries: FxHashMap::default(), - recently_used: FxHashSet::default(), - hasher: RandomXxHashBuilder64::default(), - } - } - - fn get(&self, key: &KeyHash) -> Option<&glyphon::Buffer<'static>> { - self.entries.get(key) - } - - fn allocate( - &mut self, - key: Key<'_>, - ) -> (KeyHash, &mut glyphon::Buffer<'static>) { - let hash = { - let mut hasher = self.hasher.build_hasher(); - - key.content.hash(&mut hasher); - (key.size as i32).hash(&mut hasher); - key.font.hash(&mut hasher); - (key.bounds.width as i32).hash(&mut hasher); - (key.bounds.height as i32).hash(&mut hasher); - key.color.into_rgba8().hash(&mut hasher); - - hasher.finish() - }; +#[ouroboros::self_referencing] +struct System { + fonts: glyphon::FontSystem, - if !self.entries.contains_key(&hash) { - let metrics = - glyphon::Metrics::new(key.size as i32, (key.size * 1.2) as i32); + #[borrows(fonts)] + #[not_covariant] + cache: glyphon::SwashCache<'this>, - let mut buffer = glyphon::Buffer::new(&FONT_SYSTEM, metrics); + #[borrows(fonts)] + #[not_covariant] + measurement_cache: RefCell>, - buffer.set_size(key.bounds.width as i32, key.bounds.height as i32); - buffer.set_text( - key.content, - glyphon::Attrs::new().family(to_family(key.font)).color({ - let [r, g, b, a] = key.color.into_linear(); - - glyphon::Color::rgba( - (r * 255.0) as u8, - (g * 255.0) as u8, - (b * 255.0) as u8, - (a * 255.0) as u8, - ) - }), - ); - - let _ = self.entries.insert(hash, buffer); - } - - let _ = self.recently_used.insert(hash); - - (hash, self.entries.get_mut(&hash).unwrap()) - } - - fn trim(&mut self) { - self.entries - .retain(|key, _| self.recently_used.contains(key)); - - self.recently_used.clear(); - } + #[borrows(fonts)] + #[not_covariant] + render_cache: Cache<'this>, } -#[derive(Debug, Clone, Copy)] -struct Key<'a> { - content: &'a str, - size: f32, - font: Font, - bounds: Size, - color: Color, -} - -type KeyHash = u64; - -// TODO: Share with `iced_graphics` -static FONT_SYSTEM: once_cell::sync::Lazy = - once_cell::sync::Lazy::new(glyphon::FontSystem::new); - impl Pipeline { pub fn new( device: &wgpu::Device, @@ -114,15 +43,41 @@ impl Pipeline { format: wgpu::TextureFormat, ) -> Self { Pipeline { + system: Some( + SystemBuilder { + fonts: glyphon::FontSystem::new(), + cache_builder: |fonts| glyphon::SwashCache::new(fonts), + measurement_cache_builder: |_| RefCell::new(Cache::new()), + render_cache_builder: |_| Cache::new(), + } + .build(), + ), renderers: Vec::new(), atlas: glyphon::TextAtlas::new(device, queue, format), - cache: glyphon::SwashCache::new(&FONT_SYSTEM), - measurement_cache: RefCell::new(Cache::new()), - render_cache: Cache::new(), layer: 0, } } + pub fn load_font(&mut self, bytes: Cow<'static, [u8]>) { + let heads = self.system.take().unwrap().into_heads(); + + let (locale, mut db) = heads.fonts.into_locale_and_db(); + + db.load_font_source(glyphon::fontdb::Source::Binary(Arc::new( + bytes.to_owned(), + ))); + + self.system = Some( + SystemBuilder { + fonts: glyphon::FontSystem::new_with_locale_and_db(locale, db), + cache_builder: |fonts| glyphon::SwashCache::new(fonts), + measurement_cache_builder: |_| RefCell::new(Cache::new()), + render_cache_builder: |_| Cache::new(), + } + .build(), + ); + } + pub fn prepare( &mut self, device: &wgpu::Device, @@ -132,93 +87,100 @@ impl Pipeline { scale_factor: f32, target_size: Size, ) { - if self.renderers.len() <= self.layer { - self.renderers - .push(glyphon::TextRenderer::new(device, queue)); - } - - let renderer = &mut self.renderers[self.layer]; - - let keys: Vec<_> = sections - .iter() - .map(|section| { - let (key, _) = self.render_cache.allocate(Key { - content: section.content, - size: section.size * scale_factor, - font: section.font, - bounds: Size { - width: section.bounds.width * scale_factor, - height: section.bounds.height * scale_factor, + self.system.as_mut().unwrap().with_mut(|fields| { + if self.renderers.len() <= self.layer { + self.renderers + .push(glyphon::TextRenderer::new(device, queue)); + } + + let renderer = &mut self.renderers[self.layer]; + + let keys: Vec<_> = sections + .iter() + .map(|section| { + let (key, _) = fields.render_cache.allocate( + fields.fonts, + Key { + content: section.content, + size: section.size * scale_factor, + font: section.font, + bounds: Size { + width: section.bounds.width * scale_factor, + height: section.bounds.height * scale_factor, + }, + color: section.color, + }, + ); + + key + }) + .collect(); + + let bounds = glyphon::TextBounds { + left: (bounds.x * scale_factor) as i32, + top: (bounds.y * scale_factor) as i32, + right: ((bounds.x + bounds.width) * scale_factor) as i32, + bottom: ((bounds.y + bounds.height) * scale_factor) as i32, + }; + + let text_areas: Vec<_> = sections + .iter() + .zip(keys.iter()) + .map(|(section, key)| { + let buffer = fields + .render_cache + .get(key) + .expect("Get cached buffer"); + + let x = section.bounds.x * scale_factor; + let y = section.bounds.y * scale_factor; + + let (total_lines, max_width) = buffer + .layout_runs() + .enumerate() + .fold((0, 0.0), |(_, max), (i, buffer)| { + (i + 1, buffer.line_w.max(max)) + }); + + let total_height = + total_lines as f32 * section.size * 1.2 * scale_factor; + + let left = match section.horizontal_alignment { + alignment::Horizontal::Left => x, + alignment::Horizontal::Center => x - max_width / 2.0, + alignment::Horizontal::Right => x - max_width, + }; + + let top = match section.vertical_alignment { + alignment::Vertical::Top => y, + alignment::Vertical::Center => y - total_height / 2.0, + alignment::Vertical::Bottom => y - total_height, + }; + + glyphon::TextArea { + buffer, + left: left as i32, + top: top as i32, + bounds, + } + }) + .collect(); + + renderer + .prepare( + device, + queue, + &mut self.atlas, + glyphon::Resolution { + width: target_size.width, + height: target_size.height, }, - color: section.color, - }); - - key - }) - .collect(); - - let bounds = glyphon::TextBounds { - left: (bounds.x * scale_factor) as i32, - top: (bounds.y * scale_factor) as i32, - right: ((bounds.x + bounds.width) * scale_factor) as i32, - bottom: ((bounds.y + bounds.height) * scale_factor) as i32, - }; - - let text_areas: Vec<_> = sections - .iter() - .zip(keys.iter()) - .map(|(section, key)| { - let buffer = - self.render_cache.get(key).expect("Get cached buffer"); - - let x = section.bounds.x * scale_factor; - let y = section.bounds.y * scale_factor; - - let (total_lines, max_width) = buffer - .layout_runs() - .enumerate() - .fold((0, 0.0), |(_, max), (i, buffer)| { - (i + 1, buffer.line_w.max(max)) - }); - - let total_height = - total_lines as f32 * section.size * 1.2 * scale_factor; - - let left = match section.horizontal_alignment { - alignment::Horizontal::Left => x, - alignment::Horizontal::Center => x - max_width / 2.0, - alignment::Horizontal::Right => x - max_width, - }; - - let top = match section.vertical_alignment { - alignment::Vertical::Top => y, - alignment::Vertical::Center => y - total_height / 2.0, - alignment::Vertical::Bottom => y - total_height, - }; - - glyphon::TextArea { - buffer, - left: left as i32, - top: top as i32, - bounds, - } - }) - .collect(); - - renderer - .prepare( - device, - queue, - &mut self.atlas, - glyphon::Resolution { - width: target_size.width, - height: target_size.height, - }, - &text_areas, - glyphon::Color::rgb(0, 0, 0), - &mut self.cache, - ) - .expect("Prepare text sections"); + &text_areas, + glyphon::Color::rgb(0, 0, 0), + fields.cache, + ) + .expect("Prepare text sections"); + }); } pub fn render( @@ -251,7 +213,10 @@ impl Pipeline { pub fn end_frame(&mut self) { self.renderers.truncate(self.layer); - self.render_cache.trim(); + self.system + .as_mut() + .unwrap() + .with_render_cache_mut(|cache| cache.trim()); self.layer = 0; } @@ -263,24 +228,29 @@ impl Pipeline { font: Font, bounds: Size, ) -> (f32, f32) { - let mut measurement_cache = self.measurement_cache.borrow_mut(); - - let (_, paragraph) = measurement_cache.allocate(Key { - content, - size: size, - font, - bounds, - color: Color::BLACK, - }); + self.system.as_ref().unwrap().with(|fields| { + let mut measurement_cache = fields.measurement_cache.borrow_mut(); + + let (_, paragraph) = measurement_cache.allocate( + fields.fonts, + Key { + content, + size: size, + font, + bounds, + color: Color::BLACK, + }, + ); - let (total_lines, max_width) = paragraph - .layout_runs() - .enumerate() - .fold((0, 0.0), |(_, max), (i, buffer)| { - (i + 1, buffer.line_w.max(max)) - }); + let (total_lines, max_width) = paragraph + .layout_runs() + .enumerate() + .fold((0, 0.0), |(_, max), (i, buffer)| { + (i + 1, buffer.line_w.max(max)) + }); - (max_width, size * 1.2 * total_lines as f32) + (max_width, size * 1.2 * total_lines as f32) + }) } pub fn hit_test( @@ -292,23 +262,31 @@ impl Pipeline { point: iced_native::Point, _nearest_only: bool, ) -> Option { - let mut measurement_cache = self.measurement_cache.borrow_mut(); - - let (_, paragraph) = measurement_cache.allocate(Key { - content, - size: size, - font, - bounds, - color: Color::BLACK, - }); + self.system.as_ref().unwrap().with(|fields| { + let mut measurement_cache = fields.measurement_cache.borrow_mut(); + + let (_, paragraph) = measurement_cache.allocate( + fields.fonts, + Key { + content, + size: size, + font, + bounds, + color: Color::BLACK, + }, + ); - let cursor = paragraph.hit(point.x as i32, point.y as i32)?; + let cursor = paragraph.hit(point.x as i32, point.y as i32)?; - Some(Hit::CharOffset(cursor.index)) + Some(Hit::CharOffset(cursor.index)) + }) } pub fn trim_measurement_cache(&mut self) { - self.measurement_cache.borrow_mut().trim(); + self.system + .as_mut() + .unwrap() + .with_measurement_cache_mut(|cache| cache.borrow_mut().trim()); } } @@ -322,3 +300,87 @@ fn to_family(font: Font) -> glyphon::Family<'static> { Font::Monospace => glyphon::Family::Monospace, } } + +struct Cache<'a> { + entries: FxHashMap>, + recently_used: FxHashSet, + hasher: RandomXxHashBuilder64, +} + +impl<'a> Cache<'a> { + fn new() -> Self { + Self { + entries: FxHashMap::default(), + recently_used: FxHashSet::default(), + hasher: RandomXxHashBuilder64::default(), + } + } + + fn get(&self, key: &KeyHash) -> Option<&glyphon::Buffer<'a>> { + self.entries.get(key) + } + + fn allocate( + &mut self, + fonts: &'a glyphon::FontSystem, + key: Key<'_>, + ) -> (KeyHash, &mut glyphon::Buffer<'a>) { + let hash = { + let mut hasher = self.hasher.build_hasher(); + + key.content.hash(&mut hasher); + (key.size as i32).hash(&mut hasher); + key.font.hash(&mut hasher); + (key.bounds.width as i32).hash(&mut hasher); + (key.bounds.height as i32).hash(&mut hasher); + key.color.into_rgba8().hash(&mut hasher); + + hasher.finish() + }; + + if !self.entries.contains_key(&hash) { + let metrics = + glyphon::Metrics::new(key.size as i32, (key.size * 1.2) as i32); + let mut buffer = glyphon::Buffer::new(&fonts, metrics); + + buffer.set_size(key.bounds.width as i32, key.bounds.height as i32); + buffer.set_text( + key.content, + glyphon::Attrs::new().family(to_family(key.font)).color({ + let [r, g, b, a] = key.color.into_linear(); + + glyphon::Color::rgba( + (r * 255.0) as u8, + (g * 255.0) as u8, + (b * 255.0) as u8, + (a * 255.0) as u8, + ) + }), + ); + + let _ = self.entries.insert(hash, buffer); + } + + let _ = self.recently_used.insert(hash); + + (hash, self.entries.get_mut(&hash).unwrap()) + } + + fn trim(&mut self) { + self.entries + .retain(|key, _| self.recently_used.contains(key)); + + self.recently_used.clear(); + } +} + +#[derive(Debug, Clone, Copy)] +struct Key<'a> { + content: &'a str, + size: f32, + font: Font, + bounds: Size, + color: Color, +} + +type KeyHash = u64; -- cgit From 5a82fc654e2933c4c93dac5393685861feb07b1f Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Sat, 4 Feb 2023 11:21:35 +0100 Subject: Use floating coordinates directly in `text::Pipeline` --- wgpu/Cargo.toml | 3 ++- wgpu/src/text.rs | 13 ++++++------- 2 files changed, 8 insertions(+), 8 deletions(-) (limited to 'wgpu') diff --git a/wgpu/Cargo.toml b/wgpu/Cargo.toml index dffbbab0..280ce29b 100644 --- a/wgpu/Cargo.toml +++ b/wgpu/Cargo.toml @@ -52,7 +52,8 @@ path = "../graphics" [dependencies.glyphon] version = "0.2" -path = "../../glyphon" +git = "https://github.com/hecrj/glyphon.git" +rev = "bffca9b958af11d1bfd0c0d1a281fc8799cc5a5b" [dependencies.tracing] version = "0.1.6" diff --git a/wgpu/src/text.rs b/wgpu/src/text.rs index cdfcd576..2ca3f0d8 100644 --- a/wgpu/src/text.rs +++ b/wgpu/src/text.rs @@ -276,7 +276,7 @@ impl Pipeline { }, ); - let cursor = paragraph.hit(point.x as i32, point.y as i32)?; + let cursor = paragraph.hit(point.x, point.y)?; Some(Hit::CharOffset(cursor.index)) }) @@ -329,21 +329,20 @@ impl<'a> Cache<'a> { let mut hasher = self.hasher.build_hasher(); key.content.hash(&mut hasher); - (key.size as i32).hash(&mut hasher); + key.size.to_bits().hash(&mut hasher); key.font.hash(&mut hasher); - (key.bounds.width as i32).hash(&mut hasher); - (key.bounds.height as i32).hash(&mut hasher); + key.bounds.width.to_bits().hash(&mut hasher); + key.bounds.height.to_bits().hash(&mut hasher); key.color.into_rgba8().hash(&mut hasher); hasher.finish() }; if !self.entries.contains_key(&hash) { - let metrics = - glyphon::Metrics::new(key.size as i32, (key.size * 1.2) as i32); + let metrics = glyphon::Metrics::new(key.size, key.size * 1.2); let mut buffer = glyphon::Buffer::new(&fonts, metrics); - buffer.set_size(key.bounds.width as i32, key.bounds.height as i32); + buffer.set_size(key.bounds.width, key.bounds.height); buffer.set_text( key.content, glyphon::Attrs::new().family(to_family(key.font)).color({ -- cgit From d2825360a75600bb6b4097737c987e2d9e05da6a Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Sat, 4 Feb 2023 11:35:12 +0100 Subject: Load `Iced-Icons.ttf` font in `text::Pipeline::new` --- wgpu/Cargo.toml | 2 +- wgpu/src/backend.rs | 4 ++-- wgpu/src/text.rs | 8 +++++++- 3 files changed, 10 insertions(+), 4 deletions(-) (limited to 'wgpu') diff --git a/wgpu/Cargo.toml b/wgpu/Cargo.toml index 280ce29b..1ab3d93f 100644 --- a/wgpu/Cargo.toml +++ b/wgpu/Cargo.toml @@ -53,7 +53,7 @@ path = "../graphics" [dependencies.glyphon] version = "0.2" git = "https://github.com/hecrj/glyphon.git" -rev = "bffca9b958af11d1bfd0c0d1a281fc8799cc5a5b" +rev = "4b5e5106f05332dc324bae2095468845a61f36b9" [dependencies.tracing] version = "0.1.6" diff --git a/wgpu/src/backend.rs b/wgpu/src/backend.rs index 5a275c8a..f19959bd 100644 --- a/wgpu/src/backend.rs +++ b/wgpu/src/backend.rs @@ -197,8 +197,8 @@ impl iced_graphics::Backend for Backend { impl backend::Text for Backend { const ICON_FONT: Font = Font::Name("Iced-Icons"); - const CHECKMARK_ICON: char = '\u{e800}'; - const ARROW_DOWN_ICON: char = '\u{f00c}'; + const CHECKMARK_ICON: char = '\u{f00c}'; + const ARROW_DOWN_ICON: char = '\u{e800}'; fn default_font(&self) -> Font { self.default_font diff --git a/wgpu/src/text.rs b/wgpu/src/text.rs index 2ca3f0d8..4af57af3 100644 --- a/wgpu/src/text.rs +++ b/wgpu/src/text.rs @@ -45,7 +45,13 @@ impl Pipeline { Pipeline { system: Some( SystemBuilder { - fonts: glyphon::FontSystem::new(), + fonts: glyphon::FontSystem::new_with_fonts( + [glyphon::fontdb::Source::Binary(Arc::new( + include_bytes!("../fonts/Iced-Icons.ttf") + .as_slice(), + ))] + .into_iter(), + ), cache_builder: |fonts| glyphon::SwashCache::new(fonts), measurement_cache_builder: |_| RefCell::new(Cache::new()), render_cache_builder: |_| Cache::new(), -- cgit From 17470bf7d36ee164311020b9d8c79662ac49c166 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Sat, 4 Feb 2023 12:35:10 +0100 Subject: Fix `clippy` lints :tada: --- wgpu/src/text.rs | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) (limited to 'wgpu') diff --git a/wgpu/src/text.rs b/wgpu/src/text.rs index 4af57af3..967596f2 100644 --- a/wgpu/src/text.rs +++ b/wgpu/src/text.rs @@ -7,6 +7,7 @@ use iced_native::{Color, Font, Rectangle, Size}; use rustc_hash::{FxHashMap, FxHashSet}; use std::borrow::Cow; use std::cell::RefCell; +use std::collections::hash_map; use std::hash::{BuildHasher, Hash, Hasher}; use std::sync::Arc; use twox_hash::RandomXxHashBuilder64; @@ -70,7 +71,7 @@ impl Pipeline { let (locale, mut db) = heads.fonts.into_locale_and_db(); db.load_font_source(glyphon::fontdb::Source::Binary(Arc::new( - bytes.to_owned(), + bytes.into_owned(), ))); self.system = Some( @@ -241,7 +242,7 @@ impl Pipeline { fields.fonts, Key { content, - size: size, + size, font, bounds, color: Color::BLACK, @@ -275,7 +276,7 @@ impl Pipeline { fields.fonts, Key { content, - size: size, + size, font, bounds, color: Color::BLACK, @@ -344,9 +345,9 @@ impl<'a> Cache<'a> { hasher.finish() }; - if !self.entries.contains_key(&hash) { + if let hash_map::Entry::Vacant(entry) = self.entries.entry(hash) { let metrics = glyphon::Metrics::new(key.size, key.size * 1.2); - let mut buffer = glyphon::Buffer::new(&fonts, metrics); + let mut buffer = glyphon::Buffer::new(fonts, metrics); buffer.set_size(key.bounds.width, key.bounds.height); buffer.set_text( @@ -363,7 +364,7 @@ impl<'a> Cache<'a> { }), ); - let _ = self.entries.insert(hash, buffer); + let _ = entry.insert(buffer); } let _ = self.recently_used.insert(hash); -- cgit From da4182099db703d59006ca72de4cb4d54c9d7855 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Sat, 4 Feb 2023 12:42:02 +0100 Subject: Disable `std` feature for `twox-hash` to fix Wasm build --- wgpu/Cargo.toml | 9 ++++++++- wgpu/src/text.rs | 11 ++++++++--- 2 files changed, 16 insertions(+), 4 deletions(-) (limited to 'wgpu') diff --git a/wgpu/Cargo.toml b/wgpu/Cargo.toml index 1ab3d93f..0d9fb75b 100644 --- a/wgpu/Cargo.toml +++ b/wgpu/Cargo.toml @@ -35,9 +35,16 @@ futures = "0.3" bitflags = "1.2" once_cell = "1.0" rustc-hash = "1.1" -twox-hash = "1.6" ouroboros = "0.15" +[dependencies.twox-hash] +version = "1.6" +default-features = false + +[target.'cfg(not(target_arch = "wasm32"))'.dependencies.twox-hash] +version = "1.6.1" +features = ["std"] + [dependencies.bytemuck] version = "1.9" features = ["derive"] diff --git a/wgpu/src/text.rs b/wgpu/src/text.rs index 967596f2..6c88acc3 100644 --- a/wgpu/src/text.rs +++ b/wgpu/src/text.rs @@ -10,7 +10,6 @@ use std::cell::RefCell; use std::collections::hash_map; use std::hash::{BuildHasher, Hash, Hasher}; use std::sync::Arc; -use twox_hash::RandomXxHashBuilder64; #[allow(missing_debug_implementations)] pub struct Pipeline { @@ -311,15 +310,21 @@ fn to_family(font: Font) -> glyphon::Family<'static> { struct Cache<'a> { entries: FxHashMap>, recently_used: FxHashSet, - hasher: RandomXxHashBuilder64, + hasher: HashBuilder, } +#[cfg(not(target_arch = "wasm32"))] +type HashBuilder = twox_hash::RandomXxHashBuilder64; + +#[cfg(target_arch = "wasm32")] +type HashBuilder = std::hash::BuildHasherDefault; + impl<'a> Cache<'a> { fn new() -> Self { Self { entries: FxHashMap::default(), recently_used: FxHashSet::default(), - hasher: RandomXxHashBuilder64::default(), + hasher: HashBuilder::default(), } } -- cgit From b4dd9b6d93479f28304ba23fec59816d6fcec564 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Sat, 4 Feb 2023 13:24:11 +0100 Subject: Update `glyphon` fork (sRGB support) --- wgpu/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'wgpu') diff --git a/wgpu/Cargo.toml b/wgpu/Cargo.toml index 0d9fb75b..da8cdf9f 100644 --- a/wgpu/Cargo.toml +++ b/wgpu/Cargo.toml @@ -60,7 +60,7 @@ path = "../graphics" [dependencies.glyphon] version = "0.2" git = "https://github.com/hecrj/glyphon.git" -rev = "4b5e5106f05332dc324bae2095468845a61f36b9" +rev = "304dcaf8443bec7194891fdef3df468a6eaeb7d9" [dependencies.tracing] version = "0.1.6" -- cgit From 6cf4a10906e777645b6bf47faf0e7b8f3c36549e Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Sun, 5 Feb 2023 18:28:46 +0100 Subject: Stop reusing `SwashCache` in `text::Pipeline` `SwashCache` can't be easily trimmed and it's not really getting us anything since `glyphon` already caches using a glyph atlas anyways. --- wgpu/src/text.rs | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) (limited to 'wgpu') diff --git a/wgpu/src/text.rs b/wgpu/src/text.rs index 6c88acc3..59a840ca 100644 --- a/wgpu/src/text.rs +++ b/wgpu/src/text.rs @@ -23,10 +23,6 @@ pub struct Pipeline { struct System { fonts: glyphon::FontSystem, - #[borrows(fonts)] - #[not_covariant] - cache: glyphon::SwashCache<'this>, - #[borrows(fonts)] #[not_covariant] measurement_cache: RefCell>, @@ -52,7 +48,6 @@ impl Pipeline { ))] .into_iter(), ), - cache_builder: |fonts| glyphon::SwashCache::new(fonts), measurement_cache_builder: |_| RefCell::new(Cache::new()), render_cache_builder: |_| Cache::new(), } @@ -76,7 +71,6 @@ impl Pipeline { self.system = Some( SystemBuilder { fonts: glyphon::FontSystem::new_with_locale_and_db(locale, db), - cache_builder: |fonts| glyphon::SwashCache::new(fonts), measurement_cache_builder: |_| RefCell::new(Cache::new()), render_cache_builder: |_| Cache::new(), } @@ -183,7 +177,7 @@ impl Pipeline { }, &text_areas, glyphon::Color::rgb(0, 0, 0), - fields.cache, + &mut glyphon::SwashCache::new(fields.fonts), ) .expect("Prepare text sections"); }); -- cgit From f37b87fbabf09296ad7fea695baded25020d5fbc Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Sun, 5 Feb 2023 18:30:11 +0100 Subject: Avoid allocating `text_areas` in `text::Pipeline` --- wgpu/Cargo.toml | 2 +- wgpu/src/text.rs | 11 ++++------- 2 files changed, 5 insertions(+), 8 deletions(-) (limited to 'wgpu') diff --git a/wgpu/Cargo.toml b/wgpu/Cargo.toml index da8cdf9f..b3fbfc00 100644 --- a/wgpu/Cargo.toml +++ b/wgpu/Cargo.toml @@ -60,7 +60,7 @@ path = "../graphics" [dependencies.glyphon] version = "0.2" git = "https://github.com/hecrj/glyphon.git" -rev = "304dcaf8443bec7194891fdef3df468a6eaeb7d9" +rev = "3c2acb9dea5b9fcb0fa650b3c73b3a3242c62f4a" [dependencies.tracing] version = "0.1.6" diff --git a/wgpu/src/text.rs b/wgpu/src/text.rs index 59a840ca..e8c3e385 100644 --- a/wgpu/src/text.rs +++ b/wgpu/src/text.rs @@ -123,10 +123,8 @@ impl Pipeline { bottom: ((bounds.y + bounds.height) * scale_factor) as i32, }; - let text_areas: Vec<_> = sections - .iter() - .zip(keys.iter()) - .map(|(section, key)| { + let text_areas = + sections.iter().zip(keys.iter()).map(|(section, key)| { let buffer = fields .render_cache .get(key) @@ -163,8 +161,7 @@ impl Pipeline { top: top as i32, bounds, } - }) - .collect(); + }); renderer .prepare( @@ -175,7 +172,7 @@ impl Pipeline { width: target_size.width, height: target_size.height, }, - &text_areas, + text_areas, glyphon::Color::rgb(0, 0, 0), &mut glyphon::SwashCache::new(fields.fonts), ) -- cgit From 15d257a52a994f0110d775eb49ca7e6100b0e1a8 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Sun, 5 Feb 2023 18:30:32 +0100 Subject: Stop truncating the `renderers` in `text::Pipeline` We avoid recreating buffers this way, and the amount of layers should stay relatively low anyways. --- wgpu/src/text.rs | 1 - 1 file changed, 1 deletion(-) (limited to 'wgpu') diff --git a/wgpu/src/text.rs b/wgpu/src/text.rs index e8c3e385..16eea234 100644 --- a/wgpu/src/text.rs +++ b/wgpu/src/text.rs @@ -209,7 +209,6 @@ impl Pipeline { } pub fn end_frame(&mut self) { - self.renderers.truncate(self.layer); self.system .as_mut() .unwrap() -- cgit From dd80772da9ce89230d5a96c96923837f9887befa Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Sun, 5 Feb 2023 18:31:53 +0100 Subject: Set a minimum `height` for `Buffer` of `size * 1.2` This avoids text from misteriously disappearing, even if the user uses a `height` that isn't enough to fit the text. --- wgpu/src/text.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) (limited to 'wgpu') diff --git a/wgpu/src/text.rs b/wgpu/src/text.rs index 16eea234..f1171b5f 100644 --- a/wgpu/src/text.rs +++ b/wgpu/src/text.rs @@ -344,7 +344,10 @@ impl<'a> Cache<'a> { let metrics = glyphon::Metrics::new(key.size, key.size * 1.2); let mut buffer = glyphon::Buffer::new(fonts, metrics); - buffer.set_size(key.bounds.width, key.bounds.height); + buffer.set_size( + key.bounds.width, + key.bounds.height.max(key.size * 1.2), + ); buffer.set_text( key.content, glyphon::Attrs::new().family(to_family(key.font)).color({ -- cgit From 7d4c63d4119c505801daf138be44e65599e748d0 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Sun, 5 Feb 2023 18:33:09 +0100 Subject: Set `Attrs::monospaced` if `Font::Monospace` is selected --- wgpu/src/text.rs | 23 +++++++++++++---------- 1 file changed, 13 insertions(+), 10 deletions(-) (limited to 'wgpu') diff --git a/wgpu/src/text.rs b/wgpu/src/text.rs index f1171b5f..6e7e60d8 100644 --- a/wgpu/src/text.rs +++ b/wgpu/src/text.rs @@ -350,16 +350,19 @@ impl<'a> Cache<'a> { ); buffer.set_text( key.content, - glyphon::Attrs::new().family(to_family(key.font)).color({ - let [r, g, b, a] = key.color.into_linear(); - - glyphon::Color::rgba( - (r * 255.0) as u8, - (g * 255.0) as u8, - (b * 255.0) as u8, - (a * 255.0) as u8, - ) - }), + glyphon::Attrs::new() + .family(to_family(key.font)) + .color({ + let [r, g, b, a] = key.color.into_linear(); + + glyphon::Color::rgba( + (r * 255.0) as u8, + (g * 255.0) as u8, + (b * 255.0) as u8, + (a * 255.0) as u8, + ) + }) + .monospaced(matches!(key.font, Font::Monospace)), ); let _ = entry.insert(buffer); -- cgit From 680ea5dcca37ecf71ab2e5194e907d9414715d22 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Mon, 6 Feb 2023 22:21:27 +0100 Subject: Refactor `quad::Pipeline` to `prepare` and `render` architecture --- wgpu/src/backend.rs | 31 ++++++-- wgpu/src/buffer.rs | 86 ++++++++++++++++++++ wgpu/src/quad.rs | 225 +++++++++++++++++++++++++++------------------------- 3 files changed, 230 insertions(+), 112 deletions(-) (limited to 'wgpu') diff --git a/wgpu/src/backend.rs b/wgpu/src/backend.rs index f19959bd..2c35936c 100644 --- a/wgpu/src/backend.rs +++ b/wgpu/src/backend.rs @@ -88,13 +88,14 @@ impl Backend { let mut layers = Layer::generate(primitives, viewport); layers.push(Layer::overlay(overlay_text, viewport)); - for layer in layers { + for (i, layer) in layers.iter().enumerate() { self.flush( device, queue, scale_factor, transformation, &layer, + i, staging_belt, encoder, frame, @@ -102,6 +103,7 @@ impl Backend { ); } + self.quad_pipeline.end_frame(); self.text_pipeline.end_frame(); #[cfg(any(feature = "image", feature = "svg"))] @@ -115,6 +117,7 @@ impl Backend { scale_factor: f32, transformation: Transformation, layer: &Layer<'_>, + layer_index: usize, staging_belt: &mut wgpu::util::StagingBelt, encoder: &mut wgpu::CommandEncoder, target: &wgpu::TextureView, @@ -127,16 +130,32 @@ impl Backend { } if !layer.quads.is_empty() { - self.quad_pipeline.draw( + self.quad_pipeline.prepare( device, - staging_belt, - encoder, + queue, &layer.quads, transformation, scale_factor, - bounds, - target, ); + + let mut render_pass = + encoder.begin_render_pass(&wgpu::RenderPassDescriptor { + label: Some("iced_wgpu::quad render pass"), + color_attachments: &[Some( + wgpu::RenderPassColorAttachment { + view: target, + resolve_target: None, + ops: wgpu::Operations { + load: wgpu::LoadOp::Load, + store: true, + }, + }, + )], + depth_stencil_attachment: None, + }); + + self.quad_pipeline + .render(layer_index, bounds, &mut render_pass); } if !layer.meshes.is_empty() { diff --git a/wgpu/src/buffer.rs b/wgpu/src/buffer.rs index 7c092d0b..c210dd4e 100644 --- a/wgpu/src/buffer.rs +++ b/wgpu/src/buffer.rs @@ -1,3 +1,89 @@ //! Utilities for buffer operations. pub mod dynamic; pub mod r#static; + +use std::marker::PhantomData; +use std::ops::RangeBounds; + +#[derive(Debug)] +pub struct Buffer { + label: &'static str, + size: u64, + usage: wgpu::BufferUsages, + raw: wgpu::Buffer, + type_: PhantomData, +} + +impl Buffer { + pub fn new( + device: &wgpu::Device, + label: &'static str, + amount: usize, + usage: wgpu::BufferUsages, + ) -> Self { + let size = next_copy_size::(amount); + + let raw = device.create_buffer(&wgpu::BufferDescriptor { + label: Some(label), + size, + usage, + mapped_at_creation: false, + }); + + Self { + label, + size, + usage, + raw, + type_: PhantomData, + } + } + + pub fn resize(&mut self, device: &wgpu::Device, new_count: usize) -> bool { + let new_size = (std::mem::size_of::() * new_count) as u64; + + if self.size < new_size { + self.raw = device.create_buffer(&wgpu::BufferDescriptor { + label: Some(self.label), + size: new_size, + usage: self.usage, + mapped_at_creation: false, + }); + + self.size = new_size; + + true + } else { + false + } + } + + pub fn write( + &self, + queue: &wgpu::Queue, + offset_count: usize, + contents: &[T], + ) { + queue.write_buffer( + &self.raw, + (std::mem::size_of::() * offset_count) as u64, + bytemuck::cast_slice(contents), + ); + } + + pub fn slice( + &self, + bounds: impl RangeBounds, + ) -> wgpu::BufferSlice<'_> { + self.raw.slice(bounds) + } +} + +fn next_copy_size(amount: usize) -> u64 { + let align_mask = wgpu::COPY_BUFFER_ALIGNMENT - 1; + + (((std::mem::size_of::() * amount).next_power_of_two() as u64 + + align_mask) + & !align_mask) + .max(wgpu::COPY_BUFFER_ALIGNMENT) +} diff --git a/wgpu/src/quad.rs b/wgpu/src/quad.rs index 2f5fcc6b..ebbe7a9d 100644 --- a/wgpu/src/quad.rs +++ b/wgpu/src/quad.rs @@ -1,3 +1,4 @@ +use crate::buffer::Buffer; use crate::Transformation; use iced_graphics::layer; use iced_native::Rectangle; @@ -12,11 +13,11 @@ use tracing::info_span; #[derive(Debug)] pub struct Pipeline { pipeline: wgpu::RenderPipeline, - constants: wgpu::BindGroup, - constants_buffer: wgpu::Buffer, + constant_layout: wgpu::BindGroupLayout, vertices: wgpu::Buffer, indices: wgpu::Buffer, - instances: wgpu::Buffer, + layers: Vec, + current_layer: usize, } impl Pipeline { @@ -38,22 +39,6 @@ impl Pipeline { }], }); - let constants_buffer = device.create_buffer(&wgpu::BufferDescriptor { - label: Some("iced_wgpu::quad uniforms buffer"), - size: mem::size_of::() as wgpu::BufferAddress, - usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST, - mapped_at_creation: false, - }); - - let constants = device.create_bind_group(&wgpu::BindGroupDescriptor { - label: Some("iced_wgpu::quad uniforms bind group"), - layout: &constant_layout, - entries: &[wgpu::BindGroupEntry { - binding: 0, - resource: constants_buffer.as_entire_binding(), - }], - }); - let layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor { label: Some("iced_wgpu::quad pipeline layout"), @@ -148,118 +133,146 @@ impl Pipeline { usage: wgpu::BufferUsages::INDEX, }); - let instances = device.create_buffer(&wgpu::BufferDescriptor { - label: Some("iced_wgpu::quad instance buffer"), - size: mem::size_of::() as u64 * MAX_INSTANCES as u64, - usage: wgpu::BufferUsages::VERTEX | wgpu::BufferUsages::COPY_DST, - mapped_at_creation: false, - }); - Pipeline { pipeline, - constants, - constants_buffer, + constant_layout, vertices, indices, - instances, + layers: Vec::new(), + current_layer: 0, } } - pub fn draw( + pub fn prepare( &mut self, device: &wgpu::Device, - staging_belt: &mut wgpu::util::StagingBelt, - encoder: &mut wgpu::CommandEncoder, + queue: &wgpu::Queue, instances: &[layer::Quad], transformation: Transformation, scale: f32, - bounds: Rectangle, - target: &wgpu::TextureView, ) { - #[cfg(feature = "tracing")] - let _ = info_span!("Wgpu::Quad", "DRAW").entered(); + if self.layers.len() <= self.current_layer { + self.layers.push(Layer::new(device, &self.constant_layout)); + } - let uniforms = Uniforms::new(transformation, scale); + let layer = &mut self.layers[self.current_layer]; + layer.prepare(device, queue, instances, transformation, scale); - { - let mut constants_buffer = staging_belt.write_buffer( - encoder, - &self.constants_buffer, - 0, - wgpu::BufferSize::new(mem::size_of::() as u64) - .unwrap(), - device, + self.current_layer += 1; + } + + pub fn render<'a>( + &'a self, + layer: usize, + bounds: Rectangle, + render_pass: &mut wgpu::RenderPass<'a>, + ) { + if let Some(layer) = self.layers.get(layer) { + render_pass.set_pipeline(&self.pipeline); + + render_pass.set_scissor_rect( + bounds.x, + bounds.y, + bounds.width, + bounds.height, + ); + + render_pass.set_index_buffer( + self.indices.slice(..), + wgpu::IndexFormat::Uint16, ); + render_pass.set_vertex_buffer(0, self.vertices.slice(..)); - constants_buffer.copy_from_slice(bytemuck::bytes_of(&uniforms)); + layer.draw(render_pass); } + } - let mut i = 0; - let total = instances.len(); + pub fn end_frame(&mut self) { + self.current_layer = 0; + } +} - while i < total { - let end = (i + MAX_INSTANCES).min(total); - let amount = end - i; +#[derive(Debug)] +struct Layer { + constants: wgpu::BindGroup, + constants_buffer: wgpu::Buffer, + instances: Buffer, + instance_count: usize, +} - let instance_bytes = bytemuck::cast_slice(&instances[i..end]); +impl Layer { + pub fn new( + device: &wgpu::Device, + constant_layout: &wgpu::BindGroupLayout, + ) -> Self { + let constants_buffer = device.create_buffer(&wgpu::BufferDescriptor { + label: Some("iced_wgpu::quad uniforms buffer"), + size: mem::size_of::() as wgpu::BufferAddress, + usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST, + mapped_at_creation: false, + }); - let mut instance_buffer = staging_belt.write_buffer( - encoder, - &self.instances, - 0, - wgpu::BufferSize::new(instance_bytes.len() as u64).unwrap(), - device, - ); + let constants = device.create_bind_group(&wgpu::BindGroupDescriptor { + label: Some("iced_wgpu::quad uniforms bind group"), + layout: &constant_layout, + entries: &[wgpu::BindGroupEntry { + binding: 0, + resource: constants_buffer.as_entire_binding(), + }], + }); - instance_buffer.copy_from_slice(instance_bytes); - - #[cfg(feature = "tracing")] - let _ = info_span!("Wgpu::Quad", "BEGIN_RENDER_PASS").enter(); - - { - let mut render_pass = - encoder.begin_render_pass(&wgpu::RenderPassDescriptor { - label: Some("iced_wgpu::quad render pass"), - color_attachments: &[Some( - wgpu::RenderPassColorAttachment { - view: target, - resolve_target: None, - ops: wgpu::Operations { - load: wgpu::LoadOp::Load, - store: true, - }, - }, - )], - depth_stencil_attachment: None, - }); - - render_pass.set_pipeline(&self.pipeline); - render_pass.set_bind_group(0, &self.constants, &[]); - render_pass.set_index_buffer( - self.indices.slice(..), - wgpu::IndexFormat::Uint16, - ); - render_pass.set_vertex_buffer(0, self.vertices.slice(..)); - render_pass.set_vertex_buffer(1, self.instances.slice(..)); - - render_pass.set_scissor_rect( - bounds.x, - bounds.y, - bounds.width, - // TODO: Address anti-aliasing adjustments properly - bounds.height, - ); - - render_pass.draw_indexed( - 0..QUAD_INDICES.len() as u32, - 0, - 0..amount as u32, - ); - } - - i += MAX_INSTANCES; + let instances = Buffer::new( + device, + "iced_wgpu::quad instance buffer", + MAX_INSTANCES, + wgpu::BufferUsages::VERTEX | wgpu::BufferUsages::COPY_DST, + ); + + Self { + constants, + constants_buffer, + instances, + instance_count: 0, } } + + pub fn prepare( + &mut self, + device: &wgpu::Device, + queue: &wgpu::Queue, + instances: &[layer::Quad], + transformation: Transformation, + scale: f32, + ) { + #[cfg(feature = "tracing")] + let _ = info_span!("Wgpu::Quad", "PREPARE").entered(); + + let uniforms = Uniforms::new(transformation, scale); + + queue.write_buffer( + &self.constants_buffer, + 0, + bytemuck::bytes_of(&uniforms), + ); + + let _ = self.instances.resize(device, instances.len()); + self.instances.write(queue, 0, instances); + self.instance_count = instances.len(); + } + + pub fn draw<'a>(&'a self, render_pass: &mut wgpu::RenderPass<'a>) { + #[cfg(feature = "tracing")] + let _ = info_span!("Wgpu::Quad", "DRAW").entered(); + + render_pass.set_bind_group(0, &self.constants, &[]); + render_pass.set_vertex_buffer(1, self.instances.slice(..)); + + render_pass.draw_indexed( + 0..QUAD_INDICES.len() as u32, + 0, + 0..self.instance_count as u32, + ); + } } #[repr(C)] -- cgit From a970f34cb4968bfe913c1f77b5087f44f29aba8a Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Tue, 7 Feb 2023 18:35:05 +0100 Subject: Apply `ceil` to text bounds when drawing --- wgpu/src/text.rs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) (limited to 'wgpu') diff --git a/wgpu/src/text.rs b/wgpu/src/text.rs index 6e7e60d8..95994a4e 100644 --- a/wgpu/src/text.rs +++ b/wgpu/src/text.rs @@ -105,8 +105,10 @@ impl Pipeline { size: section.size * scale_factor, font: section.font, bounds: Size { - width: section.bounds.width * scale_factor, - height: section.bounds.height * scale_factor, + width: (section.bounds.width * scale_factor) + .ceil(), + height: (section.bounds.height * scale_factor) + .ceil(), }, color: section.color, }, -- cgit From 77b59496b0a138510938aca8367572e08019ddb6 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Tue, 7 Feb 2023 19:26:41 +0100 Subject: Fix rendering order for `quad::Pipeline` --- wgpu/src/backend.rs | 7 ++----- wgpu/src/quad.rs | 22 +++++++++++++--------- 2 files changed, 15 insertions(+), 14 deletions(-) (limited to 'wgpu') diff --git a/wgpu/src/backend.rs b/wgpu/src/backend.rs index 2c35936c..395d28d5 100644 --- a/wgpu/src/backend.rs +++ b/wgpu/src/backend.rs @@ -88,14 +88,13 @@ impl Backend { let mut layers = Layer::generate(primitives, viewport); layers.push(Layer::overlay(overlay_text, viewport)); - for (i, layer) in layers.iter().enumerate() { + for layer in layers { self.flush( device, queue, scale_factor, transformation, &layer, - i, staging_belt, encoder, frame, @@ -117,7 +116,6 @@ impl Backend { scale_factor: f32, transformation: Transformation, layer: &Layer<'_>, - layer_index: usize, staging_belt: &mut wgpu::util::StagingBelt, encoder: &mut wgpu::CommandEncoder, target: &wgpu::TextureView, @@ -154,8 +152,7 @@ impl Backend { depth_stencil_attachment: None, }); - self.quad_pipeline - .render(layer_index, bounds, &mut render_pass); + self.quad_pipeline.render(bounds, &mut render_pass); } if !layer.meshes.is_empty() { diff --git a/wgpu/src/quad.rs b/wgpu/src/quad.rs index ebbe7a9d..e4173b12 100644 --- a/wgpu/src/quad.rs +++ b/wgpu/src/quad.rs @@ -17,7 +17,8 @@ pub struct Pipeline { vertices: wgpu::Buffer, indices: wgpu::Buffer, layers: Vec, - current_layer: usize, + prepare_layer: usize, + render_layer: usize, } impl Pipeline { @@ -139,7 +140,8 @@ impl Pipeline { vertices, indices, layers: Vec::new(), - current_layer: 0, + prepare_layer: 0, + render_layer: 0, } } @@ -151,23 +153,22 @@ impl Pipeline { transformation: Transformation, scale: f32, ) { - if self.layers.len() <= self.current_layer { + if self.layers.len() <= self.prepare_layer { self.layers.push(Layer::new(device, &self.constant_layout)); } - let layer = &mut self.layers[self.current_layer]; + let layer = &mut self.layers[self.prepare_layer]; layer.prepare(device, queue, instances, transformation, scale); - self.current_layer += 1; + self.prepare_layer += 1; } pub fn render<'a>( - &'a self, - layer: usize, + &'a mut self, bounds: Rectangle, render_pass: &mut wgpu::RenderPass<'a>, ) { - if let Some(layer) = self.layers.get(layer) { + if let Some(layer) = self.layers.get(self.render_layer) { render_pass.set_pipeline(&self.pipeline); render_pass.set_scissor_rect( @@ -184,11 +185,14 @@ impl Pipeline { render_pass.set_vertex_buffer(0, self.vertices.slice(..)); layer.draw(render_pass); + + self.render_layer += 1; } } pub fn end_frame(&mut self) { - self.current_layer = 0; + self.prepare_layer = 0; + self.render_layer = 0; } } -- cgit From 34c963f7b39e3f16b55665a978948ead5b869f0f Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Tue, 7 Feb 2023 20:08:26 +0100 Subject: Remove unnecessary borrow in `quad::Pipeline` --- wgpu/src/quad.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'wgpu') diff --git a/wgpu/src/quad.rs b/wgpu/src/quad.rs index e4173b12..94bcec92 100644 --- a/wgpu/src/quad.rs +++ b/wgpu/src/quad.rs @@ -218,7 +218,7 @@ impl Layer { let constants = device.create_bind_group(&wgpu::BindGroupDescriptor { label: Some("iced_wgpu::quad uniforms bind group"), - layout: &constant_layout, + layout: constant_layout, entries: &[wgpu::BindGroupEntry { binding: 0, resource: constants_buffer.as_entire_binding(), -- cgit From 363966ee9e7aa81a3679eaea776d2c867aa6c3b0 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Tue, 7 Feb 2023 22:53:08 +0100 Subject: Refactor `image::Pipeline` into `prepare` and `render` architecture --- wgpu/src/backend.rs | 26 ++++- wgpu/src/image.rs | 259 ++++++++++++++++++++++++++---------------------- wgpu/src/image/atlas.rs | 49 +++++---- wgpu/src/lib.rs | 3 +- 4 files changed, 190 insertions(+), 147 deletions(-) (limited to 'wgpu') diff --git a/wgpu/src/backend.rs b/wgpu/src/backend.rs index 395d28d5..90a511ef 100644 --- a/wgpu/src/backend.rs +++ b/wgpu/src/backend.rs @@ -106,7 +106,7 @@ impl Backend { self.text_pipeline.end_frame(); #[cfg(any(feature = "image", feature = "svg"))] - self.image_pipeline.trim_cache(device, encoder); + self.image_pipeline.end_frame(device, queue, encoder); } fn flush( @@ -177,16 +177,32 @@ impl Backend { let scaled = transformation * Transformation::scale(scale_factor, scale_factor); - self.image_pipeline.draw( + self.image_pipeline.prepare( device, - staging_belt, + queue, encoder, &layer.images, scaled, - bounds, - target, scale_factor, ); + + let mut render_pass = + encoder.begin_render_pass(&wgpu::RenderPassDescriptor { + label: Some("iced_wgpu::image render pass"), + color_attachments: &[Some( + wgpu::RenderPassColorAttachment { + view: target, + resolve_target: None, + ops: wgpu::Operations { + load: wgpu::LoadOp::Load, + store: true, + }, + }, + )], + depth_stencil_attachment: None, + }); + + self.image_pipeline.render(bounds, &mut render_pass); } } diff --git a/wgpu/src/image.rs b/wgpu/src/image.rs index a5e63b17..f6c39f1a 100644 --- a/wgpu/src/image.rs +++ b/wgpu/src/image.rs @@ -6,7 +6,7 @@ use iced_graphics::image::raster; #[cfg(feature = "svg")] use iced_graphics::image::vector; -use crate::Transformation; +use crate::{Buffer, Transformation}; use atlas::Atlas; use iced_graphics::layer; @@ -34,15 +34,108 @@ pub struct Pipeline { vector_cache: RefCell>, pipeline: wgpu::RenderPipeline, - uniforms: wgpu::Buffer, vertices: wgpu::Buffer, indices: wgpu::Buffer, - instances: wgpu::Buffer, - constants: wgpu::BindGroup, + sampler: wgpu::Sampler, texture: wgpu::BindGroup, texture_version: usize, - texture_layout: wgpu::BindGroupLayout, texture_atlas: Atlas, + texture_layout: wgpu::BindGroupLayout, + constant_layout: wgpu::BindGroupLayout, + + layers: Vec, + prepare_layer: usize, + render_layer: usize, +} + +#[derive(Debug)] +struct Layer { + uniforms: wgpu::Buffer, + constants: wgpu::BindGroup, + instances: Buffer, + instance_count: usize, +} + +impl Layer { + fn new( + device: &wgpu::Device, + constant_layout: &wgpu::BindGroupLayout, + sampler: &wgpu::Sampler, + ) -> Self { + let uniforms = device.create_buffer(&wgpu::BufferDescriptor { + label: Some("iced_wgpu::image uniforms buffer"), + size: mem::size_of::() as u64, + usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST, + mapped_at_creation: false, + }); + + let constants = device.create_bind_group(&wgpu::BindGroupDescriptor { + label: Some("iced_wgpu::image constants bind group"), + layout: &constant_layout, + entries: &[ + wgpu::BindGroupEntry { + binding: 0, + resource: wgpu::BindingResource::Buffer( + wgpu::BufferBinding { + buffer: &uniforms, + offset: 0, + size: None, + }, + ), + }, + wgpu::BindGroupEntry { + binding: 1, + resource: wgpu::BindingResource::Sampler(&sampler), + }, + ], + }); + + let instances = Buffer::new( + device, + "iced_wgpu::image instance buffer", + Instance::INITIAL, + wgpu::BufferUsages::VERTEX | wgpu::BufferUsages::COPY_DST, + ); + + Self { + uniforms, + constants, + instances, + instance_count: 0, + } + } + + fn prepare( + &mut self, + device: &wgpu::Device, + queue: &wgpu::Queue, + instances: &[Instance], + transformation: Transformation, + ) { + queue.write_buffer( + &self.uniforms, + 0, + bytemuck::bytes_of(&Uniforms { + transform: transformation.into(), + }), + ); + + let _ = self.instances.resize(device, instances.len()); + self.instances.write(queue, 0, instances); + + self.instance_count = instances.len(); + } + + fn render<'a>(&'a self, render_pass: &mut wgpu::RenderPass<'a>) { + render_pass.set_bind_group(0, &self.constants, &[]); + render_pass.set_vertex_buffer(1, self.instances.slice(..)); + + render_pass.draw_indexed( + 0..QUAD_INDICES.len() as u32, + 0, + 0..self.instance_count as u32, + ); + } } impl Pipeline { @@ -86,35 +179,6 @@ impl Pipeline { ], }); - let uniforms_buffer = device.create_buffer(&wgpu::BufferDescriptor { - label: Some("iced_wgpu::image uniforms buffer"), - size: mem::size_of::() as u64, - usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST, - mapped_at_creation: false, - }); - - let constant_bind_group = - device.create_bind_group(&wgpu::BindGroupDescriptor { - label: Some("iced_wgpu::image constants bind group"), - layout: &constant_layout, - entries: &[ - wgpu::BindGroupEntry { - binding: 0, - resource: wgpu::BindingResource::Buffer( - wgpu::BufferBinding { - buffer: &uniforms_buffer, - offset: 0, - size: None, - }, - ), - }, - wgpu::BindGroupEntry { - binding: 1, - resource: wgpu::BindingResource::Sampler(&sampler), - }, - ], - }); - let texture_layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { label: Some("iced_wgpu::image texture atlas layout"), @@ -225,13 +289,6 @@ impl Pipeline { usage: wgpu::BufferUsages::INDEX, }); - let instances = device.create_buffer(&wgpu::BufferDescriptor { - label: Some("iced_wgpu::image instance buffer"), - size: mem::size_of::() as u64 * Instance::MAX as u64, - usage: wgpu::BufferUsages::VERTEX | wgpu::BufferUsages::COPY_DST, - mapped_at_creation: false, - }); - let texture_atlas = Atlas::new(device); let texture = device.create_bind_group(&wgpu::BindGroupDescriptor { @@ -253,15 +310,18 @@ impl Pipeline { vector_cache: RefCell::new(vector::Cache::default()), pipeline, - uniforms: uniforms_buffer, vertices, indices, - instances, - constants: constant_bind_group, + sampler, texture, texture_version: texture_atlas.layer_count(), - texture_layout, texture_atlas, + texture_layout, + constant_layout, + + layers: Vec::new(), + prepare_layer: 0, + render_layer: 0, } } @@ -281,17 +341,18 @@ impl Pipeline { svg.viewport_dimensions() } - pub fn draw( + pub fn prepare( &mut self, device: &wgpu::Device, - staging_belt: &mut wgpu::util::StagingBelt, + queue: &wgpu::Queue, encoder: &mut wgpu::CommandEncoder, images: &[layer::Image], transformation: Transformation, - bounds: Rectangle, - target: &wgpu::TextureView, _scale: f32, ) { + #[cfg(feature = "tracing")] + let _ = info_span!("Wgpu::Image", "PREPARE").entered(); + #[cfg(feature = "tracing")] let _ = info_span!("Wgpu::Image", "DRAW").entered(); @@ -309,7 +370,7 @@ impl Pipeline { layer::Image::Raster { handle, bounds } => { if let Some(atlas_entry) = raster_cache.upload( handle, - &mut (device, encoder), + &mut (device, queue, encoder), &mut self.texture_atlas, ) { add_instances( @@ -336,7 +397,7 @@ impl Pipeline { *color, size, _scale, - &mut (device, encoder), + &mut (device, queue, encoder), &mut self.texture_atlas, ) { add_instances( @@ -376,68 +437,27 @@ impl Pipeline { self.texture_version = texture_version; } - { - let mut uniforms_buffer = staging_belt.write_buffer( - encoder, - &self.uniforms, - 0, - wgpu::BufferSize::new(mem::size_of::() as u64) - .unwrap(), + if self.layers.len() <= self.prepare_layer { + self.layers.push(Layer::new( device, - ); - - uniforms_buffer.copy_from_slice(bytemuck::bytes_of(&Uniforms { - transform: transformation.into(), - })); + &self.constant_layout, + &self.sampler, + )); } - let mut i = 0; - let total = instances.len(); - - while i < total { - let end = (i + Instance::MAX).min(total); - let amount = end - i; - - let mut instances_buffer = staging_belt.write_buffer( - encoder, - &self.instances, - 0, - wgpu::BufferSize::new( - (amount * std::mem::size_of::()) as u64, - ) - .unwrap(), - device, - ); - - instances_buffer.copy_from_slice(bytemuck::cast_slice( - &instances[i..i + amount], - )); + let layer = &mut self.layers[self.prepare_layer]; + layer.prepare(device, queue, instances, transformation); - let mut render_pass = - encoder.begin_render_pass(&wgpu::RenderPassDescriptor { - label: Some("iced_wgpu::image render pass"), - color_attachments: &[Some( - wgpu::RenderPassColorAttachment { - view: target, - resolve_target: None, - ops: wgpu::Operations { - load: wgpu::LoadOp::Load, - store: true, - }, - }, - )], - depth_stencil_attachment: None, - }); + self.prepare_layer += 1; + } + pub fn render<'a>( + &'a mut self, + bounds: Rectangle, + render_pass: &mut wgpu::RenderPass<'a>, + ) { + if let Some(layer) = self.layers.get(self.render_layer) { render_pass.set_pipeline(&self.pipeline); - render_pass.set_bind_group(0, &self.constants, &[]); - render_pass.set_bind_group(1, &self.texture, &[]); - render_pass.set_index_buffer( - self.indices.slice(..), - wgpu::IndexFormat::Uint16, - ); - render_pass.set_vertex_buffer(0, self.vertices.slice(..)); - render_pass.set_vertex_buffer(1, self.instances.slice(..)); render_pass.set_scissor_rect( bounds.x, @@ -446,30 +466,37 @@ impl Pipeline { bounds.height, ); - render_pass.draw_indexed( - 0..QUAD_INDICES.len() as u32, - 0, - 0..amount as u32, + render_pass.set_bind_group(1, &self.texture, &[]); + render_pass.set_index_buffer( + self.indices.slice(..), + wgpu::IndexFormat::Uint16, ); + render_pass.set_vertex_buffer(0, self.vertices.slice(..)); - i += Instance::MAX; + layer.render(render_pass); + + self.render_layer += 1; } } - pub fn trim_cache( + pub fn end_frame( &mut self, device: &wgpu::Device, + queue: &wgpu::Queue, encoder: &mut wgpu::CommandEncoder, ) { #[cfg(feature = "image")] self.raster_cache .borrow_mut() - .trim(&mut self.texture_atlas, &mut (device, encoder)); + .trim(&mut self.texture_atlas, &mut (device, queue, encoder)); #[cfg(feature = "svg")] self.vector_cache .borrow_mut() - .trim(&mut self.texture_atlas, &mut (device, encoder)); + .trim(&mut self.texture_atlas, &mut (device, queue, encoder)); + + self.prepare_layer = 0; + self.render_layer = 0; } } @@ -507,7 +534,7 @@ struct Instance { } impl Instance { - pub const MAX: usize = 1_000; + pub const INITIAL: usize = 1_000; } #[repr(C)] diff --git a/wgpu/src/image/atlas.rs b/wgpu/src/image/atlas.rs index eafe2f96..7df67abd 100644 --- a/wgpu/src/image/atlas.rs +++ b/wgpu/src/image/atlas.rs @@ -185,13 +185,13 @@ impl Atlas { fn upload_allocation( &mut self, - buffer: &wgpu::Buffer, + data: &[u8], image_width: u32, image_height: u32, padding: u32, offset: usize, allocation: &Allocation, - encoder: &mut wgpu::CommandEncoder, + queue: &wgpu::Queue, ) { let (x, y) = allocation.position(); let Size { width, height } = allocation.size(); @@ -203,15 +203,7 @@ impl Atlas { depth_or_array_layers: 1, }; - encoder.copy_buffer_to_texture( - wgpu::ImageCopyBuffer { - buffer, - layout: wgpu::ImageDataLayout { - offset: offset as u64, - bytes_per_row: NonZeroU32::new(4 * image_width + padding), - rows_per_image: NonZeroU32::new(image_height), - }, - }, + queue.write_texture( wgpu::ImageCopyTexture { texture: &self.texture, mip_level: 0, @@ -222,6 +214,12 @@ impl Atlas { }, aspect: wgpu::TextureAspect::default(), }, + data, + wgpu::ImageDataLayout { + offset: offset as u64, + bytes_per_row: NonZeroU32::new(4 * image_width + padding), + rows_per_image: NonZeroU32::new(image_height), + }, extent, ); } @@ -301,17 +299,19 @@ impl Atlas { impl image::Storage for Atlas { type Entry = Entry; - type State<'a> = (&'a wgpu::Device, &'a mut wgpu::CommandEncoder); + type State<'a> = ( + &'a wgpu::Device, + &'a wgpu::Queue, + &'a mut wgpu::CommandEncoder, + ); fn upload( &mut self, width: u32, height: u32, data: &[u8], - (device, encoder): &mut Self::State<'_>, + (device, queue, encoder): &mut Self::State<'_>, ) -> Option { - use wgpu::util::DeviceExt; - let entry = { let current_size = self.layers.len(); let entry = self.allocate(width, height)?; @@ -344,17 +344,16 @@ impl image::Storage for Atlas { ) } - let buffer = - device.create_buffer_init(&wgpu::util::BufferInitDescriptor { - label: Some("iced_wgpu::image staging buffer"), - contents: &padded_data, - usage: wgpu::BufferUsages::COPY_SRC, - }); - match &entry { Entry::Contiguous(allocation) => { self.upload_allocation( - &buffer, width, height, padding, 0, allocation, encoder, + &padded_data, + width, + height, + padding, + 0, + allocation, + queue, ); } Entry::Fragmented { fragments, .. } => { @@ -363,13 +362,13 @@ impl image::Storage for Atlas { let offset = (y * padded_width as u32 + 4 * x) as usize; self.upload_allocation( - &buffer, + &padded_data, width, height, padding, offset, &fragment.allocation, - encoder, + queue, ); } } diff --git a/wgpu/src/lib.rs b/wgpu/src/lib.rs index 6d6d3fd6..9da40572 100644 --- a/wgpu/src/lib.rs +++ b/wgpu/src/lib.rs @@ -56,7 +56,8 @@ pub use wgpu; pub use backend::Backend; pub use settings::Settings; -pub(crate) use iced_graphics::Transformation; +use crate::buffer::Buffer; +use iced_graphics::Transformation; #[cfg(any(feature = "image", feature = "svg"))] mod image; -- cgit From 23ed352e83dcb8a13acdac1cd4c7e2a9df492ebd Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Tue, 7 Feb 2023 22:53:41 +0100 Subject: Fix needless borrows in `image::Pipeline` --- wgpu/src/image.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'wgpu') diff --git a/wgpu/src/image.rs b/wgpu/src/image.rs index f6c39f1a..5d1ae8d7 100644 --- a/wgpu/src/image.rs +++ b/wgpu/src/image.rs @@ -71,7 +71,7 @@ impl Layer { let constants = device.create_bind_group(&wgpu::BindGroupDescriptor { label: Some("iced_wgpu::image constants bind group"), - layout: &constant_layout, + layout: constant_layout, entries: &[ wgpu::BindGroupEntry { binding: 0, @@ -85,7 +85,7 @@ impl Layer { }, wgpu::BindGroupEntry { binding: 1, - resource: wgpu::BindingResource::Sampler(&sampler), + resource: wgpu::BindingResource::Sampler(sampler), }, ], }); -- cgit From b8c1809ea101cece6943432fd3597f785c39af09 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Tue, 7 Feb 2023 23:55:16 +0100 Subject: Refactor `triangle::Pipeline` into `prepare` and `render` architecture And get rid of the staging belt! :tada: --- wgpu/src/backend.rs | 17 +- wgpu/src/buffer/dynamic.rs | 23 +- wgpu/src/buffer/static.rs | 21 +- wgpu/src/quad.rs | 7 +- wgpu/src/triangle.rs | 561 +++++++++++++++++++++++++----------------- wgpu/src/window/compositor.rs | 11 - 6 files changed, 358 insertions(+), 282 deletions(-) (limited to 'wgpu') diff --git a/wgpu/src/backend.rs b/wgpu/src/backend.rs index 90a511ef..8c4c0daa 100644 --- a/wgpu/src/backend.rs +++ b/wgpu/src/backend.rs @@ -70,7 +70,6 @@ impl Backend { &mut self, device: &wgpu::Device, queue: &wgpu::Queue, - staging_belt: &mut wgpu::util::StagingBelt, encoder: &mut wgpu::CommandEncoder, frame: &wgpu::TextureView, primitives: &[Primitive], @@ -95,7 +94,6 @@ impl Backend { scale_factor, transformation, &layer, - staging_belt, encoder, frame, target_size, @@ -104,6 +102,7 @@ impl Backend { self.quad_pipeline.end_frame(); self.text_pipeline.end_frame(); + self.triangle_pipeline.end_frame(); #[cfg(any(feature = "image", feature = "svg"))] self.image_pipeline.end_frame(device, queue, encoder); @@ -116,7 +115,6 @@ impl Backend { scale_factor: f32, transformation: Transformation, layer: &Layer<'_>, - staging_belt: &mut wgpu::util::StagingBelt, encoder: &mut wgpu::CommandEncoder, target: &wgpu::TextureView, target_size: Size, @@ -159,15 +157,20 @@ impl Backend { let scaled = transformation * Transformation::scale(scale_factor, scale_factor); - self.triangle_pipeline.draw( + self.triangle_pipeline.prepare( + device, + queue, + &layer.meshes, + scaled, + ); + + self.triangle_pipeline.render( device, - staging_belt, encoder, target, target_size, - scaled, - scale_factor, &layer.meshes, + scale_factor, ); } diff --git a/wgpu/src/buffer/dynamic.rs b/wgpu/src/buffer/dynamic.rs index 88289b98..43fc47ac 100644 --- a/wgpu/src/buffer/dynamic.rs +++ b/wgpu/src/buffer/dynamic.rs @@ -112,25 +112,8 @@ impl Buffer { } /// Write the contents of this dynamic buffer to the GPU via staging belt command. - pub fn write( - &mut self, - device: &wgpu::Device, - staging_belt: &mut wgpu::util::StagingBelt, - encoder: &mut wgpu::CommandEncoder, - ) { - let size = self.cpu.get_ref().len(); - - if let Some(buffer_size) = wgpu::BufferSize::new(size as u64) { - let mut buffer = staging_belt.write_buffer( - encoder, - &self.gpu, - 0, - buffer_size, - device, - ); - - buffer.copy_from_slice(self.cpu.get_ref()); - } + pub fn write(&mut self, queue: &wgpu::Queue) { + queue.write_buffer(&self.gpu, 0, self.cpu.get_ref()); } // Gets the aligned offset at the given index from the CPU buffer. @@ -184,7 +167,7 @@ impl Internal { } /// Returns bytearray of aligned CPU buffer. - pub(super) fn get_ref(&self) -> &Vec { + pub(super) fn get_ref(&self) -> &[u8] { match self { Internal::Uniform(buf) => buf.as_ref(), #[cfg(not(target_arch = "wasm32"))] diff --git a/wgpu/src/buffer/static.rs b/wgpu/src/buffer/static.rs index ef87422f..7ae7edd2 100644 --- a/wgpu/src/buffer/static.rs +++ b/wgpu/src/buffer/static.rs @@ -3,7 +3,7 @@ use std::marker::PhantomData; use std::mem; //128 triangles/indices -const DEFAULT_STATIC_BUFFER_COUNT: wgpu::BufferAddress = 128; +const DEFAULT_STATIC_BUFFER_COUNT: wgpu::BufferAddress = 1_000; /// A generic buffer struct useful for items which have no alignment requirements /// (e.g. Vertex, Index buffers) & no dynamic offsets. @@ -71,28 +71,15 @@ impl Buffer { /// Returns the size of the written bytes. pub fn write( &mut self, - device: &wgpu::Device, - staging_belt: &mut wgpu::util::StagingBelt, - encoder: &mut wgpu::CommandEncoder, + queue: &wgpu::Queue, offset: u64, content: &[T], ) -> u64 { let bytes = bytemuck::cast_slice(content); let bytes_size = bytes.len() as u64; - if let Some(buffer_size) = wgpu::BufferSize::new(bytes_size) { - let mut buffer = staging_belt.write_buffer( - encoder, - &self.gpu, - offset, - buffer_size, - device, - ); - - buffer.copy_from_slice(bytes); - - self.offsets.push(offset); - } + queue.write_buffer(&self.gpu, offset, bytes); + self.offsets.push(offset); bytes_size } diff --git a/wgpu/src/quad.rs b/wgpu/src/quad.rs index 94bcec92..c1aa49c4 100644 --- a/wgpu/src/quad.rs +++ b/wgpu/src/quad.rs @@ -1,5 +1,4 @@ -use crate::buffer::Buffer; -use crate::Transformation; +use crate::{Buffer, Transformation}; use iced_graphics::layer; use iced_native::Rectangle; @@ -228,7 +227,7 @@ impl Layer { let instances = Buffer::new( device, "iced_wgpu::quad instance buffer", - MAX_INSTANCES, + INITIAL_INSTANCES, wgpu::BufferUsages::VERTEX | wgpu::BufferUsages::COPY_DST, ); @@ -302,7 +301,7 @@ const QUAD_VERTS: [Vertex; 4] = [ }, ]; -const MAX_INSTANCES: usize = 100_000; +const INITIAL_INSTANCES: usize = 10_000; #[repr(C)] #[derive(Debug, Clone, Copy, Zeroable, Pod)] diff --git a/wgpu/src/triangle.rs b/wgpu/src/triangle.rs index efdd214b..572af1e8 100644 --- a/wgpu/src/triangle.rs +++ b/wgpu/src/triangle.rs @@ -14,50 +14,56 @@ use tracing::info_span; #[derive(Debug)] pub struct Pipeline { blit: Option, - index_buffer: Buffer, - index_strides: Vec, solid: solid::Pipeline, /// Gradients are currently not supported on WASM targets due to their need of storage buffers. #[cfg(not(target_arch = "wasm32"))] gradient: gradient::Pipeline, + + layers: Vec, + prepare_layer: usize, + render_layer: usize, } -impl Pipeline { - pub fn new( +#[derive(Debug)] +struct Layer { + index_buffer: Buffer, + index_strides: Vec, + solid: solid::Layer, + + #[cfg(not(target_arch = "wasm32"))] + gradient: gradient::Layer, +} + +impl Layer { + fn new( device: &wgpu::Device, - format: wgpu::TextureFormat, - antialiasing: Option, - ) -> Pipeline { - Pipeline { - blit: antialiasing.map(|a| msaa::Blit::new(device, format, a)), + solid: &solid::Pipeline, + #[cfg(not(target_arch = "wasm32"))] gradient: &gradient::Pipeline, + ) -> Self { + Self { index_buffer: Buffer::new( device, "iced_wgpu::triangle vertex buffer", wgpu::BufferUsages::INDEX | wgpu::BufferUsages::COPY_DST, ), index_strides: Vec::new(), - solid: solid::Pipeline::new(device, format, antialiasing), + solid: solid::Layer::new(device, &solid.constants_layout), #[cfg(not(target_arch = "wasm32"))] - gradient: gradient::Pipeline::new(device, format, antialiasing), + gradient: gradient::Layer::new(device, &gradient.constants_layout), } } - pub fn draw( + fn prepare( &mut self, device: &wgpu::Device, - staging_belt: &mut wgpu::util::StagingBelt, - encoder: &mut wgpu::CommandEncoder, - target: &wgpu::TextureView, - target_size: Size, - transformation: Transformation, - scale_factor: f32, + queue: &wgpu::Queue, + solid: &solid::Pipeline, + #[cfg(not(target_arch = "wasm32"))] gradient: &gradient::Pipeline, meshes: &[Mesh<'_>], + transformation: Transformation, ) { - #[cfg(feature = "tracing")] - let _ = info_span!("Wgpu::Triangle", "DRAW").entered(); - // Count the total amount of vertices & indices we need to handle let count = mesh::attribute_count_of(meshes); @@ -75,6 +81,7 @@ impl Pipeline { .resize(device, count.gradient_vertices); // Prepare dynamic buffers & data store for writing + self.index_buffer.clear(); self.index_strides.clear(); self.solid.vertices.clear(); self.solid.uniforms.clear(); @@ -99,13 +106,8 @@ impl Pipeline { let transform = transformation * Transformation::translate(origin.x, origin.y); - let new_index_offset = self.index_buffer.write( - device, - staging_belt, - encoder, - index_offset, - indices, - ); + let new_index_offset = + self.index_buffer.write(queue, index_offset, indices); index_offset += new_index_offset; self.index_strides.push(indices.len() as u32); @@ -116,9 +118,7 @@ impl Pipeline { self.solid.uniforms.push(&solid::Uniforms::new(transform)); let written_bytes = self.solid.vertices.write( - device, - staging_belt, - encoder, + queue, solid_vertex_offset, &buffers.vertices, ); @@ -130,9 +130,7 @@ impl Pipeline { buffers, gradient, .. } => { let written_bytes = self.gradient.vertices.write( - device, - staging_belt, - encoder, + queue, gradient_vertex_offset, &buffers.vertices, ); @@ -196,14 +194,14 @@ impl Pipeline { let uniforms_resized = self.solid.uniforms.resize(device); if uniforms_resized { - self.solid.bind_group = solid::Pipeline::bind_group( + self.solid.constants = solid::Layer::bind_group( device, self.solid.uniforms.raw(), - &self.solid.bind_group_layout, + &solid.constants_layout, ) } - self.solid.uniforms.write(device, staging_belt, encoder); + self.solid.uniforms.write(queue); } #[cfg(not(target_arch = "wasm32"))] @@ -218,22 +216,169 @@ impl Pipeline { let storage_resized = self.gradient.storage.resize(device); if uniforms_resized || storage_resized { - self.gradient.bind_group = gradient::Pipeline::bind_group( + self.gradient.constants = gradient::Layer::bind_group( device, self.gradient.uniforms.raw(), self.gradient.storage.raw(), - &self.gradient.bind_group_layout, + &gradient.constants_layout, ); } // Write to GPU - self.gradient.uniforms.write(device, staging_belt, encoder); - self.gradient.storage.write(device, staging_belt, encoder); + self.gradient.uniforms.write(queue); + self.gradient.storage.write(queue); // Cleanup self.gradient.color_stop_offset = 0; self.gradient.color_stops_pending_write.color_stops.clear(); } + } + + fn render<'a>( + &'a mut self, + solid: &'a solid::Pipeline, + #[cfg(not(target_arch = "wasm32"))] gradient: &'a gradient::Pipeline, + meshes: &[Mesh<'_>], + scale_factor: f32, + render_pass: &mut wgpu::RenderPass<'a>, + ) { + let mut num_solids = 0; + #[cfg(not(target_arch = "wasm32"))] + let mut num_gradients = 0; + let mut last_is_solid = None; + + for (index, mesh) in meshes.iter().enumerate() { + let clip_bounds = (mesh.clip_bounds() * scale_factor).snap(); + + render_pass.set_scissor_rect( + clip_bounds.x, + clip_bounds.y, + clip_bounds.width, + clip_bounds.height, + ); + + match mesh { + Mesh::Solid { .. } => { + if !last_is_solid.unwrap_or(false) { + render_pass.set_pipeline(&solid.pipeline); + + last_is_solid = Some(true); + } + + render_pass.set_bind_group( + 0, + &self.solid.constants, + &[self.solid.uniforms.offset_at_index(num_solids)], + ); + + render_pass.set_vertex_buffer( + 0, + self.solid.vertices.slice_from_index(num_solids), + ); + + num_solids += 1; + } + #[cfg(not(target_arch = "wasm32"))] + Mesh::Gradient { .. } => { + if last_is_solid.unwrap_or(true) { + render_pass.set_pipeline(&gradient.pipeline); + + last_is_solid = Some(false); + } + + render_pass.set_bind_group( + 0, + &self.gradient.constants, + &[self + .gradient + .uniforms + .offset_at_index(num_gradients)], + ); + + render_pass.set_vertex_buffer( + 0, + self.gradient.vertices.slice_from_index(num_gradients), + ); + + num_gradients += 1; + } + #[cfg(target_arch = "wasm32")] + Mesh::Gradient { .. } => {} + }; + + render_pass.set_index_buffer( + self.index_buffer.slice_from_index(index), + wgpu::IndexFormat::Uint32, + ); + + render_pass.draw_indexed(0..self.index_strides[index], 0, 0..1); + } + } +} + +impl Pipeline { + pub fn new( + device: &wgpu::Device, + format: wgpu::TextureFormat, + antialiasing: Option, + ) -> Pipeline { + Pipeline { + blit: antialiasing.map(|a| msaa::Blit::new(device, format, a)), + solid: solid::Pipeline::new(device, format, antialiasing), + + #[cfg(not(target_arch = "wasm32"))] + gradient: gradient::Pipeline::new(device, format, antialiasing), + + layers: Vec::new(), + prepare_layer: 0, + render_layer: 0, + } + } + + pub fn prepare( + &mut self, + device: &wgpu::Device, + queue: &wgpu::Queue, + meshes: &[Mesh<'_>], + transformation: Transformation, + ) { + #[cfg(feature = "tracing")] + let _ = info_span!("Wgpu::Triangle", "PREPARE").entered(); + + if self.layers.len() <= self.prepare_layer { + self.layers.push(Layer::new( + device, + &self.solid, + #[cfg(not(target_arch = "wasm32"))] + &self.gradient, + )); + } + + let layer = &mut self.layers[self.prepare_layer]; + layer.prepare( + device, + queue, + &self.solid, + #[cfg(not(target_arch = "wasm32"))] + &self.gradient, + meshes, + transformation, + ); + + self.prepare_layer += 1; + } + + pub fn render( + &mut self, + device: &wgpu::Device, + encoder: &mut wgpu::CommandEncoder, + target: &wgpu::TextureView, + target_size: Size, + meshes: &[Mesh<'_>], + scale_factor: f32, + ) { + #[cfg(feature = "tracing")] + let _ = info_span!("Wgpu::Triangle", "DRAW").entered(); // Configure render pass { @@ -268,87 +413,29 @@ impl Pipeline { depth_stencil_attachment: None, }); - let mut num_solids = 0; - #[cfg(not(target_arch = "wasm32"))] - let mut num_gradients = 0; - let mut last_is_solid = None; - - for (index, mesh) in meshes.iter().enumerate() { - let clip_bounds = (mesh.clip_bounds() * scale_factor).snap(); - - render_pass.set_scissor_rect( - clip_bounds.x, - clip_bounds.y, - clip_bounds.width, - clip_bounds.height, - ); - - match mesh { - Mesh::Solid { .. } => { - if !last_is_solid.unwrap_or(false) { - render_pass.set_pipeline(&self.solid.pipeline); - - last_is_solid = Some(true); - } - - render_pass.set_bind_group( - 0, - &self.solid.bind_group, - &[self.solid.uniforms.offset_at_index(num_solids)], - ); - - render_pass.set_vertex_buffer( - 0, - self.solid.vertices.slice_from_index(num_solids), - ); - - num_solids += 1; - } - #[cfg(not(target_arch = "wasm32"))] - Mesh::Gradient { .. } => { - if last_is_solid.unwrap_or(true) { - render_pass.set_pipeline(&self.gradient.pipeline); - - last_is_solid = Some(false); - } - - render_pass.set_bind_group( - 0, - &self.gradient.bind_group, - &[self - .gradient - .uniforms - .offset_at_index(num_gradients)], - ); - - render_pass.set_vertex_buffer( - 0, - self.gradient - .vertices - .slice_from_index(num_gradients), - ); - - num_gradients += 1; - } - #[cfg(target_arch = "wasm32")] - Mesh::Gradient { .. } => {} - }; - - render_pass.set_index_buffer( - self.index_buffer.slice_from_index(index), - wgpu::IndexFormat::Uint32, - ); + let layer = &mut self.layers[self.render_layer]; - render_pass.draw_indexed(0..self.index_strides[index], 0, 0..1); - } + layer.render( + &self.solid, + #[cfg(not(target_arch = "wasm32"))] + &self.gradient, + meshes, + scale_factor, + &mut render_pass, + ); } - self.index_buffer.clear(); + self.render_layer += 1; if let Some(blit) = &mut self.blit { blit.draw(encoder, target); } } + + pub fn end_frame(&mut self) { + self.prepare_layer = 0; + self.render_layer = 0; + } } fn fragment_target( @@ -390,10 +477,62 @@ mod solid { #[derive(Debug)] pub struct Pipeline { pub pipeline: wgpu::RenderPipeline, + pub constants_layout: wgpu::BindGroupLayout, + } + + #[derive(Debug)] + pub struct Layer { pub vertices: Buffer, pub uniforms: dynamic::Buffer, - pub bind_group_layout: wgpu::BindGroupLayout, - pub bind_group: wgpu::BindGroup, + pub constants: wgpu::BindGroup, + } + + impl Layer { + pub fn new( + device: &wgpu::Device, + constants_layout: &wgpu::BindGroupLayout, + ) -> Self { + let vertices = Buffer::new( + device, + "iced_wgpu::triangle::solid vertex buffer", + wgpu::BufferUsages::VERTEX | wgpu::BufferUsages::COPY_DST, + ); + + let uniforms = dynamic::Buffer::uniform( + device, + "iced_wgpu::triangle::solid uniforms", + ); + + let constants = + Self::bind_group(device, uniforms.raw(), constants_layout); + + Self { + vertices, + uniforms, + constants, + } + } + + pub fn bind_group( + device: &wgpu::Device, + buffer: &wgpu::Buffer, + layout: &wgpu::BindGroupLayout, + ) -> wgpu::BindGroup { + device.create_bind_group(&wgpu::BindGroupDescriptor { + label: Some("iced_wgpu::triangle::solid bind group"), + layout, + entries: &[wgpu::BindGroupEntry { + binding: 0, + resource: wgpu::BindingResource::Buffer( + wgpu::BufferBinding { + buffer, + offset: 0, + size: Some(Uniforms::min_size()), + }, + ), + }], + }) + } } #[derive(Debug, Clone, Copy, ShaderType)] @@ -416,18 +555,7 @@ mod solid { format: wgpu::TextureFormat, antialiasing: Option, ) -> Self { - let vertices = Buffer::new( - device, - "iced_wgpu::triangle::solid vertex buffer", - wgpu::BufferUsages::VERTEX | wgpu::BufferUsages::COPY_DST, - ); - - let uniforms = dynamic::Buffer::uniform( - device, - "iced_wgpu::triangle::solid uniforms", - ); - - let bind_group_layout = device.create_bind_group_layout( + let constants_layout = device.create_bind_group_layout( &wgpu::BindGroupLayoutDescriptor { label: Some("iced_wgpu::triangle::solid bind group layout"), entries: &[wgpu::BindGroupLayoutEntry { @@ -443,13 +571,10 @@ mod solid { }, ); - let bind_group = - Self::bind_group(device, uniforms.raw(), &bind_group_layout); - let layout = device.create_pipeline_layout( &wgpu::PipelineLayoutDescriptor { label: Some("iced_wgpu::triangle::solid pipeline layout"), - bind_group_layouts: &[&bind_group_layout], + bind_group_layouts: &[&constants_layout], push_constant_ranges: &[], }, ); @@ -501,33 +626,9 @@ mod solid { Self { pipeline, - vertices, - uniforms, - bind_group_layout, - bind_group, + constants_layout, } } - - pub fn bind_group( - device: &wgpu::Device, - buffer: &wgpu::Buffer, - layout: &wgpu::BindGroupLayout, - ) -> wgpu::BindGroup { - device.create_bind_group(&wgpu::BindGroupDescriptor { - label: Some("iced_wgpu::triangle::solid bind group"), - layout, - entries: &[wgpu::BindGroupEntry { - binding: 0, - resource: wgpu::BindingResource::Buffer( - wgpu::BufferBinding { - buffer, - offset: 0, - size: Some(Uniforms::min_size()), - }, - ), - }], - }) - } } } @@ -545,15 +646,90 @@ mod gradient { #[derive(Debug)] pub struct Pipeline { pub pipeline: wgpu::RenderPipeline, + pub constants_layout: wgpu::BindGroupLayout, + } + + #[derive(Debug)] + pub struct Layer { pub vertices: Buffer, pub uniforms: dynamic::Buffer, pub storage: dynamic::Buffer, + pub constants: wgpu::BindGroup, pub color_stop_offset: i32, //Need to store these and then write them all at once //or else they will be padded to 256 and cause gaps in the storage buffer pub color_stops_pending_write: Storage, - pub bind_group_layout: wgpu::BindGroupLayout, - pub bind_group: wgpu::BindGroup, + } + + impl Layer { + pub fn new( + device: &wgpu::Device, + constants_layout: &wgpu::BindGroupLayout, + ) -> Self { + let vertices = Buffer::new( + device, + "iced_wgpu::triangle::gradient vertex buffer", + wgpu::BufferUsages::VERTEX | wgpu::BufferUsages::COPY_DST, + ); + + let uniforms = dynamic::Buffer::uniform( + device, + "iced_wgpu::triangle::gradient uniforms", + ); + + // Note: with a WASM target storage buffers are not supported. Will need to use UBOs & static + // sized array (eg like the 32-sized array on OpenGL side right now) to make gradients work + let storage = dynamic::Buffer::storage( + device, + "iced_wgpu::triangle::gradient storage", + ); + + let constants = Self::bind_group( + device, + uniforms.raw(), + storage.raw(), + constants_layout, + ); + + Self { + vertices, + uniforms, + storage, + constants, + color_stop_offset: 0, + color_stops_pending_write: Storage { + color_stops: vec![], + }, + } + } + + pub fn bind_group( + device: &wgpu::Device, + uniform_buffer: &wgpu::Buffer, + storage_buffer: &wgpu::Buffer, + layout: &wgpu::BindGroupLayout, + ) -> wgpu::BindGroup { + device.create_bind_group(&wgpu::BindGroupDescriptor { + label: Some("iced_wgpu::triangle::gradient bind group"), + layout, + entries: &[ + wgpu::BindGroupEntry { + binding: 0, + resource: wgpu::BindingResource::Buffer( + wgpu::BufferBinding { + buffer: uniform_buffer, + offset: 0, + size: Some(Uniforms::min_size()), + }, + ), + }, + wgpu::BindGroupEntry { + binding: 1, + resource: storage_buffer.as_entire_binding(), + }, + ], + }) + } } #[derive(Debug, ShaderType)] @@ -584,25 +760,7 @@ mod gradient { format: wgpu::TextureFormat, antialiasing: Option, ) -> Self { - let vertices = Buffer::new( - device, - "iced_wgpu::triangle::gradient vertex buffer", - wgpu::BufferUsages::VERTEX | wgpu::BufferUsages::COPY_DST, - ); - - let uniforms = dynamic::Buffer::uniform( - device, - "iced_wgpu::triangle::gradient uniforms", - ); - - //Note: with a WASM target storage buffers are not supported. Will need to use UBOs & static - // sized array (eg like the 32-sized array on OpenGL side right now) to make gradients work - let storage = dynamic::Buffer::storage( - device, - "iced_wgpu::triangle::gradient storage", - ); - - let bind_group_layout = device.create_bind_group_layout( + let constants_layout = device.create_bind_group_layout( &wgpu::BindGroupLayoutDescriptor { label: Some( "iced_wgpu::triangle::gradient bind group layout", @@ -634,19 +792,12 @@ mod gradient { }, ); - let bind_group = Pipeline::bind_group( - device, - uniforms.raw(), - storage.raw(), - &bind_group_layout, - ); - let layout = device.create_pipeline_layout( &wgpu::PipelineLayoutDescriptor { label: Some( "iced_wgpu::triangle::gradient pipeline layout", ), - bind_group_layouts: &[&bind_group_layout], + bind_group_layouts: &[&constants_layout], push_constant_ranges: &[], }, ); @@ -694,44 +845,8 @@ mod gradient { Self { pipeline, - vertices, - uniforms, - storage, - color_stop_offset: 0, - color_stops_pending_write: Storage { - color_stops: vec![], - }, - bind_group_layout, - bind_group, + constants_layout, } } - - pub fn bind_group( - device: &wgpu::Device, - uniform_buffer: &wgpu::Buffer, - storage_buffer: &wgpu::Buffer, - layout: &wgpu::BindGroupLayout, - ) -> wgpu::BindGroup { - device.create_bind_group(&wgpu::BindGroupDescriptor { - label: Some("iced_wgpu::triangle::gradient bind group"), - layout, - entries: &[ - wgpu::BindGroupEntry { - binding: 0, - resource: wgpu::BindingResource::Buffer( - wgpu::BufferBinding { - buffer: uniform_buffer, - offset: 0, - size: Some(Uniforms::min_size()), - }, - ), - }, - wgpu::BindGroupEntry { - binding: 1, - resource: storage_buffer.as_entire_binding(), - }, - ], - }) - } } } diff --git a/wgpu/src/window/compositor.rs b/wgpu/src/window/compositor.rs index 50231f7c..6e1acc06 100644 --- a/wgpu/src/window/compositor.rs +++ b/wgpu/src/window/compositor.rs @@ -16,14 +16,11 @@ pub struct Compositor { adapter: wgpu::Adapter, device: wgpu::Device, queue: wgpu::Queue, - staging_belt: wgpu::util::StagingBelt, format: wgpu::TextureFormat, theme: PhantomData, } impl Compositor { - const CHUNK_SIZE: u64 = 10 * 1024; - /// Requests a new [`Compositor`] with the given [`Settings`]. /// /// Returns `None` if no compatible graphics adapter could be found. @@ -98,15 +95,12 @@ impl Compositor { .next() .await?; - let staging_belt = wgpu::util::StagingBelt::new(Self::CHUNK_SIZE); - Some(Compositor { instance, settings, adapter, device, queue, - staging_belt, format, theme: PhantomData, }) @@ -228,7 +222,6 @@ impl iced_graphics::window::Compositor for Compositor { backend.present( &self.device, &self.queue, - &mut self.staging_belt, &mut encoder, view, primitives, @@ -238,13 +231,9 @@ impl iced_graphics::window::Compositor for Compositor { }); // Submit work - self.staging_belt.finish(); let _submission = self.queue.submit(Some(encoder.finish())); frame.present(); - // Recall staging buffers - self.staging_belt.recall(); - Ok(()) } Err(error) => match error { -- cgit From 730d6a07564d014c470e02f233394ec98325d463 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Wed, 8 Feb 2023 00:47:16 +0100 Subject: Reuse a `RenderPass` as much as possible in `iced_wgpu` --- wgpu/Cargo.toml | 5 +- wgpu/src/backend.rs | 261 +++++++++++++++++++++++++++--------------- wgpu/src/image.rs | 10 +- wgpu/src/quad.rs | 10 +- wgpu/src/text.rs | 40 +++---- wgpu/src/triangle.rs | 10 +- wgpu/src/window/compositor.rs | 29 +---- 7 files changed, 195 insertions(+), 170 deletions(-) (limited to 'wgpu') diff --git a/wgpu/Cargo.toml b/wgpu/Cargo.toml index b3fbfc00..7a6b75f0 100644 --- a/wgpu/Cargo.toml +++ b/wgpu/Cargo.toml @@ -59,8 +59,9 @@ path = "../graphics" [dependencies.glyphon] version = "0.2" -git = "https://github.com/hecrj/glyphon.git" -rev = "3c2acb9dea5b9fcb0fa650b3c73b3a3242c62f4a" +# git = "https://github.com/hecrj/glyphon.git" +# rev = "3c2acb9dea5b9fcb0fa650b3c73b3a3242c62f4a" +path = "../../glyphon" [dependencies.tracing] version = "0.1.6" diff --git a/wgpu/src/backend.rs b/wgpu/src/backend.rs index 8c4c0daa..9d1e3d1c 100644 --- a/wgpu/src/backend.rs +++ b/wgpu/src/backend.rs @@ -5,8 +5,7 @@ use crate::{Settings, Transformation}; use iced_graphics::backend; use iced_graphics::layer::Layer; -use iced_graphics::{Primitive, Viewport}; -use iced_native::{Font, Size}; +use iced_graphics::{Color, Font, Primitive, Size, Viewport}; #[cfg(feature = "tracing")] use tracing::info_span; @@ -71,6 +70,7 @@ impl Backend { device: &wgpu::Device, queue: &wgpu::Queue, encoder: &mut wgpu::CommandEncoder, + clear_color: Option, frame: &wgpu::TextureView, primitives: &[Primitive], viewport: &Viewport, @@ -87,18 +87,25 @@ impl Backend { let mut layers = Layer::generate(primitives, viewport); layers.push(Layer::overlay(overlay_text, viewport)); - for layer in layers { - self.flush( - device, - queue, - scale_factor, - transformation, - &layer, - encoder, - frame, - target_size, - ); - } + self.prepare( + device, + queue, + encoder, + scale_factor, + transformation, + target_size, + &layers, + ); + + self.render( + device, + encoder, + frame, + clear_color, + scale_factor, + target_size, + &layers, + ); self.quad_pipeline.end_frame(); self.text_pipeline.end_frame(); @@ -108,90 +115,153 @@ impl Backend { self.image_pipeline.end_frame(device, queue, encoder); } - fn flush( + fn prepare( &mut self, device: &wgpu::Device, queue: &wgpu::Queue, + _encoder: &mut wgpu::CommandEncoder, scale_factor: f32, transformation: Transformation, - layer: &Layer<'_>, - encoder: &mut wgpu::CommandEncoder, - target: &wgpu::TextureView, target_size: Size, + layers: &[Layer<'_>], ) { - let bounds = (layer.bounds * scale_factor).snap(); + for layer in layers { + let bounds = (layer.bounds * scale_factor).snap(); - if bounds.width < 1 || bounds.height < 1 { - return; - } + if bounds.width < 1 || bounds.height < 1 { + return; + } - if !layer.quads.is_empty() { - self.quad_pipeline.prepare( - device, - queue, - &layer.quads, - transformation, - scale_factor, - ); - - let mut render_pass = - encoder.begin_render_pass(&wgpu::RenderPassDescriptor { - label: Some("iced_wgpu::quad render pass"), - color_attachments: &[Some( - wgpu::RenderPassColorAttachment { - view: target, - resolve_target: None, - ops: wgpu::Operations { - load: wgpu::LoadOp::Load, - store: true, - }, - }, - )], - depth_stencil_attachment: None, - }); + if !layer.quads.is_empty() { + self.quad_pipeline.prepare( + device, + queue, + &layer.quads, + transformation, + scale_factor, + ); + } - self.quad_pipeline.render(bounds, &mut render_pass); - } + if !layer.meshes.is_empty() { + let scaled = transformation + * Transformation::scale(scale_factor, scale_factor); + + self.triangle_pipeline.prepare( + device, + queue, + &layer.meshes, + scaled, + ); + } - if !layer.meshes.is_empty() { - let scaled = transformation - * Transformation::scale(scale_factor, scale_factor); - - self.triangle_pipeline.prepare( - device, - queue, - &layer.meshes, - scaled, - ); - - self.triangle_pipeline.render( - device, - encoder, - target, - target_size, - &layer.meshes, - scale_factor, - ); + #[cfg(any(feature = "image", feature = "svg"))] + { + if !layer.images.is_empty() { + let scaled = transformation + * Transformation::scale(scale_factor, scale_factor); + + self.image_pipeline.prepare( + device, + queue, + _encoder, + &layer.images, + scaled, + scale_factor, + ); + } + } + + if !layer.text.is_empty() { + self.text_pipeline.prepare( + device, + queue, + &layer.text, + layer.bounds, + scale_factor, + target_size, + ); + } } + } + fn render( + &mut self, + device: &wgpu::Device, + encoder: &mut wgpu::CommandEncoder, + target: &wgpu::TextureView, + clear_color: Option, + scale_factor: f32, + target_size: Size, + layers: &[Layer<'_>], + ) { + use std::mem::ManuallyDrop; + + let mut quad_layer = 0; + let mut triangle_layer = 0; #[cfg(any(feature = "image", feature = "svg"))] - { - if !layer.images.is_empty() { - let scaled = transformation - * Transformation::scale(scale_factor, scale_factor); + let mut image_layer = 0; + let mut text_layer = 0; + + let mut render_pass = ManuallyDrop::new(encoder.begin_render_pass( + &wgpu::RenderPassDescriptor { + label: Some("iced_wgpu::quad render pass"), + color_attachments: &[Some(wgpu::RenderPassColorAttachment { + view: target, + resolve_target: None, + ops: wgpu::Operations { + load: match clear_color { + Some(background_color) => wgpu::LoadOp::Clear({ + let [r, g, b, a] = + background_color.into_linear(); + + wgpu::Color { + r: f64::from(r), + g: f64::from(g), + b: f64::from(b), + a: f64::from(a), + } + }), + None => wgpu::LoadOp::Load, + }, + store: true, + }, + })], + depth_stencil_attachment: None, + }, + )); + + for layer in layers { + let bounds = (layer.bounds * scale_factor).snap(); - self.image_pipeline.prepare( + if bounds.width < 1 || bounds.height < 1 { + return; + } + + if !layer.quads.is_empty() { + self.quad_pipeline + .render(quad_layer, bounds, &mut render_pass); + + quad_layer += 1; + } + + if !layer.meshes.is_empty() { + let _ = ManuallyDrop::into_inner(render_pass); + + self.triangle_pipeline.render( device, - queue, encoder, - &layer.images, - scaled, + target, + triangle_layer, + target_size, + &layer.meshes, scale_factor, ); - let mut render_pass = - encoder.begin_render_pass(&wgpu::RenderPassDescriptor { - label: Some("iced_wgpu::image render pass"), + triangle_layer += 1; + + render_pass = ManuallyDrop::new(encoder.begin_render_pass( + &wgpu::RenderPassDescriptor { + label: Some("iced_wgpu::quad render pass"), color_attachments: &[Some( wgpu::RenderPassColorAttachment { view: target, @@ -203,24 +273,31 @@ impl Backend { }, )], depth_stencil_attachment: None, - }); + }, + )); + } - self.image_pipeline.render(bounds, &mut render_pass); + #[cfg(any(feature = "image", feature = "svg"))] + { + if !layer.images.is_empty() { + self.image_pipeline.render( + image_layer, + bounds, + &mut render_pass, + ); + + image_layer += 1; + } } - } - if !layer.text.is_empty() { - self.text_pipeline.prepare( - device, - queue, - &layer.text, - layer.bounds, - scale_factor, - target_size, - ); - - self.text_pipeline.render(encoder, target); + if !layer.text.is_empty() { + self.text_pipeline.render(text_layer, &mut render_pass); + + text_layer += 1; + } } + + let _ = ManuallyDrop::into_inner(render_pass); } } diff --git a/wgpu/src/image.rs b/wgpu/src/image.rs index 5d1ae8d7..db05d2ff 100644 --- a/wgpu/src/image.rs +++ b/wgpu/src/image.rs @@ -45,7 +45,6 @@ pub struct Pipeline { layers: Vec, prepare_layer: usize, - render_layer: usize, } #[derive(Debug)] @@ -321,7 +320,6 @@ impl Pipeline { layers: Vec::new(), prepare_layer: 0, - render_layer: 0, } } @@ -452,11 +450,12 @@ impl Pipeline { } pub fn render<'a>( - &'a mut self, + &'a self, + layer: usize, bounds: Rectangle, render_pass: &mut wgpu::RenderPass<'a>, ) { - if let Some(layer) = self.layers.get(self.render_layer) { + if let Some(layer) = self.layers.get(layer) { render_pass.set_pipeline(&self.pipeline); render_pass.set_scissor_rect( @@ -474,8 +473,6 @@ impl Pipeline { render_pass.set_vertex_buffer(0, self.vertices.slice(..)); layer.render(render_pass); - - self.render_layer += 1; } } @@ -496,7 +493,6 @@ impl Pipeline { .trim(&mut self.texture_atlas, &mut (device, queue, encoder)); self.prepare_layer = 0; - self.render_layer = 0; } } diff --git a/wgpu/src/quad.rs b/wgpu/src/quad.rs index c1aa49c4..246cc5e1 100644 --- a/wgpu/src/quad.rs +++ b/wgpu/src/quad.rs @@ -17,7 +17,6 @@ pub struct Pipeline { indices: wgpu::Buffer, layers: Vec, prepare_layer: usize, - render_layer: usize, } impl Pipeline { @@ -140,7 +139,6 @@ impl Pipeline { indices, layers: Vec::new(), prepare_layer: 0, - render_layer: 0, } } @@ -163,11 +161,12 @@ impl Pipeline { } pub fn render<'a>( - &'a mut self, + &'a self, + layer: usize, bounds: Rectangle, render_pass: &mut wgpu::RenderPass<'a>, ) { - if let Some(layer) = self.layers.get(self.render_layer) { + if let Some(layer) = self.layers.get(layer) { render_pass.set_pipeline(&self.pipeline); render_pass.set_scissor_rect( @@ -184,14 +183,11 @@ impl Pipeline { render_pass.set_vertex_buffer(0, self.vertices.slice(..)); layer.draw(render_pass); - - self.render_layer += 1; } } pub fn end_frame(&mut self) { self.prepare_layer = 0; - self.render_layer = 0; } } diff --git a/wgpu/src/text.rs b/wgpu/src/text.rs index 95994a4e..73708fd8 100644 --- a/wgpu/src/text.rs +++ b/wgpu/src/text.rs @@ -16,7 +16,7 @@ pub struct Pipeline { system: Option, renderers: Vec, atlas: glyphon::TextAtlas, - layer: usize, + prepare_layer: usize, } #[ouroboros::self_referencing] @@ -55,7 +55,7 @@ impl Pipeline { ), renderers: Vec::new(), atlas: glyphon::TextAtlas::new(device, queue, format), - layer: 0, + prepare_layer: 0, } } @@ -88,12 +88,12 @@ impl Pipeline { target_size: Size, ) { self.system.as_mut().unwrap().with_mut(|fields| { - if self.renderers.len() <= self.layer { + if self.renderers.len() <= self.prepare_layer { self.renderers .push(glyphon::TextRenderer::new(device, queue)); } - let renderer = &mut self.renderers[self.layer]; + let renderer = &mut self.renderers[self.prepare_layer]; let keys: Vec<_> = sections .iter() @@ -179,35 +179,21 @@ impl Pipeline { &mut glyphon::SwashCache::new(fields.fonts), ) .expect("Prepare text sections"); + + self.prepare_layer += 1; }); } - pub fn render( - &mut self, - encoder: &mut wgpu::CommandEncoder, - target: &wgpu::TextureView, + pub fn render<'a>( + &'a self, + layer: usize, + render_pass: &mut wgpu::RenderPass<'a>, ) { - let mut render_pass = - encoder.begin_render_pass(&wgpu::RenderPassDescriptor { - label: None, - color_attachments: &[Some(wgpu::RenderPassColorAttachment { - view: target, - resolve_target: None, - ops: wgpu::Operations { - load: wgpu::LoadOp::Load, - store: true, - }, - })], - depth_stencil_attachment: None, - }); - - let renderer = &mut self.renderers[self.layer]; + let renderer = &self.renderers[layer]; renderer - .render(&self.atlas, &mut render_pass) + .render(&self.atlas, render_pass) .expect("Render text"); - - self.layer += 1; } pub fn end_frame(&mut self) { @@ -216,7 +202,7 @@ impl Pipeline { .unwrap() .with_render_cache_mut(|cache| cache.trim()); - self.layer = 0; + self.prepare_layer = 0; } pub fn measure( diff --git a/wgpu/src/triangle.rs b/wgpu/src/triangle.rs index 572af1e8..1b537bf4 100644 --- a/wgpu/src/triangle.rs +++ b/wgpu/src/triangle.rs @@ -22,7 +22,6 @@ pub struct Pipeline { layers: Vec, prepare_layer: usize, - render_layer: usize, } #[derive(Debug)] @@ -235,7 +234,7 @@ impl Layer { } fn render<'a>( - &'a mut self, + &'a self, solid: &'a solid::Pipeline, #[cfg(not(target_arch = "wasm32"))] gradient: &'a gradient::Pipeline, meshes: &[Mesh<'_>], @@ -331,7 +330,6 @@ impl Pipeline { layers: Vec::new(), prepare_layer: 0, - render_layer: 0, } } @@ -373,6 +371,7 @@ impl Pipeline { device: &wgpu::Device, encoder: &mut wgpu::CommandEncoder, target: &wgpu::TextureView, + layer: usize, target_size: Size, meshes: &[Mesh<'_>], scale_factor: f32, @@ -413,7 +412,7 @@ impl Pipeline { depth_stencil_attachment: None, }); - let layer = &mut self.layers[self.render_layer]; + let layer = &mut self.layers[layer]; layer.render( &self.solid, @@ -425,8 +424,6 @@ impl Pipeline { ); } - self.render_layer += 1; - if let Some(blit) = &mut self.blit { blit.draw(encoder, target); } @@ -434,7 +431,6 @@ impl Pipeline { pub fn end_frame(&mut self) { self.prepare_layer = 0; - self.render_layer = 0; } } diff --git a/wgpu/src/window/compositor.rs b/wgpu/src/window/compositor.rs index 6e1acc06..365cb603 100644 --- a/wgpu/src/window/compositor.rs +++ b/wgpu/src/window/compositor.rs @@ -190,39 +190,12 @@ impl iced_graphics::window::Compositor for Compositor { .texture .create_view(&wgpu::TextureViewDescriptor::default()); - let _ = - encoder.begin_render_pass(&wgpu::RenderPassDescriptor { - label: Some( - "iced_wgpu::window::Compositor render pass", - ), - color_attachments: &[Some( - wgpu::RenderPassColorAttachment { - view, - resolve_target: None, - ops: wgpu::Operations { - load: wgpu::LoadOp::Clear({ - let [r, g, b, a] = - background_color.into_linear(); - - wgpu::Color { - r: f64::from(r), - g: f64::from(g), - b: f64::from(b), - a: f64::from(a), - } - }), - store: true, - }, - }, - )], - depth_stencil_attachment: None, - }); - renderer.with_primitives(|backend, primitives| { backend.present( &self.device, &self.queue, &mut encoder, + Some(background_color), view, primitives, viewport, -- cgit From 21886d7e9cde17904719f1642f0b3af9791102ad Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Wed, 8 Feb 2023 00:48:38 +0100 Subject: Use my GitHub fork of `glyphon` --- wgpu/Cargo.toml | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) (limited to 'wgpu') diff --git a/wgpu/Cargo.toml b/wgpu/Cargo.toml index 7a6b75f0..46115a3c 100644 --- a/wgpu/Cargo.toml +++ b/wgpu/Cargo.toml @@ -59,9 +59,8 @@ path = "../graphics" [dependencies.glyphon] version = "0.2" -# git = "https://github.com/hecrj/glyphon.git" -# rev = "3c2acb9dea5b9fcb0fa650b3c73b3a3242c62f4a" -path = "../../glyphon" +git = "https://github.com/hecrj/glyphon.git" +rev = "ccf19c67e8a4564263626bc6b86b6154540768c4" [dependencies.tracing] version = "0.1.6" -- cgit From ddbf93a82ff2ee0ca3265baf2f5b4442717b9101 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Wed, 8 Feb 2023 01:23:40 +0100 Subject: Set scissoring properly in `text::Pipeline` --- wgpu/src/backend.rs | 3 ++- wgpu/src/text.rs | 8 ++++++++ 2 files changed, 10 insertions(+), 1 deletion(-) (limited to 'wgpu') diff --git a/wgpu/src/backend.rs b/wgpu/src/backend.rs index 9d1e3d1c..d07898f7 100644 --- a/wgpu/src/backend.rs +++ b/wgpu/src/backend.rs @@ -291,7 +291,8 @@ impl Backend { } if !layer.text.is_empty() { - self.text_pipeline.render(text_layer, &mut render_pass); + self.text_pipeline + .render(text_layer, bounds, &mut render_pass); text_layer += 1; } diff --git a/wgpu/src/text.rs b/wgpu/src/text.rs index 73708fd8..655ad987 100644 --- a/wgpu/src/text.rs +++ b/wgpu/src/text.rs @@ -187,10 +187,18 @@ impl Pipeline { pub fn render<'a>( &'a self, layer: usize, + bounds: Rectangle, render_pass: &mut wgpu::RenderPass<'a>, ) { let renderer = &self.renderers[layer]; + render_pass.set_scissor_rect( + bounds.x, + bounds.y, + bounds.width, + bounds.height, + ); + renderer .render(&self.atlas, render_pass) .expect("Render text"); -- cgit From 05c787c2efbd8c8bc11925e1605b8b09ba744268 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Wed, 8 Feb 2023 23:21:04 +0100 Subject: Grow atlas in `text::Pipeline` when necessary --- wgpu/Cargo.toml | 2 +- wgpu/src/backend.rs | 55 +++++++++++++++++++++++++++++++++++++++-------------- wgpu/src/text.rs | 54 ++++++++++++++++++++++++++++++++++------------------ 3 files changed, 78 insertions(+), 33 deletions(-) (limited to 'wgpu') diff --git a/wgpu/Cargo.toml b/wgpu/Cargo.toml index 46115a3c..6132768d 100644 --- a/wgpu/Cargo.toml +++ b/wgpu/Cargo.toml @@ -60,7 +60,7 @@ path = "../graphics" [dependencies.glyphon] version = "0.2" git = "https://github.com/hecrj/glyphon.git" -rev = "ccf19c67e8a4564263626bc6b86b6154540768c4" +rev = "541efc5df644b1a25e96113f602a3f6803ce8a07" [dependencies.tracing] version = "0.1.6" diff --git a/wgpu/src/backend.rs b/wgpu/src/backend.rs index d07898f7..7f44fafa 100644 --- a/wgpu/src/backend.rs +++ b/wgpu/src/backend.rs @@ -93,10 +93,17 @@ impl Backend { encoder, scale_factor, transformation, - target_size, &layers, ); + while !self.prepare_text( + device, + queue, + scale_factor, + target_size, + &layers, + ) {} + self.render( device, encoder, @@ -115,6 +122,38 @@ impl Backend { self.image_pipeline.end_frame(device, queue, encoder); } + fn prepare_text( + &mut self, + device: &wgpu::Device, + queue: &wgpu::Queue, + scale_factor: f32, + target_size: Size, + layers: &[Layer<'_>], + ) -> bool { + for layer in layers { + let bounds = (layer.bounds * scale_factor).snap(); + + if bounds.width < 1 || bounds.height < 1 { + continue; + } + + if !layer.text.is_empty() { + if !self.text_pipeline.prepare( + device, + queue, + &layer.text, + layer.bounds, + scale_factor, + target_size, + ) { + return false; + } + } + } + + true + } + fn prepare( &mut self, device: &wgpu::Device, @@ -122,14 +161,13 @@ impl Backend { _encoder: &mut wgpu::CommandEncoder, scale_factor: f32, transformation: Transformation, - target_size: Size, layers: &[Layer<'_>], ) { for layer in layers { let bounds = (layer.bounds * scale_factor).snap(); if bounds.width < 1 || bounds.height < 1 { - return; + continue; } if !layer.quads.is_empty() { @@ -170,17 +208,6 @@ impl Backend { ); } } - - if !layer.text.is_empty() { - self.text_pipeline.prepare( - device, - queue, - &layer.text, - layer.bounds, - scale_factor, - target_size, - ); - } } } diff --git a/wgpu/src/text.rs b/wgpu/src/text.rs index 655ad987..4406177a 100644 --- a/wgpu/src/text.rs +++ b/wgpu/src/text.rs @@ -86,7 +86,7 @@ impl Pipeline { bounds: Rectangle, scale_factor: f32, target_size: Size, - ) { + ) -> bool { self.system.as_mut().unwrap().with_mut(|fields| { if self.renderers.len() <= self.prepare_layer { self.renderers @@ -165,23 +165,39 @@ impl Pipeline { } }); - renderer - .prepare( - device, - queue, - &mut self.atlas, - glyphon::Resolution { - width: target_size.width, - height: target_size.height, - }, - text_areas, - glyphon::Color::rgb(0, 0, 0), - &mut glyphon::SwashCache::new(fields.fonts), - ) - .expect("Prepare text sections"); - - self.prepare_layer += 1; - }); + let result = renderer.prepare( + device, + queue, + &mut self.atlas, + glyphon::Resolution { + width: target_size.width, + height: target_size.height, + }, + text_areas, + glyphon::Color::rgb(0, 0, 0), + &mut glyphon::SwashCache::new(fields.fonts), + ); + + match result { + Ok(()) => { + self.prepare_layer += 1; + + true + } + Err(glyphon::PrepareError::AtlasFull(content_type)) => { + self.prepare_layer = 0; + + if self.atlas.grow(device, content_type) { + false + } else { + // If the atlas cannot grow, then all bets are off. + // Instead of panicking, we will just pray that the result + // will be somewhat readable... + true + } + } + } + }) } pub fn render<'a>( @@ -205,6 +221,8 @@ impl Pipeline { } pub fn end_frame(&mut self) { + self.atlas.trim(); + self.system .as_mut() .unwrap() -- cgit From 0715d7d0c781a4c7ead4dfdbf0f4656022df67e1 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Thu, 9 Feb 2023 00:20:07 +0100 Subject: Update `glyphon` in `iced_wgpu` --- wgpu/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'wgpu') diff --git a/wgpu/Cargo.toml b/wgpu/Cargo.toml index 6132768d..632873a3 100644 --- a/wgpu/Cargo.toml +++ b/wgpu/Cargo.toml @@ -60,7 +60,7 @@ path = "../graphics" [dependencies.glyphon] version = "0.2" git = "https://github.com/hecrj/glyphon.git" -rev = "541efc5df644b1a25e96113f602a3f6803ce8a07" +rev = "65b481d758f50fd13fc21af2cc5ef62ddee64955" [dependencies.tracing] version = "0.1.6" -- cgit From 2097a56b58d8fe1784a625f69c1cdd38fcfb9a52 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Thu, 9 Feb 2023 06:54:51 +0100 Subject: Provide some margin to static buffers when growing --- wgpu/src/buffer/static.rs | 13 ++++++++----- wgpu/src/triangle.rs | 2 +- 2 files changed, 9 insertions(+), 6 deletions(-) (limited to 'wgpu') diff --git a/wgpu/src/buffer/static.rs b/wgpu/src/buffer/static.rs index 7ae7edd2..d8ae116e 100644 --- a/wgpu/src/buffer/static.rs +++ b/wgpu/src/buffer/static.rs @@ -2,8 +2,7 @@ use bytemuck::{Pod, Zeroable}; use std::marker::PhantomData; use std::mem; -//128 triangles/indices -const DEFAULT_STATIC_BUFFER_COUNT: wgpu::BufferAddress = 1_000; +const DEFAULT_COUNT: wgpu::BufferAddress = 128; /// A generic buffer struct useful for items which have no alignment requirements /// (e.g. Vertex, Index buffers) & no dynamic offsets. @@ -25,7 +24,7 @@ impl Buffer { label: &'static str, usages: wgpu::BufferUsages, ) -> Self { - let size = (mem::size_of::() as u64) * DEFAULT_STATIC_BUFFER_COUNT; + let size = (mem::size_of::() as u64) * DEFAULT_COUNT; Self { offsets: Vec::new(), @@ -57,9 +56,13 @@ impl Buffer { let size = (mem::size_of::() * new_count) as u64; if self.size < size { + self.size = + (mem::size_of::() * (new_count + new_count / 2)) as u64; + + self.gpu = + Self::gpu_buffer(device, self.label, self.size, self.usages); + self.offsets.clear(); - self.size = size; - self.gpu = Self::gpu_buffer(device, self.label, size, self.usages); true } else { false diff --git a/wgpu/src/triangle.rs b/wgpu/src/triangle.rs index 1b537bf4..4b4fa16d 100644 --- a/wgpu/src/triangle.rs +++ b/wgpu/src/triangle.rs @@ -43,7 +43,7 @@ impl Layer { Self { index_buffer: Buffer::new( device, - "iced_wgpu::triangle vertex buffer", + "iced_wgpu::triangle index buffer", wgpu::BufferUsages::INDEX | wgpu::BufferUsages::COPY_DST, ), index_strides: Vec::new(), -- cgit From 17a4d817c45da732e9fdc6559b6bbc7d5be94052 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Thu, 9 Feb 2023 07:00:21 +0100 Subject: Collapse conditional and please `clippy` --- wgpu/src/backend.rs | 10 +++++----- wgpu/src/text.rs | 1 + 2 files changed, 6 insertions(+), 5 deletions(-) (limited to 'wgpu') diff --git a/wgpu/src/backend.rs b/wgpu/src/backend.rs index 7f44fafa..e650d9a5 100644 --- a/wgpu/src/backend.rs +++ b/wgpu/src/backend.rs @@ -137,17 +137,17 @@ impl Backend { continue; } - if !layer.text.is_empty() { - if !self.text_pipeline.prepare( + if !layer.text.is_empty() + && !self.text_pipeline.prepare( device, queue, &layer.text, layer.bounds, scale_factor, target_size, - ) { - return false; - } + ) + { + return false; } } diff --git a/wgpu/src/text.rs b/wgpu/src/text.rs index 4406177a..c0f49417 100644 --- a/wgpu/src/text.rs +++ b/wgpu/src/text.rs @@ -187,6 +187,7 @@ impl Pipeline { Err(glyphon::PrepareError::AtlasFull(content_type)) => { self.prepare_layer = 0; + #[allow(clippy::needless_bool)] if self.atlas.grow(device, content_type) { false } else { -- cgit From 51844c5d0c7f7c9b65c56330862b69f0baa0e3c1 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Fri, 10 Feb 2023 18:52:35 +0100 Subject: Trim `Cache` every 300 frames in `text::Pipeline` --- wgpu/src/text.rs | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) (limited to 'wgpu') diff --git a/wgpu/src/text.rs b/wgpu/src/text.rs index c0f49417..dea6ab18 100644 --- a/wgpu/src/text.rs +++ b/wgpu/src/text.rs @@ -316,6 +316,7 @@ struct Cache<'a> { entries: FxHashMap>, recently_used: FxHashSet, hasher: HashBuilder, + trim_count: usize, } #[cfg(not(target_arch = "wasm32"))] @@ -325,11 +326,14 @@ type HashBuilder = twox_hash::RandomXxHashBuilder64; type HashBuilder = std::hash::BuildHasherDefault; impl<'a> Cache<'a> { + const TRIM_INTERVAL: usize = 300; + fn new() -> Self { Self { entries: FxHashMap::default(), recently_used: FxHashSet::default(), hasher: HashBuilder::default(), + trim_count: 0, } } @@ -389,10 +393,16 @@ impl<'a> Cache<'a> { } fn trim(&mut self) { - self.entries - .retain(|key, _| self.recently_used.contains(key)); + if self.trim_count >= Self::TRIM_INTERVAL { + self.entries + .retain(|key, _| self.recently_used.contains(key)); + + self.recently_used.clear(); - self.recently_used.clear(); + self.trim_count = 0; + } else { + self.trim_count += 1; + } } } -- cgit From 5100b5d0a1f654ec1254b7765ceadfb9091d6939 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Fri, 24 Feb 2023 23:24:48 +0100 Subject: Introduce `iced_renderer` subcrate featuring runtime renderer fallback --- wgpu/src/window.rs | 3 +- wgpu/src/window/compositor.rs | 139 ++++++++++++++++++++++++++---------------- 2 files changed, 88 insertions(+), 54 deletions(-) (limited to 'wgpu') diff --git a/wgpu/src/window.rs b/wgpu/src/window.rs index aac5fb9e..9545a14e 100644 --- a/wgpu/src/window.rs +++ b/wgpu/src/window.rs @@ -1,4 +1,5 @@ //! Display rendering results on windows. -mod compositor; +pub mod compositor; pub use compositor::Compositor; +pub use wgpu::Surface; diff --git a/wgpu/src/window/compositor.rs b/wgpu/src/window/compositor.rs index 365cb603..7406bfb8 100644 --- a/wgpu/src/window/compositor.rs +++ b/wgpu/src/window/compositor.rs @@ -1,8 +1,9 @@ -use crate::{Backend, Color, Error, Renderer, Settings, Viewport}; +//! Connect a window with a renderer. +use crate::{Backend, Color, Error, Primitive, Renderer, Settings, Viewport}; use futures::stream::{self, StreamExt}; -use iced_graphics::compositor; +use iced_graphics::window::compositor; use iced_native::futures; use raw_window_handle::{HasRawDisplayHandle, HasRawWindowHandle}; @@ -112,6 +113,77 @@ impl Compositor { } } +/// Creates a [`Compositor`] and its [`Backend`] for the given [`Settings`] and +/// window. +pub fn new( + settings: Settings, + compatible_window: Option<&W>, +) -> Result<(Compositor, Backend), Error> { + let compositor = futures::executor::block_on(Compositor::request( + settings, + compatible_window, + )) + .ok_or(Error::GraphicsAdapterNotFound)?; + + let backend = compositor.create_backend(); + + Ok((compositor, backend)) +} + +/// Presents the given primitives with the given [`Compositor`] and [`Backend`]. +pub fn present>( + compositor: &mut Compositor, + backend: &mut Backend, + surface: &mut wgpu::Surface, + primitives: &[Primitive], + viewport: &Viewport, + background_color: Color, + overlay: &[T], +) -> Result<(), compositor::SurfaceError> { + match surface.get_current_texture() { + Ok(frame) => { + let mut encoder = compositor.device.create_command_encoder( + &wgpu::CommandEncoderDescriptor { + label: Some("iced_wgpu encoder"), + }, + ); + + let view = &frame + .texture + .create_view(&wgpu::TextureViewDescriptor::default()); + + backend.present( + &compositor.device, + &compositor.queue, + &mut encoder, + Some(background_color), + view, + primitives, + viewport, + overlay, + ); + + // Submit work + let _submission = compositor.queue.submit(Some(encoder.finish())); + frame.present(); + + Ok(()) + } + Err(error) => match error { + wgpu::SurfaceError::Timeout => { + Err(compositor::SurfaceError::Timeout) + } + wgpu::SurfaceError::Outdated => { + Err(compositor::SurfaceError::Outdated) + } + wgpu::SurfaceError::Lost => Err(compositor::SurfaceError::Lost), + wgpu::SurfaceError::OutOfMemory => { + Err(compositor::SurfaceError::OutOfMemory) + } + }, + } +} + impl iced_graphics::window::Compositor for Compositor { type Settings = Settings; type Renderer = Renderer; @@ -121,13 +193,7 @@ impl iced_graphics::window::Compositor for Compositor { settings: Self::Settings, compatible_window: Option<&W>, ) -> Result<(Self, Self::Renderer), Error> { - let compositor = futures::executor::block_on(Self::request( - settings, - compatible_window, - )) - .ok_or(Error::GraphicsAdapterNotFound)?; - - let backend = compositor.create_backend(); + let (compositor, backend) = new(settings, compatible_window)?; Ok((compositor, Renderer::new(backend))) } @@ -178,49 +244,16 @@ impl iced_graphics::window::Compositor for Compositor { background_color: Color, overlay: &[T], ) -> Result<(), compositor::SurfaceError> { - match surface.get_current_texture() { - Ok(frame) => { - let mut encoder = self.device.create_command_encoder( - &wgpu::CommandEncoderDescriptor { - label: Some("iced_wgpu encoder"), - }, - ); - - let view = &frame - .texture - .create_view(&wgpu::TextureViewDescriptor::default()); - - renderer.with_primitives(|backend, primitives| { - backend.present( - &self.device, - &self.queue, - &mut encoder, - Some(background_color), - view, - primitives, - viewport, - overlay, - ); - }); - - // Submit work - let _submission = self.queue.submit(Some(encoder.finish())); - frame.present(); - - Ok(()) - } - Err(error) => match error { - wgpu::SurfaceError::Timeout => { - Err(compositor::SurfaceError::Timeout) - } - wgpu::SurfaceError::Outdated => { - Err(compositor::SurfaceError::Outdated) - } - wgpu::SurfaceError::Lost => Err(compositor::SurfaceError::Lost), - wgpu::SurfaceError::OutOfMemory => { - Err(compositor::SurfaceError::OutOfMemory) - } - }, - } + renderer.with_primitives(|backend, primitives| { + present( + self, + backend, + surface, + primitives, + viewport, + background_color, + overlay, + ) + }) } } -- cgit From 535d7a4d57e131e661587b36e41820dd6ccccc3e Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Sat, 25 Feb 2023 16:05:42 +0100 Subject: Implement basic presentation with `softbuffer` for `iced_tiny_skia` --- wgpu/src/window/compositor.rs | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) (limited to 'wgpu') diff --git a/wgpu/src/window/compositor.rs b/wgpu/src/window/compositor.rs index 7406bfb8..3a4a7123 100644 --- a/wgpu/src/window/compositor.rs +++ b/wgpu/src/window/compositor.rs @@ -201,11 +201,15 @@ impl iced_graphics::window::Compositor for Compositor { fn create_surface( &mut self, window: &W, + width: u32, + height: u32, ) -> wgpu::Surface { #[allow(unsafe_code)] - unsafe { - self.instance.create_surface(window) - } + let mut surface = unsafe { self.instance.create_surface(window) }; + + self.configure_surface(&mut surface, width, height); + + surface } fn configure_surface( -- cgit From 11b2c3bbe31a43e73a61b9bd9f022233f302ae27 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Mon, 27 Feb 2023 03:40:58 +0100 Subject: Reuse text buffers independently of color in `iced_wgpu` --- wgpu/Cargo.toml | 2 +- wgpu/src/text.rs | 28 +++++++++++----------------- 2 files changed, 12 insertions(+), 18 deletions(-) (limited to 'wgpu') diff --git a/wgpu/Cargo.toml b/wgpu/Cargo.toml index 632873a3..9e80a76d 100644 --- a/wgpu/Cargo.toml +++ b/wgpu/Cargo.toml @@ -60,7 +60,7 @@ path = "../graphics" [dependencies.glyphon] version = "0.2" git = "https://github.com/hecrj/glyphon.git" -rev = "65b481d758f50fd13fc21af2cc5ef62ddee64955" +rev = "810bc979f9005e2bd343b72b980e57e46174283f" [dependencies.tracing] version = "0.1.6" diff --git a/wgpu/src/text.rs b/wgpu/src/text.rs index dea6ab18..3839b31f 100644 --- a/wgpu/src/text.rs +++ b/wgpu/src/text.rs @@ -2,7 +2,7 @@ pub use iced_native::text::Hit; use iced_graphics::layer::Text; use iced_native::alignment; -use iced_native::{Color, Font, Rectangle, Size}; +use iced_native::{Font, Rectangle, Size}; use rustc_hash::{FxHashMap, FxHashSet}; use std::borrow::Cow; @@ -110,7 +110,6 @@ impl Pipeline { height: (section.bounds.height * scale_factor) .ceil(), }, - color: section.color, }, ); @@ -162,6 +161,16 @@ impl Pipeline { left: left as i32, top: top as i32, bounds, + default_color: { + let [r, g, b, a] = section.color.into_linear(); + + glyphon::Color::rgba( + (r * 255.0) as u8, + (g * 255.0) as u8, + (b * 255.0) as u8, + (a * 255.0) as u8, + ) + }, } }); @@ -174,7 +183,6 @@ impl Pipeline { height: target_size.height, }, text_areas, - glyphon::Color::rgb(0, 0, 0), &mut glyphon::SwashCache::new(fields.fonts), ); @@ -249,7 +257,6 @@ impl Pipeline { size, font, bounds, - color: Color::BLACK, }, ); @@ -283,7 +290,6 @@ impl Pipeline { size, font, bounds, - color: Color::BLACK, }, ); @@ -354,7 +360,6 @@ impl<'a> Cache<'a> { key.font.hash(&mut hasher); key.bounds.width.to_bits().hash(&mut hasher); key.bounds.height.to_bits().hash(&mut hasher); - key.color.into_rgba8().hash(&mut hasher); hasher.finish() }; @@ -371,16 +376,6 @@ impl<'a> Cache<'a> { key.content, glyphon::Attrs::new() .family(to_family(key.font)) - .color({ - let [r, g, b, a] = key.color.into_linear(); - - glyphon::Color::rgba( - (r * 255.0) as u8, - (g * 255.0) as u8, - (b * 255.0) as u8, - (a * 255.0) as u8, - ) - }) .monospaced(matches!(key.font, Font::Monospace)), ); @@ -412,7 +407,6 @@ struct Key<'a> { size: f32, font: Font, bounds: Size, - color: Color, } type KeyHash = u64; -- cgit From 5fd5d1cdf8e5354788dc40729c4565ef377d3bba Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Wed, 1 Mar 2023 21:34:26 +0100 Subject: Implement `Canvas` support for `iced_tiny_skia` --- wgpu/Cargo.toml | 15 +- wgpu/src/backend.rs | 7 +- wgpu/src/image.rs | 2 +- wgpu/src/layer.rs | 274 +++++++++++++++++ wgpu/src/layer/image.rs | 27 ++ wgpu/src/layer/mesh.rs | 93 ++++++ wgpu/src/layer/quad.rs | 30 ++ wgpu/src/layer/text.rs | 26 ++ wgpu/src/lib.rs | 15 +- wgpu/src/quad.rs | 3 +- wgpu/src/text.rs | 3 +- wgpu/src/triangle.rs | 71 +++-- wgpu/src/widget.rs | 9 + wgpu/src/widget/canvas.rs | 16 + wgpu/src/widget/canvas/cache.rs | 93 ++++++ wgpu/src/widget/canvas/frame.rs | 609 +++++++++++++++++++++++++++++++++++++ wgpu/src/widget/canvas/geometry.rs | 24 ++ 17 files changed, 1269 insertions(+), 48 deletions(-) create mode 100644 wgpu/src/layer.rs create mode 100644 wgpu/src/layer/image.rs create mode 100644 wgpu/src/layer/mesh.rs create mode 100644 wgpu/src/layer/quad.rs create mode 100644 wgpu/src/layer/text.rs create mode 100644 wgpu/src/widget.rs create mode 100644 wgpu/src/widget/canvas.rs create mode 100644 wgpu/src/widget/canvas/cache.rs create mode 100644 wgpu/src/widget/canvas/frame.rs create mode 100644 wgpu/src/widget/canvas/geometry.rs (limited to 'wgpu') diff --git a/wgpu/Cargo.toml b/wgpu/Cargo.toml index 632873a3..0bcef71c 100644 --- a/wgpu/Cargo.toml +++ b/wgpu/Cargo.toml @@ -21,8 +21,7 @@ bmp = ["iced_graphics/bmp"] hdr = ["iced_graphics/hdr"] dds = ["iced_graphics/dds"] farbfeld = ["iced_graphics/farbfeld"] -canvas = ["iced_graphics/canvas"] -qr_code = ["iced_graphics/qr_code"] +canvas = ["iced_graphics/canvas", "lyon"] spirv = ["wgpu/spirv"] webgl = ["wgpu/webgl"] @@ -62,10 +61,6 @@ version = "0.2" git = "https://github.com/hecrj/glyphon.git" rev = "65b481d758f50fd13fc21af2cc5ef62ddee64955" -[dependencies.tracing] -version = "0.1.6" -optional = true - [dependencies.encase] version = "0.3.0" features = ["glam"] @@ -73,6 +68,14 @@ features = ["glam"] [dependencies.glam] version = "0.21.3" +[dependencies.lyon] +version = "1.0" +optional = true + +[dependencies.tracing] +version = "0.1.6" +optional = true + [package.metadata.docs.rs] rustdoc-args = ["--cfg", "docsrs"] all-features = true diff --git a/wgpu/src/backend.rs b/wgpu/src/backend.rs index e650d9a5..10dc5b4f 100644 --- a/wgpu/src/backend.rs +++ b/wgpu/src/backend.rs @@ -1,11 +1,10 @@ use crate::quad; use crate::text; use crate::triangle; -use crate::{Settings, Transformation}; +use crate::{Layer, Primitive, Settings, Transformation}; use iced_graphics::backend; -use iced_graphics::layer::Layer; -use iced_graphics::{Color, Font, Primitive, Size, Viewport}; +use iced_graphics::{Color, Font, Size, Viewport}; #[cfg(feature = "tracing")] use tracing::info_span; @@ -330,6 +329,8 @@ impl Backend { } impl iced_graphics::Backend for Backend { + type Geometry = (); + fn trim_measurements(&mut self) { self.text_pipeline.trim_measurement_cache() } diff --git a/wgpu/src/image.rs b/wgpu/src/image.rs index db05d2ff..2159a3ec 100644 --- a/wgpu/src/image.rs +++ b/wgpu/src/image.rs @@ -6,10 +6,10 @@ use iced_graphics::image::raster; #[cfg(feature = "svg")] use iced_graphics::image::vector; +use crate::layer; use crate::{Buffer, Transformation}; use atlas::Atlas; -use iced_graphics::layer; use iced_native::{Rectangle, Size}; use std::cell::RefCell; diff --git a/wgpu/src/layer.rs b/wgpu/src/layer.rs new file mode 100644 index 00000000..0840555a --- /dev/null +++ b/wgpu/src/layer.rs @@ -0,0 +1,274 @@ +//! Organize rendering primitives into a flattened list of layers. +mod image; +mod quad; +mod text; + +pub mod mesh; + +pub use image::Image; +pub use mesh::Mesh; +pub use quad::Quad; +pub use text::Text; + +use crate::Primitive; + +use iced_graphics::alignment; +use iced_graphics::{ + Background, Color, Font, Point, Rectangle, Size, Vector, Viewport, +}; + +/// A group of primitives that should be clipped together. +#[derive(Debug)] +pub struct Layer<'a> { + /// The clipping bounds of the [`Layer`]. + pub bounds: Rectangle, + + /// The quads of the [`Layer`]. + pub quads: Vec, + + /// The triangle meshes of the [`Layer`]. + pub meshes: Vec>, + + /// The text of the [`Layer`]. + pub text: Vec>, + + /// The images of the [`Layer`]. + pub images: Vec, +} + +impl<'a> Layer<'a> { + /// Creates a new [`Layer`] with the given clipping bounds. + pub fn new(bounds: Rectangle) -> Self { + Self { + bounds, + quads: Vec::new(), + meshes: Vec::new(), + text: Vec::new(), + images: Vec::new(), + } + } + + /// Creates a new [`Layer`] for the provided overlay text. + /// + /// This can be useful for displaying debug information. + pub fn overlay(lines: &'a [impl AsRef], viewport: &Viewport) -> Self { + let mut overlay = + Layer::new(Rectangle::with_size(viewport.logical_size())); + + for (i, line) in lines.iter().enumerate() { + let text = Text { + content: line.as_ref(), + bounds: Rectangle::new( + Point::new(11.0, 11.0 + 25.0 * i as f32), + Size::INFINITY, + ), + color: Color::new(0.9, 0.9, 0.9, 1.0), + size: 20.0, + font: Font::Monospace, + horizontal_alignment: alignment::Horizontal::Left, + vertical_alignment: alignment::Vertical::Top, + }; + + overlay.text.push(text); + + overlay.text.push(Text { + bounds: text.bounds + Vector::new(-1.0, -1.0), + color: Color::BLACK, + ..text + }); + } + + overlay + } + + /// Distributes the given [`Primitive`] and generates a list of layers based + /// on its contents. + pub fn generate( + primitives: &'a [Primitive], + viewport: &Viewport, + ) -> Vec { + let first_layer = + Layer::new(Rectangle::with_size(viewport.logical_size())); + + let mut layers = vec![first_layer]; + + for primitive in primitives { + Self::process_primitive( + &mut layers, + Vector::new(0.0, 0.0), + primitive, + 0, + ); + } + + layers + } + + fn process_primitive( + layers: &mut Vec, + translation: Vector, + primitive: &'a Primitive, + current_layer: usize, + ) { + match primitive { + Primitive::Text { + content, + bounds, + size, + color, + font, + horizontal_alignment, + vertical_alignment, + } => { + let layer = &mut layers[current_layer]; + + layer.text.push(Text { + content, + bounds: *bounds + translation, + size: *size, + color: *color, + font: *font, + horizontal_alignment: *horizontal_alignment, + vertical_alignment: *vertical_alignment, + }); + } + Primitive::Quad { + bounds, + background, + border_radius, + border_width, + border_color, + } => { + let layer = &mut layers[current_layer]; + + // TODO: Move some of these computations to the GPU (?) + layer.quads.push(Quad { + position: [ + bounds.x + translation.x, + bounds.y + translation.y, + ], + size: [bounds.width, bounds.height], + color: match background { + Background::Color(color) => color.into_linear(), + }, + border_radius: *border_radius, + border_width: *border_width, + border_color: border_color.into_linear(), + }); + } + Primitive::Image { handle, bounds } => { + let layer = &mut layers[current_layer]; + + layer.images.push(Image::Raster { + handle: handle.clone(), + bounds: *bounds + translation, + }); + } + Primitive::Svg { + handle, + color, + bounds, + } => { + let layer = &mut layers[current_layer]; + + layer.images.push(Image::Vector { + handle: handle.clone(), + color: *color, + bounds: *bounds + translation, + }); + } + Primitive::SolidMesh { buffers, size } => { + let layer = &mut layers[current_layer]; + + let bounds = Rectangle::new( + Point::new(translation.x, translation.y), + *size, + ); + + // Only draw visible content + if let Some(clip_bounds) = layer.bounds.intersection(&bounds) { + layer.meshes.push(Mesh::Solid { + origin: Point::new(translation.x, translation.y), + buffers, + clip_bounds, + }); + } + } + Primitive::GradientMesh { + buffers, + size, + gradient, + } => { + let layer = &mut layers[current_layer]; + + let bounds = Rectangle::new( + Point::new(translation.x, translation.y), + *size, + ); + + // Only draw visible content + if let Some(clip_bounds) = layer.bounds.intersection(&bounds) { + layer.meshes.push(Mesh::Gradient { + origin: Point::new(translation.x, translation.y), + buffers, + clip_bounds, + gradient, + }); + } + } + Primitive::Group { primitives } => { + // TODO: Inspect a bit and regroup (?) + for primitive in primitives { + Self::process_primitive( + layers, + translation, + primitive, + current_layer, + ) + } + } + Primitive::Clip { bounds, content } => { + let layer = &mut layers[current_layer]; + let translated_bounds = *bounds + translation; + + // Only draw visible content + if let Some(clip_bounds) = + layer.bounds.intersection(&translated_bounds) + { + let clip_layer = Layer::new(clip_bounds); + layers.push(clip_layer); + + Self::process_primitive( + layers, + translation, + content, + layers.len() - 1, + ); + } + } + Primitive::Translate { + translation: new_translation, + content, + } => { + Self::process_primitive( + layers, + translation + *new_translation, + content, + current_layer, + ); + } + Primitive::Cache { content } => { + Self::process_primitive( + layers, + translation, + content, + current_layer, + ); + } + Primitive::Fill { .. } | Primitive::Stroke { .. } => { + // Unsupported! + // TODO: Draw a placeholder (?) + } + } + } +} diff --git a/wgpu/src/layer/image.rs b/wgpu/src/layer/image.rs new file mode 100644 index 00000000..3eff2397 --- /dev/null +++ b/wgpu/src/layer/image.rs @@ -0,0 +1,27 @@ +use crate::{Color, Rectangle}; + +use iced_native::{image, svg}; + +/// A raster or vector image. +#[derive(Debug, Clone)] +pub enum Image { + /// A raster image. + Raster { + /// The handle of a raster image. + handle: image::Handle, + + /// The bounds of the image. + bounds: Rectangle, + }, + /// A vector image. + Vector { + /// The handle of a vector image. + handle: svg::Handle, + + /// The [`Color`] filter + color: Option, + + /// The bounds of the image. + bounds: Rectangle, + }, +} diff --git a/wgpu/src/layer/mesh.rs b/wgpu/src/layer/mesh.rs new file mode 100644 index 00000000..5c1e41ad --- /dev/null +++ b/wgpu/src/layer/mesh.rs @@ -0,0 +1,93 @@ +//! A collection of triangle primitives. +use crate::primitive; +use crate::{Gradient, Point, Rectangle}; + +/// A mesh of triangles. +#[derive(Debug, Clone, Copy)] +pub enum Mesh<'a> { + /// A mesh of triangles with a solid color. + Solid { + /// The origin of the vertices of the [`Mesh`]. + origin: Point, + + /// The vertex and index buffers of the [`Mesh`]. + buffers: &'a primitive::Mesh2D, + + /// The clipping bounds of the [`Mesh`]. + clip_bounds: Rectangle, + }, + /// A mesh of triangles with a gradient color. + Gradient { + /// The origin of the vertices of the [`Mesh`]. + origin: Point, + + /// The vertex and index buffers of the [`Mesh`]. + buffers: &'a primitive::Mesh2D, + + /// The clipping bounds of the [`Mesh`]. + clip_bounds: Rectangle, + + /// The gradient to apply to the [`Mesh`]. + gradient: &'a Gradient, + }, +} + +impl Mesh<'_> { + /// Returns the origin of the [`Mesh`]. + pub fn origin(&self) -> Point { + match self { + Self::Solid { origin, .. } | Self::Gradient { origin, .. } => { + *origin + } + } + } + + /// Returns the indices of the [`Mesh`]. + pub fn indices(&self) -> &[u32] { + match self { + Self::Solid { buffers, .. } => &buffers.indices, + Self::Gradient { buffers, .. } => &buffers.indices, + } + } + + /// Returns the clip bounds of the [`Mesh`]. + pub fn clip_bounds(&self) -> Rectangle { + match self { + Self::Solid { clip_bounds, .. } + | Self::Gradient { clip_bounds, .. } => *clip_bounds, + } + } +} + +/// The result of counting the attributes of a set of meshes. +#[derive(Debug, Clone, Copy, Default)] +pub struct AttributeCount { + /// The total amount of solid vertices. + pub solid_vertices: usize, + + /// The total amount of gradient vertices. + pub gradient_vertices: usize, + + /// The total amount of indices. + pub indices: usize, +} + +/// Returns the number of total vertices & total indices of all [`Mesh`]es. +pub fn attribute_count_of<'a>(meshes: &'a [Mesh<'a>]) -> AttributeCount { + meshes + .iter() + .fold(AttributeCount::default(), |mut count, mesh| { + match mesh { + Mesh::Solid { buffers, .. } => { + count.solid_vertices += buffers.vertices.len(); + count.indices += buffers.indices.len(); + } + Mesh::Gradient { buffers, .. } => { + count.gradient_vertices += buffers.vertices.len(); + count.indices += buffers.indices.len(); + } + } + + count + }) +} diff --git a/wgpu/src/layer/quad.rs b/wgpu/src/layer/quad.rs new file mode 100644 index 00000000..0d8bde9d --- /dev/null +++ b/wgpu/src/layer/quad.rs @@ -0,0 +1,30 @@ +/// A colored rectangle with a border. +/// +/// This type can be directly uploaded to GPU memory. +#[derive(Debug, Clone, Copy)] +#[repr(C)] +pub struct Quad { + /// The position of the [`Quad`]. + pub position: [f32; 2], + + /// The size of the [`Quad`]. + pub size: [f32; 2], + + /// The color of the [`Quad`], in __linear RGB__. + pub color: [f32; 4], + + /// The border color of the [`Quad`], in __linear RGB__. + pub border_color: [f32; 4], + + /// The border radius of the [`Quad`]. + pub border_radius: [f32; 4], + + /// The border width of the [`Quad`]. + pub border_width: f32, +} + +#[allow(unsafe_code)] +unsafe impl bytemuck::Zeroable for Quad {} + +#[allow(unsafe_code)] +unsafe impl bytemuck::Pod for Quad {} diff --git a/wgpu/src/layer/text.rs b/wgpu/src/layer/text.rs new file mode 100644 index 00000000..38d62616 --- /dev/null +++ b/wgpu/src/layer/text.rs @@ -0,0 +1,26 @@ +use crate::{alignment, Color, Font, Rectangle}; + +/// A paragraph of text. +#[derive(Debug, Clone, Copy)] +pub struct Text<'a> { + /// The content of the [`Text`]. + pub content: &'a str, + + /// The layout bounds of the [`Text`]. + pub bounds: Rectangle, + + /// The color of the [`Text`], in __linear RGB_. + pub color: Color, + + /// The size of the [`Text`]. + pub size: f32, + + /// The font of the [`Text`]. + pub font: Font, + + /// The horizontal alignment of the [`Text`]. + pub horizontal_alignment: alignment::Horizontal, + + /// The vertical alignment of the [`Text`]. + pub vertical_alignment: alignment::Vertical, +} diff --git a/wgpu/src/lib.rs b/wgpu/src/lib.rs index 9da40572..8be12602 100644 --- a/wgpu/src/lib.rs +++ b/wgpu/src/lib.rs @@ -25,7 +25,7 @@ )] #![deny( missing_debug_implementations, - missing_docs, + //missing_docs, unsafe_code, unused_results, clippy::extra_unused_lifetimes, @@ -38,7 +38,9 @@ #![allow(clippy::inherent_to_string, clippy::type_complexity)] #![cfg_attr(docsrs, feature(doc_cfg))] +pub mod layer; pub mod settings; +pub mod widget; pub mod window; mod backend; @@ -47,16 +49,23 @@ mod quad; mod text; mod triangle; +pub use iced_graphics::primitive; pub use iced_graphics::{ - Antialiasing, Color, Error, Font, Primitive, Viewport, + Antialiasing, Color, Error, Font, Gradient, Point, Rectangle, Size, Vector, + Viewport, }; +pub use iced_native::alignment; + pub use iced_native::Theme; pub use wgpu; pub use backend::Backend; +pub use layer::Layer; +pub use primitive::Primitive; pub use settings::Settings; -use crate::buffer::Buffer; +use buffer::Buffer; + use iced_graphics::Transformation; #[cfg(any(feature = "image", feature = "svg"))] diff --git a/wgpu/src/quad.rs b/wgpu/src/quad.rs index 246cc5e1..8a568968 100644 --- a/wgpu/src/quad.rs +++ b/wgpu/src/quad.rs @@ -1,5 +1,6 @@ +use crate::layer; use crate::{Buffer, Transformation}; -use iced_graphics::layer; + use iced_native::Rectangle; use bytemuck::{Pod, Zeroable}; diff --git a/wgpu/src/text.rs b/wgpu/src/text.rs index dea6ab18..0dc8a64c 100644 --- a/wgpu/src/text.rs +++ b/wgpu/src/text.rs @@ -1,6 +1,7 @@ +use crate::layer::Text; + pub use iced_native::text::Hit; -use iced_graphics::layer::Text; use iced_native::alignment; use iced_native::{Color, Font, Rectangle, Size}; diff --git a/wgpu/src/triangle.rs b/wgpu/src/triangle.rs index 4b4fa16d..706e4282 100644 --- a/wgpu/src/triangle.rs +++ b/wgpu/src/triangle.rs @@ -2,12 +2,12 @@ mod msaa; use crate::buffer::r#static::Buffer; +use crate::layer::mesh::{self, Mesh}; use crate::settings; use crate::Transformation; -use iced_graphics::layer::mesh::{self, Mesh}; -use iced_graphics::triangle::ColoredVertex2D; use iced_graphics::Size; + #[cfg(feature = "tracing")] use tracing::info_span; @@ -468,6 +468,7 @@ mod solid { use crate::settings; use crate::triangle; use encase::ShaderType; + use iced_graphics::primitive; use iced_graphics::Transformation; #[derive(Debug)] @@ -478,7 +479,7 @@ mod solid { #[derive(Debug)] pub struct Layer { - pub vertices: Buffer, + pub vertices: Buffer, pub uniforms: dynamic::Buffer, pub constants: wgpu::BindGroup, } @@ -596,7 +597,7 @@ mod solid { entry_point: "vs_main", buffers: &[wgpu::VertexBufferLayout { array_stride: std::mem::size_of::< - triangle::ColoredVertex2D, + primitive::ColoredVertex2D, >() as u64, step_mode: wgpu::VertexStepMode::Vertex, @@ -637,7 +638,7 @@ mod gradient { use encase::ShaderType; use glam::{IVec4, Vec4}; - use iced_graphics::triangle::Vertex2D; + use iced_graphics::primitive; #[derive(Debug)] pub struct Pipeline { @@ -647,7 +648,7 @@ mod gradient { #[derive(Debug)] pub struct Layer { - pub vertices: Buffer, + pub vertices: Buffer, pub uniforms: dynamic::Buffer, pub storage: dynamic::Buffer, pub constants: wgpu::BindGroup, @@ -810,34 +811,38 @@ mod gradient { ), }); - let pipeline = device.create_render_pipeline( - &wgpu::RenderPipelineDescriptor { - label: Some("iced_wgpu::triangle::gradient pipeline"), - layout: Some(&layout), - vertex: wgpu::VertexState { - module: &shader, - entry_point: "vs_main", - buffers: &[wgpu::VertexBufferLayout { - array_stride: std::mem::size_of::() - as u64, - step_mode: wgpu::VertexStepMode::Vertex, - attributes: &wgpu::vertex_attr_array!( - // Position - 0 => Float32x2, - ), - }], + let pipeline = + device.create_render_pipeline( + &wgpu::RenderPipelineDescriptor { + label: Some("iced_wgpu::triangle::gradient pipeline"), + layout: Some(&layout), + vertex: wgpu::VertexState { + module: &shader, + entry_point: "vs_main", + buffers: &[wgpu::VertexBufferLayout { + array_stride: std::mem::size_of::< + primitive::Vertex2D, + >( + ) + as u64, + step_mode: wgpu::VertexStepMode::Vertex, + attributes: &wgpu::vertex_attr_array!( + // Position + 0 => Float32x2, + ), + }], + }, + fragment: Some(wgpu::FragmentState { + module: &shader, + entry_point: "fs_main", + targets: &[triangle::fragment_target(format)], + }), + primitive: triangle::primitive_state(), + depth_stencil: None, + multisample: triangle::multisample_state(antialiasing), + multiview: None, }, - fragment: Some(wgpu::FragmentState { - module: &shader, - entry_point: "fs_main", - targets: &[triangle::fragment_target(format)], - }), - primitive: triangle::primitive_state(), - depth_stencil: None, - multisample: triangle::multisample_state(antialiasing), - multiview: None, - }, - ); + ); Self { pipeline, diff --git a/wgpu/src/widget.rs b/wgpu/src/widget.rs new file mode 100644 index 00000000..8d05041e --- /dev/null +++ b/wgpu/src/widget.rs @@ -0,0 +1,9 @@ +//! Use the graphical widgets supported out-of-the-box. + +#[cfg(feature = "canvas")] +#[cfg_attr(docsrs, doc(cfg(feature = "canvas")))] +pub mod canvas; + +#[cfg(feature = "canvas")] +#[doc(no_inline)] +pub use canvas::Canvas; diff --git a/wgpu/src/widget/canvas.rs b/wgpu/src/widget/canvas.rs new file mode 100644 index 00000000..41444fcf --- /dev/null +++ b/wgpu/src/widget/canvas.rs @@ -0,0 +1,16 @@ +mod cache; +mod frame; +mod geometry; + +pub use cache::Cache; +pub use frame::Frame; +pub use geometry::Geometry; + +pub use iced_native::widget::canvas::event::{self, Event}; +pub use iced_native::widget::canvas::fill::{self, Fill}; +pub use iced_native::widget::canvas::gradient::{self, Gradient}; +pub use iced_native::widget::canvas::path::{self, Path}; +pub use iced_native::widget::canvas::stroke::{self, Stroke}; +pub use iced_native::widget::canvas::{ + Canvas, Cursor, LineCap, LineDash, LineJoin, Program, Renderer, Style, Text, +}; diff --git a/wgpu/src/widget/canvas/cache.rs b/wgpu/src/widget/canvas/cache.rs new file mode 100644 index 00000000..09b26b90 --- /dev/null +++ b/wgpu/src/widget/canvas/cache.rs @@ -0,0 +1,93 @@ +use crate::widget::canvas::{Frame, Geometry}; +use crate::Primitive; + +use iced_native::Size; +use std::{cell::RefCell, sync::Arc}; + +#[derive(Default)] +enum State { + #[default] + Empty, + Filled { + bounds: Size, + primitive: Arc, + }, +} + +/// A simple cache that stores generated [`Geometry`] to avoid recomputation. +/// +/// A [`Cache`] will not redraw its geometry unless the dimensions of its layer +/// change or it is explicitly cleared. +#[derive(Debug, Default)] +pub struct Cache { + state: RefCell, +} + +impl Cache { + /// Creates a new empty [`Cache`]. + pub fn new() -> Self { + Cache { + state: Default::default(), + } + } + + /// Clears the [`Cache`], forcing a redraw the next time it is used. + pub fn clear(&self) { + *self.state.borrow_mut() = State::Empty; + } + + /// Draws [`Geometry`] using the provided closure and stores it in the + /// [`Cache`]. + /// + /// The closure will only be called when + /// - the bounds have changed since the previous draw call. + /// - the [`Cache`] is empty or has been explicitly cleared. + /// + /// Otherwise, the previously stored [`Geometry`] will be returned. The + /// [`Cache`] is not cleared in this case. In other words, it will keep + /// returning the stored [`Geometry`] if needed. + pub fn draw( + &self, + bounds: Size, + draw_fn: impl FnOnce(&mut Frame), + ) -> Geometry { + use std::ops::Deref; + + if let State::Filled { + bounds: cached_bounds, + primitive, + } = self.state.borrow().deref() + { + if *cached_bounds == bounds { + return Geometry::from_primitive(Primitive::Cache { + content: primitive.clone(), + }); + } + } + + let mut frame = Frame::new(bounds); + draw_fn(&mut frame); + + let primitive = Arc::new(frame.into_primitive()); + + *self.state.borrow_mut() = State::Filled { + bounds, + primitive: primitive.clone(), + }; + + Geometry::from_primitive(Primitive::Cache { content: primitive }) + } +} + +impl std::fmt::Debug for State { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + State::Empty => write!(f, "Empty"), + State::Filled { primitive, bounds } => f + .debug_struct("Filled") + .field("primitive", primitive) + .field("bounds", bounds) + .finish(), + } + } +} diff --git a/wgpu/src/widget/canvas/frame.rs b/wgpu/src/widget/canvas/frame.rs new file mode 100644 index 00000000..987570ec --- /dev/null +++ b/wgpu/src/widget/canvas/frame.rs @@ -0,0 +1,609 @@ +use crate::primitive::{self, Primitive}; +use crate::widget::canvas::fill::{self, Fill}; +use crate::widget::canvas::{ + LineCap, LineDash, LineJoin, Path, Stroke, Style, Text, +}; + +use iced_native::{Gradient, Point, Rectangle, Size, Vector}; + +use lyon::geom::euclid; +use lyon::tessellation; +use std::borrow::Cow; + +/// The frame of a [`Canvas`]. +/// +/// [`Canvas`]: crate::widget::Canvas +#[allow(missing_debug_implementations)] +pub struct Frame { + size: Size, + buffers: BufferStack, + primitives: Vec, + transforms: Transforms, + fill_tessellator: tessellation::FillTessellator, + stroke_tessellator: tessellation::StrokeTessellator, +} + +enum Buffer { + Solid(tessellation::VertexBuffers), + Gradient( + tessellation::VertexBuffers, + Gradient, + ), +} + +struct BufferStack { + stack: Vec, +} + +impl BufferStack { + fn new() -> Self { + Self { stack: Vec::new() } + } + + fn get_mut(&mut self, style: &Style) -> &mut Buffer { + match style { + Style::Solid(_) => match self.stack.last() { + Some(Buffer::Solid(_)) => {} + _ => { + self.stack.push(Buffer::Solid( + tessellation::VertexBuffers::new(), + )); + } + }, + Style::Gradient(gradient) => match self.stack.last() { + Some(Buffer::Gradient(_, last)) if gradient == last => {} + _ => { + self.stack.push(Buffer::Gradient( + tessellation::VertexBuffers::new(), + gradient.clone(), + )); + } + }, + } + + self.stack.last_mut().unwrap() + } + + fn get_fill<'a>( + &'a mut self, + style: &Style, + ) -> Box { + match (style, self.get_mut(style)) { + (Style::Solid(color), Buffer::Solid(buffer)) => { + Box::new(tessellation::BuffersBuilder::new( + buffer, + TriangleVertex2DBuilder(color.into_linear()), + )) + } + (Style::Gradient(_), Buffer::Gradient(buffer, _)) => Box::new( + tessellation::BuffersBuilder::new(buffer, Vertex2DBuilder), + ), + _ => unreachable!(), + } + } + + fn get_stroke<'a>( + &'a mut self, + style: &Style, + ) -> Box { + match (style, self.get_mut(style)) { + (Style::Solid(color), Buffer::Solid(buffer)) => { + Box::new(tessellation::BuffersBuilder::new( + buffer, + TriangleVertex2DBuilder(color.into_linear()), + )) + } + (Style::Gradient(_), Buffer::Gradient(buffer, _)) => Box::new( + tessellation::BuffersBuilder::new(buffer, Vertex2DBuilder), + ), + _ => unreachable!(), + } + } +} + +#[derive(Debug)] +struct Transforms { + previous: Vec, + current: Transform, +} + +#[derive(Debug, Clone, Copy)] +struct Transform { + raw: lyon::math::Transform, + is_identity: bool, +} + +impl Transform { + /// Transforms the given [Point] by the transformation matrix. + fn transform_point(&self, point: &mut Point) { + let transformed = self + .raw + .transform_point(euclid::Point2D::new(point.x, point.y)); + point.x = transformed.x; + point.y = transformed.y; + } + + fn transform_style(&self, style: Style) -> Style { + match style { + Style::Solid(color) => Style::Solid(color), + Style::Gradient(gradient) => { + Style::Gradient(self.transform_gradient(gradient)) + } + } + } + + fn transform_gradient(&self, mut gradient: Gradient) -> Gradient { + let (start, end) = match &mut gradient { + Gradient::Linear(linear) => (&mut linear.start, &mut linear.end), + }; + self.transform_point(start); + self.transform_point(end); + gradient + } +} + +impl Frame { + /// Creates a new empty [`Frame`] with the given dimensions. + /// + /// The default coordinate system of a [`Frame`] has its origin at the + /// top-left corner of its bounds. + pub fn new(size: Size) -> Frame { + Frame { + size, + buffers: BufferStack::new(), + primitives: Vec::new(), + transforms: Transforms { + previous: Vec::new(), + current: Transform { + raw: lyon::math::Transform::identity(), + is_identity: true, + }, + }, + fill_tessellator: tessellation::FillTessellator::new(), + stroke_tessellator: tessellation::StrokeTessellator::new(), + } + } + + /// Returns the width of the [`Frame`]. + #[inline] + pub fn width(&self) -> f32 { + self.size.width + } + + /// Returns the height of the [`Frame`]. + #[inline] + pub fn height(&self) -> f32 { + self.size.height + } + + /// Returns the dimensions of the [`Frame`]. + #[inline] + pub fn size(&self) -> Size { + self.size + } + + /// Returns the coordinate of the center of the [`Frame`]. + #[inline] + pub fn center(&self) -> Point { + Point::new(self.size.width / 2.0, self.size.height / 2.0) + } + + /// Draws the given [`Path`] on the [`Frame`] by filling it with the + /// provided style. + pub fn fill(&mut self, path: &Path, fill: impl Into) { + let Fill { style, rule } = fill.into(); + + let mut buffer = self + .buffers + .get_fill(&self.transforms.current.transform_style(style)); + + let options = tessellation::FillOptions::default() + .with_fill_rule(into_fill_rule(rule)); + + if self.transforms.current.is_identity { + self.fill_tessellator.tessellate_path( + path.raw(), + &options, + buffer.as_mut(), + ) + } else { + let path = path.transform(&self.transforms.current.raw); + + self.fill_tessellator.tessellate_path( + path.raw(), + &options, + buffer.as_mut(), + ) + } + .expect("Tessellate path."); + } + + /// Draws an axis-aligned rectangle given its top-left corner coordinate and + /// its `Size` on the [`Frame`] by filling it with the provided style. + pub fn fill_rectangle( + &mut self, + top_left: Point, + size: Size, + fill: impl Into, + ) { + let Fill { style, rule } = fill.into(); + + let mut buffer = self + .buffers + .get_fill(&self.transforms.current.transform_style(style)); + + let top_left = + self.transforms.current.raw.transform_point( + lyon::math::Point::new(top_left.x, top_left.y), + ); + + let size = + self.transforms.current.raw.transform_vector( + lyon::math::Vector::new(size.width, size.height), + ); + + let options = tessellation::FillOptions::default() + .with_fill_rule(into_fill_rule(rule)); + + self.fill_tessellator + .tessellate_rectangle( + &lyon::math::Box2D::new(top_left, top_left + size), + &options, + buffer.as_mut(), + ) + .expect("Fill rectangle"); + } + + /// Draws the stroke of the given [`Path`] on the [`Frame`] with the + /// provided style. + pub fn stroke<'a>(&mut self, path: &Path, stroke: impl Into>) { + let stroke = stroke.into(); + + let mut buffer = self + .buffers + .get_stroke(&self.transforms.current.transform_style(stroke.style)); + + 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); + + let path = if stroke.line_dash.segments.is_empty() { + Cow::Borrowed(path) + } else { + Cow::Owned(dashed(path, stroke.line_dash)) + }; + + if self.transforms.current.is_identity { + self.stroke_tessellator.tessellate_path( + path.raw(), + &options, + buffer.as_mut(), + ) + } else { + let path = path.transform(&self.transforms.current.raw); + + self.stroke_tessellator.tessellate_path( + path.raw(), + &options, + buffer.as_mut(), + ) + } + .expect("Stroke path"); + } + + /// Draws the characters of the given [`Text`] on the [`Frame`], filling + /// them with the given color. + /// + /// __Warning:__ Text currently does not work well with rotations and scale + /// transforms! The position will be correctly transformed, but the + /// resulting glyphs will not be rotated or scaled properly. + /// + /// Additionally, all text will be rendered on top of all the layers of + /// a [`Canvas`]. Therefore, it is currently only meant to be used for + /// overlays, which is the most common use case. + /// + /// Support for vectorial text is planned, and should address all these + /// limitations. + /// + /// [`Canvas`]: crate::widget::Canvas + pub fn fill_text(&mut self, text: impl Into) { + let text = text.into(); + + let position = if self.transforms.current.is_identity { + text.position + } else { + let transformed = self.transforms.current.raw.transform_point( + lyon::math::Point::new(text.position.x, text.position.y), + ); + + Point::new(transformed.x, transformed.y) + }; + + // TODO: Use vectorial text instead of primitive + self.primitives.push(Primitive::Text { + content: text.content, + bounds: Rectangle { + x: position.x, + y: position.y, + width: f32::INFINITY, + height: f32::INFINITY, + }, + color: text.color, + size: text.size, + font: text.font, + horizontal_alignment: text.horizontal_alignment, + vertical_alignment: text.vertical_alignment, + }); + } + + /// Stores the current transform of the [`Frame`] and executes the given + /// drawing operations, restoring the transform afterwards. + /// + /// This method is useful to compose transforms and perform drawing + /// operations in different coordinate systems. + #[inline] + pub fn with_save(&mut self, f: impl FnOnce(&mut Frame)) { + self.push_transform(); + + f(self); + + self.pop_transform(); + } + + pub fn push_transform(&mut self) { + self.transforms.previous.push(self.transforms.current); + } + + pub fn pop_transform(&mut self) { + self.transforms.current = self.transforms.previous.pop().unwrap(); + } + + /// Executes the given drawing operations within a [`Rectangle`] region, + /// clipping any geometry that overflows its bounds. Any transformations + /// performed are local to the provided closure. + /// + /// This method is useful to perform drawing operations that need to be + /// clipped. + #[inline] + pub fn with_clip(&mut self, region: Rectangle, f: impl FnOnce(&mut Frame)) { + let mut frame = Frame::new(region.size()); + + f(&mut frame); + + let translation = Vector::new(region.x, region.y); + + self.clip(frame, translation); + } + + pub fn clip(&mut self, frame: Frame, translation: Vector) { + let size = frame.size(); + let primitives = frame.into_primitives(); + + let (text, meshes) = primitives + .into_iter() + .partition(|primitive| matches!(primitive, Primitive::Text { .. })); + + self.primitives.push(Primitive::Group { + primitives: vec![ + Primitive::Translate { + translation, + content: Box::new(Primitive::Group { primitives: meshes }), + }, + Primitive::Translate { + translation, + content: Box::new(Primitive::Clip { + bounds: Rectangle::with_size(size), + content: Box::new(Primitive::Group { + primitives: text, + }), + }), + }, + ], + }); + } + + /// Applies a translation to the current transform of the [`Frame`]. + #[inline] + pub fn translate(&mut self, translation: Vector) { + self.transforms.current.raw = self + .transforms + .current + .raw + .pre_translate(lyon::math::Vector::new( + translation.x, + translation.y, + )); + self.transforms.current.is_identity = false; + } + + /// Applies a rotation in radians to the current transform of the [`Frame`]. + #[inline] + pub fn rotate(&mut self, angle: f32) { + self.transforms.current.raw = self + .transforms + .current + .raw + .pre_rotate(lyon::math::Angle::radians(angle)); + self.transforms.current.is_identity = false; + } + + /// Applies a scaling to the current transform of the [`Frame`]. + #[inline] + pub fn scale(&mut self, scale: f32) { + self.transforms.current.raw = + self.transforms.current.raw.pre_scale(scale, scale); + self.transforms.current.is_identity = false; + } + + /// Produces the [`Primitive`] representing everything drawn on the [`Frame`]. + pub fn into_primitive(self) -> Primitive { + Primitive::Group { + primitives: self.into_primitives(), + } + } + + fn into_primitives(mut self) -> Vec { + for buffer in self.buffers.stack { + match buffer { + Buffer::Solid(buffer) => { + if !buffer.indices.is_empty() { + self.primitives.push(Primitive::SolidMesh { + buffers: primitive::Mesh2D { + vertices: buffer.vertices, + indices: buffer.indices, + }, + size: self.size, + }) + } + } + Buffer::Gradient(buffer, gradient) => { + if !buffer.indices.is_empty() { + self.primitives.push(Primitive::GradientMesh { + buffers: primitive::Mesh2D { + vertices: buffer.vertices, + indices: buffer.indices, + }, + size: self.size, + gradient, + }) + } + } + } + } + + self.primitives + } +} + +struct Vertex2DBuilder; + +impl tessellation::FillVertexConstructor + for Vertex2DBuilder +{ + fn new_vertex( + &mut self, + vertex: tessellation::FillVertex<'_>, + ) -> primitive::Vertex2D { + let position = vertex.position(); + + primitive::Vertex2D { + position: [position.x, position.y], + } + } +} + +impl tessellation::StrokeVertexConstructor + for Vertex2DBuilder +{ + fn new_vertex( + &mut self, + vertex: tessellation::StrokeVertex<'_, '_>, + ) -> primitive::Vertex2D { + let position = vertex.position(); + + primitive::Vertex2D { + position: [position.x, position.y], + } + } +} + +struct TriangleVertex2DBuilder([f32; 4]); + +impl tessellation::FillVertexConstructor + for TriangleVertex2DBuilder +{ + fn new_vertex( + &mut self, + vertex: tessellation::FillVertex<'_>, + ) -> primitive::ColoredVertex2D { + let position = vertex.position(); + + primitive::ColoredVertex2D { + position: [position.x, position.y], + color: self.0, + } + } +} + +impl tessellation::StrokeVertexConstructor + for TriangleVertex2DBuilder +{ + fn new_vertex( + &mut self, + vertex: tessellation::StrokeVertex<'_, '_>, + ) -> primitive::ColoredVertex2D { + let position = vertex.position(); + + primitive::ColoredVertex2D { + position: [position.x, position.y], + color: self.0, + } + } +} + +fn into_line_join(line_join: LineJoin) -> lyon::tessellation::LineJoin { + match line_join { + LineJoin::Miter => lyon::tessellation::LineJoin::Miter, + LineJoin::Round => lyon::tessellation::LineJoin::Round, + LineJoin::Bevel => lyon::tessellation::LineJoin::Bevel, + } +} + +fn into_line_cap(line_cap: LineCap) -> lyon::tessellation::LineCap { + match line_cap { + LineCap::Butt => lyon::tessellation::LineCap::Butt, + LineCap::Square => lyon::tessellation::LineCap::Square, + LineCap::Round => lyon::tessellation::LineCap::Round, + } +} + +fn into_fill_rule(rule: fill::Rule) -> lyon::tessellation::FillRule { + match rule { + fill::Rule::NonZero => lyon::tessellation::FillRule::NonZero, + fill::Rule::EvenOdd => lyon::tessellation::FillRule::EvenOdd, + } +} + +pub(super) fn dashed(path: &Path, line_dash: LineDash<'_>) -> Path { + use lyon::algorithms::walk::{ + walk_along_path, RepeatedPattern, WalkerEvent, + }; + use lyon::path::iterator::PathIterator; + + Path::new(|builder| { + let segments_odd = (line_dash.segments.len() % 2 == 1) + .then(|| [line_dash.segments, line_dash.segments].concat()); + + let mut draw_line = false; + + walk_along_path( + path.raw().iter().flattened(0.01), + 0.0, + lyon::tessellation::StrokeOptions::DEFAULT_TOLERANCE, + &mut RepeatedPattern { + callback: |event: WalkerEvent<'_>| { + let point = Point { + x: event.position.x, + y: event.position.y, + }; + + if draw_line { + builder.line_to(point); + } else { + builder.move_to(point); + } + + draw_line = !draw_line; + + true + }, + index: line_dash.offset, + intervals: segments_odd + .as_deref() + .unwrap_or(line_dash.segments), + }, + ); + }) +} diff --git a/wgpu/src/widget/canvas/geometry.rs b/wgpu/src/widget/canvas/geometry.rs new file mode 100644 index 00000000..e8ac621d --- /dev/null +++ b/wgpu/src/widget/canvas/geometry.rs @@ -0,0 +1,24 @@ +use crate::Primitive; + +/// A bunch of shapes that can be drawn. +/// +/// [`Geometry`] can be easily generated with a [`Frame`] or stored in a +/// [`Cache`]. +/// +/// [`Frame`]: crate::widget::canvas::Frame +/// [`Cache`]: crate::widget::canvas::Cache +#[derive(Debug, Clone)] +pub struct Geometry(Primitive); + +impl Geometry { + pub(crate) fn from_primitive(primitive: Primitive) -> Self { + Self(primitive) + } + + /// Turns the [`Geometry`] into a [`Primitive`]. + /// + /// This can be useful if you are building a custom widget. + pub fn into_primitive(self) -> Primitive { + self.0 + } +} -- cgit From bbeaf10c04a922af5c1c3b898f0c4301d23feab0 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Fri, 3 Mar 2023 03:55:07 +0100 Subject: Mark `Primitive` as `non-exhaustive` in `iced_graphics` --- wgpu/src/layer.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) (limited to 'wgpu') diff --git a/wgpu/src/layer.rs b/wgpu/src/layer.rs index 0840555a..69fcf899 100644 --- a/wgpu/src/layer.rs +++ b/wgpu/src/layer.rs @@ -265,9 +265,8 @@ impl<'a> Layer<'a> { current_layer, ); } - Primitive::Fill { .. } | Primitive::Stroke { .. } => { + _ => { // Unsupported! - // TODO: Draw a placeholder (?) } } } -- cgit From d13d19ba3569560edd67f20b48f37548d10ceee9 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Fri, 3 Mar 2023 04:00:44 +0100 Subject: Rename `canvas::frame` to `canvas` in `iced_wgpu` --- wgpu/src/canvas.rs | 608 ++++++++++++++++++++++++++++++++++++ wgpu/src/lib.rs | 5 +- wgpu/src/widget.rs | 9 - wgpu/src/widget/canvas.rs | 16 - wgpu/src/widget/canvas/cache.rs | 93 ------ wgpu/src/widget/canvas/frame.rs | 609 ------------------------------------- wgpu/src/widget/canvas/geometry.rs | 24 -- 7 files changed, 611 insertions(+), 753 deletions(-) create mode 100644 wgpu/src/canvas.rs delete mode 100644 wgpu/src/widget.rs delete mode 100644 wgpu/src/widget/canvas.rs delete mode 100644 wgpu/src/widget/canvas/cache.rs delete mode 100644 wgpu/src/widget/canvas/frame.rs delete mode 100644 wgpu/src/widget/canvas/geometry.rs (limited to 'wgpu') diff --git a/wgpu/src/canvas.rs b/wgpu/src/canvas.rs new file mode 100644 index 00000000..e8d540c3 --- /dev/null +++ b/wgpu/src/canvas.rs @@ -0,0 +1,608 @@ +use iced_graphics::primitive::{self, Primitive}; +use iced_native::widget::canvas::fill::{self, Fill}; +use iced_native::widget::canvas::{ + LineCap, LineDash, LineJoin, Path, Stroke, Style, Text, +}; +use iced_native::{Gradient, Point, Rectangle, Size, Vector}; + +use lyon::geom::euclid; +use lyon::tessellation; +use std::borrow::Cow; + +/// The frame of a [`Canvas`]. +/// +/// [`Canvas`]: crate::widget::Canvas +#[allow(missing_debug_implementations)] +pub struct Frame { + size: Size, + buffers: BufferStack, + primitives: Vec, + transforms: Transforms, + fill_tessellator: tessellation::FillTessellator, + stroke_tessellator: tessellation::StrokeTessellator, +} + +enum Buffer { + Solid(tessellation::VertexBuffers), + Gradient( + tessellation::VertexBuffers, + Gradient, + ), +} + +struct BufferStack { + stack: Vec, +} + +impl BufferStack { + fn new() -> Self { + Self { stack: Vec::new() } + } + + fn get_mut(&mut self, style: &Style) -> &mut Buffer { + match style { + Style::Solid(_) => match self.stack.last() { + Some(Buffer::Solid(_)) => {} + _ => { + self.stack.push(Buffer::Solid( + tessellation::VertexBuffers::new(), + )); + } + }, + Style::Gradient(gradient) => match self.stack.last() { + Some(Buffer::Gradient(_, last)) if gradient == last => {} + _ => { + self.stack.push(Buffer::Gradient( + tessellation::VertexBuffers::new(), + gradient.clone(), + )); + } + }, + } + + self.stack.last_mut().unwrap() + } + + fn get_fill<'a>( + &'a mut self, + style: &Style, + ) -> Box { + match (style, self.get_mut(style)) { + (Style::Solid(color), Buffer::Solid(buffer)) => { + Box::new(tessellation::BuffersBuilder::new( + buffer, + TriangleVertex2DBuilder(color.into_linear()), + )) + } + (Style::Gradient(_), Buffer::Gradient(buffer, _)) => Box::new( + tessellation::BuffersBuilder::new(buffer, Vertex2DBuilder), + ), + _ => unreachable!(), + } + } + + fn get_stroke<'a>( + &'a mut self, + style: &Style, + ) -> Box { + match (style, self.get_mut(style)) { + (Style::Solid(color), Buffer::Solid(buffer)) => { + Box::new(tessellation::BuffersBuilder::new( + buffer, + TriangleVertex2DBuilder(color.into_linear()), + )) + } + (Style::Gradient(_), Buffer::Gradient(buffer, _)) => Box::new( + tessellation::BuffersBuilder::new(buffer, Vertex2DBuilder), + ), + _ => unreachable!(), + } + } +} + +#[derive(Debug)] +struct Transforms { + previous: Vec, + current: Transform, +} + +#[derive(Debug, Clone, Copy)] +struct Transform { + raw: lyon::math::Transform, + is_identity: bool, +} + +impl Transform { + /// Transforms the given [Point] by the transformation matrix. + fn transform_point(&self, point: &mut Point) { + let transformed = self + .raw + .transform_point(euclid::Point2D::new(point.x, point.y)); + point.x = transformed.x; + point.y = transformed.y; + } + + fn transform_style(&self, style: Style) -> Style { + match style { + Style::Solid(color) => Style::Solid(color), + Style::Gradient(gradient) => { + Style::Gradient(self.transform_gradient(gradient)) + } + } + } + + fn transform_gradient(&self, mut gradient: Gradient) -> Gradient { + let (start, end) = match &mut gradient { + Gradient::Linear(linear) => (&mut linear.start, &mut linear.end), + }; + self.transform_point(start); + self.transform_point(end); + gradient + } +} + +impl Frame { + /// Creates a new empty [`Frame`] with the given dimensions. + /// + /// The default coordinate system of a [`Frame`] has its origin at the + /// top-left corner of its bounds. + pub fn new(size: Size) -> Frame { + Frame { + size, + buffers: BufferStack::new(), + primitives: Vec::new(), + transforms: Transforms { + previous: Vec::new(), + current: Transform { + raw: lyon::math::Transform::identity(), + is_identity: true, + }, + }, + fill_tessellator: tessellation::FillTessellator::new(), + stroke_tessellator: tessellation::StrokeTessellator::new(), + } + } + + /// Returns the width of the [`Frame`]. + #[inline] + pub fn width(&self) -> f32 { + self.size.width + } + + /// Returns the height of the [`Frame`]. + #[inline] + pub fn height(&self) -> f32 { + self.size.height + } + + /// Returns the dimensions of the [`Frame`]. + #[inline] + pub fn size(&self) -> Size { + self.size + } + + /// Returns the coordinate of the center of the [`Frame`]. + #[inline] + pub fn center(&self) -> Point { + Point::new(self.size.width / 2.0, self.size.height / 2.0) + } + + /// Draws the given [`Path`] on the [`Frame`] by filling it with the + /// provided style. + pub fn fill(&mut self, path: &Path, fill: impl Into) { + let Fill { style, rule } = fill.into(); + + let mut buffer = self + .buffers + .get_fill(&self.transforms.current.transform_style(style)); + + let options = tessellation::FillOptions::default() + .with_fill_rule(into_fill_rule(rule)); + + if self.transforms.current.is_identity { + self.fill_tessellator.tessellate_path( + path.raw(), + &options, + buffer.as_mut(), + ) + } else { + let path = path.transform(&self.transforms.current.raw); + + self.fill_tessellator.tessellate_path( + path.raw(), + &options, + buffer.as_mut(), + ) + } + .expect("Tessellate path."); + } + + /// Draws an axis-aligned rectangle given its top-left corner coordinate and + /// its `Size` on the [`Frame`] by filling it with the provided style. + pub fn fill_rectangle( + &mut self, + top_left: Point, + size: Size, + fill: impl Into, + ) { + let Fill { style, rule } = fill.into(); + + let mut buffer = self + .buffers + .get_fill(&self.transforms.current.transform_style(style)); + + let top_left = + self.transforms.current.raw.transform_point( + lyon::math::Point::new(top_left.x, top_left.y), + ); + + let size = + self.transforms.current.raw.transform_vector( + lyon::math::Vector::new(size.width, size.height), + ); + + let options = tessellation::FillOptions::default() + .with_fill_rule(into_fill_rule(rule)); + + self.fill_tessellator + .tessellate_rectangle( + &lyon::math::Box2D::new(top_left, top_left + size), + &options, + buffer.as_mut(), + ) + .expect("Fill rectangle"); + } + + /// Draws the stroke of the given [`Path`] on the [`Frame`] with the + /// provided style. + pub fn stroke<'a>(&mut self, path: &Path, stroke: impl Into>) { + let stroke = stroke.into(); + + let mut buffer = self + .buffers + .get_stroke(&self.transforms.current.transform_style(stroke.style)); + + 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); + + let path = if stroke.line_dash.segments.is_empty() { + Cow::Borrowed(path) + } else { + Cow::Owned(dashed(path, stroke.line_dash)) + }; + + if self.transforms.current.is_identity { + self.stroke_tessellator.tessellate_path( + path.raw(), + &options, + buffer.as_mut(), + ) + } else { + let path = path.transform(&self.transforms.current.raw); + + self.stroke_tessellator.tessellate_path( + path.raw(), + &options, + buffer.as_mut(), + ) + } + .expect("Stroke path"); + } + + /// Draws the characters of the given [`Text`] on the [`Frame`], filling + /// them with the given color. + /// + /// __Warning:__ Text currently does not work well with rotations and scale + /// transforms! The position will be correctly transformed, but the + /// resulting glyphs will not be rotated or scaled properly. + /// + /// Additionally, all text will be rendered on top of all the layers of + /// a [`Canvas`]. Therefore, it is currently only meant to be used for + /// overlays, which is the most common use case. + /// + /// Support for vectorial text is planned, and should address all these + /// limitations. + /// + /// [`Canvas`]: crate::widget::Canvas + pub fn fill_text(&mut self, text: impl Into) { + let text = text.into(); + + let position = if self.transforms.current.is_identity { + text.position + } else { + let transformed = self.transforms.current.raw.transform_point( + lyon::math::Point::new(text.position.x, text.position.y), + ); + + Point::new(transformed.x, transformed.y) + }; + + // TODO: Use vectorial text instead of primitive + self.primitives.push(Primitive::Text { + content: text.content, + bounds: Rectangle { + x: position.x, + y: position.y, + width: f32::INFINITY, + height: f32::INFINITY, + }, + color: text.color, + size: text.size, + font: text.font, + horizontal_alignment: text.horizontal_alignment, + vertical_alignment: text.vertical_alignment, + }); + } + + /// Stores the current transform of the [`Frame`] and executes the given + /// drawing operations, restoring the transform afterwards. + /// + /// This method is useful to compose transforms and perform drawing + /// operations in different coordinate systems. + #[inline] + pub fn with_save(&mut self, f: impl FnOnce(&mut Frame)) { + self.push_transform(); + + f(self); + + self.pop_transform(); + } + + pub fn push_transform(&mut self) { + self.transforms.previous.push(self.transforms.current); + } + + pub fn pop_transform(&mut self) { + self.transforms.current = self.transforms.previous.pop().unwrap(); + } + + /// Executes the given drawing operations within a [`Rectangle`] region, + /// clipping any geometry that overflows its bounds. Any transformations + /// performed are local to the provided closure. + /// + /// This method is useful to perform drawing operations that need to be + /// clipped. + #[inline] + pub fn with_clip(&mut self, region: Rectangle, f: impl FnOnce(&mut Frame)) { + let mut frame = Frame::new(region.size()); + + f(&mut frame); + + let translation = Vector::new(region.x, region.y); + + self.clip(frame, translation); + } + + pub fn clip(&mut self, frame: Frame, translation: Vector) { + let size = frame.size(); + let primitives = frame.into_primitives(); + + let (text, meshes) = primitives + .into_iter() + .partition(|primitive| matches!(primitive, Primitive::Text { .. })); + + self.primitives.push(Primitive::Group { + primitives: vec![ + Primitive::Translate { + translation, + content: Box::new(Primitive::Group { primitives: meshes }), + }, + Primitive::Translate { + translation, + content: Box::new(Primitive::Clip { + bounds: Rectangle::with_size(size), + content: Box::new(Primitive::Group { + primitives: text, + }), + }), + }, + ], + }); + } + + /// Applies a translation to the current transform of the [`Frame`]. + #[inline] + pub fn translate(&mut self, translation: Vector) { + self.transforms.current.raw = self + .transforms + .current + .raw + .pre_translate(lyon::math::Vector::new( + translation.x, + translation.y, + )); + self.transforms.current.is_identity = false; + } + + /// Applies a rotation in radians to the current transform of the [`Frame`]. + #[inline] + pub fn rotate(&mut self, angle: f32) { + self.transforms.current.raw = self + .transforms + .current + .raw + .pre_rotate(lyon::math::Angle::radians(angle)); + self.transforms.current.is_identity = false; + } + + /// Applies a scaling to the current transform of the [`Frame`]. + #[inline] + pub fn scale(&mut self, scale: f32) { + self.transforms.current.raw = + self.transforms.current.raw.pre_scale(scale, scale); + self.transforms.current.is_identity = false; + } + + /// Produces the [`Primitive`] representing everything drawn on the [`Frame`]. + pub fn into_primitive(self) -> Primitive { + Primitive::Group { + primitives: self.into_primitives(), + } + } + + fn into_primitives(mut self) -> Vec { + for buffer in self.buffers.stack { + match buffer { + Buffer::Solid(buffer) => { + if !buffer.indices.is_empty() { + self.primitives.push(Primitive::SolidMesh { + buffers: primitive::Mesh2D { + vertices: buffer.vertices, + indices: buffer.indices, + }, + size: self.size, + }) + } + } + Buffer::Gradient(buffer, gradient) => { + if !buffer.indices.is_empty() { + self.primitives.push(Primitive::GradientMesh { + buffers: primitive::Mesh2D { + vertices: buffer.vertices, + indices: buffer.indices, + }, + size: self.size, + gradient, + }) + } + } + } + } + + self.primitives + } +} + +struct Vertex2DBuilder; + +impl tessellation::FillVertexConstructor + for Vertex2DBuilder +{ + fn new_vertex( + &mut self, + vertex: tessellation::FillVertex<'_>, + ) -> primitive::Vertex2D { + let position = vertex.position(); + + primitive::Vertex2D { + position: [position.x, position.y], + } + } +} + +impl tessellation::StrokeVertexConstructor + for Vertex2DBuilder +{ + fn new_vertex( + &mut self, + vertex: tessellation::StrokeVertex<'_, '_>, + ) -> primitive::Vertex2D { + let position = vertex.position(); + + primitive::Vertex2D { + position: [position.x, position.y], + } + } +} + +struct TriangleVertex2DBuilder([f32; 4]); + +impl tessellation::FillVertexConstructor + for TriangleVertex2DBuilder +{ + fn new_vertex( + &mut self, + vertex: tessellation::FillVertex<'_>, + ) -> primitive::ColoredVertex2D { + let position = vertex.position(); + + primitive::ColoredVertex2D { + position: [position.x, position.y], + color: self.0, + } + } +} + +impl tessellation::StrokeVertexConstructor + for TriangleVertex2DBuilder +{ + fn new_vertex( + &mut self, + vertex: tessellation::StrokeVertex<'_, '_>, + ) -> primitive::ColoredVertex2D { + let position = vertex.position(); + + primitive::ColoredVertex2D { + position: [position.x, position.y], + color: self.0, + } + } +} + +fn into_line_join(line_join: LineJoin) -> lyon::tessellation::LineJoin { + match line_join { + LineJoin::Miter => lyon::tessellation::LineJoin::Miter, + LineJoin::Round => lyon::tessellation::LineJoin::Round, + LineJoin::Bevel => lyon::tessellation::LineJoin::Bevel, + } +} + +fn into_line_cap(line_cap: LineCap) -> lyon::tessellation::LineCap { + match line_cap { + LineCap::Butt => lyon::tessellation::LineCap::Butt, + LineCap::Square => lyon::tessellation::LineCap::Square, + LineCap::Round => lyon::tessellation::LineCap::Round, + } +} + +fn into_fill_rule(rule: fill::Rule) -> lyon::tessellation::FillRule { + match rule { + fill::Rule::NonZero => lyon::tessellation::FillRule::NonZero, + fill::Rule::EvenOdd => lyon::tessellation::FillRule::EvenOdd, + } +} + +pub(super) fn dashed(path: &Path, line_dash: LineDash<'_>) -> Path { + use lyon::algorithms::walk::{ + walk_along_path, RepeatedPattern, WalkerEvent, + }; + use lyon::path::iterator::PathIterator; + + Path::new(|builder| { + let segments_odd = (line_dash.segments.len() % 2 == 1) + .then(|| [line_dash.segments, line_dash.segments].concat()); + + let mut draw_line = false; + + walk_along_path( + path.raw().iter().flattened(0.01), + 0.0, + lyon::tessellation::StrokeOptions::DEFAULT_TOLERANCE, + &mut RepeatedPattern { + callback: |event: WalkerEvent<'_>| { + let point = Point { + x: event.position.x, + y: event.position.y, + }; + + if draw_line { + builder.line_to(point); + } else { + builder.move_to(point); + } + + draw_line = !draw_line; + + true + }, + index: line_dash.offset, + intervals: segments_odd + .as_deref() + .unwrap_or(line_dash.segments), + }, + ); + }) +} diff --git a/wgpu/src/lib.rs b/wgpu/src/lib.rs index 8be12602..31db16a8 100644 --- a/wgpu/src/lib.rs +++ b/wgpu/src/lib.rs @@ -37,12 +37,13 @@ #![forbid(rust_2018_idioms)] #![allow(clippy::inherent_to_string, clippy::type_complexity)] #![cfg_attr(docsrs, feature(doc_cfg))] - pub mod layer; pub mod settings; -pub mod widget; pub mod window; +#[cfg(feature = "canvas")] +pub mod canvas; + mod backend; mod buffer; mod quad; diff --git a/wgpu/src/widget.rs b/wgpu/src/widget.rs deleted file mode 100644 index 8d05041e..00000000 --- a/wgpu/src/widget.rs +++ /dev/null @@ -1,9 +0,0 @@ -//! Use the graphical widgets supported out-of-the-box. - -#[cfg(feature = "canvas")] -#[cfg_attr(docsrs, doc(cfg(feature = "canvas")))] -pub mod canvas; - -#[cfg(feature = "canvas")] -#[doc(no_inline)] -pub use canvas::Canvas; diff --git a/wgpu/src/widget/canvas.rs b/wgpu/src/widget/canvas.rs deleted file mode 100644 index 41444fcf..00000000 --- a/wgpu/src/widget/canvas.rs +++ /dev/null @@ -1,16 +0,0 @@ -mod cache; -mod frame; -mod geometry; - -pub use cache::Cache; -pub use frame::Frame; -pub use geometry::Geometry; - -pub use iced_native::widget::canvas::event::{self, Event}; -pub use iced_native::widget::canvas::fill::{self, Fill}; -pub use iced_native::widget::canvas::gradient::{self, Gradient}; -pub use iced_native::widget::canvas::path::{self, Path}; -pub use iced_native::widget::canvas::stroke::{self, Stroke}; -pub use iced_native::widget::canvas::{ - Canvas, Cursor, LineCap, LineDash, LineJoin, Program, Renderer, Style, Text, -}; diff --git a/wgpu/src/widget/canvas/cache.rs b/wgpu/src/widget/canvas/cache.rs deleted file mode 100644 index 09b26b90..00000000 --- a/wgpu/src/widget/canvas/cache.rs +++ /dev/null @@ -1,93 +0,0 @@ -use crate::widget::canvas::{Frame, Geometry}; -use crate::Primitive; - -use iced_native::Size; -use std::{cell::RefCell, sync::Arc}; - -#[derive(Default)] -enum State { - #[default] - Empty, - Filled { - bounds: Size, - primitive: Arc, - }, -} - -/// A simple cache that stores generated [`Geometry`] to avoid recomputation. -/// -/// A [`Cache`] will not redraw its geometry unless the dimensions of its layer -/// change or it is explicitly cleared. -#[derive(Debug, Default)] -pub struct Cache { - state: RefCell, -} - -impl Cache { - /// Creates a new empty [`Cache`]. - pub fn new() -> Self { - Cache { - state: Default::default(), - } - } - - /// Clears the [`Cache`], forcing a redraw the next time it is used. - pub fn clear(&self) { - *self.state.borrow_mut() = State::Empty; - } - - /// Draws [`Geometry`] using the provided closure and stores it in the - /// [`Cache`]. - /// - /// The closure will only be called when - /// - the bounds have changed since the previous draw call. - /// - the [`Cache`] is empty or has been explicitly cleared. - /// - /// Otherwise, the previously stored [`Geometry`] will be returned. The - /// [`Cache`] is not cleared in this case. In other words, it will keep - /// returning the stored [`Geometry`] if needed. - pub fn draw( - &self, - bounds: Size, - draw_fn: impl FnOnce(&mut Frame), - ) -> Geometry { - use std::ops::Deref; - - if let State::Filled { - bounds: cached_bounds, - primitive, - } = self.state.borrow().deref() - { - if *cached_bounds == bounds { - return Geometry::from_primitive(Primitive::Cache { - content: primitive.clone(), - }); - } - } - - let mut frame = Frame::new(bounds); - draw_fn(&mut frame); - - let primitive = Arc::new(frame.into_primitive()); - - *self.state.borrow_mut() = State::Filled { - bounds, - primitive: primitive.clone(), - }; - - Geometry::from_primitive(Primitive::Cache { content: primitive }) - } -} - -impl std::fmt::Debug for State { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - State::Empty => write!(f, "Empty"), - State::Filled { primitive, bounds } => f - .debug_struct("Filled") - .field("primitive", primitive) - .field("bounds", bounds) - .finish(), - } - } -} diff --git a/wgpu/src/widget/canvas/frame.rs b/wgpu/src/widget/canvas/frame.rs deleted file mode 100644 index 987570ec..00000000 --- a/wgpu/src/widget/canvas/frame.rs +++ /dev/null @@ -1,609 +0,0 @@ -use crate::primitive::{self, Primitive}; -use crate::widget::canvas::fill::{self, Fill}; -use crate::widget::canvas::{ - LineCap, LineDash, LineJoin, Path, Stroke, Style, Text, -}; - -use iced_native::{Gradient, Point, Rectangle, Size, Vector}; - -use lyon::geom::euclid; -use lyon::tessellation; -use std::borrow::Cow; - -/// The frame of a [`Canvas`]. -/// -/// [`Canvas`]: crate::widget::Canvas -#[allow(missing_debug_implementations)] -pub struct Frame { - size: Size, - buffers: BufferStack, - primitives: Vec, - transforms: Transforms, - fill_tessellator: tessellation::FillTessellator, - stroke_tessellator: tessellation::StrokeTessellator, -} - -enum Buffer { - Solid(tessellation::VertexBuffers), - Gradient( - tessellation::VertexBuffers, - Gradient, - ), -} - -struct BufferStack { - stack: Vec, -} - -impl BufferStack { - fn new() -> Self { - Self { stack: Vec::new() } - } - - fn get_mut(&mut self, style: &Style) -> &mut Buffer { - match style { - Style::Solid(_) => match self.stack.last() { - Some(Buffer::Solid(_)) => {} - _ => { - self.stack.push(Buffer::Solid( - tessellation::VertexBuffers::new(), - )); - } - }, - Style::Gradient(gradient) => match self.stack.last() { - Some(Buffer::Gradient(_, last)) if gradient == last => {} - _ => { - self.stack.push(Buffer::Gradient( - tessellation::VertexBuffers::new(), - gradient.clone(), - )); - } - }, - } - - self.stack.last_mut().unwrap() - } - - fn get_fill<'a>( - &'a mut self, - style: &Style, - ) -> Box { - match (style, self.get_mut(style)) { - (Style::Solid(color), Buffer::Solid(buffer)) => { - Box::new(tessellation::BuffersBuilder::new( - buffer, - TriangleVertex2DBuilder(color.into_linear()), - )) - } - (Style::Gradient(_), Buffer::Gradient(buffer, _)) => Box::new( - tessellation::BuffersBuilder::new(buffer, Vertex2DBuilder), - ), - _ => unreachable!(), - } - } - - fn get_stroke<'a>( - &'a mut self, - style: &Style, - ) -> Box { - match (style, self.get_mut(style)) { - (Style::Solid(color), Buffer::Solid(buffer)) => { - Box::new(tessellation::BuffersBuilder::new( - buffer, - TriangleVertex2DBuilder(color.into_linear()), - )) - } - (Style::Gradient(_), Buffer::Gradient(buffer, _)) => Box::new( - tessellation::BuffersBuilder::new(buffer, Vertex2DBuilder), - ), - _ => unreachable!(), - } - } -} - -#[derive(Debug)] -struct Transforms { - previous: Vec, - current: Transform, -} - -#[derive(Debug, Clone, Copy)] -struct Transform { - raw: lyon::math::Transform, - is_identity: bool, -} - -impl Transform { - /// Transforms the given [Point] by the transformation matrix. - fn transform_point(&self, point: &mut Point) { - let transformed = self - .raw - .transform_point(euclid::Point2D::new(point.x, point.y)); - point.x = transformed.x; - point.y = transformed.y; - } - - fn transform_style(&self, style: Style) -> Style { - match style { - Style::Solid(color) => Style::Solid(color), - Style::Gradient(gradient) => { - Style::Gradient(self.transform_gradient(gradient)) - } - } - } - - fn transform_gradient(&self, mut gradient: Gradient) -> Gradient { - let (start, end) = match &mut gradient { - Gradient::Linear(linear) => (&mut linear.start, &mut linear.end), - }; - self.transform_point(start); - self.transform_point(end); - gradient - } -} - -impl Frame { - /// Creates a new empty [`Frame`] with the given dimensions. - /// - /// The default coordinate system of a [`Frame`] has its origin at the - /// top-left corner of its bounds. - pub fn new(size: Size) -> Frame { - Frame { - size, - buffers: BufferStack::new(), - primitives: Vec::new(), - transforms: Transforms { - previous: Vec::new(), - current: Transform { - raw: lyon::math::Transform::identity(), - is_identity: true, - }, - }, - fill_tessellator: tessellation::FillTessellator::new(), - stroke_tessellator: tessellation::StrokeTessellator::new(), - } - } - - /// Returns the width of the [`Frame`]. - #[inline] - pub fn width(&self) -> f32 { - self.size.width - } - - /// Returns the height of the [`Frame`]. - #[inline] - pub fn height(&self) -> f32 { - self.size.height - } - - /// Returns the dimensions of the [`Frame`]. - #[inline] - pub fn size(&self) -> Size { - self.size - } - - /// Returns the coordinate of the center of the [`Frame`]. - #[inline] - pub fn center(&self) -> Point { - Point::new(self.size.width / 2.0, self.size.height / 2.0) - } - - /// Draws the given [`Path`] on the [`Frame`] by filling it with the - /// provided style. - pub fn fill(&mut self, path: &Path, fill: impl Into) { - let Fill { style, rule } = fill.into(); - - let mut buffer = self - .buffers - .get_fill(&self.transforms.current.transform_style(style)); - - let options = tessellation::FillOptions::default() - .with_fill_rule(into_fill_rule(rule)); - - if self.transforms.current.is_identity { - self.fill_tessellator.tessellate_path( - path.raw(), - &options, - buffer.as_mut(), - ) - } else { - let path = path.transform(&self.transforms.current.raw); - - self.fill_tessellator.tessellate_path( - path.raw(), - &options, - buffer.as_mut(), - ) - } - .expect("Tessellate path."); - } - - /// Draws an axis-aligned rectangle given its top-left corner coordinate and - /// its `Size` on the [`Frame`] by filling it with the provided style. - pub fn fill_rectangle( - &mut self, - top_left: Point, - size: Size, - fill: impl Into, - ) { - let Fill { style, rule } = fill.into(); - - let mut buffer = self - .buffers - .get_fill(&self.transforms.current.transform_style(style)); - - let top_left = - self.transforms.current.raw.transform_point( - lyon::math::Point::new(top_left.x, top_left.y), - ); - - let size = - self.transforms.current.raw.transform_vector( - lyon::math::Vector::new(size.width, size.height), - ); - - let options = tessellation::FillOptions::default() - .with_fill_rule(into_fill_rule(rule)); - - self.fill_tessellator - .tessellate_rectangle( - &lyon::math::Box2D::new(top_left, top_left + size), - &options, - buffer.as_mut(), - ) - .expect("Fill rectangle"); - } - - /// Draws the stroke of the given [`Path`] on the [`Frame`] with the - /// provided style. - pub fn stroke<'a>(&mut self, path: &Path, stroke: impl Into>) { - let stroke = stroke.into(); - - let mut buffer = self - .buffers - .get_stroke(&self.transforms.current.transform_style(stroke.style)); - - 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); - - let path = if stroke.line_dash.segments.is_empty() { - Cow::Borrowed(path) - } else { - Cow::Owned(dashed(path, stroke.line_dash)) - }; - - if self.transforms.current.is_identity { - self.stroke_tessellator.tessellate_path( - path.raw(), - &options, - buffer.as_mut(), - ) - } else { - let path = path.transform(&self.transforms.current.raw); - - self.stroke_tessellator.tessellate_path( - path.raw(), - &options, - buffer.as_mut(), - ) - } - .expect("Stroke path"); - } - - /// Draws the characters of the given [`Text`] on the [`Frame`], filling - /// them with the given color. - /// - /// __Warning:__ Text currently does not work well with rotations and scale - /// transforms! The position will be correctly transformed, but the - /// resulting glyphs will not be rotated or scaled properly. - /// - /// Additionally, all text will be rendered on top of all the layers of - /// a [`Canvas`]. Therefore, it is currently only meant to be used for - /// overlays, which is the most common use case. - /// - /// Support for vectorial text is planned, and should address all these - /// limitations. - /// - /// [`Canvas`]: crate::widget::Canvas - pub fn fill_text(&mut self, text: impl Into) { - let text = text.into(); - - let position = if self.transforms.current.is_identity { - text.position - } else { - let transformed = self.transforms.current.raw.transform_point( - lyon::math::Point::new(text.position.x, text.position.y), - ); - - Point::new(transformed.x, transformed.y) - }; - - // TODO: Use vectorial text instead of primitive - self.primitives.push(Primitive::Text { - content: text.content, - bounds: Rectangle { - x: position.x, - y: position.y, - width: f32::INFINITY, - height: f32::INFINITY, - }, - color: text.color, - size: text.size, - font: text.font, - horizontal_alignment: text.horizontal_alignment, - vertical_alignment: text.vertical_alignment, - }); - } - - /// Stores the current transform of the [`Frame`] and executes the given - /// drawing operations, restoring the transform afterwards. - /// - /// This method is useful to compose transforms and perform drawing - /// operations in different coordinate systems. - #[inline] - pub fn with_save(&mut self, f: impl FnOnce(&mut Frame)) { - self.push_transform(); - - f(self); - - self.pop_transform(); - } - - pub fn push_transform(&mut self) { - self.transforms.previous.push(self.transforms.current); - } - - pub fn pop_transform(&mut self) { - self.transforms.current = self.transforms.previous.pop().unwrap(); - } - - /// Executes the given drawing operations within a [`Rectangle`] region, - /// clipping any geometry that overflows its bounds. Any transformations - /// performed are local to the provided closure. - /// - /// This method is useful to perform drawing operations that need to be - /// clipped. - #[inline] - pub fn with_clip(&mut self, region: Rectangle, f: impl FnOnce(&mut Frame)) { - let mut frame = Frame::new(region.size()); - - f(&mut frame); - - let translation = Vector::new(region.x, region.y); - - self.clip(frame, translation); - } - - pub fn clip(&mut self, frame: Frame, translation: Vector) { - let size = frame.size(); - let primitives = frame.into_primitives(); - - let (text, meshes) = primitives - .into_iter() - .partition(|primitive| matches!(primitive, Primitive::Text { .. })); - - self.primitives.push(Primitive::Group { - primitives: vec![ - Primitive::Translate { - translation, - content: Box::new(Primitive::Group { primitives: meshes }), - }, - Primitive::Translate { - translation, - content: Box::new(Primitive::Clip { - bounds: Rectangle::with_size(size), - content: Box::new(Primitive::Group { - primitives: text, - }), - }), - }, - ], - }); - } - - /// Applies a translation to the current transform of the [`Frame`]. - #[inline] - pub fn translate(&mut self, translation: Vector) { - self.transforms.current.raw = self - .transforms - .current - .raw - .pre_translate(lyon::math::Vector::new( - translation.x, - translation.y, - )); - self.transforms.current.is_identity = false; - } - - /// Applies a rotation in radians to the current transform of the [`Frame`]. - #[inline] - pub fn rotate(&mut self, angle: f32) { - self.transforms.current.raw = self - .transforms - .current - .raw - .pre_rotate(lyon::math::Angle::radians(angle)); - self.transforms.current.is_identity = false; - } - - /// Applies a scaling to the current transform of the [`Frame`]. - #[inline] - pub fn scale(&mut self, scale: f32) { - self.transforms.current.raw = - self.transforms.current.raw.pre_scale(scale, scale); - self.transforms.current.is_identity = false; - } - - /// Produces the [`Primitive`] representing everything drawn on the [`Frame`]. - pub fn into_primitive(self) -> Primitive { - Primitive::Group { - primitives: self.into_primitives(), - } - } - - fn into_primitives(mut self) -> Vec { - for buffer in self.buffers.stack { - match buffer { - Buffer::Solid(buffer) => { - if !buffer.indices.is_empty() { - self.primitives.push(Primitive::SolidMesh { - buffers: primitive::Mesh2D { - vertices: buffer.vertices, - indices: buffer.indices, - }, - size: self.size, - }) - } - } - Buffer::Gradient(buffer, gradient) => { - if !buffer.indices.is_empty() { - self.primitives.push(Primitive::GradientMesh { - buffers: primitive::Mesh2D { - vertices: buffer.vertices, - indices: buffer.indices, - }, - size: self.size, - gradient, - }) - } - } - } - } - - self.primitives - } -} - -struct Vertex2DBuilder; - -impl tessellation::FillVertexConstructor - for Vertex2DBuilder -{ - fn new_vertex( - &mut self, - vertex: tessellation::FillVertex<'_>, - ) -> primitive::Vertex2D { - let position = vertex.position(); - - primitive::Vertex2D { - position: [position.x, position.y], - } - } -} - -impl tessellation::StrokeVertexConstructor - for Vertex2DBuilder -{ - fn new_vertex( - &mut self, - vertex: tessellation::StrokeVertex<'_, '_>, - ) -> primitive::Vertex2D { - let position = vertex.position(); - - primitive::Vertex2D { - position: [position.x, position.y], - } - } -} - -struct TriangleVertex2DBuilder([f32; 4]); - -impl tessellation::FillVertexConstructor - for TriangleVertex2DBuilder -{ - fn new_vertex( - &mut self, - vertex: tessellation::FillVertex<'_>, - ) -> primitive::ColoredVertex2D { - let position = vertex.position(); - - primitive::ColoredVertex2D { - position: [position.x, position.y], - color: self.0, - } - } -} - -impl tessellation::StrokeVertexConstructor - for TriangleVertex2DBuilder -{ - fn new_vertex( - &mut self, - vertex: tessellation::StrokeVertex<'_, '_>, - ) -> primitive::ColoredVertex2D { - let position = vertex.position(); - - primitive::ColoredVertex2D { - position: [position.x, position.y], - color: self.0, - } - } -} - -fn into_line_join(line_join: LineJoin) -> lyon::tessellation::LineJoin { - match line_join { - LineJoin::Miter => lyon::tessellation::LineJoin::Miter, - LineJoin::Round => lyon::tessellation::LineJoin::Round, - LineJoin::Bevel => lyon::tessellation::LineJoin::Bevel, - } -} - -fn into_line_cap(line_cap: LineCap) -> lyon::tessellation::LineCap { - match line_cap { - LineCap::Butt => lyon::tessellation::LineCap::Butt, - LineCap::Square => lyon::tessellation::LineCap::Square, - LineCap::Round => lyon::tessellation::LineCap::Round, - } -} - -fn into_fill_rule(rule: fill::Rule) -> lyon::tessellation::FillRule { - match rule { - fill::Rule::NonZero => lyon::tessellation::FillRule::NonZero, - fill::Rule::EvenOdd => lyon::tessellation::FillRule::EvenOdd, - } -} - -pub(super) fn dashed(path: &Path, line_dash: LineDash<'_>) -> Path { - use lyon::algorithms::walk::{ - walk_along_path, RepeatedPattern, WalkerEvent, - }; - use lyon::path::iterator::PathIterator; - - Path::new(|builder| { - let segments_odd = (line_dash.segments.len() % 2 == 1) - .then(|| [line_dash.segments, line_dash.segments].concat()); - - let mut draw_line = false; - - walk_along_path( - path.raw().iter().flattened(0.01), - 0.0, - lyon::tessellation::StrokeOptions::DEFAULT_TOLERANCE, - &mut RepeatedPattern { - callback: |event: WalkerEvent<'_>| { - let point = Point { - x: event.position.x, - y: event.position.y, - }; - - if draw_line { - builder.line_to(point); - } else { - builder.move_to(point); - } - - draw_line = !draw_line; - - true - }, - index: line_dash.offset, - intervals: segments_odd - .as_deref() - .unwrap_or(line_dash.segments), - }, - ); - }) -} diff --git a/wgpu/src/widget/canvas/geometry.rs b/wgpu/src/widget/canvas/geometry.rs deleted file mode 100644 index e8ac621d..00000000 --- a/wgpu/src/widget/canvas/geometry.rs +++ /dev/null @@ -1,24 +0,0 @@ -use crate::Primitive; - -/// A bunch of shapes that can be drawn. -/// -/// [`Geometry`] can be easily generated with a [`Frame`] or stored in a -/// [`Cache`]. -/// -/// [`Frame`]: crate::widget::canvas::Frame -/// [`Cache`]: crate::widget::canvas::Cache -#[derive(Debug, Clone)] -pub struct Geometry(Primitive); - -impl Geometry { - pub(crate) fn from_primitive(primitive: Primitive) -> Self { - Self(primitive) - } - - /// Turns the [`Geometry`] into a [`Primitive`]. - /// - /// This can be useful if you are building a custom widget. - pub fn into_primitive(self) -> Primitive { - self.0 - } -} -- cgit From 6cc48b5c62bac287b8f9f1c79c1fb7486c51b18f Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Fri, 3 Mar 2023 04:57:55 +0100 Subject: Move `Canvas` and `QRCode` to `iced` crate Rename `canvas` modules to `geometry` in graphics subcrates --- wgpu/Cargo.toml | 2 +- wgpu/src/backend.rs | 2 - wgpu/src/canvas.rs | 608 --------------------------------------------------- wgpu/src/geometry.rs | 608 +++++++++++++++++++++++++++++++++++++++++++++++++++ wgpu/src/lib.rs | 4 +- 5 files changed, 611 insertions(+), 613 deletions(-) delete mode 100644 wgpu/src/canvas.rs create mode 100644 wgpu/src/geometry.rs (limited to 'wgpu') diff --git a/wgpu/Cargo.toml b/wgpu/Cargo.toml index 0bcef71c..2e39a9e7 100644 --- a/wgpu/Cargo.toml +++ b/wgpu/Cargo.toml @@ -21,7 +21,7 @@ bmp = ["iced_graphics/bmp"] hdr = ["iced_graphics/hdr"] dds = ["iced_graphics/dds"] farbfeld = ["iced_graphics/farbfeld"] -canvas = ["iced_graphics/canvas", "lyon"] +geometry = ["iced_graphics/geometry", "lyon"] spirv = ["wgpu/spirv"] webgl = ["wgpu/webgl"] diff --git a/wgpu/src/backend.rs b/wgpu/src/backend.rs index 10dc5b4f..6f39a5fe 100644 --- a/wgpu/src/backend.rs +++ b/wgpu/src/backend.rs @@ -329,8 +329,6 @@ impl Backend { } impl iced_graphics::Backend for Backend { - type Geometry = (); - fn trim_measurements(&mut self) { self.text_pipeline.trim_measurement_cache() } diff --git a/wgpu/src/canvas.rs b/wgpu/src/canvas.rs deleted file mode 100644 index e8d540c3..00000000 --- a/wgpu/src/canvas.rs +++ /dev/null @@ -1,608 +0,0 @@ -use iced_graphics::primitive::{self, Primitive}; -use iced_native::widget::canvas::fill::{self, Fill}; -use iced_native::widget::canvas::{ - LineCap, LineDash, LineJoin, Path, Stroke, Style, Text, -}; -use iced_native::{Gradient, Point, Rectangle, Size, Vector}; - -use lyon::geom::euclid; -use lyon::tessellation; -use std::borrow::Cow; - -/// The frame of a [`Canvas`]. -/// -/// [`Canvas`]: crate::widget::Canvas -#[allow(missing_debug_implementations)] -pub struct Frame { - size: Size, - buffers: BufferStack, - primitives: Vec, - transforms: Transforms, - fill_tessellator: tessellation::FillTessellator, - stroke_tessellator: tessellation::StrokeTessellator, -} - -enum Buffer { - Solid(tessellation::VertexBuffers), - Gradient( - tessellation::VertexBuffers, - Gradient, - ), -} - -struct BufferStack { - stack: Vec, -} - -impl BufferStack { - fn new() -> Self { - Self { stack: Vec::new() } - } - - fn get_mut(&mut self, style: &Style) -> &mut Buffer { - match style { - Style::Solid(_) => match self.stack.last() { - Some(Buffer::Solid(_)) => {} - _ => { - self.stack.push(Buffer::Solid( - tessellation::VertexBuffers::new(), - )); - } - }, - Style::Gradient(gradient) => match self.stack.last() { - Some(Buffer::Gradient(_, last)) if gradient == last => {} - _ => { - self.stack.push(Buffer::Gradient( - tessellation::VertexBuffers::new(), - gradient.clone(), - )); - } - }, - } - - self.stack.last_mut().unwrap() - } - - fn get_fill<'a>( - &'a mut self, - style: &Style, - ) -> Box { - match (style, self.get_mut(style)) { - (Style::Solid(color), Buffer::Solid(buffer)) => { - Box::new(tessellation::BuffersBuilder::new( - buffer, - TriangleVertex2DBuilder(color.into_linear()), - )) - } - (Style::Gradient(_), Buffer::Gradient(buffer, _)) => Box::new( - tessellation::BuffersBuilder::new(buffer, Vertex2DBuilder), - ), - _ => unreachable!(), - } - } - - fn get_stroke<'a>( - &'a mut self, - style: &Style, - ) -> Box { - match (style, self.get_mut(style)) { - (Style::Solid(color), Buffer::Solid(buffer)) => { - Box::new(tessellation::BuffersBuilder::new( - buffer, - TriangleVertex2DBuilder(color.into_linear()), - )) - } - (Style::Gradient(_), Buffer::Gradient(buffer, _)) => Box::new( - tessellation::BuffersBuilder::new(buffer, Vertex2DBuilder), - ), - _ => unreachable!(), - } - } -} - -#[derive(Debug)] -struct Transforms { - previous: Vec, - current: Transform, -} - -#[derive(Debug, Clone, Copy)] -struct Transform { - raw: lyon::math::Transform, - is_identity: bool, -} - -impl Transform { - /// Transforms the given [Point] by the transformation matrix. - fn transform_point(&self, point: &mut Point) { - let transformed = self - .raw - .transform_point(euclid::Point2D::new(point.x, point.y)); - point.x = transformed.x; - point.y = transformed.y; - } - - fn transform_style(&self, style: Style) -> Style { - match style { - Style::Solid(color) => Style::Solid(color), - Style::Gradient(gradient) => { - Style::Gradient(self.transform_gradient(gradient)) - } - } - } - - fn transform_gradient(&self, mut gradient: Gradient) -> Gradient { - let (start, end) = match &mut gradient { - Gradient::Linear(linear) => (&mut linear.start, &mut linear.end), - }; - self.transform_point(start); - self.transform_point(end); - gradient - } -} - -impl Frame { - /// Creates a new empty [`Frame`] with the given dimensions. - /// - /// The default coordinate system of a [`Frame`] has its origin at the - /// top-left corner of its bounds. - pub fn new(size: Size) -> Frame { - Frame { - size, - buffers: BufferStack::new(), - primitives: Vec::new(), - transforms: Transforms { - previous: Vec::new(), - current: Transform { - raw: lyon::math::Transform::identity(), - is_identity: true, - }, - }, - fill_tessellator: tessellation::FillTessellator::new(), - stroke_tessellator: tessellation::StrokeTessellator::new(), - } - } - - /// Returns the width of the [`Frame`]. - #[inline] - pub fn width(&self) -> f32 { - self.size.width - } - - /// Returns the height of the [`Frame`]. - #[inline] - pub fn height(&self) -> f32 { - self.size.height - } - - /// Returns the dimensions of the [`Frame`]. - #[inline] - pub fn size(&self) -> Size { - self.size - } - - /// Returns the coordinate of the center of the [`Frame`]. - #[inline] - pub fn center(&self) -> Point { - Point::new(self.size.width / 2.0, self.size.height / 2.0) - } - - /// Draws the given [`Path`] on the [`Frame`] by filling it with the - /// provided style. - pub fn fill(&mut self, path: &Path, fill: impl Into) { - let Fill { style, rule } = fill.into(); - - let mut buffer = self - .buffers - .get_fill(&self.transforms.current.transform_style(style)); - - let options = tessellation::FillOptions::default() - .with_fill_rule(into_fill_rule(rule)); - - if self.transforms.current.is_identity { - self.fill_tessellator.tessellate_path( - path.raw(), - &options, - buffer.as_mut(), - ) - } else { - let path = path.transform(&self.transforms.current.raw); - - self.fill_tessellator.tessellate_path( - path.raw(), - &options, - buffer.as_mut(), - ) - } - .expect("Tessellate path."); - } - - /// Draws an axis-aligned rectangle given its top-left corner coordinate and - /// its `Size` on the [`Frame`] by filling it with the provided style. - pub fn fill_rectangle( - &mut self, - top_left: Point, - size: Size, - fill: impl Into, - ) { - let Fill { style, rule } = fill.into(); - - let mut buffer = self - .buffers - .get_fill(&self.transforms.current.transform_style(style)); - - let top_left = - self.transforms.current.raw.transform_point( - lyon::math::Point::new(top_left.x, top_left.y), - ); - - let size = - self.transforms.current.raw.transform_vector( - lyon::math::Vector::new(size.width, size.height), - ); - - let options = tessellation::FillOptions::default() - .with_fill_rule(into_fill_rule(rule)); - - self.fill_tessellator - .tessellate_rectangle( - &lyon::math::Box2D::new(top_left, top_left + size), - &options, - buffer.as_mut(), - ) - .expect("Fill rectangle"); - } - - /// Draws the stroke of the given [`Path`] on the [`Frame`] with the - /// provided style. - pub fn stroke<'a>(&mut self, path: &Path, stroke: impl Into>) { - let stroke = stroke.into(); - - let mut buffer = self - .buffers - .get_stroke(&self.transforms.current.transform_style(stroke.style)); - - 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); - - let path = if stroke.line_dash.segments.is_empty() { - Cow::Borrowed(path) - } else { - Cow::Owned(dashed(path, stroke.line_dash)) - }; - - if self.transforms.current.is_identity { - self.stroke_tessellator.tessellate_path( - path.raw(), - &options, - buffer.as_mut(), - ) - } else { - let path = path.transform(&self.transforms.current.raw); - - self.stroke_tessellator.tessellate_path( - path.raw(), - &options, - buffer.as_mut(), - ) - } - .expect("Stroke path"); - } - - /// Draws the characters of the given [`Text`] on the [`Frame`], filling - /// them with the given color. - /// - /// __Warning:__ Text currently does not work well with rotations and scale - /// transforms! The position will be correctly transformed, but the - /// resulting glyphs will not be rotated or scaled properly. - /// - /// Additionally, all text will be rendered on top of all the layers of - /// a [`Canvas`]. Therefore, it is currently only meant to be used for - /// overlays, which is the most common use case. - /// - /// Support for vectorial text is planned, and should address all these - /// limitations. - /// - /// [`Canvas`]: crate::widget::Canvas - pub fn fill_text(&mut self, text: impl Into) { - let text = text.into(); - - let position = if self.transforms.current.is_identity { - text.position - } else { - let transformed = self.transforms.current.raw.transform_point( - lyon::math::Point::new(text.position.x, text.position.y), - ); - - Point::new(transformed.x, transformed.y) - }; - - // TODO: Use vectorial text instead of primitive - self.primitives.push(Primitive::Text { - content: text.content, - bounds: Rectangle { - x: position.x, - y: position.y, - width: f32::INFINITY, - height: f32::INFINITY, - }, - color: text.color, - size: text.size, - font: text.font, - horizontal_alignment: text.horizontal_alignment, - vertical_alignment: text.vertical_alignment, - }); - } - - /// Stores the current transform of the [`Frame`] and executes the given - /// drawing operations, restoring the transform afterwards. - /// - /// This method is useful to compose transforms and perform drawing - /// operations in different coordinate systems. - #[inline] - pub fn with_save(&mut self, f: impl FnOnce(&mut Frame)) { - self.push_transform(); - - f(self); - - self.pop_transform(); - } - - pub fn push_transform(&mut self) { - self.transforms.previous.push(self.transforms.current); - } - - pub fn pop_transform(&mut self) { - self.transforms.current = self.transforms.previous.pop().unwrap(); - } - - /// Executes the given drawing operations within a [`Rectangle`] region, - /// clipping any geometry that overflows its bounds. Any transformations - /// performed are local to the provided closure. - /// - /// This method is useful to perform drawing operations that need to be - /// clipped. - #[inline] - pub fn with_clip(&mut self, region: Rectangle, f: impl FnOnce(&mut Frame)) { - let mut frame = Frame::new(region.size()); - - f(&mut frame); - - let translation = Vector::new(region.x, region.y); - - self.clip(frame, translation); - } - - pub fn clip(&mut self, frame: Frame, translation: Vector) { - let size = frame.size(); - let primitives = frame.into_primitives(); - - let (text, meshes) = primitives - .into_iter() - .partition(|primitive| matches!(primitive, Primitive::Text { .. })); - - self.primitives.push(Primitive::Group { - primitives: vec![ - Primitive::Translate { - translation, - content: Box::new(Primitive::Group { primitives: meshes }), - }, - Primitive::Translate { - translation, - content: Box::new(Primitive::Clip { - bounds: Rectangle::with_size(size), - content: Box::new(Primitive::Group { - primitives: text, - }), - }), - }, - ], - }); - } - - /// Applies a translation to the current transform of the [`Frame`]. - #[inline] - pub fn translate(&mut self, translation: Vector) { - self.transforms.current.raw = self - .transforms - .current - .raw - .pre_translate(lyon::math::Vector::new( - translation.x, - translation.y, - )); - self.transforms.current.is_identity = false; - } - - /// Applies a rotation in radians to the current transform of the [`Frame`]. - #[inline] - pub fn rotate(&mut self, angle: f32) { - self.transforms.current.raw = self - .transforms - .current - .raw - .pre_rotate(lyon::math::Angle::radians(angle)); - self.transforms.current.is_identity = false; - } - - /// Applies a scaling to the current transform of the [`Frame`]. - #[inline] - pub fn scale(&mut self, scale: f32) { - self.transforms.current.raw = - self.transforms.current.raw.pre_scale(scale, scale); - self.transforms.current.is_identity = false; - } - - /// Produces the [`Primitive`] representing everything drawn on the [`Frame`]. - pub fn into_primitive(self) -> Primitive { - Primitive::Group { - primitives: self.into_primitives(), - } - } - - fn into_primitives(mut self) -> Vec { - for buffer in self.buffers.stack { - match buffer { - Buffer::Solid(buffer) => { - if !buffer.indices.is_empty() { - self.primitives.push(Primitive::SolidMesh { - buffers: primitive::Mesh2D { - vertices: buffer.vertices, - indices: buffer.indices, - }, - size: self.size, - }) - } - } - Buffer::Gradient(buffer, gradient) => { - if !buffer.indices.is_empty() { - self.primitives.push(Primitive::GradientMesh { - buffers: primitive::Mesh2D { - vertices: buffer.vertices, - indices: buffer.indices, - }, - size: self.size, - gradient, - }) - } - } - } - } - - self.primitives - } -} - -struct Vertex2DBuilder; - -impl tessellation::FillVertexConstructor - for Vertex2DBuilder -{ - fn new_vertex( - &mut self, - vertex: tessellation::FillVertex<'_>, - ) -> primitive::Vertex2D { - let position = vertex.position(); - - primitive::Vertex2D { - position: [position.x, position.y], - } - } -} - -impl tessellation::StrokeVertexConstructor - for Vertex2DBuilder -{ - fn new_vertex( - &mut self, - vertex: tessellation::StrokeVertex<'_, '_>, - ) -> primitive::Vertex2D { - let position = vertex.position(); - - primitive::Vertex2D { - position: [position.x, position.y], - } - } -} - -struct TriangleVertex2DBuilder([f32; 4]); - -impl tessellation::FillVertexConstructor - for TriangleVertex2DBuilder -{ - fn new_vertex( - &mut self, - vertex: tessellation::FillVertex<'_>, - ) -> primitive::ColoredVertex2D { - let position = vertex.position(); - - primitive::ColoredVertex2D { - position: [position.x, position.y], - color: self.0, - } - } -} - -impl tessellation::StrokeVertexConstructor - for TriangleVertex2DBuilder -{ - fn new_vertex( - &mut self, - vertex: tessellation::StrokeVertex<'_, '_>, - ) -> primitive::ColoredVertex2D { - let position = vertex.position(); - - primitive::ColoredVertex2D { - position: [position.x, position.y], - color: self.0, - } - } -} - -fn into_line_join(line_join: LineJoin) -> lyon::tessellation::LineJoin { - match line_join { - LineJoin::Miter => lyon::tessellation::LineJoin::Miter, - LineJoin::Round => lyon::tessellation::LineJoin::Round, - LineJoin::Bevel => lyon::tessellation::LineJoin::Bevel, - } -} - -fn into_line_cap(line_cap: LineCap) -> lyon::tessellation::LineCap { - match line_cap { - LineCap::Butt => lyon::tessellation::LineCap::Butt, - LineCap::Square => lyon::tessellation::LineCap::Square, - LineCap::Round => lyon::tessellation::LineCap::Round, - } -} - -fn into_fill_rule(rule: fill::Rule) -> lyon::tessellation::FillRule { - match rule { - fill::Rule::NonZero => lyon::tessellation::FillRule::NonZero, - fill::Rule::EvenOdd => lyon::tessellation::FillRule::EvenOdd, - } -} - -pub(super) fn dashed(path: &Path, line_dash: LineDash<'_>) -> Path { - use lyon::algorithms::walk::{ - walk_along_path, RepeatedPattern, WalkerEvent, - }; - use lyon::path::iterator::PathIterator; - - Path::new(|builder| { - let segments_odd = (line_dash.segments.len() % 2 == 1) - .then(|| [line_dash.segments, line_dash.segments].concat()); - - let mut draw_line = false; - - walk_along_path( - path.raw().iter().flattened(0.01), - 0.0, - lyon::tessellation::StrokeOptions::DEFAULT_TOLERANCE, - &mut RepeatedPattern { - callback: |event: WalkerEvent<'_>| { - let point = Point { - x: event.position.x, - y: event.position.y, - }; - - if draw_line { - builder.line_to(point); - } else { - builder.move_to(point); - } - - draw_line = !draw_line; - - true - }, - index: line_dash.offset, - intervals: segments_odd - .as_deref() - .unwrap_or(line_dash.segments), - }, - ); - }) -} diff --git a/wgpu/src/geometry.rs b/wgpu/src/geometry.rs new file mode 100644 index 00000000..11e8126f --- /dev/null +++ b/wgpu/src/geometry.rs @@ -0,0 +1,608 @@ +use iced_graphics::geometry::fill::{self, Fill}; +use iced_graphics::geometry::{ + LineCap, LineDash, LineJoin, Path, Stroke, Style, Text, +}; +use iced_graphics::primitive::{self, Primitive}; +use iced_graphics::{Gradient, Point, Rectangle, Size, Vector}; + +use lyon::geom::euclid; +use lyon::tessellation; +use std::borrow::Cow; + +/// The frame of a [`Canvas`]. +/// +/// [`Canvas`]: crate::widget::Canvas +#[allow(missing_debug_implementations)] +pub struct Frame { + size: Size, + buffers: BufferStack, + primitives: Vec, + transforms: Transforms, + fill_tessellator: tessellation::FillTessellator, + stroke_tessellator: tessellation::StrokeTessellator, +} + +enum Buffer { + Solid(tessellation::VertexBuffers), + Gradient( + tessellation::VertexBuffers, + Gradient, + ), +} + +struct BufferStack { + stack: Vec, +} + +impl BufferStack { + fn new() -> Self { + Self { stack: Vec::new() } + } + + fn get_mut(&mut self, style: &Style) -> &mut Buffer { + match style { + Style::Solid(_) => match self.stack.last() { + Some(Buffer::Solid(_)) => {} + _ => { + self.stack.push(Buffer::Solid( + tessellation::VertexBuffers::new(), + )); + } + }, + Style::Gradient(gradient) => match self.stack.last() { + Some(Buffer::Gradient(_, last)) if gradient == last => {} + _ => { + self.stack.push(Buffer::Gradient( + tessellation::VertexBuffers::new(), + gradient.clone(), + )); + } + }, + } + + self.stack.last_mut().unwrap() + } + + fn get_fill<'a>( + &'a mut self, + style: &Style, + ) -> Box { + match (style, self.get_mut(style)) { + (Style::Solid(color), Buffer::Solid(buffer)) => { + Box::new(tessellation::BuffersBuilder::new( + buffer, + TriangleVertex2DBuilder(color.into_linear()), + )) + } + (Style::Gradient(_), Buffer::Gradient(buffer, _)) => Box::new( + tessellation::BuffersBuilder::new(buffer, Vertex2DBuilder), + ), + _ => unreachable!(), + } + } + + fn get_stroke<'a>( + &'a mut self, + style: &Style, + ) -> Box { + match (style, self.get_mut(style)) { + (Style::Solid(color), Buffer::Solid(buffer)) => { + Box::new(tessellation::BuffersBuilder::new( + buffer, + TriangleVertex2DBuilder(color.into_linear()), + )) + } + (Style::Gradient(_), Buffer::Gradient(buffer, _)) => Box::new( + tessellation::BuffersBuilder::new(buffer, Vertex2DBuilder), + ), + _ => unreachable!(), + } + } +} + +#[derive(Debug)] +struct Transforms { + previous: Vec, + current: Transform, +} + +#[derive(Debug, Clone, Copy)] +struct Transform { + raw: lyon::math::Transform, + is_identity: bool, +} + +impl Transform { + /// Transforms the given [Point] by the transformation matrix. + fn transform_point(&self, point: &mut Point) { + let transformed = self + .raw + .transform_point(euclid::Point2D::new(point.x, point.y)); + point.x = transformed.x; + point.y = transformed.y; + } + + fn transform_style(&self, style: Style) -> Style { + match style { + Style::Solid(color) => Style::Solid(color), + Style::Gradient(gradient) => { + Style::Gradient(self.transform_gradient(gradient)) + } + } + } + + fn transform_gradient(&self, mut gradient: Gradient) -> Gradient { + let (start, end) = match &mut gradient { + Gradient::Linear(linear) => (&mut linear.start, &mut linear.end), + }; + self.transform_point(start); + self.transform_point(end); + gradient + } +} + +impl Frame { + /// Creates a new empty [`Frame`] with the given dimensions. + /// + /// The default coordinate system of a [`Frame`] has its origin at the + /// top-left corner of its bounds. + pub fn new(size: Size) -> Frame { + Frame { + size, + buffers: BufferStack::new(), + primitives: Vec::new(), + transforms: Transforms { + previous: Vec::new(), + current: Transform { + raw: lyon::math::Transform::identity(), + is_identity: true, + }, + }, + fill_tessellator: tessellation::FillTessellator::new(), + stroke_tessellator: tessellation::StrokeTessellator::new(), + } + } + + /// Returns the width of the [`Frame`]. + #[inline] + pub fn width(&self) -> f32 { + self.size.width + } + + /// Returns the height of the [`Frame`]. + #[inline] + pub fn height(&self) -> f32 { + self.size.height + } + + /// Returns the dimensions of the [`Frame`]. + #[inline] + pub fn size(&self) -> Size { + self.size + } + + /// Returns the coordinate of the center of the [`Frame`]. + #[inline] + pub fn center(&self) -> Point { + Point::new(self.size.width / 2.0, self.size.height / 2.0) + } + + /// Draws the given [`Path`] on the [`Frame`] by filling it with the + /// provided style. + pub fn fill(&mut self, path: &Path, fill: impl Into) { + let Fill { style, rule } = fill.into(); + + let mut buffer = self + .buffers + .get_fill(&self.transforms.current.transform_style(style)); + + let options = tessellation::FillOptions::default() + .with_fill_rule(into_fill_rule(rule)); + + if self.transforms.current.is_identity { + self.fill_tessellator.tessellate_path( + path.raw(), + &options, + buffer.as_mut(), + ) + } else { + let path = path.transform(&self.transforms.current.raw); + + self.fill_tessellator.tessellate_path( + path.raw(), + &options, + buffer.as_mut(), + ) + } + .expect("Tessellate path."); + } + + /// Draws an axis-aligned rectangle given its top-left corner coordinate and + /// its `Size` on the [`Frame`] by filling it with the provided style. + pub fn fill_rectangle( + &mut self, + top_left: Point, + size: Size, + fill: impl Into, + ) { + let Fill { style, rule } = fill.into(); + + let mut buffer = self + .buffers + .get_fill(&self.transforms.current.transform_style(style)); + + let top_left = + self.transforms.current.raw.transform_point( + lyon::math::Point::new(top_left.x, top_left.y), + ); + + let size = + self.transforms.current.raw.transform_vector( + lyon::math::Vector::new(size.width, size.height), + ); + + let options = tessellation::FillOptions::default() + .with_fill_rule(into_fill_rule(rule)); + + self.fill_tessellator + .tessellate_rectangle( + &lyon::math::Box2D::new(top_left, top_left + size), + &options, + buffer.as_mut(), + ) + .expect("Fill rectangle"); + } + + /// Draws the stroke of the given [`Path`] on the [`Frame`] with the + /// provided style. + pub fn stroke<'a>(&mut self, path: &Path, stroke: impl Into>) { + let stroke = stroke.into(); + + let mut buffer = self + .buffers + .get_stroke(&self.transforms.current.transform_style(stroke.style)); + + 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); + + let path = if stroke.line_dash.segments.is_empty() { + Cow::Borrowed(path) + } else { + Cow::Owned(dashed(path, stroke.line_dash)) + }; + + if self.transforms.current.is_identity { + self.stroke_tessellator.tessellate_path( + path.raw(), + &options, + buffer.as_mut(), + ) + } else { + let path = path.transform(&self.transforms.current.raw); + + self.stroke_tessellator.tessellate_path( + path.raw(), + &options, + buffer.as_mut(), + ) + } + .expect("Stroke path"); + } + + /// Draws the characters of the given [`Text`] on the [`Frame`], filling + /// them with the given color. + /// + /// __Warning:__ Text currently does not work well with rotations and scale + /// transforms! The position will be correctly transformed, but the + /// resulting glyphs will not be rotated or scaled properly. + /// + /// Additionally, all text will be rendered on top of all the layers of + /// a [`Canvas`]. Therefore, it is currently only meant to be used for + /// overlays, which is the most common use case. + /// + /// Support for vectorial text is planned, and should address all these + /// limitations. + /// + /// [`Canvas`]: crate::widget::Canvas + pub fn fill_text(&mut self, text: impl Into) { + let text = text.into(); + + let position = if self.transforms.current.is_identity { + text.position + } else { + let transformed = self.transforms.current.raw.transform_point( + lyon::math::Point::new(text.position.x, text.position.y), + ); + + Point::new(transformed.x, transformed.y) + }; + + // TODO: Use vectorial text instead of primitive + self.primitives.push(Primitive::Text { + content: text.content, + bounds: Rectangle { + x: position.x, + y: position.y, + width: f32::INFINITY, + height: f32::INFINITY, + }, + color: text.color, + size: text.size, + font: text.font, + horizontal_alignment: text.horizontal_alignment, + vertical_alignment: text.vertical_alignment, + }); + } + + /// Stores the current transform of the [`Frame`] and executes the given + /// drawing operations, restoring the transform afterwards. + /// + /// This method is useful to compose transforms and perform drawing + /// operations in different coordinate systems. + #[inline] + pub fn with_save(&mut self, f: impl FnOnce(&mut Frame)) { + self.push_transform(); + + f(self); + + self.pop_transform(); + } + + pub fn push_transform(&mut self) { + self.transforms.previous.push(self.transforms.current); + } + + pub fn pop_transform(&mut self) { + self.transforms.current = self.transforms.previous.pop().unwrap(); + } + + /// Executes the given drawing operations within a [`Rectangle`] region, + /// clipping any geometry that overflows its bounds. Any transformations + /// performed are local to the provided closure. + /// + /// This method is useful to perform drawing operations that need to be + /// clipped. + #[inline] + pub fn with_clip(&mut self, region: Rectangle, f: impl FnOnce(&mut Frame)) { + let mut frame = Frame::new(region.size()); + + f(&mut frame); + + let translation = Vector::new(region.x, region.y); + + self.clip(frame, translation); + } + + pub fn clip(&mut self, frame: Frame, translation: Vector) { + let size = frame.size(); + let primitives = frame.into_primitives(); + + let (text, meshes) = primitives + .into_iter() + .partition(|primitive| matches!(primitive, Primitive::Text { .. })); + + self.primitives.push(Primitive::Group { + primitives: vec![ + Primitive::Translate { + translation, + content: Box::new(Primitive::Group { primitives: meshes }), + }, + Primitive::Translate { + translation, + content: Box::new(Primitive::Clip { + bounds: Rectangle::with_size(size), + content: Box::new(Primitive::Group { + primitives: text, + }), + }), + }, + ], + }); + } + + /// Applies a translation to the current transform of the [`Frame`]. + #[inline] + pub fn translate(&mut self, translation: Vector) { + self.transforms.current.raw = self + .transforms + .current + .raw + .pre_translate(lyon::math::Vector::new( + translation.x, + translation.y, + )); + self.transforms.current.is_identity = false; + } + + /// Applies a rotation in radians to the current transform of the [`Frame`]. + #[inline] + pub fn rotate(&mut self, angle: f32) { + self.transforms.current.raw = self + .transforms + .current + .raw + .pre_rotate(lyon::math::Angle::radians(angle)); + self.transforms.current.is_identity = false; + } + + /// Applies a scaling to the current transform of the [`Frame`]. + #[inline] + pub fn scale(&mut self, scale: f32) { + self.transforms.current.raw = + self.transforms.current.raw.pre_scale(scale, scale); + self.transforms.current.is_identity = false; + } + + /// Produces the [`Primitive`] representing everything drawn on the [`Frame`]. + pub fn into_primitive(self) -> Primitive { + Primitive::Group { + primitives: self.into_primitives(), + } + } + + fn into_primitives(mut self) -> Vec { + for buffer in self.buffers.stack { + match buffer { + Buffer::Solid(buffer) => { + if !buffer.indices.is_empty() { + self.primitives.push(Primitive::SolidMesh { + buffers: primitive::Mesh2D { + vertices: buffer.vertices, + indices: buffer.indices, + }, + size: self.size, + }) + } + } + Buffer::Gradient(buffer, gradient) => { + if !buffer.indices.is_empty() { + self.primitives.push(Primitive::GradientMesh { + buffers: primitive::Mesh2D { + vertices: buffer.vertices, + indices: buffer.indices, + }, + size: self.size, + gradient, + }) + } + } + } + } + + self.primitives + } +} + +struct Vertex2DBuilder; + +impl tessellation::FillVertexConstructor + for Vertex2DBuilder +{ + fn new_vertex( + &mut self, + vertex: tessellation::FillVertex<'_>, + ) -> primitive::Vertex2D { + let position = vertex.position(); + + primitive::Vertex2D { + position: [position.x, position.y], + } + } +} + +impl tessellation::StrokeVertexConstructor + for Vertex2DBuilder +{ + fn new_vertex( + &mut self, + vertex: tessellation::StrokeVertex<'_, '_>, + ) -> primitive::Vertex2D { + let position = vertex.position(); + + primitive::Vertex2D { + position: [position.x, position.y], + } + } +} + +struct TriangleVertex2DBuilder([f32; 4]); + +impl tessellation::FillVertexConstructor + for TriangleVertex2DBuilder +{ + fn new_vertex( + &mut self, + vertex: tessellation::FillVertex<'_>, + ) -> primitive::ColoredVertex2D { + let position = vertex.position(); + + primitive::ColoredVertex2D { + position: [position.x, position.y], + color: self.0, + } + } +} + +impl tessellation::StrokeVertexConstructor + for TriangleVertex2DBuilder +{ + fn new_vertex( + &mut self, + vertex: tessellation::StrokeVertex<'_, '_>, + ) -> primitive::ColoredVertex2D { + let position = vertex.position(); + + primitive::ColoredVertex2D { + position: [position.x, position.y], + color: self.0, + } + } +} + +fn into_line_join(line_join: LineJoin) -> lyon::tessellation::LineJoin { + match line_join { + LineJoin::Miter => lyon::tessellation::LineJoin::Miter, + LineJoin::Round => lyon::tessellation::LineJoin::Round, + LineJoin::Bevel => lyon::tessellation::LineJoin::Bevel, + } +} + +fn into_line_cap(line_cap: LineCap) -> lyon::tessellation::LineCap { + match line_cap { + LineCap::Butt => lyon::tessellation::LineCap::Butt, + LineCap::Square => lyon::tessellation::LineCap::Square, + LineCap::Round => lyon::tessellation::LineCap::Round, + } +} + +fn into_fill_rule(rule: fill::Rule) -> lyon::tessellation::FillRule { + match rule { + fill::Rule::NonZero => lyon::tessellation::FillRule::NonZero, + fill::Rule::EvenOdd => lyon::tessellation::FillRule::EvenOdd, + } +} + +pub(super) fn dashed(path: &Path, line_dash: LineDash<'_>) -> Path { + use lyon::algorithms::walk::{ + walk_along_path, RepeatedPattern, WalkerEvent, + }; + use lyon::path::iterator::PathIterator; + + Path::new(|builder| { + let segments_odd = (line_dash.segments.len() % 2 == 1) + .then(|| [line_dash.segments, line_dash.segments].concat()); + + let mut draw_line = false; + + walk_along_path( + path.raw().iter().flattened(0.01), + 0.0, + lyon::tessellation::StrokeOptions::DEFAULT_TOLERANCE, + &mut RepeatedPattern { + callback: |event: WalkerEvent<'_>| { + let point = Point { + x: event.position.x, + y: event.position.y, + }; + + if draw_line { + builder.line_to(point); + } else { + builder.move_to(point); + } + + draw_line = !draw_line; + + true + }, + index: line_dash.offset, + intervals: segments_odd + .as_deref() + .unwrap_or(line_dash.segments), + }, + ); + }) +} diff --git a/wgpu/src/lib.rs b/wgpu/src/lib.rs index 31db16a8..4439b185 100644 --- a/wgpu/src/lib.rs +++ b/wgpu/src/lib.rs @@ -41,8 +41,8 @@ pub mod layer; pub mod settings; pub mod window; -#[cfg(feature = "canvas")] -pub mod canvas; +#[cfg(feature = "geometry")] +pub mod geometry; mod backend; mod buffer; -- cgit From 3a0d34c0240f4421737a6a08761f99d6f8140d02 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Sat, 4 Mar 2023 05:37:11 +0100 Subject: Create `iced_widget` subcrate and re-organize the whole codebase --- wgpu/Cargo.toml | 4 ---- wgpu/src/backend.rs | 20 +++++++++----------- wgpu/src/geometry.rs | 8 ++++---- wgpu/src/image.rs | 21 +++++++++++---------- wgpu/src/image/atlas.rs | 4 ++-- wgpu/src/image/atlas/allocation.rs | 3 +-- wgpu/src/image/atlas/allocator.rs | 4 ++-- wgpu/src/image/atlas/entry.rs | 5 ++--- wgpu/src/layer.rs | 9 +++------ wgpu/src/layer/image.rs | 6 +++--- wgpu/src/layer/mesh.rs | 4 ++-- wgpu/src/layer/text.rs | 3 ++- wgpu/src/lib.rs | 15 +++------------ wgpu/src/quad.rs | 6 +++--- wgpu/src/settings.rs | 5 ++--- wgpu/src/text.rs | 14 ++++++-------- wgpu/src/triangle.rs | 24 +++++++++++------------- wgpu/src/triangle/msaa.rs | 4 ++-- wgpu/src/window/compositor.rs | 10 ++++++---- 19 files changed, 74 insertions(+), 95 deletions(-) (limited to 'wgpu') diff --git a/wgpu/Cargo.toml b/wgpu/Cargo.toml index 2e39a9e7..f3ea66dd 100644 --- a/wgpu/Cargo.toml +++ b/wgpu/Cargo.toml @@ -48,10 +48,6 @@ features = ["std"] version = "1.9" features = ["derive"] -[dependencies.iced_native] -version = "0.9" -path = "../native" - [dependencies.iced_graphics] version = "0.7" path = "../graphics" diff --git a/wgpu/src/backend.rs b/wgpu/src/backend.rs index 6f39a5fe..9c9a1b76 100644 --- a/wgpu/src/backend.rs +++ b/wgpu/src/backend.rs @@ -1,10 +1,11 @@ +use crate::core; +use crate::core::{Color, Font, Point, Size}; +use crate::graphics::backend; +use crate::graphics::{Primitive, Transformation, Viewport}; use crate::quad; use crate::text; use crate::triangle; -use crate::{Layer, Primitive, Settings, Transformation}; - -use iced_graphics::backend; -use iced_graphics::{Color, Font, Size, Viewport}; +use crate::{Layer, Settings}; #[cfg(feature = "tracing")] use tracing::info_span; @@ -363,9 +364,9 @@ impl backend::Text for Backend { size: f32, font: Font, bounds: Size, - point: iced_native::Point, + point: Point, nearest_only: bool, - ) -> Option { + ) -> Option { self.text_pipeline.hit_test( contents, size, @@ -383,17 +384,14 @@ impl backend::Text for Backend { #[cfg(feature = "image")] impl backend::Image for Backend { - fn dimensions(&self, handle: &iced_native::image::Handle) -> Size { + fn dimensions(&self, handle: &core::image::Handle) -> Size { self.image_pipeline.dimensions(handle) } } #[cfg(feature = "svg")] impl backend::Svg for Backend { - fn viewport_dimensions( - &self, - handle: &iced_native::svg::Handle, - ) -> Size { + fn viewport_dimensions(&self, handle: &core::svg::Handle) -> Size { self.image_pipeline.viewport_dimensions(handle) } } diff --git a/wgpu/src/geometry.rs b/wgpu/src/geometry.rs index 11e8126f..59ec31fe 100644 --- a/wgpu/src/geometry.rs +++ b/wgpu/src/geometry.rs @@ -1,9 +1,9 @@ -use iced_graphics::geometry::fill::{self, Fill}; -use iced_graphics::geometry::{ +use crate::core::{Gradient, Point, Rectangle, Size, Vector}; +use crate::graphics::geometry::fill::{self, Fill}; +use crate::graphics::geometry::{ LineCap, LineDash, LineJoin, Path, Stroke, Style, Text, }; -use iced_graphics::primitive::{self, Primitive}; -use iced_graphics::{Gradient, Point, Rectangle, Size, Vector}; +use crate::graphics::primitive::{self, Primitive}; use lyon::geom::euclid; use lyon::tessellation; diff --git a/wgpu/src/image.rs b/wgpu/src/image.rs index 2159a3ec..5eaa9a86 100644 --- a/wgpu/src/image.rs +++ b/wgpu/src/image.rs @@ -1,16 +1,17 @@ mod atlas; -#[cfg(feature = "image")] -use iced_graphics::image::raster; - -#[cfg(feature = "svg")] -use iced_graphics::image::vector; +use atlas::Atlas; +use crate::core::{Rectangle, Size}; +use crate::graphics::Transformation; use crate::layer; -use crate::{Buffer, Transformation}; -use atlas::Atlas; +use crate::Buffer; + +#[cfg(feature = "image")] +use crate::graphics::image::raster; -use iced_native::{Rectangle, Size}; +#[cfg(feature = "svg")] +use crate::graphics::image::vector; use std::cell::RefCell; use std::mem; @@ -18,10 +19,10 @@ use std::mem; use bytemuck::{Pod, Zeroable}; #[cfg(feature = "image")] -use iced_native::image; +use crate::core::image; #[cfg(feature = "svg")] -use iced_native::svg; +use crate::core::svg; #[cfg(feature = "tracing")] use tracing::info_span; diff --git a/wgpu/src/image/atlas.rs b/wgpu/src/image/atlas.rs index 7df67abd..0a17ca33 100644 --- a/wgpu/src/image/atlas.rs +++ b/wgpu/src/image/atlas.rs @@ -12,8 +12,8 @@ use allocator::Allocator; pub const SIZE: u32 = 2048; -use iced_graphics::image; -use iced_graphics::Size; +use crate::core::Size; +use crate::graphics::image; use std::num::NonZeroU32; diff --git a/wgpu/src/image/atlas/allocation.rs b/wgpu/src/image/atlas/allocation.rs index 43aba875..11289771 100644 --- a/wgpu/src/image/atlas/allocation.rs +++ b/wgpu/src/image/atlas/allocation.rs @@ -1,7 +1,6 @@ +use crate::core::Size; use crate::image::atlas::{self, allocator}; -use iced_graphics::Size; - #[derive(Debug)] pub enum Allocation { Partial { diff --git a/wgpu/src/image/atlas/allocator.rs b/wgpu/src/image/atlas/allocator.rs index 03effdcb..204a5c26 100644 --- a/wgpu/src/image/atlas/allocator.rs +++ b/wgpu/src/image/atlas/allocator.rs @@ -46,10 +46,10 @@ impl Region { (rectangle.min.x as u32, rectangle.min.y as u32) } - pub fn size(&self) -> iced_graphics::Size { + pub fn size(&self) -> crate::core::Size { let size = self.allocation.rectangle.size(); - iced_graphics::Size::new(size.width as u32, size.height as u32) + crate::core::Size::new(size.width as u32, size.height as u32) } } diff --git a/wgpu/src/image/atlas/entry.rs b/wgpu/src/image/atlas/entry.rs index 69c05a50..4b06bd95 100644 --- a/wgpu/src/image/atlas/entry.rs +++ b/wgpu/src/image/atlas/entry.rs @@ -1,8 +1,7 @@ +use crate::core::Size; +use crate::graphics::image; use crate::image::atlas; -use iced_graphics::image; -use iced_graphics::Size; - #[derive(Debug)] pub enum Entry { Contiguous(atlas::Allocation), diff --git a/wgpu/src/layer.rs b/wgpu/src/layer.rs index 69fcf899..cb9d5e2f 100644 --- a/wgpu/src/layer.rs +++ b/wgpu/src/layer.rs @@ -10,12 +10,9 @@ pub use mesh::Mesh; pub use quad::Quad; pub use text::Text; -use crate::Primitive; - -use iced_graphics::alignment; -use iced_graphics::{ - Background, Color, Font, Point, Rectangle, Size, Vector, Viewport, -}; +use crate::core::alignment; +use crate::core::{Background, Color, Font, Point, Rectangle, Size, Vector}; +use crate::graphics::{Primitive, Viewport}; /// A group of primitives that should be clipped together. #[derive(Debug)] diff --git a/wgpu/src/layer/image.rs b/wgpu/src/layer/image.rs index 3eff2397..0de589f8 100644 --- a/wgpu/src/layer/image.rs +++ b/wgpu/src/layer/image.rs @@ -1,6 +1,6 @@ -use crate::{Color, Rectangle}; - -use iced_native::{image, svg}; +use crate::core::image; +use crate::core::svg; +use crate::core::{Color, Rectangle}; /// A raster or vector image. #[derive(Debug, Clone)] diff --git a/wgpu/src/layer/mesh.rs b/wgpu/src/layer/mesh.rs index 5c1e41ad..9dd14391 100644 --- a/wgpu/src/layer/mesh.rs +++ b/wgpu/src/layer/mesh.rs @@ -1,6 +1,6 @@ //! A collection of triangle primitives. -use crate::primitive; -use crate::{Gradient, Point, Rectangle}; +use crate::core::{Gradient, Point, Rectangle}; +use crate::graphics::primitive; /// A mesh of triangles. #[derive(Debug, Clone, Copy)] diff --git a/wgpu/src/layer/text.rs b/wgpu/src/layer/text.rs index 38d62616..fdbdaafb 100644 --- a/wgpu/src/layer/text.rs +++ b/wgpu/src/layer/text.rs @@ -1,4 +1,5 @@ -use crate::{alignment, Color, Font, Rectangle}; +use crate::core::alignment; +use crate::core::{Color, Font, Rectangle}; /// A paragraph of text. #[derive(Debug, Clone, Copy)] diff --git a/wgpu/src/lib.rs b/wgpu/src/lib.rs index 4439b185..473f3621 100644 --- a/wgpu/src/lib.rs +++ b/wgpu/src/lib.rs @@ -50,25 +50,17 @@ mod quad; mod text; mod triangle; -pub use iced_graphics::primitive; -pub use iced_graphics::{ - Antialiasing, Color, Error, Font, Gradient, Point, Rectangle, Size, Vector, - Viewport, -}; -pub use iced_native::alignment; +pub use iced_graphics as graphics; +pub use iced_graphics::core; -pub use iced_native::Theme; pub use wgpu; pub use backend::Backend; pub use layer::Layer; -pub use primitive::Primitive; pub use settings::Settings; use buffer::Buffer; -use iced_graphics::Transformation; - #[cfg(any(feature = "image", feature = "svg"))] mod image; @@ -76,5 +68,4 @@ mod image; /// /// [`wgpu`]: https://github.com/gfx-rs/wgpu-rs /// [`iced`]: https://github.com/iced-rs/iced -pub type Renderer = - iced_graphics::Renderer; +pub type Renderer = iced_graphics::Renderer; diff --git a/wgpu/src/quad.rs b/wgpu/src/quad.rs index 8a568968..b55216d7 100644 --- a/wgpu/src/quad.rs +++ b/wgpu/src/quad.rs @@ -1,7 +1,7 @@ +use crate::core::Rectangle; +use crate::graphics::Transformation; use crate::layer; -use crate::{Buffer, Transformation}; - -use iced_native::Rectangle; +use crate::Buffer; use bytemuck::{Pod, Zeroable}; use std::mem; diff --git a/wgpu/src/settings.rs b/wgpu/src/settings.rs index bd9cf473..7c0750ef 100644 --- a/wgpu/src/settings.rs +++ b/wgpu/src/settings.rs @@ -1,7 +1,6 @@ //! Configure a renderer. -pub use crate::Antialiasing; - -use crate::Font; +use crate::core::Font; +use crate::graphics::Antialiasing; /// The settings of a [`Backend`]. /// diff --git a/wgpu/src/text.rs b/wgpu/src/text.rs index 0dc8a64c..d7a27f3a 100644 --- a/wgpu/src/text.rs +++ b/wgpu/src/text.rs @@ -1,10 +1,8 @@ +use crate::core::alignment; +use crate::core::text::Hit; +use crate::core::{Color, Font, Point, Rectangle, Size}; use crate::layer::Text; -pub use iced_native::text::Hit; - -use iced_native::alignment; -use iced_native::{Color, Font, Rectangle, Size}; - use rustc_hash::{FxHashMap, FxHashSet}; use std::borrow::Cow; use std::cell::RefCell; @@ -269,9 +267,9 @@ impl Pipeline { &self, content: &str, size: f32, - font: iced_native::Font, - bounds: iced_native::Size, - point: iced_native::Point, + font: Font, + bounds: Size, + point: Point, _nearest_only: bool, ) -> Option { self.system.as_ref().unwrap().with(|fields| { diff --git a/wgpu/src/triangle.rs b/wgpu/src/triangle.rs index 706e4282..9fa521d7 100644 --- a/wgpu/src/triangle.rs +++ b/wgpu/src/triangle.rs @@ -2,11 +2,9 @@ mod msaa; use crate::buffer::r#static::Buffer; +use crate::core::{Gradient, Size}; +use crate::graphics::{Antialiasing, Transformation}; use crate::layer::mesh::{self, Mesh}; -use crate::settings; -use crate::Transformation; - -use iced_graphics::Size; #[cfg(feature = "tracing")] use tracing::info_span; @@ -137,7 +135,7 @@ impl Layer { gradient_vertex_offset += written_bytes; match gradient { - iced_graphics::Gradient::Linear(linear) => { + Gradient::Linear(linear) => { use glam::{IVec4, Vec4}; let start_offset = self.gradient.color_stop_offset; @@ -319,7 +317,7 @@ impl Pipeline { pub fn new( device: &wgpu::Device, format: wgpu::TextureFormat, - antialiasing: Option, + antialiasing: Option, ) -> Pipeline { Pipeline { blit: antialiasing.map(|a| msaa::Blit::new(device, format, a)), @@ -453,7 +451,7 @@ fn primitive_state() -> wgpu::PrimitiveState { } fn multisample_state( - antialiasing: Option, + antialiasing: Option, ) -> wgpu::MultisampleState { wgpu::MultisampleState { count: antialiasing.map(|a| a.sample_count()).unwrap_or(1), @@ -465,11 +463,11 @@ fn multisample_state( mod solid { use crate::buffer::dynamic; use crate::buffer::r#static::Buffer; - use crate::settings; + use crate::graphics::primitive; + use crate::graphics::{Antialiasing, Transformation}; use crate::triangle; + use encase::ShaderType; - use iced_graphics::primitive; - use iced_graphics::Transformation; #[derive(Debug)] pub struct Pipeline { @@ -550,7 +548,7 @@ mod solid { pub fn new( device: &wgpu::Device, format: wgpu::TextureFormat, - antialiasing: Option, + antialiasing: Option, ) -> Self { let constants_layout = device.create_bind_group_layout( &wgpu::BindGroupLayoutDescriptor { @@ -633,7 +631,7 @@ mod solid { mod gradient { use crate::buffer::dynamic; use crate::buffer::r#static::Buffer; - use crate::settings; + use crate::graphics::Antialiasing; use crate::triangle; use encase::ShaderType; @@ -755,7 +753,7 @@ mod gradient { pub(super) fn new( device: &wgpu::Device, format: wgpu::TextureFormat, - antialiasing: Option, + antialiasing: Option, ) -> Self { let constants_layout = device.create_bind_group_layout( &wgpu::BindGroupLayoutDescriptor { diff --git a/wgpu/src/triangle/msaa.rs b/wgpu/src/triangle/msaa.rs index a3016ff8..7144125c 100644 --- a/wgpu/src/triangle/msaa.rs +++ b/wgpu/src/triangle/msaa.rs @@ -1,4 +1,4 @@ -use crate::settings; +use crate::graphics; #[derive(Debug)] pub struct Blit { @@ -14,7 +14,7 @@ impl Blit { pub fn new( device: &wgpu::Device, format: wgpu::TextureFormat, - antialiasing: settings::Antialiasing, + antialiasing: graphics::Antialiasing, ) -> Blit { let sampler = device.create_sampler(&wgpu::SamplerDescriptor { address_mode_u: wgpu::AddressMode::ClampToEdge, diff --git a/wgpu/src/window/compositor.rs b/wgpu/src/window/compositor.rs index 3a4a7123..a67ac3c0 100644 --- a/wgpu/src/window/compositor.rs +++ b/wgpu/src/window/compositor.rs @@ -1,10 +1,12 @@ //! Connect a window with a renderer. -use crate::{Backend, Color, Error, Primitive, Renderer, Settings, Viewport}; +use crate::core::Color; +use crate::graphics; +use crate::graphics::compositor; +use crate::graphics::{Error, Primitive, Viewport}; +use crate::{Backend, Renderer, Settings}; use futures::stream::{self, StreamExt}; -use iced_graphics::window::compositor; -use iced_native::futures; use raw_window_handle::{HasRawDisplayHandle, HasRawWindowHandle}; use std::marker::PhantomData; @@ -184,7 +186,7 @@ pub fn present>( } } -impl iced_graphics::window::Compositor for Compositor { +impl graphics::Compositor for Compositor { type Settings = Settings; type Renderer = Renderer; type Surface = wgpu::Surface; -- cgit From 5fed065dc3aa3d2f9ff8d229cbffe003a89ba033 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Sat, 4 Mar 2023 05:56:10 +0100 Subject: Update `glyphon` in `iced_wgpu` --- wgpu/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'wgpu') diff --git a/wgpu/Cargo.toml b/wgpu/Cargo.toml index 4cdd6c68..50a81a91 100644 --- a/wgpu/Cargo.toml +++ b/wgpu/Cargo.toml @@ -55,7 +55,7 @@ path = "../graphics" [dependencies.glyphon] version = "0.2" git = "https://github.com/hecrj/glyphon.git" -rev = "810bc979f9005e2bd343b72b980e57e46174283f" +rev = "edd23695ad53db5f89d455c3c130172fd107d6a2" [dependencies.encase] version = "0.3.0" -- cgit From 06bbcc310e6e759a0839df6ca391ea5e0f0ee609 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Sun, 5 Mar 2023 06:40:20 +0100 Subject: Move `webgl` feature selection for `wgpu` into `iced_wgpu` --- wgpu/Cargo.toml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) (limited to 'wgpu') diff --git a/wgpu/Cargo.toml b/wgpu/Cargo.toml index 50a81a91..5601c0de 100644 --- a/wgpu/Cargo.toml +++ b/wgpu/Cargo.toml @@ -23,7 +23,6 @@ dds = ["iced_graphics/dds"] farbfeld = ["iced_graphics/farbfeld"] geometry = ["iced_graphics/geometry", "lyon"] spirv = ["wgpu/spirv"] -webgl = ["wgpu/webgl"] [dependencies] wgpu = "0.14" @@ -36,6 +35,9 @@ once_cell = "1.0" rustc-hash = "1.1" ouroboros = "0.15" +[target.'cfg(target_arch = "wasm32")'.dependencies] +wgpu = { version = "0.14", features = ["webgl"] } + [dependencies.twox-hash] version = "1.6" default-features = false -- cgit From 3a26baa564524b0f25c5cb180b592c8b004b68a9 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Tue, 7 Mar 2023 03:47:49 +0100 Subject: Remove `image` abstractions in `iced_graphics` --- wgpu/Cargo.toml | 20 ++--- wgpu/src/backend.rs | 2 +- wgpu/src/image.rs | 39 ++++----- wgpu/src/image/atlas.rs | 190 ++++++++++++++++++++---------------------- wgpu/src/image/atlas/entry.rs | 6 +- wgpu/src/image/raster.rs | 121 +++++++++++++++++++++++++++ wgpu/src/image/vector.rs | 181 ++++++++++++++++++++++++++++++++++++++++ 7 files changed, 420 insertions(+), 139 deletions(-) create mode 100644 wgpu/src/image/raster.rs create mode 100644 wgpu/src/image/vector.rs (limited to 'wgpu') diff --git a/wgpu/Cargo.toml b/wgpu/Cargo.toml index 5601c0de..6a313d4f 100644 --- a/wgpu/Cargo.toml +++ b/wgpu/Cargo.toml @@ -8,21 +8,9 @@ license = "MIT AND OFL-1.1" repository = "https://github.com/iced-rs/iced" [features] -svg = ["iced_graphics/svg"] -image = ["iced_graphics/image"] -png = ["iced_graphics/png"] -jpeg = ["iced_graphics/jpeg"] -jpeg_rayon = ["iced_graphics/jpeg_rayon"] -gif = ["iced_graphics/gif"] -webp = ["iced_graphics/webp"] -pnm = ["iced_graphics/pnm"] -ico = ["iced_graphics/ico"] -bmp = ["iced_graphics/bmp"] -hdr = ["iced_graphics/hdr"] -dds = ["iced_graphics/dds"] -farbfeld = ["iced_graphics/farbfeld"] geometry = ["iced_graphics/geometry", "lyon"] -spirv = ["wgpu/spirv"] +image = ["iced_graphics/image"] +svg = ["resvg"] [dependencies] wgpu = "0.14" @@ -70,6 +58,10 @@ version = "0.21.3" version = "1.0" optional = true +[dependencies.resvg] +version = "0.29" +optional = true + [dependencies.tracing] version = "0.1.6" optional = true diff --git a/wgpu/src/backend.rs b/wgpu/src/backend.rs index 9c9a1b76..88c58554 100644 --- a/wgpu/src/backend.rs +++ b/wgpu/src/backend.rs @@ -119,7 +119,7 @@ impl Backend { self.triangle_pipeline.end_frame(); #[cfg(any(feature = "image", feature = "svg"))] - self.image_pipeline.end_frame(device, queue, encoder); + self.image_pipeline.end_frame(); } fn prepare_text( diff --git a/wgpu/src/image.rs b/wgpu/src/image.rs index 5eaa9a86..4163e338 100644 --- a/wgpu/src/image.rs +++ b/wgpu/src/image.rs @@ -1,5 +1,11 @@ mod atlas; +#[cfg(feature = "image")] +mod raster; + +#[cfg(feature = "svg")] +mod vector; + use atlas::Atlas; use crate::core::{Rectangle, Size}; @@ -7,12 +13,6 @@ use crate::graphics::Transformation; use crate::layer; use crate::Buffer; -#[cfg(feature = "image")] -use crate::graphics::image::raster; - -#[cfg(feature = "svg")] -use crate::graphics::image::vector; - use std::cell::RefCell; use std::mem; @@ -30,9 +30,9 @@ use tracing::info_span; #[derive(Debug)] pub struct Pipeline { #[cfg(feature = "image")] - raster_cache: RefCell>, + raster_cache: RefCell, #[cfg(feature = "svg")] - vector_cache: RefCell>, + vector_cache: RefCell, pipeline: wgpu::RenderPipeline, vertices: wgpu::Buffer, @@ -368,8 +368,10 @@ impl Pipeline { #[cfg(feature = "image")] layer::Image::Raster { handle, bounds } => { if let Some(atlas_entry) = raster_cache.upload( + device, + queue, + encoder, handle, - &mut (device, queue, encoder), &mut self.texture_atlas, ) { add_instances( @@ -392,11 +394,13 @@ impl Pipeline { let size = [bounds.width, bounds.height]; if let Some(atlas_entry) = vector_cache.upload( + device, + queue, + encoder, handle, *color, size, _scale, - &mut (device, queue, encoder), &mut self.texture_atlas, ) { add_instances( @@ -477,21 +481,12 @@ impl Pipeline { } } - pub fn end_frame( - &mut self, - device: &wgpu::Device, - queue: &wgpu::Queue, - encoder: &mut wgpu::CommandEncoder, - ) { + pub fn end_frame(&mut self) { #[cfg(feature = "image")] - self.raster_cache - .borrow_mut() - .trim(&mut self.texture_atlas, &mut (device, queue, encoder)); + self.raster_cache.borrow_mut().trim(&mut self.texture_atlas); #[cfg(feature = "svg")] - self.vector_cache - .borrow_mut() - .trim(&mut self.texture_atlas, &mut (device, queue, encoder)); + self.vector_cache.borrow_mut().trim(&mut self.texture_atlas); self.prepare_layer = 0; } diff --git a/wgpu/src/image/atlas.rs b/wgpu/src/image/atlas.rs index 0a17ca33..c00b8cef 100644 --- a/wgpu/src/image/atlas.rs +++ b/wgpu/src/image/atlas.rs @@ -13,7 +13,6 @@ use allocator::Allocator; pub const SIZE: u32 = 2048; use crate::core::Size; -use crate::graphics::image; use std::num::NonZeroU32; @@ -64,6 +63,97 @@ impl Atlas { self.layers.len() } + pub fn upload( + &mut self, + device: &wgpu::Device, + queue: &wgpu::Queue, + encoder: &mut wgpu::CommandEncoder, + width: u32, + height: u32, + data: &[u8], + ) -> Option { + let entry = { + let current_size = self.layers.len(); + let entry = self.allocate(width, height)?; + + // We grow the internal texture after allocating if necessary + let new_layers = self.layers.len() - current_size; + self.grow(new_layers, device, encoder); + + entry + }; + + log::info!("Allocated atlas entry: {:?}", entry); + + // It is a webgpu requirement that: + // BufferCopyView.layout.bytes_per_row % wgpu::COPY_BYTES_PER_ROW_ALIGNMENT == 0 + // So we calculate padded_width by rounding width up to the next + // multiple of wgpu::COPY_BYTES_PER_ROW_ALIGNMENT. + let align = wgpu::COPY_BYTES_PER_ROW_ALIGNMENT; + let padding = (align - (4 * width) % align) % align; + let padded_width = (4 * width + padding) as usize; + let padded_data_size = padded_width * height as usize; + + let mut padded_data = vec![0; padded_data_size]; + + for row in 0..height as usize { + let offset = row * padded_width; + + padded_data[offset..offset + 4 * width as usize].copy_from_slice( + &data[row * 4 * width as usize..(row + 1) * 4 * width as usize], + ) + } + + match &entry { + Entry::Contiguous(allocation) => { + self.upload_allocation( + &padded_data, + width, + height, + padding, + 0, + allocation, + queue, + ); + } + Entry::Fragmented { fragments, .. } => { + for fragment in fragments { + let (x, y) = fragment.position; + let offset = (y * padded_width as u32 + 4 * x) as usize; + + self.upload_allocation( + &padded_data, + width, + height, + padding, + offset, + &fragment.allocation, + queue, + ); + } + } + } + + log::info!("Current atlas: {:?}", self); + + Some(entry) + } + + pub fn remove(&mut self, entry: &Entry) { + log::info!("Removing atlas entry: {:?}", entry); + + match entry { + Entry::Contiguous(allocation) => { + self.deallocate(allocation); + } + Entry::Fragmented { fragments, .. } => { + for fragment in fragments { + self.deallocate(&fragment.allocation); + } + } + } + } + fn allocate(&mut self, width: u32, height: u32) -> Option { // Allocate one layer if texture fits perfectly if width == SIZE && height == SIZE { @@ -296,101 +386,3 @@ impl Atlas { }); } } - -impl image::Storage for Atlas { - type Entry = Entry; - type State<'a> = ( - &'a wgpu::Device, - &'a wgpu::Queue, - &'a mut wgpu::CommandEncoder, - ); - - fn upload( - &mut self, - width: u32, - height: u32, - data: &[u8], - (device, queue, encoder): &mut Self::State<'_>, - ) -> Option { - let entry = { - let current_size = self.layers.len(); - let entry = self.allocate(width, height)?; - - // We grow the internal texture after allocating if necessary - let new_layers = self.layers.len() - current_size; - self.grow(new_layers, device, encoder); - - entry - }; - - log::info!("Allocated atlas entry: {:?}", entry); - - // It is a webgpu requirement that: - // BufferCopyView.layout.bytes_per_row % wgpu::COPY_BYTES_PER_ROW_ALIGNMENT == 0 - // So we calculate padded_width by rounding width up to the next - // multiple of wgpu::COPY_BYTES_PER_ROW_ALIGNMENT. - let align = wgpu::COPY_BYTES_PER_ROW_ALIGNMENT; - let padding = (align - (4 * width) % align) % align; - let padded_width = (4 * width + padding) as usize; - let padded_data_size = padded_width * height as usize; - - let mut padded_data = vec![0; padded_data_size]; - - for row in 0..height as usize { - let offset = row * padded_width; - - padded_data[offset..offset + 4 * width as usize].copy_from_slice( - &data[row * 4 * width as usize..(row + 1) * 4 * width as usize], - ) - } - - match &entry { - Entry::Contiguous(allocation) => { - self.upload_allocation( - &padded_data, - width, - height, - padding, - 0, - allocation, - queue, - ); - } - Entry::Fragmented { fragments, .. } => { - for fragment in fragments { - let (x, y) = fragment.position; - let offset = (y * padded_width as u32 + 4 * x) as usize; - - self.upload_allocation( - &padded_data, - width, - height, - padding, - offset, - &fragment.allocation, - queue, - ); - } - } - } - - log::info!("Current atlas: {:?}", self); - - Some(entry) - } - - fn remove(&mut self, entry: &Entry, _: &mut Self::State<'_>) { - log::info!("Removing atlas entry: {:?}", entry); - - match entry { - Entry::Contiguous(allocation) => { - self.deallocate(allocation); - } - Entry::Fragmented { fragments, .. } => { - for fragment in fragments { - self.deallocate(&fragment.allocation); - } - } - } - } -} diff --git a/wgpu/src/image/atlas/entry.rs b/wgpu/src/image/atlas/entry.rs index 4b06bd95..7e4c92a2 100644 --- a/wgpu/src/image/atlas/entry.rs +++ b/wgpu/src/image/atlas/entry.rs @@ -1,5 +1,4 @@ use crate::core::Size; -use crate::graphics::image; use crate::image::atlas; #[derive(Debug)] @@ -11,8 +10,9 @@ pub enum Entry { }, } -impl image::storage::Entry for Entry { - fn size(&self) -> Size { +impl Entry { + #[cfg(feature = "image")] + pub fn size(&self) -> Size { match self { Entry::Contiguous(allocation) => allocation.size(), Entry::Fragmented { size, .. } => *size, diff --git a/wgpu/src/image/raster.rs b/wgpu/src/image/raster.rs new file mode 100644 index 00000000..9b38dce4 --- /dev/null +++ b/wgpu/src/image/raster.rs @@ -0,0 +1,121 @@ +use crate::core::image; +use crate::core::Size; +use crate::graphics; +use crate::graphics::image::image_rs; +use crate::image::atlas::{self, Atlas}; + +use std::collections::{HashMap, HashSet}; + +/// Entry in cache corresponding to an image handle +#[derive(Debug)] +pub enum Memory { + /// Image data on host + Host(image_rs::ImageBuffer, Vec>), + /// Storage entry + Device(atlas::Entry), + /// Image not found + NotFound, + /// Invalid image data + Invalid, +} + +impl Memory { + /// Width and height of image + pub fn dimensions(&self) -> Size { + match self { + Memory::Host(image) => { + let (width, height) = image.dimensions(); + + Size::new(width, height) + } + Memory::Device(entry) => entry.size(), + Memory::NotFound => Size::new(1, 1), + Memory::Invalid => Size::new(1, 1), + } + } +} + +/// Caches image raster data +#[derive(Debug, Default)] +pub struct Cache { + map: HashMap, + hits: HashSet, +} + +impl Cache { + /// Load image + pub fn load(&mut self, handle: &image::Handle) -> &mut Memory { + if self.contains(handle) { + return self.get(handle).unwrap(); + } + + let memory = match graphics::image::load(handle) { + Ok(image) => Memory::Host(image.to_rgba8()), + Err(image_rs::error::ImageError::IoError(_)) => Memory::NotFound, + Err(_) => Memory::Invalid, + }; + + self.insert(handle, memory); + self.get(handle).unwrap() + } + + /// Load image and upload raster data + pub fn upload( + &mut self, + device: &wgpu::Device, + queue: &wgpu::Queue, + encoder: &mut wgpu::CommandEncoder, + handle: &image::Handle, + atlas: &mut Atlas, + ) -> Option<&atlas::Entry> { + let memory = self.load(handle); + + if let Memory::Host(image) = memory { + let (width, height) = image.dimensions(); + + let entry = + atlas.upload(device, queue, encoder, width, height, image)?; + + *memory = Memory::Device(entry); + } + + if let Memory::Device(allocation) = memory { + Some(allocation) + } else { + None + } + } + + /// Trim cache misses from cache + pub fn trim(&mut self, atlas: &mut Atlas) { + let hits = &self.hits; + + self.map.retain(|k, memory| { + let retain = hits.contains(k); + + if !retain { + if let Memory::Device(entry) = memory { + atlas.remove(entry); + } + } + + retain + }); + + 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..3624e46b --- /dev/null +++ b/wgpu/src/image/vector.rs @@ -0,0 +1,181 @@ +use crate::core::svg; +use crate::core::{Color, Size}; +use crate::image::atlas::{self, Atlas}; + +use resvg::tiny_skia; +use resvg::usvg; +use std::collections::{HashMap, HashSet}; +use std::fs; + +/// Entry in cache corresponding to an svg handle +pub enum Svg { + /// Parsed svg + Loaded(usvg::Tree), + /// Svg not found or failed to parse + NotFound, +} + +impl Svg { + /// Viewport width and height + pub fn viewport_dimensions(&self) -> Size { + match self { + Svg::Loaded(tree) => { + let size = tree.size; + + Size::new(size.width() as u32, size.height() as u32) + } + Svg::NotFound => Size::new(1, 1), + } + } +} + +/// Caches svg vector and raster data +#[derive(Debug, Default)] +pub struct Cache { + svgs: HashMap, + rasterized: HashMap<(u64, u32, u32, ColorFilter), atlas::Entry>, + svg_hits: HashSet, + rasterized_hits: HashSet<(u64, u32, u32, ColorFilter)>, +} + +type ColorFilter = Option<[u8; 4]>; + +impl Cache { + /// Load svg + pub fn load(&mut self, handle: &svg::Handle) -> &Svg { + if self.svgs.contains_key(&handle.id()) { + return self.svgs.get(&handle.id()).unwrap(); + } + + let svg = match handle.data() { + svg::Data::Path(path) => { + let tree = fs::read_to_string(path).ok().and_then(|contents| { + usvg::Tree::from_str(&contents, &usvg::Options::default()) + .ok() + }); + + tree.map(Svg::Loaded).unwrap_or(Svg::NotFound) + } + svg::Data::Bytes(bytes) => { + match usvg::Tree::from_data(bytes, &usvg::Options::default()) { + Ok(tree) => Svg::Loaded(tree), + Err(_) => Svg::NotFound, + } + } + }; + + let _ = self.svgs.insert(handle.id(), svg); + self.svgs.get(&handle.id()).unwrap() + } + + /// Load svg and upload raster data + pub fn upload( + &mut self, + device: &wgpu::Device, + queue: &wgpu::Queue, + encoder: &mut wgpu::CommandEncoder, + handle: &svg::Handle, + color: Option, + [width, height]: [f32; 2], + scale: f32, + atlas: &mut Atlas, + ) -> Option<&atlas::Entry> { + let id = handle.id(); + + let (width, height) = ( + (scale * width).ceil() as u32, + (scale * height).ceil() as u32, + ); + + let color = color.map(Color::into_rgba8); + let key = (id, width, height, color); + + // 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 `svg` example. + if self.rasterized.contains_key(&key) { + let _ = self.svg_hits.insert(id); + let _ = self.rasterized_hits.insert(key); + + return self.rasterized.get(&key); + } + + match self.load(handle) { + Svg::Loaded(tree) => { + if width == 0 || height == 0 { + return None; + } + + // 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 `svg` example. + let mut img = tiny_skia::Pixmap::new(width, height)?; + + resvg::render( + tree, + if width > height { + usvg::FitTo::Width(width) + } else { + usvg::FitTo::Height(height) + }, + tiny_skia::Transform::default(), + img.as_mut(), + )?; + + let mut rgba = img.take(); + + if let Some(color) = color { + rgba.chunks_exact_mut(4).for_each(|rgba| { + if rgba[3] > 0 { + rgba[0] = color[0]; + rgba[1] = color[1]; + rgba[2] = color[2]; + } + }); + } + + let allocation = atlas + .upload(device, queue, encoder, width, height, &rgba)?; + + log::debug!("allocating {} {}x{}", id, width, height); + + let _ = self.svg_hits.insert(id); + let _ = self.rasterized_hits.insert(key); + let _ = self.rasterized.insert(key, allocation); + + self.rasterized.get(&key) + } + Svg::NotFound => None, + } + } + + /// Load svg and upload raster data + pub fn trim(&mut self, atlas: &mut Atlas) { + let svg_hits = &self.svg_hits; + let rasterized_hits = &self.rasterized_hits; + + self.svgs.retain(|k, _| svg_hits.contains(k)); + self.rasterized.retain(|k, entry| { + let retain = rasterized_hits.contains(k); + + if !retain { + atlas.remove(entry); + } + + retain + }); + self.svg_hits.clear(); + self.rasterized_hits.clear(); + } +} + +impl std::fmt::Debug for Svg { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Svg::Loaded(_) => write!(f, "Svg::Loaded"), + Svg::NotFound => write!(f, "Svg::NotFound"), + } + } +} -- cgit From ea50ec8df1431c9c6aa8077cd1578c4698dc0314 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Fri, 17 Mar 2023 19:58:42 +0100 Subject: Trim text `Buffer` cache every frame in `iced_wgpu` and `iced_tiny_skia` --- wgpu/src/text.rs | 16 +++------------- 1 file changed, 3 insertions(+), 13 deletions(-) (limited to 'wgpu') diff --git a/wgpu/src/text.rs b/wgpu/src/text.rs index e99844e6..35f24cd9 100644 --- a/wgpu/src/text.rs +++ b/wgpu/src/text.rs @@ -321,7 +321,6 @@ struct Cache<'a> { entries: FxHashMap>, recently_used: FxHashSet, hasher: HashBuilder, - trim_count: usize, } #[cfg(not(target_arch = "wasm32"))] @@ -331,14 +330,11 @@ type HashBuilder = twox_hash::RandomXxHashBuilder64; type HashBuilder = std::hash::BuildHasherDefault; impl<'a> Cache<'a> { - const TRIM_INTERVAL: usize = 300; - fn new() -> Self { Self { entries: FxHashMap::default(), recently_used: FxHashSet::default(), hasher: HashBuilder::default(), - trim_count: 0, } } @@ -387,16 +383,10 @@ impl<'a> Cache<'a> { } fn trim(&mut self) { - if self.trim_count >= Self::TRIM_INTERVAL { - self.entries - .retain(|key, _| self.recently_used.contains(key)); - - self.recently_used.clear(); + self.entries + .retain(|key, _| self.recently_used.contains(key)); - self.trim_count = 0; - } else { - self.trim_count += 1; - } + self.recently_used.clear(); } } -- cgit From 5f9e7f6cb99467363d691086cb697b2390793afd Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Sun, 19 Mar 2023 14:52:30 +0100 Subject: Update `cosmic-text` to latest :tada: --- wgpu/Cargo.toml | 3 +- wgpu/src/text.rs | 372 ++++++++++++++++++++++++------------------------------- 2 files changed, 166 insertions(+), 209 deletions(-) (limited to 'wgpu') diff --git a/wgpu/Cargo.toml b/wgpu/Cargo.toml index 6a313d4f..3aef4ff4 100644 --- a/wgpu/Cargo.toml +++ b/wgpu/Cargo.toml @@ -21,7 +21,6 @@ futures = "0.3" bitflags = "1.2" once_cell = "1.0" rustc-hash = "1.1" -ouroboros = "0.15" [target.'cfg(target_arch = "wasm32")'.dependencies] wgpu = { version = "0.14", features = ["webgl"] } @@ -45,7 +44,7 @@ path = "../graphics" [dependencies.glyphon] version = "0.2" git = "https://github.com/hecrj/glyphon.git" -rev = "edd23695ad53db5f89d455c3c130172fd107d6a2" +rev = "47050174841a4f58fc8d85c943a2117f72f19e8e" [dependencies.encase] version = "0.3.0" diff --git a/wgpu/src/text.rs b/wgpu/src/text.rs index 35f24cd9..ac116f69 100644 --- a/wgpu/src/text.rs +++ b/wgpu/src/text.rs @@ -12,23 +12,12 @@ use std::sync::Arc; #[allow(missing_debug_implementations)] pub struct Pipeline { - system: Option, + font_system: RefCell, renderers: Vec, atlas: glyphon::TextAtlas, prepare_layer: usize, -} - -#[ouroboros::self_referencing] -struct System { - fonts: glyphon::FontSystem, - - #[borrows(fonts)] - #[not_covariant] - measurement_cache: RefCell>, - - #[borrows(fonts)] - #[not_covariant] - render_cache: Cache<'this>, + measurement_cache: RefCell, + render_cache: Cache, } impl Pipeline { @@ -38,42 +27,23 @@ impl Pipeline { format: wgpu::TextureFormat, ) -> Self { Pipeline { - system: Some( - SystemBuilder { - fonts: glyphon::FontSystem::new_with_fonts( - [glyphon::fontdb::Source::Binary(Arc::new( - include_bytes!("../fonts/Iced-Icons.ttf") - .as_slice(), - ))] - .into_iter(), - ), - measurement_cache_builder: |_| RefCell::new(Cache::new()), - render_cache_builder: |_| Cache::new(), - } - .build(), - ), + font_system: RefCell::new(glyphon::FontSystem::new_with_fonts( + [glyphon::fontdb::Source::Binary(Arc::new( + include_bytes!("../fonts/Iced-Icons.ttf").as_slice(), + ))] + .into_iter(), + )), renderers: Vec::new(), atlas: glyphon::TextAtlas::new(device, queue, format), prepare_layer: 0, + measurement_cache: RefCell::new(Cache::new()), + render_cache: Cache::new(), } } pub fn load_font(&mut self, bytes: Cow<'static, [u8]>) { - let heads = self.system.take().unwrap().into_heads(); - - let (locale, mut db) = heads.fonts.into_locale_and_db(); - - db.load_font_source(glyphon::fontdb::Source::Binary(Arc::new( - bytes.into_owned(), - ))); - - self.system = Some( - SystemBuilder { - fonts: glyphon::FontSystem::new_with_locale_and_db(locale, db), - measurement_cache_builder: |_| RefCell::new(Cache::new()), - render_cache_builder: |_| Cache::new(), - } - .build(), + self.font_system.get_mut().db_mut().load_font_source( + glyphon::fontdb::Source::Binary(Arc::new(bytes.into_owned())), ); } @@ -86,126 +56,123 @@ impl Pipeline { scale_factor: f32, target_size: Size, ) -> bool { - self.system.as_mut().unwrap().with_mut(|fields| { - if self.renderers.len() <= self.prepare_layer { - self.renderers - .push(glyphon::TextRenderer::new(device, queue)); - } + if self.renderers.len() <= self.prepare_layer { + self.renderers + .push(glyphon::TextRenderer::new(device, queue)); + } - let renderer = &mut self.renderers[self.prepare_layer]; - - let keys: Vec<_> = sections - .iter() - .map(|section| { - let (key, _) = fields.render_cache.allocate( - fields.fonts, - Key { - content: section.content, - size: section.size * scale_factor, - font: section.font, - bounds: Size { - width: (section.bounds.width * scale_factor) - .ceil(), - height: (section.bounds.height * scale_factor) - .ceil(), - }, + let font_system = self.font_system.get_mut(); + let renderer = &mut self.renderers[self.prepare_layer]; + + let keys: Vec<_> = sections + .iter() + .map(|section| { + let (key, _) = self.render_cache.allocate( + font_system, + Key { + content: section.content, + size: section.size * scale_factor, + font: section.font, + bounds: Size { + width: (section.bounds.width * scale_factor).ceil(), + height: (section.bounds.height * scale_factor) + .ceil(), }, - ); - - key - }) - .collect(); - - let bounds = glyphon::TextBounds { - left: (bounds.x * scale_factor) as i32, - top: (bounds.y * scale_factor) as i32, - right: ((bounds.x + bounds.width) * scale_factor) as i32, - bottom: ((bounds.y + bounds.height) * scale_factor) as i32, - }; - - let text_areas = - sections.iter().zip(keys.iter()).map(|(section, key)| { - let buffer = fields - .render_cache - .get(key) - .expect("Get cached buffer"); - - let x = section.bounds.x * scale_factor; - let y = section.bounds.y * scale_factor; - - let (total_lines, max_width) = buffer - .layout_runs() - .enumerate() - .fold((0, 0.0), |(_, max), (i, buffer)| { - (i + 1, buffer.line_w.max(max)) - }); - - let total_height = - total_lines as f32 * section.size * 1.2 * scale_factor; - - let left = match section.horizontal_alignment { - alignment::Horizontal::Left => x, - alignment::Horizontal::Center => x - max_width / 2.0, - alignment::Horizontal::Right => x - max_width, - }; - - let top = match section.vertical_alignment { - alignment::Vertical::Top => y, - alignment::Vertical::Center => y - total_height / 2.0, - alignment::Vertical::Bottom => y - total_height, - }; - - glyphon::TextArea { - buffer, - left: left as i32, - top: top as i32, - bounds, - default_color: { - let [r, g, b, a] = section.color.into_linear(); - - glyphon::Color::rgba( - (r * 255.0) as u8, - (g * 255.0) as u8, - (b * 255.0) as u8, - (a * 255.0) as u8, - ) - }, - } - }); - - let result = renderer.prepare( - device, - queue, - &mut self.atlas, - glyphon::Resolution { - width: target_size.width, - height: target_size.height, - }, - text_areas, - &mut glyphon::SwashCache::new(fields.fonts), - ); + }, + ); + + key + }) + .collect(); + + let bounds = glyphon::TextBounds { + left: (bounds.x * scale_factor) as i32, + top: (bounds.y * scale_factor) as i32, + right: ((bounds.x + bounds.width) * scale_factor) as i32, + bottom: ((bounds.y + bounds.height) * scale_factor) as i32, + }; - match result { - Ok(()) => { - self.prepare_layer += 1; + let text_areas = + sections.iter().zip(keys.iter()).map(|(section, key)| { + let buffer = + self.render_cache.get(key).expect("Get cached buffer"); + + let x = section.bounds.x * scale_factor; + let y = section.bounds.y * scale_factor; + + let (total_lines, max_width) = buffer + .layout_runs() + .enumerate() + .fold((0, 0.0), |(_, max), (i, buffer)| { + (i + 1, buffer.line_w.max(max)) + }); + + let total_height = + total_lines as f32 * section.size * 1.2 * scale_factor; + + let left = match section.horizontal_alignment { + alignment::Horizontal::Left => x, + alignment::Horizontal::Center => x - max_width / 2.0, + alignment::Horizontal::Right => x - max_width, + }; + + let top = match section.vertical_alignment { + alignment::Vertical::Top => y, + alignment::Vertical::Center => y - total_height / 2.0, + alignment::Vertical::Bottom => y - total_height, + }; + + glyphon::TextArea { + buffer, + left: left as i32, + top: top as i32, + bounds, + default_color: { + let [r, g, b, a] = section.color.into_linear(); + + glyphon::Color::rgba( + (r * 255.0) as u8, + (g * 255.0) as u8, + (b * 255.0) as u8, + (a * 255.0) as u8, + ) + }, + } + }); + + let result = renderer.prepare( + device, + queue, + font_system, + &mut self.atlas, + glyphon::Resolution { + width: target_size.width, + height: target_size.height, + }, + text_areas, + &mut glyphon::SwashCache::new(), + ); + + match result { + Ok(()) => { + self.prepare_layer += 1; + true + } + Err(glyphon::PrepareError::AtlasFull(content_type)) => { + self.prepare_layer = 0; + + #[allow(clippy::needless_bool)] + if self.atlas.grow(device, content_type) { + false + } else { + // If the atlas cannot grow, then all bets are off. + // Instead of panicking, we will just pray that the result + // will be somewhat readable... true } - Err(glyphon::PrepareError::AtlasFull(content_type)) => { - self.prepare_layer = 0; - - #[allow(clippy::needless_bool)] - if self.atlas.grow(device, content_type) { - false - } else { - // If the atlas cannot grow, then all bets are off. - // Instead of panicking, we will just pray that the result - // will be somewhat readable... - true - } - } } - }) + } } pub fn render<'a>( @@ -230,11 +197,7 @@ impl Pipeline { pub fn end_frame(&mut self) { self.atlas.trim(); - - self.system - .as_mut() - .unwrap() - .with_render_cache_mut(|cache| cache.trim()); + self.render_cache.trim(); self.prepare_layer = 0; } @@ -246,28 +209,26 @@ impl Pipeline { font: Font, bounds: Size, ) -> (f32, f32) { - self.system.as_ref().unwrap().with(|fields| { - let mut measurement_cache = fields.measurement_cache.borrow_mut(); - - let (_, paragraph) = measurement_cache.allocate( - fields.fonts, - Key { - content, - size, - font, - bounds, - }, - ); + let mut measurement_cache = self.measurement_cache.borrow_mut(); + + let (_, paragraph) = measurement_cache.allocate( + &mut self.font_system.borrow_mut(), + Key { + content, + size, + font, + bounds, + }, + ); - let (total_lines, max_width) = paragraph - .layout_runs() - .enumerate() - .fold((0, 0.0), |(_, max), (i, buffer)| { - (i + 1, buffer.line_w.max(max)) - }); + let (total_lines, max_width) = paragraph + .layout_runs() + .enumerate() + .fold((0, 0.0), |(_, max), (i, buffer)| { + (i + 1, buffer.line_w.max(max)) + }); - (max_width, size * 1.2 * total_lines as f32) - }) + (max_width, size * 1.2 * total_lines as f32) } pub fn hit_test( @@ -279,30 +240,25 @@ impl Pipeline { point: Point, _nearest_only: bool, ) -> Option { - self.system.as_ref().unwrap().with(|fields| { - let mut measurement_cache = fields.measurement_cache.borrow_mut(); - - let (_, paragraph) = measurement_cache.allocate( - fields.fonts, - Key { - content, - size, - font, - bounds, - }, - ); + let mut measurement_cache = self.measurement_cache.borrow_mut(); + + let (_, paragraph) = measurement_cache.allocate( + &mut self.font_system.borrow_mut(), + Key { + content, + size, + font, + bounds, + }, + ); - let cursor = paragraph.hit(point.x, point.y)?; + let cursor = paragraph.hit(point.x, point.y)?; - Some(Hit::CharOffset(cursor.index)) - }) + Some(Hit::CharOffset(cursor.index)) } pub fn trim_measurement_cache(&mut self) { - self.system - .as_mut() - .unwrap() - .with_measurement_cache_mut(|cache| cache.borrow_mut().trim()); + self.measurement_cache.borrow_mut().trim(); } } @@ -317,8 +273,8 @@ fn to_family(font: Font) -> glyphon::Family<'static> { } } -struct Cache<'a> { - entries: FxHashMap>, +struct Cache { + entries: FxHashMap, recently_used: FxHashSet, hasher: HashBuilder, } @@ -329,7 +285,7 @@ type HashBuilder = twox_hash::RandomXxHashBuilder64; #[cfg(target_arch = "wasm32")] type HashBuilder = std::hash::BuildHasherDefault; -impl<'a> Cache<'a> { +impl Cache { fn new() -> Self { Self { entries: FxHashMap::default(), @@ -338,15 +294,15 @@ impl<'a> Cache<'a> { } } - fn get(&self, key: &KeyHash) -> Option<&glyphon::Buffer<'a>> { + fn get(&self, key: &KeyHash) -> Option<&glyphon::Buffer> { self.entries.get(key) } fn allocate( &mut self, - fonts: &'a glyphon::FontSystem, + font_system: &mut glyphon::FontSystem, key: Key<'_>, - ) -> (KeyHash, &mut glyphon::Buffer<'a>) { + ) -> (KeyHash, &mut glyphon::Buffer) { let hash = { let mut hasher = self.hasher.build_hasher(); @@ -361,13 +317,15 @@ impl<'a> Cache<'a> { if let hash_map::Entry::Vacant(entry) = self.entries.entry(hash) { let metrics = glyphon::Metrics::new(key.size, key.size * 1.2); - let mut buffer = glyphon::Buffer::new(fonts, metrics); + let mut buffer = glyphon::Buffer::new(font_system, metrics); buffer.set_size( + font_system, key.bounds.width, key.bounds.height.max(key.size * 1.2), ); buffer.set_text( + font_system, key.content, glyphon::Attrs::new() .family(to_family(key.font)) -- cgit From 707de9d788dc3c49d4ac57a19afac1bb938b78d9 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Thu, 30 Mar 2023 00:56:00 +0200 Subject: Introduce support for `Font` attributes --- wgpu/src/backend.rs | 2 +- wgpu/src/layer.rs | 2 +- wgpu/src/settings.rs | 2 +- wgpu/src/text.rs | 44 +++++++++++++++++++++++++++++++++----------- 4 files changed, 36 insertions(+), 14 deletions(-) (limited to 'wgpu') diff --git a/wgpu/src/backend.rs b/wgpu/src/backend.rs index 88c58554..9772781a 100644 --- a/wgpu/src/backend.rs +++ b/wgpu/src/backend.rs @@ -336,7 +336,7 @@ impl iced_graphics::Backend for Backend { } impl backend::Text for Backend { - const ICON_FONT: Font = Font::Name("Iced-Icons"); + const ICON_FONT: Font = Font::with_name("Iced-Icons"); const CHECKMARK_ICON: char = '\u{f00c}'; const ARROW_DOWN_ICON: char = '\u{e800}'; diff --git a/wgpu/src/layer.rs b/wgpu/src/layer.rs index cb9d5e2f..c4723397 100644 --- a/wgpu/src/layer.rs +++ b/wgpu/src/layer.rs @@ -61,7 +61,7 @@ impl<'a> Layer<'a> { ), color: Color::new(0.9, 0.9, 0.9, 1.0), size: 20.0, - font: Font::Monospace, + font: Font::MONOSPACE, horizontal_alignment: alignment::Horizontal::Left, vertical_alignment: alignment::Vertical::Top, }; diff --git a/wgpu/src/settings.rs b/wgpu/src/settings.rs index 7c0750ef..ff041bdf 100644 --- a/wgpu/src/settings.rs +++ b/wgpu/src/settings.rs @@ -58,7 +58,7 @@ impl Default for Settings { Settings { present_mode: wgpu::PresentMode::AutoVsync, internal_backend: wgpu::Backends::all(), - default_font: Font::SansSerif, + default_font: Font::default(), default_text_size: 16.0, antialiasing: None, } diff --git a/wgpu/src/text.rs b/wgpu/src/text.rs index ac116f69..b0b7a198 100644 --- a/wgpu/src/text.rs +++ b/wgpu/src/text.rs @@ -1,6 +1,7 @@ use crate::core::alignment; +use crate::core::font::{self, Font}; use crate::core::text::Hit; -use crate::core::{Font, Point, Rectangle, Size}; +use crate::core::{Point, Rectangle, Size}; use crate::layer::Text; use rustc_hash::{FxHashMap, FxHashSet}; @@ -262,14 +263,28 @@ impl Pipeline { } } -fn to_family(font: Font) -> glyphon::Family<'static> { - match font { - Font::Name(name) => glyphon::Family::Name(name), - Font::SansSerif => glyphon::Family::SansSerif, - Font::Serif => glyphon::Family::Serif, - Font::Cursive => glyphon::Family::Cursive, - Font::Fantasy => glyphon::Family::Fantasy, - Font::Monospace => glyphon::Family::Monospace, +fn to_family(family: font::Family) -> glyphon::Family<'static> { + match family { + font::Family::Name(name) => glyphon::Family::Name(name), + font::Family::SansSerif => glyphon::Family::SansSerif, + font::Family::Serif => glyphon::Family::Serif, + font::Family::Cursive => glyphon::Family::Cursive, + font::Family::Fantasy => glyphon::Family::Fantasy, + font::Family::Monospace => glyphon::Family::Monospace, + } +} + +fn to_weight(weight: font::Weight) -> glyphon::Weight { + match weight { + font::Weight::Thin => glyphon::Weight::THIN, + font::Weight::ExtraLight => glyphon::Weight::EXTRA_LIGHT, + font::Weight::Light => glyphon::Weight::LIGHT, + font::Weight::Normal => glyphon::Weight::NORMAL, + font::Weight::Medium => glyphon::Weight::MEDIUM, + font::Weight::Semibold => glyphon::Weight::SEMIBOLD, + font::Weight::Bold => glyphon::Weight::BOLD, + font::Weight::ExtraBold => glyphon::Weight::EXTRA_BOLD, + font::Weight::Black => glyphon::Weight::BLACK, } } @@ -328,8 +343,15 @@ impl Cache { font_system, key.content, glyphon::Attrs::new() - .family(to_family(key.font)) - .monospaced(matches!(key.font, Font::Monospace)), + .family(to_family(key.font.family)) + .weight(to_weight(key.font.weight)) + .monospaced( + key.font.monospaced + || matches!( + key.font.family, + font::Family::Monospace + ), + ), ); let _ = entry.insert(buffer); -- cgit From 0b459c8e240abf83bb62902a504c018194acdbb6 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Thu, 30 Mar 2023 02:01:20 +0200 Subject: Introduce `font::Stretch` --- wgpu/src/text.rs | 15 +++++++++++++++ 1 file changed, 15 insertions(+) (limited to 'wgpu') diff --git a/wgpu/src/text.rs b/wgpu/src/text.rs index b0b7a198..dd674279 100644 --- a/wgpu/src/text.rs +++ b/wgpu/src/text.rs @@ -288,6 +288,20 @@ fn to_weight(weight: font::Weight) -> glyphon::Weight { } } +fn to_stretch(stretch: font::Stretch) -> glyphon::Stretch { + match stretch { + font::Stretch::UltraCondensed => glyphon::Stretch::UltraCondensed, + font::Stretch::ExtraCondensed => glyphon::Stretch::ExtraCondensed, + font::Stretch::Condensed => glyphon::Stretch::Condensed, + font::Stretch::SemiCondensed => glyphon::Stretch::SemiCondensed, + font::Stretch::Normal => glyphon::Stretch::Normal, + font::Stretch::SemiExpanded => glyphon::Stretch::SemiExpanded, + font::Stretch::Expanded => glyphon::Stretch::Expanded, + font::Stretch::ExtraExpanded => glyphon::Stretch::ExtraExpanded, + font::Stretch::UltraExpanded => glyphon::Stretch::UltraExpanded, + } +} + struct Cache { entries: FxHashMap, recently_used: FxHashSet, @@ -345,6 +359,7 @@ impl Cache { glyphon::Attrs::new() .family(to_family(key.font.family)) .weight(to_weight(key.font.weight)) + .stretch(to_stretch(key.font.stretch)) .monospaced( key.font.monospaced || matches!( -- cgit From 703484c5fdd87c64a41d3e627b0c79b43179e124 Mon Sep 17 00:00:00 2001 From: David Huculak Date: Sat, 1 Apr 2023 16:10:28 -0400 Subject: remove colons from shader labels --- wgpu/src/image.rs | 2 +- wgpu/src/quad.rs | 2 +- wgpu/src/triangle.rs | 4 ++-- wgpu/src/triangle/msaa.rs | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) (limited to 'wgpu') diff --git a/wgpu/src/image.rs b/wgpu/src/image.rs index a5e63b17..9f56c188 100644 --- a/wgpu/src/image.rs +++ b/wgpu/src/image.rs @@ -141,7 +141,7 @@ impl Pipeline { let shader = device.create_shader_module(wgpu::ShaderModuleDescriptor { - label: Some("iced_wgpu::image::shader"), + label: Some("iced_wgpu image shader"), source: wgpu::ShaderSource::Wgsl(std::borrow::Cow::Borrowed( include_str!("shader/image.wgsl"), )), diff --git a/wgpu/src/quad.rs b/wgpu/src/quad.rs index 2f5fcc6b..1343181e 100644 --- a/wgpu/src/quad.rs +++ b/wgpu/src/quad.rs @@ -63,7 +63,7 @@ impl Pipeline { let shader = device.create_shader_module(wgpu::ShaderModuleDescriptor { - label: Some("iced_wgpu::quad::shader"), + label: Some("iced_wgpu quad shader"), source: wgpu::ShaderSource::Wgsl(std::borrow::Cow::Borrowed( include_str!("shader/quad.wgsl"), )), diff --git a/wgpu/src/triangle.rs b/wgpu/src/triangle.rs index efdd214b..162428f0 100644 --- a/wgpu/src/triangle.rs +++ b/wgpu/src/triangle.rs @@ -457,7 +457,7 @@ mod solid { let shader = device.create_shader_module(wgpu::ShaderModuleDescriptor { label: Some( - "iced_wgpu::triangle::solid create shader module", + "iced_wgpu triangle solid create shader module", ), source: wgpu::ShaderSource::Wgsl( std::borrow::Cow::Borrowed(include_str!( @@ -654,7 +654,7 @@ mod gradient { let shader = device.create_shader_module(wgpu::ShaderModuleDescriptor { label: Some( - "iced_wgpu::triangle::gradient create shader module", + "iced_wgpu triangle gradient create shader module", ), source: wgpu::ShaderSource::Wgsl( std::borrow::Cow::Borrowed(include_str!( diff --git a/wgpu/src/triangle/msaa.rs b/wgpu/src/triangle/msaa.rs index a3016ff8..e76f7d5e 100644 --- a/wgpu/src/triangle/msaa.rs +++ b/wgpu/src/triangle/msaa.rs @@ -75,7 +75,7 @@ impl Blit { let shader = device.create_shader_module(wgpu::ShaderModuleDescriptor { - label: Some("iced_wgpu::triangle::blit_shader"), + label: Some("iced_wgpu triangle blit_shader"), source: wgpu::ShaderSource::Wgsl(std::borrow::Cow::Borrowed( include_str!("../shader/blit.wgsl"), )), -- cgit From c0431aedd3bbef4161456f2fa5f29866e8f17fc5 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Sat, 8 Apr 2023 04:47:05 +0200 Subject: Update `wgpu` and `cosmic-text` --- wgpu/Cargo.toml | 4 ++-- wgpu/src/image/atlas.rs | 2 ++ wgpu/src/text.rs | 17 +++++++---------- wgpu/src/triangle/msaa.rs | 2 ++ wgpu/src/window/compositor.rs | 13 +++++++++---- 5 files changed, 22 insertions(+), 16 deletions(-) (limited to 'wgpu') diff --git a/wgpu/Cargo.toml b/wgpu/Cargo.toml index 3aef4ff4..36c891bb 100644 --- a/wgpu/Cargo.toml +++ b/wgpu/Cargo.toml @@ -13,7 +13,7 @@ image = ["iced_graphics/image"] svg = ["resvg"] [dependencies] -wgpu = "0.14" +wgpu = "0.15" raw-window-handle = "0.5" log = "0.4" guillotiere = "0.6" @@ -44,7 +44,7 @@ path = "../graphics" [dependencies.glyphon] version = "0.2" git = "https://github.com/hecrj/glyphon.git" -rev = "47050174841a4f58fc8d85c943a2117f72f19e8e" +rev = "6601deec1c7595f8fd5f83f929b2497104905400" [dependencies.encase] version = "0.3.0" diff --git a/wgpu/src/image/atlas.rs b/wgpu/src/image/atlas.rs index c00b8cef..39b6e5d2 100644 --- a/wgpu/src/image/atlas.rs +++ b/wgpu/src/image/atlas.rs @@ -41,6 +41,7 @@ impl Atlas { usage: wgpu::TextureUsages::COPY_DST | wgpu::TextureUsages::COPY_SRC | wgpu::TextureUsages::TEXTURE_BINDING, + view_formats: &[], }); let texture_view = texture.create_view(&wgpu::TextureViewDescriptor { @@ -338,6 +339,7 @@ impl Atlas { usage: wgpu::TextureUsages::COPY_DST | wgpu::TextureUsages::COPY_SRC | wgpu::TextureUsages::TEXTURE_BINDING, + view_formats: &[], }); let amount_to_copy = self.layers.len() - amount; diff --git a/wgpu/src/text.rs b/wgpu/src/text.rs index dd674279..f01e0b42 100644 --- a/wgpu/src/text.rs +++ b/wgpu/src/text.rs @@ -58,8 +58,12 @@ impl Pipeline { target_size: Size, ) -> bool { if self.renderers.len() <= self.prepare_layer { - self.renderers - .push(glyphon::TextRenderer::new(device, queue)); + self.renderers.push(glyphon::TextRenderer::new( + &mut self.atlas, + device, + Default::default(), + None, + )); } let font_system = self.font_system.get_mut(); @@ -359,14 +363,7 @@ impl Cache { glyphon::Attrs::new() .family(to_family(key.font.family)) .weight(to_weight(key.font.weight)) - .stretch(to_stretch(key.font.stretch)) - .monospaced( - key.font.monospaced - || matches!( - key.font.family, - font::Family::Monospace - ), - ), + .stretch(to_stretch(key.font.stretch)), ); let _ = entry.insert(buffer); diff --git a/wgpu/src/triangle/msaa.rs b/wgpu/src/triangle/msaa.rs index 7144125c..14522c89 100644 --- a/wgpu/src/triangle/msaa.rs +++ b/wgpu/src/triangle/msaa.rs @@ -223,6 +223,7 @@ impl Targets { dimension: wgpu::TextureDimension::D2, format, usage: wgpu::TextureUsages::RENDER_ATTACHMENT, + view_formats: &[], }); let resolve = device.create_texture(&wgpu::TextureDescriptor { @@ -234,6 +235,7 @@ impl Targets { format, usage: wgpu::TextureUsages::RENDER_ATTACHMENT | wgpu::TextureUsages::TEXTURE_BINDING, + view_formats: &[], }); let attachment = diff --git a/wgpu/src/window/compositor.rs b/wgpu/src/window/compositor.rs index a67ac3c0..025cd43a 100644 --- a/wgpu/src/window/compositor.rs +++ b/wgpu/src/window/compositor.rs @@ -31,7 +31,10 @@ impl Compositor { settings: Settings, compatible_window: Option<&W>, ) -> Option { - let instance = wgpu::Instance::new(settings.internal_backend); + let instance = wgpu::Instance::new(wgpu::InstanceDescriptor { + backends: settings.internal_backend, + ..Default::default() + }); log::info!("{:#?}", settings); @@ -46,7 +49,7 @@ impl Compositor { #[allow(unsafe_code)] let compatible_surface = compatible_window - .map(|window| unsafe { instance.create_surface(window) }); + .and_then(|window| unsafe { instance.create_surface(window).ok() }); let adapter = instance .request_adapter(&wgpu::RequestAdapterOptions { @@ -63,7 +66,7 @@ impl Compositor { log::info!("Selected: {:#?}", adapter.get_info()); let format = compatible_surface.as_ref().and_then(|surface| { - surface.get_supported_formats(&adapter).first().copied() + surface.get_capabilities(&adapter).formats.first().copied() })?; log::info!("Selected format: {:?}", format); @@ -207,7 +210,8 @@ impl graphics::Compositor for Compositor { height: u32, ) -> wgpu::Surface { #[allow(unsafe_code)] - let mut surface = unsafe { self.instance.create_surface(window) }; + let mut surface = unsafe { self.instance.create_surface(window) } + .expect("Create surface"); self.configure_surface(&mut surface, width, height); @@ -229,6 +233,7 @@ impl graphics::Compositor for Compositor { width, height, alpha_mode: wgpu::CompositeAlphaMode::Auto, + view_formats: vec![], }, ); } -- cgit From 1872f7fa6d7b9ca9fa0db8d14bf44dcd3513ffca Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Sat, 8 Apr 2023 06:14:25 +0200 Subject: Use `*_from_env` helpers from `wgpu` in `iced_wgpu` --- wgpu/src/settings.rs | 17 +---------------- wgpu/src/window/compositor.rs | 11 ++++++----- 2 files changed, 7 insertions(+), 21 deletions(-) (limited to 'wgpu') diff --git a/wgpu/src/settings.rs b/wgpu/src/settings.rs index ff041bdf..266a2c87 100644 --- a/wgpu/src/settings.rs +++ b/wgpu/src/settings.rs @@ -46,7 +46,7 @@ impl Settings { /// - `primary` pub fn from_env() -> Self { Settings { - internal_backend: backend_from_env() + internal_backend: wgpu::util::backend_bits_from_env() .unwrap_or(wgpu::Backends::all()), ..Self::default() } @@ -64,18 +64,3 @@ impl Default for Settings { } } } - -fn backend_from_env() -> Option { - std::env::var("WGPU_BACKEND").ok().map(|backend| { - match backend.to_lowercase().as_str() { - "vulkan" => wgpu::Backends::VULKAN, - "metal" => wgpu::Backends::METAL, - "dx12" => wgpu::Backends::DX12, - "dx11" => wgpu::Backends::DX11, - "gl" => wgpu::Backends::GL, - "webgpu" => wgpu::Backends::BROWSER_WEBGPU, - "primary" => wgpu::Backends::PRIMARY, - other => panic!("Unknown backend: {other}"), - } - }) -} diff --git a/wgpu/src/window/compositor.rs b/wgpu/src/window/compositor.rs index 025cd43a..15bef60c 100644 --- a/wgpu/src/window/compositor.rs +++ b/wgpu/src/window/compositor.rs @@ -53,11 +53,12 @@ impl Compositor { let adapter = instance .request_adapter(&wgpu::RequestAdapterOptions { - power_preference: if settings.antialiasing.is_none() { - wgpu::PowerPreference::LowPower - } else { - wgpu::PowerPreference::HighPerformance - }, + power_preference: wgpu::util::power_preference_from_env() + .unwrap_or(if settings.antialiasing.is_none() { + wgpu::PowerPreference::LowPower + } else { + wgpu::PowerPreference::HighPerformance + }), compatible_surface: compatible_surface.as_ref(), force_fallback_adapter: false, }) -- cgit From d206b82ebb7617337cd378bd19542a7abf8a4529 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Sat, 8 Apr 2023 07:35:16 +0200 Subject: Update `glyphon` --- wgpu/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'wgpu') diff --git a/wgpu/Cargo.toml b/wgpu/Cargo.toml index 36c891bb..c1f23246 100644 --- a/wgpu/Cargo.toml +++ b/wgpu/Cargo.toml @@ -44,7 +44,7 @@ path = "../graphics" [dependencies.glyphon] version = "0.2" git = "https://github.com/hecrj/glyphon.git" -rev = "6601deec1c7595f8fd5f83f929b2497104905400" +rev = "1d26d92b19407c5dabe4625944d4a6babbbf0715" [dependencies.encase] version = "0.3.0" -- cgit From d5453c62e9bdbf0cea030b009c41b892b700496d Mon Sep 17 00:00:00 2001 From: Elham Aryanpur Date: Wed, 12 Apr 2023 23:38:21 +0300 Subject: Update `wgpu` to `0.15` --- wgpu/Cargo.toml | 4 ++-- wgpu/src/image/atlas.rs | 2 ++ wgpu/src/triangle/msaa.rs | 2 ++ wgpu/src/window/compositor.rs | 25 +++++++++++++++++++++---- 4 files changed, 27 insertions(+), 6 deletions(-) (limited to 'wgpu') diff --git a/wgpu/Cargo.toml b/wgpu/Cargo.toml index f1e22cf6..4dcd07f7 100644 --- a/wgpu/Cargo.toml +++ b/wgpu/Cargo.toml @@ -28,8 +28,8 @@ spirv = ["wgpu/spirv"] webgl = ["wgpu/webgl"] [dependencies] -wgpu = "0.14" -wgpu_glyph = "0.18" +wgpu = "0.15" +wgpu_glyph = "0.19" glyph_brush = "0.7" raw-window-handle = "0.5" log = "0.4" diff --git a/wgpu/src/image/atlas.rs b/wgpu/src/image/atlas.rs index eafe2f96..82504147 100644 --- a/wgpu/src/image/atlas.rs +++ b/wgpu/src/image/atlas.rs @@ -39,6 +39,7 @@ impl Atlas { sample_count: 1, dimension: wgpu::TextureDimension::D2, format: wgpu::TextureFormat::Rgba8UnormSrgb, + view_formats: &[], usage: wgpu::TextureUsages::COPY_DST | wgpu::TextureUsages::COPY_SRC | wgpu::TextureUsages::TEXTURE_BINDING, @@ -247,6 +248,7 @@ impl Atlas { sample_count: 1, dimension: wgpu::TextureDimension::D2, format: wgpu::TextureFormat::Rgba8UnormSrgb, + view_formats: &[], usage: wgpu::TextureUsages::COPY_DST | wgpu::TextureUsages::COPY_SRC | wgpu::TextureUsages::TEXTURE_BINDING, diff --git a/wgpu/src/triangle/msaa.rs b/wgpu/src/triangle/msaa.rs index e76f7d5e..d24f8e1a 100644 --- a/wgpu/src/triangle/msaa.rs +++ b/wgpu/src/triangle/msaa.rs @@ -222,6 +222,7 @@ impl Targets { sample_count, dimension: wgpu::TextureDimension::D2, format, + view_formats: &[], usage: wgpu::TextureUsages::RENDER_ATTACHMENT, }); @@ -232,6 +233,7 @@ impl Targets { sample_count: 1, dimension: wgpu::TextureDimension::D2, format, + view_formats: &[], usage: wgpu::TextureUsages::RENDER_ATTACHMENT | wgpu::TextureUsages::TEXTURE_BINDING, }); diff --git a/wgpu/src/window/compositor.rs b/wgpu/src/window/compositor.rs index 6d0c36f6..d66aca71 100644 --- a/wgpu/src/window/compositor.rs +++ b/wgpu/src/window/compositor.rs @@ -31,7 +31,10 @@ impl Compositor { settings: Settings, compatible_window: Option<&W>, ) -> Option { - let instance = wgpu::Instance::new(settings.internal_backend); + let instance = wgpu::Instance::new(wgpu::InstanceDescriptor { + backends: settings.internal_backend, + ..Default::default() + }); log::info!("{:#?}", settings); @@ -46,7 +49,7 @@ impl Compositor { #[allow(unsafe_code)] let compatible_surface = compatible_window - .map(|window| unsafe { instance.create_surface(window) }); + .and_then(|window| unsafe { instance.create_surface(window).ok() }); let adapter = instance .request_adapter(&wgpu::RequestAdapterOptions { @@ -63,7 +66,18 @@ impl Compositor { log::info!("Selected: {:#?}", adapter.get_info()); let format = compatible_surface.as_ref().and_then(|surface| { - surface.get_supported_formats(&adapter).first().copied() + surface + .get_capabilities(&adapter) + .formats + .iter() + .filter(|format| format.describe().srgb) + .copied() + .next() + .or_else(|| { + log::warn!("No sRGB format found!"); + + surface.get_capabilities(&adapter).formats.first().copied() + }) })?; log::info!("Selected format: {:?}", format); @@ -144,7 +158,9 @@ impl iced_graphics::window::Compositor for Compositor { ) -> wgpu::Surface { #[allow(unsafe_code)] unsafe { - self.instance.create_surface(window) + self.instance + .create_surface(window) + .expect("Create surface") } } @@ -163,6 +179,7 @@ impl iced_graphics::window::Compositor for Compositor { width, height, alpha_mode: wgpu::CompositeAlphaMode::Auto, + view_formats: vec![], }, ); } -- cgit From b677345ac1b1d087bc7f331c9c8c5be06933ba6e Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Thu, 13 Apr 2023 05:42:56 +0200 Subject: Get surface capabilities only once in `iced_wgpu` --- wgpu/src/window/compositor.rs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) (limited to 'wgpu') diff --git a/wgpu/src/window/compositor.rs b/wgpu/src/window/compositor.rs index d66aca71..d4a59471 100644 --- a/wgpu/src/window/compositor.rs +++ b/wgpu/src/window/compositor.rs @@ -66,8 +66,9 @@ impl Compositor { log::info!("Selected: {:#?}", adapter.get_info()); let format = compatible_surface.as_ref().and_then(|surface| { - surface - .get_capabilities(&adapter) + let capabilities = surface.get_capabilities(&adapter); + + capabilities .formats .iter() .filter(|format| format.describe().srgb) @@ -76,7 +77,7 @@ impl Compositor { .or_else(|| { log::warn!("No sRGB format found!"); - surface.get_capabilities(&adapter).formats.first().copied() + capabilities.formats.first().copied() }) })?; -- cgit From c79cc2d2b3df99f69b048c68e503916c779a1102 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Thu, 13 Apr 2023 08:31:17 +0200 Subject: Bump versions :tada: --- wgpu/Cargo.toml | 6 +++--- wgpu/README.md | 2 +- wgpu/src/lib.rs | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) (limited to 'wgpu') diff --git a/wgpu/Cargo.toml b/wgpu/Cargo.toml index 4dcd07f7..3478ef59 100644 --- a/wgpu/Cargo.toml +++ b/wgpu/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "iced_wgpu" -version = "0.9.0" +version = "0.10.0" authors = ["Héctor Ramón Jiménez "] edition = "2021" description = "A wgpu renderer for Iced" @@ -42,11 +42,11 @@ version = "1.9" features = ["derive"] [dependencies.iced_native] -version = "0.9" +version = "0.10" path = "../native" [dependencies.iced_graphics] -version = "0.7" +version = "0.8" path = "../graphics" features = ["font-fallback", "font-icons"] diff --git a/wgpu/README.md b/wgpu/README.md index 3e6af103..f8c88374 100644 --- a/wgpu/README.md +++ b/wgpu/README.md @@ -30,7 +30,7 @@ Currently, `iced_wgpu` supports the following primitives: Add `iced_wgpu` as a dependency in your `Cargo.toml`: ```toml -iced_wgpu = "0.9" +iced_wgpu = "0.10" ``` __Iced moves fast and the `master` branch can contain breaking changes!__ If diff --git a/wgpu/src/lib.rs b/wgpu/src/lib.rs index 1a293681..969e3199 100644 --- a/wgpu/src/lib.rs +++ b/wgpu/src/lib.rs @@ -16,7 +16,7 @@ //! - Meshes of triangles, useful to draw geometry freely. //! //! [Iced]: https://github.com/iced-rs/iced -//! [`iced_native`]: https://github.com/iced-rs/iced/tree/0.8/native +//! [`iced_native`]: https://github.com/iced-rs/iced/tree/0.9/native //! [`wgpu`]: https://github.com/gfx-rs/wgpu-rs //! [WebGPU API]: https://gpuweb.github.io/gpuweb/ //! [`wgpu_glyph`]: https://github.com/hecrj/wgpu_glyph -- cgit From e3730106e9d4f75de199e1b83cf285b8ff031968 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Mon, 17 Apr 2023 23:44:26 +0200 Subject: Update `glyphon` to latest `dev` --- wgpu/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'wgpu') diff --git a/wgpu/Cargo.toml b/wgpu/Cargo.toml index 4559040e..14dcd550 100644 --- a/wgpu/Cargo.toml +++ b/wgpu/Cargo.toml @@ -44,7 +44,7 @@ path = "../graphics" [dependencies.glyphon] version = "0.2" git = "https://github.com/hecrj/glyphon.git" -rev = "6601deec1c7595f8fd5f83f929b2497104905400" +rev = "1d26d92b19407c5dabe4625944d4a6babbbf0715" [dependencies.encase] version = "0.3.0" -- cgit From 8122904ca46b73ceda54bd73bd68cf4138d22f1b Mon Sep 17 00:00:00 2001 From: David Huculak Date: Thu, 20 Apr 2023 21:28:47 -0400 Subject: wgpu 0.16 --- wgpu/Cargo.toml | 4 ++-- wgpu/src/image/atlas.rs | 4 ++-- wgpu/src/window/compositor.rs | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) (limited to 'wgpu') diff --git a/wgpu/Cargo.toml b/wgpu/Cargo.toml index 3478ef59..1ce07e0a 100644 --- a/wgpu/Cargo.toml +++ b/wgpu/Cargo.toml @@ -28,8 +28,8 @@ spirv = ["wgpu/spirv"] webgl = ["wgpu/webgl"] [dependencies] -wgpu = "0.15" -wgpu_glyph = "0.19" +wgpu = "0.16" +wgpu_glyph = "0.20" glyph_brush = "0.7" raw-window-handle = "0.5" log = "0.4" diff --git a/wgpu/src/image/atlas.rs b/wgpu/src/image/atlas.rs index 82504147..1384949a 100644 --- a/wgpu/src/image/atlas.rs +++ b/wgpu/src/image/atlas.rs @@ -209,8 +209,8 @@ impl Atlas { buffer, layout: wgpu::ImageDataLayout { offset: offset as u64, - bytes_per_row: NonZeroU32::new(4 * image_width + padding), - rows_per_image: NonZeroU32::new(image_height), + bytes_per_row: Some(4 * image_width + padding), + rows_per_image: Some(image_height), }, }, wgpu::ImageCopyTexture { diff --git a/wgpu/src/window/compositor.rs b/wgpu/src/window/compositor.rs index d4a59471..8969ad6c 100644 --- a/wgpu/src/window/compositor.rs +++ b/wgpu/src/window/compositor.rs @@ -71,7 +71,7 @@ impl Compositor { capabilities .formats .iter() - .filter(|format| format.describe().srgb) + .filter(|format| format.is_srgb()) .copied() .next() .or_else(|| { -- cgit From cbb2ba38faadc52b9523918a2fbc2e2a78de9087 Mon Sep 17 00:00:00 2001 From: David Huculak Date: Thu, 20 Apr 2023 21:38:52 -0400 Subject: remove unused import --- wgpu/src/image/atlas.rs | 2 -- 1 file changed, 2 deletions(-) (limited to 'wgpu') diff --git a/wgpu/src/image/atlas.rs b/wgpu/src/image/atlas.rs index 1384949a..a0fdf146 100644 --- a/wgpu/src/image/atlas.rs +++ b/wgpu/src/image/atlas.rs @@ -15,8 +15,6 @@ pub const SIZE: u32 = 2048; use iced_graphics::image; use iced_graphics::Size; -use std::num::NonZeroU32; - #[derive(Debug)] pub struct Atlas { texture: wgpu::Texture, -- cgit From 3f0c226b74c6d0271e550161b6542e652c1875ca Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Fri, 21 Apr 2023 21:36:30 +0200 Subject: Use point-free notation --- wgpu/src/window/compositor.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'wgpu') diff --git a/wgpu/src/window/compositor.rs b/wgpu/src/window/compositor.rs index 8969ad6c..3c51768a 100644 --- a/wgpu/src/window/compositor.rs +++ b/wgpu/src/window/compositor.rs @@ -71,8 +71,8 @@ impl Compositor { capabilities .formats .iter() - .filter(|format| format.is_srgb()) .copied() + .filter(wgpu::TextureFormat::is_srgb) .next() .or_else(|| { log::warn!("No sRGB format found!"); -- cgit From cc20baad6f422271f052cf68474a4aee40dcdc82 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Fri, 21 Apr 2023 21:46:02 +0200 Subject: Use `find(..)` instead of `filter(..).next()` --- wgpu/src/window/compositor.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) (limited to 'wgpu') diff --git a/wgpu/src/window/compositor.rs b/wgpu/src/window/compositor.rs index 3c51768a..53af19bf 100644 --- a/wgpu/src/window/compositor.rs +++ b/wgpu/src/window/compositor.rs @@ -72,8 +72,7 @@ impl Compositor { .formats .iter() .copied() - .filter(wgpu::TextureFormat::is_srgb) - .next() + .find(wgpu::TextureFormat::is_srgb) .or_else(|| { log::warn!("No sRGB format found!"); -- cgit From 9c63eb7df559e58b14188b4096e9bd206444bbf3 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Wed, 26 Apr 2023 16:46:27 +0200 Subject: Update `tiny-skia` and `resvg` --- wgpu/Cargo.toml | 2 +- wgpu/src/image/vector.rs | 6 ++++-- 2 files changed, 5 insertions(+), 3 deletions(-) (limited to 'wgpu') diff --git a/wgpu/Cargo.toml b/wgpu/Cargo.toml index 14dcd550..ffae6e4a 100644 --- a/wgpu/Cargo.toml +++ b/wgpu/Cargo.toml @@ -58,7 +58,7 @@ version = "1.0" optional = true [dependencies.resvg] -version = "0.29" +version = "0.32" optional = true [dependencies.tracing] diff --git a/wgpu/src/image/vector.rs b/wgpu/src/image/vector.rs index 3624e46b..58bdf64a 100644 --- a/wgpu/src/image/vector.rs +++ b/wgpu/src/image/vector.rs @@ -43,6 +43,8 @@ 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(); } @@ -116,9 +118,9 @@ impl Cache { resvg::render( tree, if width > height { - usvg::FitTo::Width(width) + resvg::FitTo::Width(width) } else { - usvg::FitTo::Height(height) + resvg::FitTo::Height(height) }, tiny_skia::Transform::default(), img.as_mut(), -- cgit From d953d12c38ad368110f4f9f40fb0f185c11dec54 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Wed, 26 Apr 2023 16:48:41 +0200 Subject: Fix incorrect `wgpu` version for Wasm builds in `iced_wgpu` --- wgpu/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'wgpu') diff --git a/wgpu/Cargo.toml b/wgpu/Cargo.toml index ffae6e4a..9f9bd066 100644 --- a/wgpu/Cargo.toml +++ b/wgpu/Cargo.toml @@ -23,7 +23,7 @@ once_cell = "1.0" rustc-hash = "1.1" [target.'cfg(target_arch = "wasm32")'.dependencies] -wgpu = { version = "0.14", features = ["webgl"] } +wgpu = { version = "0.15", features = ["webgl"] } [dependencies.twox-hash] version = "1.6" -- cgit From 200a29c069ae0c6461b6d44e075aacc9ddad9974 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Thu, 27 Apr 2023 15:17:45 +0200 Subject: Fix unused import in `triangle` pipeline for Wasm target in `iced_wgpu` --- wgpu/src/triangle.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) (limited to 'wgpu') diff --git a/wgpu/src/triangle.rs b/wgpu/src/triangle.rs index 0df8dd02..eb15a458 100644 --- a/wgpu/src/triangle.rs +++ b/wgpu/src/triangle.rs @@ -2,10 +2,13 @@ mod msaa; use crate::buffer::r#static::Buffer; -use crate::core::{Gradient, Size}; +use crate::core::Size; use crate::graphics::{Antialiasing, Transformation}; use crate::layer::mesh::{self, Mesh}; +#[cfg(not(target_arch = "wasm32"))] +use crate::core::Gradient; + #[cfg(feature = "tracing")] use tracing::info_span; -- cgit From 33b5a900197e2798a393d6d9a0834039666eddbb Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Wed, 19 Apr 2023 01:19:56 +0200 Subject: Make basic text shaping the default shaping strategy --- wgpu/Cargo.toml | 2 +- wgpu/src/backend.rs | 6 +++++- wgpu/src/geometry.rs | 1 + wgpu/src/layer.rs | 3 +++ wgpu/src/layer/text.rs | 10 ++++++++++ wgpu/src/text.rs | 7 +++++++ 6 files changed, 27 insertions(+), 2 deletions(-) (limited to 'wgpu') diff --git a/wgpu/Cargo.toml b/wgpu/Cargo.toml index 9f9bd066..254d32d6 100644 --- a/wgpu/Cargo.toml +++ b/wgpu/Cargo.toml @@ -44,7 +44,7 @@ path = "../graphics" [dependencies.glyphon] version = "0.2" git = "https://github.com/hecrj/glyphon.git" -rev = "1d26d92b19407c5dabe4625944d4a6babbbf0715" +rev = "446cf0803065b52ba5fb9a30fe0addb6d7b5f9d9" [dependencies.encase] version = "0.3.0" diff --git a/wgpu/src/backend.rs b/wgpu/src/backend.rs index 9772781a..d09b2dba 100644 --- a/wgpu/src/backend.rs +++ b/wgpu/src/backend.rs @@ -354,8 +354,10 @@ impl backend::Text for Backend { size: f32, font: Font, bounds: Size, + advanced_shape: bool, ) -> (f32, f32) { - self.text_pipeline.measure(contents, size, font, bounds) + self.text_pipeline + .measure(contents, size, font, bounds, advanced_shape) } fn hit_test( @@ -366,6 +368,7 @@ impl backend::Text for Backend { bounds: Size, point: Point, nearest_only: bool, + advanced_shape: bool, ) -> Option { self.text_pipeline.hit_test( contents, @@ -374,6 +377,7 @@ impl backend::Text for Backend { bounds, point, nearest_only, + advanced_shape, ) } diff --git a/wgpu/src/geometry.rs b/wgpu/src/geometry.rs index 59ec31fe..a85875a4 100644 --- a/wgpu/src/geometry.rs +++ b/wgpu/src/geometry.rs @@ -334,6 +334,7 @@ impl Frame { font: text.font, horizontal_alignment: text.horizontal_alignment, vertical_alignment: text.vertical_alignment, + advanced_shape: text.advanced_shape, }); } diff --git a/wgpu/src/layer.rs b/wgpu/src/layer.rs index c4723397..7c5b43a3 100644 --- a/wgpu/src/layer.rs +++ b/wgpu/src/layer.rs @@ -64,6 +64,7 @@ impl<'a> Layer<'a> { font: Font::MONOSPACE, horizontal_alignment: alignment::Horizontal::Left, vertical_alignment: alignment::Vertical::Top, + advanced_shape: false, }; overlay.text.push(text); @@ -116,6 +117,7 @@ impl<'a> Layer<'a> { font, horizontal_alignment, vertical_alignment, + advanced_shape, } => { let layer = &mut layers[current_layer]; @@ -127,6 +129,7 @@ impl<'a> Layer<'a> { font: *font, horizontal_alignment: *horizontal_alignment, vertical_alignment: *vertical_alignment, + advanced_shape: *advanced_shape, }); } Primitive::Quad { diff --git a/wgpu/src/layer/text.rs b/wgpu/src/layer/text.rs index fdbdaafb..d36ff273 100644 --- a/wgpu/src/layer/text.rs +++ b/wgpu/src/layer/text.rs @@ -24,4 +24,14 @@ pub struct Text<'a> { /// The vertical alignment of the [`Text`]. pub vertical_alignment: alignment::Vertical, + + /// Whether the text needs advanced shaping and font fallback. + /// + /// You will need to enable this flag if the text contains a complex + /// script, the font used needs it, and/or multiple fonts in your system + /// may be needed to display all of the glyphs. + /// + /// Advanced shaping is expensive! You should only enable it when + /// necessary. + pub advanced_shape: bool, } diff --git a/wgpu/src/text.rs b/wgpu/src/text.rs index f01e0b42..f433a5b6 100644 --- a/wgpu/src/text.rs +++ b/wgpu/src/text.rs @@ -83,6 +83,7 @@ impl Pipeline { height: (section.bounds.height * scale_factor) .ceil(), }, + advanced_shape: section.advanced_shape, }, ); @@ -213,6 +214,7 @@ impl Pipeline { size: f32, font: Font, bounds: Size, + advanced_shape: bool, ) -> (f32, f32) { let mut measurement_cache = self.measurement_cache.borrow_mut(); @@ -223,6 +225,7 @@ impl Pipeline { size, font, bounds, + advanced_shape, }, ); @@ -244,6 +247,7 @@ impl Pipeline { bounds: Size, point: Point, _nearest_only: bool, + advanced_shape: bool, ) -> Option { let mut measurement_cache = self.measurement_cache.borrow_mut(); @@ -254,6 +258,7 @@ impl Pipeline { size, font, bounds, + advanced_shape, }, ); @@ -364,6 +369,7 @@ impl Cache { .family(to_family(key.font.family)) .weight(to_weight(key.font.weight)) .stretch(to_stretch(key.font.stretch)), + !key.advanced_shape, ); let _ = entry.insert(buffer); @@ -388,6 +394,7 @@ struct Key<'a> { size: f32, font: Font, bounds: Size, + advanced_shape: bool, } type KeyHash = u64; -- cgit From 4bd290afe7d81d9aaf7467b3ce91491f6600261a Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Wed, 19 Apr 2023 02:00:45 +0200 Subject: Introduce `text::Shaping` enum and replace magic boolean --- wgpu/fonts/Iced-Icons.ttf | Bin 5108 -> 5108 bytes wgpu/src/backend.rs | 8 ++++---- wgpu/src/geometry.rs | 2 +- wgpu/src/layer.rs | 7 ++++--- wgpu/src/layer/text.rs | 12 +++--------- wgpu/src/text.rs | 16 ++++++++-------- 6 files changed, 20 insertions(+), 25 deletions(-) (limited to 'wgpu') diff --git a/wgpu/fonts/Iced-Icons.ttf b/wgpu/fonts/Iced-Icons.ttf index 7112f086..e3273141 100644 Binary files a/wgpu/fonts/Iced-Icons.ttf and b/wgpu/fonts/Iced-Icons.ttf differ diff --git a/wgpu/src/backend.rs b/wgpu/src/backend.rs index d09b2dba..6b847aff 100644 --- a/wgpu/src/backend.rs +++ b/wgpu/src/backend.rs @@ -354,10 +354,10 @@ impl backend::Text for Backend { size: f32, font: Font, bounds: Size, - advanced_shape: bool, + shaping: core::text::Shaping, ) -> (f32, f32) { self.text_pipeline - .measure(contents, size, font, bounds, advanced_shape) + .measure(contents, size, font, bounds, shaping) } fn hit_test( @@ -366,18 +366,18 @@ impl backend::Text for Backend { size: f32, font: Font, bounds: Size, + shaping: core::text::Shaping, point: Point, nearest_only: bool, - advanced_shape: bool, ) -> Option { self.text_pipeline.hit_test( contents, size, font, bounds, + shaping, point, nearest_only, - advanced_shape, ) } diff --git a/wgpu/src/geometry.rs b/wgpu/src/geometry.rs index a85875a4..f6397ab7 100644 --- a/wgpu/src/geometry.rs +++ b/wgpu/src/geometry.rs @@ -334,7 +334,7 @@ impl Frame { font: text.font, horizontal_alignment: text.horizontal_alignment, vertical_alignment: text.vertical_alignment, - advanced_shape: text.advanced_shape, + shaping: text.shaping, }); } diff --git a/wgpu/src/layer.rs b/wgpu/src/layer.rs index 7c5b43a3..b9fd044e 100644 --- a/wgpu/src/layer.rs +++ b/wgpu/src/layer.rs @@ -10,6 +10,7 @@ pub use mesh::Mesh; pub use quad::Quad; pub use text::Text; +use crate::core; use crate::core::alignment; use crate::core::{Background, Color, Font, Point, Rectangle, Size, Vector}; use crate::graphics::{Primitive, Viewport}; @@ -64,7 +65,7 @@ impl<'a> Layer<'a> { font: Font::MONOSPACE, horizontal_alignment: alignment::Horizontal::Left, vertical_alignment: alignment::Vertical::Top, - advanced_shape: false, + shaping: core::text::Shaping::Basic, }; overlay.text.push(text); @@ -117,7 +118,7 @@ impl<'a> Layer<'a> { font, horizontal_alignment, vertical_alignment, - advanced_shape, + shaping, } => { let layer = &mut layers[current_layer]; @@ -129,7 +130,7 @@ impl<'a> Layer<'a> { font: *font, horizontal_alignment: *horizontal_alignment, vertical_alignment: *vertical_alignment, - advanced_shape: *advanced_shape, + shaping: *shaping, }); } Primitive::Quad { diff --git a/wgpu/src/layer/text.rs b/wgpu/src/layer/text.rs index d36ff273..665f7188 100644 --- a/wgpu/src/layer/text.rs +++ b/wgpu/src/layer/text.rs @@ -1,4 +1,5 @@ use crate::core::alignment; +use crate::core::text; use crate::core::{Color, Font, Rectangle}; /// A paragraph of text. @@ -25,13 +26,6 @@ pub struct Text<'a> { /// The vertical alignment of the [`Text`]. pub vertical_alignment: alignment::Vertical, - /// Whether the text needs advanced shaping and font fallback. - /// - /// You will need to enable this flag if the text contains a complex - /// script, the font used needs it, and/or multiple fonts in your system - /// may be needed to display all of the glyphs. - /// - /// Advanced shaping is expensive! You should only enable it when - /// necessary. - pub advanced_shape: bool, + /// The shaping strategy of the text. + pub shaping: text::Shaping, } diff --git a/wgpu/src/text.rs b/wgpu/src/text.rs index f433a5b6..fc126125 100644 --- a/wgpu/src/text.rs +++ b/wgpu/src/text.rs @@ -1,6 +1,6 @@ use crate::core::alignment; use crate::core::font::{self, Font}; -use crate::core::text::Hit; +use crate::core::text::{Hit, Shaping}; use crate::core::{Point, Rectangle, Size}; use crate::layer::Text; @@ -83,7 +83,7 @@ impl Pipeline { height: (section.bounds.height * scale_factor) .ceil(), }, - advanced_shape: section.advanced_shape, + shaping: section.shaping, }, ); @@ -214,7 +214,7 @@ impl Pipeline { size: f32, font: Font, bounds: Size, - advanced_shape: bool, + shaping: Shaping, ) -> (f32, f32) { let mut measurement_cache = self.measurement_cache.borrow_mut(); @@ -225,7 +225,7 @@ impl Pipeline { size, font, bounds, - advanced_shape, + shaping, }, ); @@ -245,9 +245,9 @@ impl Pipeline { size: f32, font: Font, bounds: Size, + shaping: Shaping, point: Point, _nearest_only: bool, - advanced_shape: bool, ) -> Option { let mut measurement_cache = self.measurement_cache.borrow_mut(); @@ -258,7 +258,7 @@ impl Pipeline { size, font, bounds, - advanced_shape, + shaping, }, ); @@ -369,7 +369,7 @@ impl Cache { .family(to_family(key.font.family)) .weight(to_weight(key.font.weight)) .stretch(to_stretch(key.font.stretch)), - !key.advanced_shape, + matches!(key.shaping, Shaping::Basic), ); let _ = entry.insert(buffer); @@ -394,7 +394,7 @@ struct Key<'a> { size: f32, font: Font, bounds: Size, - advanced_shape: bool, + shaping: Shaping, } type KeyHash = u64; -- cgit From edf3432bf5176be13437b9fd5d25b890ec9dbe69 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Tue, 2 May 2023 00:58:33 +0200 Subject: Update `glyphon` and `cosmic-text` --- wgpu/Cargo.toml | 2 +- wgpu/src/text.rs | 9 ++++++++- 2 files changed, 9 insertions(+), 2 deletions(-) (limited to 'wgpu') diff --git a/wgpu/Cargo.toml b/wgpu/Cargo.toml index 254d32d6..6934ae49 100644 --- a/wgpu/Cargo.toml +++ b/wgpu/Cargo.toml @@ -44,7 +44,7 @@ path = "../graphics" [dependencies.glyphon] version = "0.2" git = "https://github.com/hecrj/glyphon.git" -rev = "446cf0803065b52ba5fb9a30fe0addb6d7b5f9d9" +rev = "504aa8a9a1fb42726f02fa244b70119e7ca25933" [dependencies.encase] version = "0.3.0" diff --git a/wgpu/src/text.rs b/wgpu/src/text.rs index fc126125..ad7bdc8d 100644 --- a/wgpu/src/text.rs +++ b/wgpu/src/text.rs @@ -311,6 +311,13 @@ fn to_stretch(stretch: font::Stretch) -> glyphon::Stretch { } } +fn to_shaping(shaping: Shaping) -> glyphon::Shaping { + match shaping { + Shaping::Basic => glyphon::Shaping::Basic, + Shaping::Advanced => glyphon::Shaping::Advanced, + } +} + struct Cache { entries: FxHashMap, recently_used: FxHashSet, @@ -369,7 +376,7 @@ impl Cache { .family(to_family(key.font.family)) .weight(to_weight(key.font.weight)) .stretch(to_stretch(key.font.stretch)), - matches!(key.shaping, Shaping::Basic), + to_shaping(key.shaping), ); let _ = entry.insert(buffer); -- cgit From 9499a8f9e6f9971dedfae563cb133232aa3cebc2 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Thu, 4 May 2023 13:00:16 +0200 Subject: Support configurable `LineHeight` in text widgets --- wgpu/src/backend.rs | 13 +++++++++++-- wgpu/src/geometry.rs | 1 + wgpu/src/layer.rs | 3 +++ wgpu/src/layer/text.rs | 5 ++++- wgpu/src/text.rs | 28 ++++++++++++++++++++++------ 5 files changed, 41 insertions(+), 9 deletions(-) (limited to 'wgpu') diff --git a/wgpu/src/backend.rs b/wgpu/src/backend.rs index 6b847aff..def80a81 100644 --- a/wgpu/src/backend.rs +++ b/wgpu/src/backend.rs @@ -352,18 +352,26 @@ impl backend::Text for Backend { &self, contents: &str, size: f32, + line_height: core::text::LineHeight, font: Font, bounds: Size, shaping: core::text::Shaping, ) -> (f32, f32) { - self.text_pipeline - .measure(contents, size, font, bounds, shaping) + self.text_pipeline.measure( + contents, + size, + line_height, + font, + bounds, + shaping, + ) } fn hit_test( &self, contents: &str, size: f32, + line_height: core::text::LineHeight, font: Font, bounds: Size, shaping: core::text::Shaping, @@ -373,6 +381,7 @@ impl backend::Text for Backend { self.text_pipeline.hit_test( contents, size, + line_height, font, bounds, shaping, diff --git a/wgpu/src/geometry.rs b/wgpu/src/geometry.rs index f6397ab7..8cfed1e5 100644 --- a/wgpu/src/geometry.rs +++ b/wgpu/src/geometry.rs @@ -331,6 +331,7 @@ impl Frame { }, color: text.color, size: text.size, + line_height: text.line_height, font: text.font, horizontal_alignment: text.horizontal_alignment, vertical_alignment: text.vertical_alignment, diff --git a/wgpu/src/layer.rs b/wgpu/src/layer.rs index b9fd044e..dcae0648 100644 --- a/wgpu/src/layer.rs +++ b/wgpu/src/layer.rs @@ -62,6 +62,7 @@ impl<'a> Layer<'a> { ), color: Color::new(0.9, 0.9, 0.9, 1.0), size: 20.0, + line_height: core::text::LineHeight::Relative(1.2), font: Font::MONOSPACE, horizontal_alignment: alignment::Horizontal::Left, vertical_alignment: alignment::Vertical::Top, @@ -114,6 +115,7 @@ impl<'a> Layer<'a> { content, bounds, size, + line_height, color, font, horizontal_alignment, @@ -126,6 +128,7 @@ impl<'a> Layer<'a> { content, bounds: *bounds + translation, size: *size, + line_height: *line_height, color: *color, font: *font, horizontal_alignment: *horizontal_alignment, diff --git a/wgpu/src/layer/text.rs b/wgpu/src/layer/text.rs index 665f7188..ba1bdca8 100644 --- a/wgpu/src/layer/text.rs +++ b/wgpu/src/layer/text.rs @@ -14,9 +14,12 @@ pub struct Text<'a> { /// The color of the [`Text`], in __linear RGB_. pub color: Color, - /// The size of the [`Text`]. + /// The size of the [`Text`] in logical pixels. pub size: f32, + /// The line height of the [`Text`]. + pub line_height: text::LineHeight, + /// The font of the [`Text`]. pub font: Font, diff --git a/wgpu/src/text.rs b/wgpu/src/text.rs index ad7bdc8d..ff68772d 100644 --- a/wgpu/src/text.rs +++ b/wgpu/src/text.rs @@ -1,7 +1,7 @@ use crate::core::alignment; use crate::core::font::{self, Font}; -use crate::core::text::{Hit, Shaping}; -use crate::core::{Point, Rectangle, Size}; +use crate::core::text::{Hit, LineHeight, Shaping}; +use crate::core::{Pixels, Point, Rectangle, Size}; use crate::layer::Text; use rustc_hash::{FxHashMap, FxHashSet}; @@ -77,6 +77,11 @@ impl Pipeline { Key { content: section.content, size: section.size * scale_factor, + line_height: f32::from( + section + .line_height + .to_absolute(Pixels(section.size)), + ) * scale_factor, font: section.font, bounds: Size { width: (section.bounds.width * scale_factor).ceil(), @@ -114,7 +119,7 @@ impl Pipeline { }); let total_height = - total_lines as f32 * section.size * 1.2 * scale_factor; + total_lines as f32 * buffer.metrics().line_height; let left = match section.horizontal_alignment { alignment::Horizontal::Left => x, @@ -212,17 +217,21 @@ impl Pipeline { &self, content: &str, size: f32, + line_height: LineHeight, font: Font, bounds: Size, shaping: Shaping, ) -> (f32, f32) { let mut measurement_cache = self.measurement_cache.borrow_mut(); + let line_height = f32::from(line_height.to_absolute(Pixels(size))); + let (_, paragraph) = measurement_cache.allocate( &mut self.font_system.borrow_mut(), Key { content, size, + line_height, font, bounds, shaping, @@ -236,13 +245,14 @@ impl Pipeline { (i + 1, buffer.line_w.max(max)) }); - (max_width, size * 1.2 * total_lines as f32) + (max_width, line_height * total_lines as f32) } pub fn hit_test( &self, content: &str, size: f32, + line_height: LineHeight, font: Font, bounds: Size, shaping: Shaping, @@ -251,11 +261,14 @@ impl Pipeline { ) -> Option { let mut measurement_cache = self.measurement_cache.borrow_mut(); + let line_height = f32::from(line_height.to_absolute(Pixels(size))); + let (_, paragraph) = measurement_cache.allocate( &mut self.font_system.borrow_mut(), Key { content, size, + line_height, font, bounds, shaping, @@ -353,21 +366,23 @@ impl Cache { key.content.hash(&mut hasher); key.size.to_bits().hash(&mut hasher); + key.line_height.to_bits().hash(&mut hasher); key.font.hash(&mut hasher); key.bounds.width.to_bits().hash(&mut hasher); key.bounds.height.to_bits().hash(&mut hasher); + key.shaping.hash(&mut hasher); hasher.finish() }; if let hash_map::Entry::Vacant(entry) = self.entries.entry(hash) { - let metrics = glyphon::Metrics::new(key.size, key.size * 1.2); + let metrics = glyphon::Metrics::new(key.size, key.line_height); let mut buffer = glyphon::Buffer::new(font_system, metrics); buffer.set_size( font_system, key.bounds.width, - key.bounds.height.max(key.size * 1.2), + key.bounds.height.max(key.line_height), ); buffer.set_text( font_system, @@ -399,6 +414,7 @@ impl Cache { struct Key<'a> { content: &'a str, size: f32, + line_height: f32, font: Font, bounds: Size, shaping: Shaping, -- cgit From c189ef62a6a5dd183b8098496ac31d30f7814ff3 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Mon, 8 May 2023 14:46:56 +0200 Subject: Use `LineHeight::default` in `iced_wgpu::layer` --- wgpu/src/layer.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'wgpu') diff --git a/wgpu/src/layer.rs b/wgpu/src/layer.rs index dcae0648..ab66264c 100644 --- a/wgpu/src/layer.rs +++ b/wgpu/src/layer.rs @@ -62,7 +62,7 @@ impl<'a> Layer<'a> { ), color: Color::new(0.9, 0.9, 0.9, 1.0), size: 20.0, - line_height: core::text::LineHeight::Relative(1.2), + line_height: core::text::LineHeight::default(), font: Font::MONOSPACE, horizontal_alignment: alignment::Horizontal::Left, vertical_alignment: alignment::Vertical::Top, -- cgit From 91ef07e6ebe56e46ea235f6657b17a3a078ddf5b Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Mon, 8 May 2023 14:51:53 +0200 Subject: Warn about unsupported primitives in `iced_wgpu` --- wgpu/src/layer.rs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) (limited to 'wgpu') diff --git a/wgpu/src/layer.rs b/wgpu/src/layer.rs index ab66264c..8af72b9d 100644 --- a/wgpu/src/layer.rs +++ b/wgpu/src/layer.rs @@ -270,7 +270,11 @@ impl<'a> Layer<'a> { ); } _ => { - // Unsupported! + // Not supported! + log::warn!( + "Unsupported primitive in `iced_wgpu`: {:?}", + primitive + ); } } } -- cgit From c6d9221ee42b2838ca07b79df710d3d224e1c52a Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Mon, 8 May 2023 16:20:05 +0200 Subject: Round paragraph position until we implement subpixel glyph positioning --- wgpu/src/text.rs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) (limited to 'wgpu') diff --git a/wgpu/src/text.rs b/wgpu/src/text.rs index ff68772d..cf9fbad3 100644 --- a/wgpu/src/text.rs +++ b/wgpu/src/text.rs @@ -133,10 +133,14 @@ impl Pipeline { alignment::Vertical::Bottom => y - total_height, }; + // TODO: Subpixel glyph positioning + let left = left.round() as i32; + let top = top.round() as i32; + glyphon::TextArea { buffer, - left: left as i32, - top: top as i32, + left, + top, bounds, default_color: { let [r, g, b, a] = section.color.into_linear(); -- cgit From 9a8b30d7e9cf76c097d68c84736e7687b452d5c0 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Mon, 8 May 2023 16:41:42 +0200 Subject: Clip text that exceeds section bounds in `iced_wgpu` --- wgpu/src/text.rs | 124 ++++++++++++++++++++++++++++++------------------------- 1 file changed, 68 insertions(+), 56 deletions(-) (limited to 'wgpu') diff --git a/wgpu/src/text.rs b/wgpu/src/text.rs index cf9fbad3..714e0400 100644 --- a/wgpu/src/text.rs +++ b/wgpu/src/text.rs @@ -96,64 +96,76 @@ impl Pipeline { }) .collect(); - let bounds = glyphon::TextBounds { - left: (bounds.x * scale_factor) as i32, - top: (bounds.y * scale_factor) as i32, - right: ((bounds.x + bounds.width) * scale_factor) as i32, - bottom: ((bounds.y + bounds.height) * scale_factor) as i32, - }; + let bounds = bounds * scale_factor; let text_areas = - sections.iter().zip(keys.iter()).map(|(section, key)| { - let buffer = - self.render_cache.get(key).expect("Get cached buffer"); - - let x = section.bounds.x * scale_factor; - let y = section.bounds.y * scale_factor; - - let (total_lines, max_width) = buffer - .layout_runs() - .enumerate() - .fold((0, 0.0), |(_, max), (i, buffer)| { - (i + 1, buffer.line_w.max(max)) - }); - - let total_height = - total_lines as f32 * buffer.metrics().line_height; - - let left = match section.horizontal_alignment { - alignment::Horizontal::Left => x, - alignment::Horizontal::Center => x - max_width / 2.0, - alignment::Horizontal::Right => x - max_width, - }; - - let top = match section.vertical_alignment { - alignment::Vertical::Top => y, - alignment::Vertical::Center => y - total_height / 2.0, - alignment::Vertical::Bottom => y - total_height, - }; - - // TODO: Subpixel glyph positioning - let left = left.round() as i32; - let top = top.round() as i32; - - glyphon::TextArea { - buffer, - left, - top, - bounds, - default_color: { - let [r, g, b, a] = section.color.into_linear(); - - glyphon::Color::rgba( - (r * 255.0) as u8, - (g * 255.0) as u8, - (b * 255.0) as u8, - (a * 255.0) as u8, - ) - }, - } - }); + sections + .iter() + .zip(keys.iter()) + .filter_map(|(section, key)| { + let buffer = + self.render_cache.get(key).expect("Get cached buffer"); + + let (total_lines, max_width) = buffer + .layout_runs() + .enumerate() + .fold((0, 0.0), |(_, max), (i, buffer)| { + (i + 1, buffer.line_w.max(max)) + }); + + let total_height = + total_lines as f32 * buffer.metrics().line_height; + + let x = section.bounds.x * scale_factor; + let y = section.bounds.y * scale_factor; + + let left = match section.horizontal_alignment { + alignment::Horizontal::Left => x, + alignment::Horizontal::Center => x - max_width / 2.0, + alignment::Horizontal::Right => x - max_width, + }; + + let top = match section.vertical_alignment { + alignment::Vertical::Top => y, + alignment::Vertical::Center => y - total_height / 2.0, + alignment::Vertical::Bottom => y - total_height, + }; + + let section_bounds = Rectangle { + x: left, + y: top, + width: section.bounds.width * scale_factor, + height: section.bounds.height * scale_factor, + }; + + let clip_bounds = bounds.intersection(§ion_bounds)?; + + // TODO: Subpixel glyph positioning + let left = left.round() as i32; + let top = top.round() as i32; + + Some(glyphon::TextArea { + buffer, + left, + top, + bounds: glyphon::TextBounds { + left: clip_bounds.x as i32, + top: clip_bounds.y as i32, + right: (clip_bounds.x + clip_bounds.width) as i32, + bottom: (clip_bounds.y + clip_bounds.height) as i32, + }, + default_color: { + let [r, g, b, a] = section.color.into_linear(); + + glyphon::Color::rgba( + (r * 255.0) as u8, + (g * 255.0) as u8, + (b * 255.0) as u8, + (a * 255.0) as u8, + ) + }, + }) + }); let result = renderer.prepare( device, -- cgit From de638f44a5c62459008a5c024b39c2443b72bf25 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Thu, 11 May 2023 15:37:56 +0200 Subject: Write missing documentation in `iced_wgpu` --- wgpu/src/geometry.rs | 15 +++++++++------ wgpu/src/lib.rs | 2 +- 2 files changed, 10 insertions(+), 7 deletions(-) (limited to 'wgpu') diff --git a/wgpu/src/geometry.rs b/wgpu/src/geometry.rs index 8cfed1e5..7e17a7ad 100644 --- a/wgpu/src/geometry.rs +++ b/wgpu/src/geometry.rs @@ -1,3 +1,4 @@ +//! Build and draw geometry. use crate::core::{Gradient, Point, Rectangle, Size, Vector}; use crate::graphics::geometry::fill::{self, Fill}; use crate::graphics::geometry::{ @@ -9,9 +10,7 @@ use lyon::geom::euclid; use lyon::tessellation; use std::borrow::Cow; -/// The frame of a [`Canvas`]. -/// -/// [`Canvas`]: crate::widget::Canvas +/// A frame for drawing some geometry. #[allow(missing_debug_implementations)] pub struct Frame { size: Size, @@ -353,10 +352,12 @@ impl Frame { self.pop_transform(); } + /// Pushes the current transform in the transform stack. pub fn push_transform(&mut self) { self.transforms.previous.push(self.transforms.current); } + /// Pops a transform from the transform stack and sets it as the current transform. pub fn pop_transform(&mut self) { self.transforms.current = self.transforms.previous.pop().unwrap(); } @@ -373,14 +374,16 @@ impl Frame { f(&mut frame); - let translation = Vector::new(region.x, region.y); + let origin = Point::new(region.x, region.y); - self.clip(frame, translation); + self.clip(frame, origin); } - pub fn clip(&mut self, frame: Frame, translation: Vector) { + /// Draws the clipped contents of the given [`Frame`] with origin at the given [`Point`]. + pub fn clip(&mut self, frame: Frame, at: Point) { let size = frame.size(); let primitives = frame.into_primitives(); + let translation = Vector::new(at.x, at.y); let (text, meshes) = primitives .into_iter() diff --git a/wgpu/src/lib.rs b/wgpu/src/lib.rs index 0b169140..4a92c345 100644 --- a/wgpu/src/lib.rs +++ b/wgpu/src/lib.rs @@ -25,7 +25,7 @@ )] #![deny( missing_debug_implementations, - //missing_docs, + missing_docs, unsafe_code, unused_results, clippy::extra_unused_lifetimes, -- cgit From cf434236e7e15e0fa05e5915b8d4d78dcaf1b7e8 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Thu, 11 May 2023 17:28:51 +0200 Subject: Enable `doc_auto_cfg` when generating documentation --- wgpu/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'wgpu') diff --git a/wgpu/src/lib.rs b/wgpu/src/lib.rs index 4a92c345..7004416f 100644 --- a/wgpu/src/lib.rs +++ b/wgpu/src/lib.rs @@ -36,7 +36,7 @@ )] #![forbid(rust_2018_idioms)] #![allow(clippy::inherent_to_string, clippy::type_complexity)] -#![cfg_attr(docsrs, feature(doc_cfg))] +#![cfg_attr(docsrs, feature(doc_auto_cfg))] pub mod layer; pub mod settings; pub mod window; -- cgit From 6551a0b2ab6c831dd1d3646ecf55180339275e22 Mon Sep 17 00:00:00 2001 From: Bingus Date: Thu, 11 May 2023 09:12:06 -0700 Subject: Added support for gradients as background variants + other optimizations. --- wgpu/Cargo.toml | 4 - wgpu/src/buffer.rs | 43 +++- wgpu/src/buffer/dynamic.rs | 202 ---------------- wgpu/src/buffer/static.rs | 107 --------- wgpu/src/geometry.rs | 111 ++++++--- wgpu/src/image.rs | 46 +--- wgpu/src/layer.rs | 94 ++++++-- wgpu/src/layer/mesh.rs | 15 +- wgpu/src/layer/quad.rs | 41 +++- wgpu/src/lib.rs | 2 - wgpu/src/quad.rs | 481 +++++++++++++++++++++++++++---------- wgpu/src/shader/gradient.wgsl | 88 ------- wgpu/src/shader/quad.wgsl | 298 +++++++++++++++++++---- wgpu/src/shader/solid.wgsl | 30 --- wgpu/src/shader/triangle.wgsl | 168 +++++++++++++ wgpu/src/triangle.rs | 543 +++++++++++++++--------------------------- 16 files changed, 1207 insertions(+), 1066 deletions(-) delete mode 100644 wgpu/src/buffer/dynamic.rs delete mode 100644 wgpu/src/buffer/static.rs delete mode 100644 wgpu/src/shader/gradient.wgsl delete mode 100644 wgpu/src/shader/solid.wgsl create mode 100644 wgpu/src/shader/triangle.wgsl (limited to 'wgpu') diff --git a/wgpu/Cargo.toml b/wgpu/Cargo.toml index 41eb4c23..1b71d6ec 100644 --- a/wgpu/Cargo.toml +++ b/wgpu/Cargo.toml @@ -46,10 +46,6 @@ version = "0.2" git = "https://github.com/hecrj/glyphon.git" rev = "f145067d292082abdd1f2b2481812d4a52c394ec" -[dependencies.encase] -version = "0.3.0" -features = ["glam"] - [dependencies.glam] version = "0.21.3" diff --git a/wgpu/src/buffer.rs b/wgpu/src/buffer.rs index c210dd4e..94122187 100644 --- a/wgpu/src/buffer.rs +++ b/wgpu/src/buffer.rs @@ -1,7 +1,3 @@ -//! Utilities for buffer operations. -pub mod dynamic; -pub mod r#static; - use std::marker::PhantomData; use std::ops::RangeBounds; @@ -10,7 +6,8 @@ pub struct Buffer { label: &'static str, size: u64, usage: wgpu::BufferUsages, - raw: wgpu::Buffer, + pub(crate) raw: wgpu::Buffer, + offsets: Vec, type_: PhantomData, } @@ -35,6 +32,7 @@ impl Buffer { size, usage, raw, + offsets: Vec::new(), type_: PhantomData, } } @@ -43,6 +41,8 @@ impl Buffer { let new_size = (std::mem::size_of::() * new_count) as u64; if self.size < new_size { + self.offsets.clear(); + self.raw = device.create_buffer(&wgpu::BufferDescriptor { label: Some(self.label), size: new_size, @@ -58,17 +58,19 @@ impl Buffer { } } + /// Returns the size of the written bytes. pub fn write( - &self, + &mut self, queue: &wgpu::Queue, - offset_count: usize, + offset: usize, contents: &[T], - ) { - queue.write_buffer( - &self.raw, - (std::mem::size_of::() * offset_count) as u64, - bytemuck::cast_slice(contents), - ); + ) -> usize { + let bytes: &[u8] = bytemuck::cast_slice(contents); + queue.write_buffer(&self.raw, offset as u64, bytes); + + self.offsets.push(offset as u64); + + bytes.len() } pub fn slice( @@ -77,6 +79,21 @@ impl Buffer { ) -> wgpu::BufferSlice<'_> { self.raw.slice(bounds) } + + /// Returns the slice calculated from the offset stored at the given index. + pub fn slice_from_index(&self, index: usize) -> wgpu::BufferSlice<'_> { + self.raw.slice(self.offset_at(index)..) + } + + /// Clears any temporary data (i.e. offsets) from the buffer. + pub fn clear(&mut self) { + self.offsets.clear() + } + + /// Returns the offset at `index`, if it exists. + fn offset_at(&self, index: usize) -> &wgpu::BufferAddress { + self.offsets.get(index).expect("No offset at index.") + } } fn next_copy_size(amount: usize) -> u64 { diff --git a/wgpu/src/buffer/dynamic.rs b/wgpu/src/buffer/dynamic.rs deleted file mode 100644 index 43fc47ac..00000000 --- a/wgpu/src/buffer/dynamic.rs +++ /dev/null @@ -1,202 +0,0 @@ -//! Utilities for uniform buffer operations. -use encase::private::WriteInto; -use encase::ShaderType; - -use std::fmt; -use std::marker::PhantomData; - -/// A dynamic buffer is any type of buffer which does not have a static offset. -#[derive(Debug)] -pub struct Buffer { - offsets: Vec, - cpu: Internal, - gpu: wgpu::Buffer, - label: &'static str, - size: u64, - _data: PhantomData, -} - -impl Buffer { - /// Creates a new dynamic uniform buffer. - pub fn uniform(device: &wgpu::Device, label: &'static str) -> Self { - Buffer::new( - device, - Internal::Uniform(encase::DynamicUniformBuffer::new(Vec::new())), - label, - wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST, - ) - } - - #[cfg(not(target_arch = "wasm32"))] - /// Creates a new dynamic storage buffer. - pub fn storage(device: &wgpu::Device, label: &'static str) -> Self { - Buffer::new( - device, - Internal::Storage(encase::DynamicStorageBuffer::new(Vec::new())), - label, - wgpu::BufferUsages::STORAGE | wgpu::BufferUsages::COPY_DST, - ) - } - - fn new( - device: &wgpu::Device, - dynamic_buffer_type: Internal, - label: &'static str, - usage: wgpu::BufferUsages, - ) -> Self { - let initial_size = u64::from(T::min_size()); - - Self { - offsets: Vec::new(), - cpu: dynamic_buffer_type, - gpu: Buffer::::create_gpu_buffer( - device, - label, - usage, - initial_size, - ), - label, - size: initial_size, - _data: Default::default(), - } - } - - fn create_gpu_buffer( - device: &wgpu::Device, - label: &'static str, - usage: wgpu::BufferUsages, - size: u64, - ) -> wgpu::Buffer { - device.create_buffer(&wgpu::BufferDescriptor { - label: Some(label), - size, - usage, - mapped_at_creation: false, - }) - } - - /// Write a new value to the CPU buffer with proper alignment. Stores the returned offset value - /// in the buffer for future use. - pub fn push(&mut self, value: &T) { - //this write operation on the cpu buffer will adjust for uniform alignment requirements - let offset = self.cpu.write(value); - self.offsets.push(offset); - } - - /// Resize buffer contents if necessary. This will re-create the GPU buffer if current size is - /// less than the newly computed size from the CPU buffer. - /// - /// If the gpu buffer is resized, its bind group will need to be recreated! - pub fn resize(&mut self, device: &wgpu::Device) -> bool { - let new_size = self.cpu.get_ref().len() as u64; - - if self.size < new_size { - let usages = match self.cpu { - Internal::Uniform(_) => { - wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST - } - #[cfg(not(target_arch = "wasm32"))] - Internal::Storage(_) => { - wgpu::BufferUsages::STORAGE | wgpu::BufferUsages::COPY_DST - } - }; - - self.gpu = Buffer::::create_gpu_buffer( - device, self.label, usages, new_size, - ); - self.size = new_size; - true - } else { - false - } - } - - /// Write the contents of this dynamic buffer to the GPU via staging belt command. - pub fn write(&mut self, queue: &wgpu::Queue) { - queue.write_buffer(&self.gpu, 0, self.cpu.get_ref()); - } - - // Gets the aligned offset at the given index from the CPU buffer. - pub fn offset_at_index(&self, index: usize) -> wgpu::DynamicOffset { - let offset = self - .offsets - .get(index) - .copied() - .expect("Index not found in offsets."); - - offset - } - - /// Returns a reference to the GPU buffer. - pub fn raw(&self) -> &wgpu::Buffer { - &self.gpu - } - - /// Reset the buffer. - pub fn clear(&mut self) { - self.offsets.clear(); - self.cpu.clear(); - } -} - -// Currently supported dynamic buffers. -enum Internal { - Uniform(encase::DynamicUniformBuffer>), - #[cfg(not(target_arch = "wasm32"))] - //storage buffers are not supported on wgpu wasm target (yet) - Storage(encase::DynamicStorageBuffer>), -} - -impl Internal { - /// Writes the current value to its CPU buffer with proper alignment. - pub(super) fn write( - &mut self, - value: &T, - ) -> wgpu::DynamicOffset { - match self { - Internal::Uniform(buf) => buf - .write(value) - .expect("Error when writing to dynamic uniform buffer.") - as u32, - #[cfg(not(target_arch = "wasm32"))] - Internal::Storage(buf) => buf - .write(value) - .expect("Error when writing to dynamic storage buffer.") - as u32, - } - } - - /// Returns bytearray of aligned CPU buffer. - pub(super) fn get_ref(&self) -> &[u8] { - match self { - Internal::Uniform(buf) => buf.as_ref(), - #[cfg(not(target_arch = "wasm32"))] - Internal::Storage(buf) => buf.as_ref(), - } - } - - /// Resets the CPU buffer. - pub(super) fn clear(&mut self) { - match self { - Internal::Uniform(buf) => { - buf.as_mut().clear(); - buf.set_offset(0); - } - #[cfg(not(target_arch = "wasm32"))] - Internal::Storage(buf) => { - buf.as_mut().clear(); - buf.set_offset(0); - } - } - } -} - -impl fmt::Debug for Internal { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - Self::Uniform(_) => write!(f, "Internal::Uniform(_)"), - #[cfg(not(target_arch = "wasm32"))] - Self::Storage(_) => write!(f, "Internal::Storage(_)"), - } - } -} diff --git a/wgpu/src/buffer/static.rs b/wgpu/src/buffer/static.rs deleted file mode 100644 index d8ae116e..00000000 --- a/wgpu/src/buffer/static.rs +++ /dev/null @@ -1,107 +0,0 @@ -use bytemuck::{Pod, Zeroable}; -use std::marker::PhantomData; -use std::mem; - -const DEFAULT_COUNT: wgpu::BufferAddress = 128; - -/// A generic buffer struct useful for items which have no alignment requirements -/// (e.g. Vertex, Index buffers) & no dynamic offsets. -#[derive(Debug)] -pub struct Buffer { - //stored sequentially per mesh iteration; refers to the offset index in the GPU buffer - offsets: Vec, - label: &'static str, - usages: wgpu::BufferUsages, - gpu: wgpu::Buffer, - size: wgpu::BufferAddress, - _data: PhantomData, -} - -impl Buffer { - /// Initialize a new static buffer. - pub fn new( - device: &wgpu::Device, - label: &'static str, - usages: wgpu::BufferUsages, - ) -> Self { - let size = (mem::size_of::() as u64) * DEFAULT_COUNT; - - Self { - offsets: Vec::new(), - label, - usages, - gpu: Self::gpu_buffer(device, label, size, usages), - size, - _data: PhantomData, - } - } - - fn gpu_buffer( - device: &wgpu::Device, - label: &'static str, - size: wgpu::BufferAddress, - usage: wgpu::BufferUsages, - ) -> wgpu::Buffer { - device.create_buffer(&wgpu::BufferDescriptor { - label: Some(label), - size, - usage, - mapped_at_creation: false, - }) - } - - /// Returns whether or not the buffer needs to be recreated. This can happen whenever mesh data - /// changes & a redraw is requested. - pub fn resize(&mut self, device: &wgpu::Device, new_count: usize) -> bool { - let size = (mem::size_of::() * new_count) as u64; - - if self.size < size { - self.size = - (mem::size_of::() * (new_count + new_count / 2)) as u64; - - self.gpu = - Self::gpu_buffer(device, self.label, self.size, self.usages); - - self.offsets.clear(); - true - } else { - false - } - } - - /// Writes the current vertex data to the gpu buffer with a memcpy & stores its offset. - /// - /// Returns the size of the written bytes. - pub fn write( - &mut self, - queue: &wgpu::Queue, - offset: u64, - content: &[T], - ) -> u64 { - let bytes = bytemuck::cast_slice(content); - let bytes_size = bytes.len() as u64; - - queue.write_buffer(&self.gpu, offset, bytes); - self.offsets.push(offset); - - bytes_size - } - - fn offset_at(&self, index: usize) -> &wgpu::BufferAddress { - self.offsets - .get(index) - .expect("Offset at index does not exist.") - } - - /// Returns the slice calculated from the offset stored at the given index. - /// e.g. to calculate the slice for the 2nd mesh in the layer, this would be the offset at index - /// 1 that we stored earlier when writing. - pub fn slice_from_index(&self, index: usize) -> wgpu::BufferSlice<'_> { - self.gpu.slice(self.offset_at(index)..) - } - - /// Clears any temporary data from the buffer. - pub fn clear(&mut self) { - self.offsets.clear() - } -} diff --git a/wgpu/src/geometry.rs b/wgpu/src/geometry.rs index 7e17a7ad..e26d9278 100644 --- a/wgpu/src/geometry.rs +++ b/wgpu/src/geometry.rs @@ -1,10 +1,11 @@ //! Build and draw geometry. -use crate::core::{Gradient, Point, Rectangle, Size, Vector}; +use crate::core::{Point, Rectangle, Size, Vector}; use crate::graphics::geometry::fill::{self, Fill}; use crate::graphics::geometry::{ LineCap, LineDash, LineJoin, Path, Stroke, Style, Text, }; use crate::graphics::primitive::{self, Primitive}; +use crate::graphics::Gradient; use lyon::geom::euclid; use lyon::tessellation; @@ -23,10 +24,7 @@ pub struct Frame { enum Buffer { Solid(tessellation::VertexBuffers), - Gradient( - tessellation::VertexBuffers, - Gradient, - ), + Gradient(tessellation::VertexBuffers), } struct BufferStack { @@ -48,12 +46,11 @@ impl BufferStack { )); } }, - Style::Gradient(gradient) => match self.stack.last() { - Some(Buffer::Gradient(_, last)) if gradient == last => {} + Style::Gradient(_) => match self.stack.last() { + Some(Buffer::Gradient(_)) => {} _ => { self.stack.push(Buffer::Gradient( tessellation::VertexBuffers::new(), - gradient.clone(), )); } }, @@ -73,9 +70,14 @@ impl BufferStack { TriangleVertex2DBuilder(color.into_linear()), )) } - (Style::Gradient(_), Buffer::Gradient(buffer, _)) => Box::new( - tessellation::BuffersBuilder::new(buffer, Vertex2DBuilder), - ), + (Style::Gradient(gradient), Buffer::Gradient(buffer)) => { + Box::new(tessellation::BuffersBuilder::new( + buffer, + GradientVertex2DBuilder { + gradient: gradient.clone(), + }, + )) + } _ => unreachable!(), } } @@ -91,9 +93,14 @@ impl BufferStack { TriangleVertex2DBuilder(color.into_linear()), )) } - (Style::Gradient(_), Buffer::Gradient(buffer, _)) => Box::new( - tessellation::BuffersBuilder::new(buffer, Vertex2DBuilder), - ), + (Style::Gradient(gradient), Buffer::Gradient(buffer)) => { + Box::new(tessellation::BuffersBuilder::new( + buffer, + GradientVertex2DBuilder { + gradient: gradient.clone(), + }, + )) + } _ => unreachable!(), } } @@ -131,11 +138,13 @@ impl Transform { } fn transform_gradient(&self, mut gradient: Gradient) -> Gradient { - let (start, end) = match &mut gradient { - Gradient::Linear(linear) => (&mut linear.start, &mut linear.end), - }; - self.transform_point(start); - self.transform_point(end); + match &mut gradient { + Gradient::Linear(linear) => { + self.transform_point(&mut linear.start); + self.transform_point(&mut linear.end); + } + } + gradient } } @@ -462,7 +471,7 @@ impl Frame { }) } } - Buffer::Gradient(buffer, gradient) => { + Buffer::Gradient(buffer) => { if !buffer.indices.is_empty() { self.primitives.push(Primitive::GradientMesh { buffers: primitive::Mesh2D { @@ -470,7 +479,6 @@ impl Frame { indices: buffer.indices, }, size: self.size, - gradient, }) } } @@ -481,34 +489,38 @@ impl Frame { } } -struct Vertex2DBuilder; +struct GradientVertex2DBuilder { + gradient: Gradient, +} -impl tessellation::FillVertexConstructor - for Vertex2DBuilder +impl tessellation::FillVertexConstructor + for GradientVertex2DBuilder { fn new_vertex( &mut self, vertex: tessellation::FillVertex<'_>, - ) -> primitive::Vertex2D { + ) -> primitive::GradientVertex2D { let position = vertex.position(); - primitive::Vertex2D { + primitive::GradientVertex2D { position: [position.x, position.y], + gradient: pack_gradient(&self.gradient), } } } -impl tessellation::StrokeVertexConstructor - for Vertex2DBuilder +impl tessellation::StrokeVertexConstructor + for GradientVertex2DBuilder { fn new_vertex( &mut self, vertex: tessellation::StrokeVertex<'_, '_>, - ) -> primitive::Vertex2D { + ) -> primitive::GradientVertex2D { let position = vertex.position(); - primitive::Vertex2D { + primitive::GradientVertex2D { position: [position.x, position.y], + gradient: pack_gradient(&self.gradient), } } } @@ -611,3 +623,42 @@ pub(super) fn dashed(path: &Path, line_dash: LineDash<'_>) -> Path { ); }) } + +/// Packs the [`Gradient`] for use in shader code. +fn pack_gradient(gradient: &Gradient) -> [f32; 44] { + match gradient { + Gradient::Linear(linear) => { + let mut pack: [f32; 44] = [0.0; 44]; + let mut offsets: [f32; 8] = [2.0; 8]; + + for (index, stop) in linear.color_stops.iter().enumerate() { + let [r, g, b, a] = stop + .map_or(crate::core::Color::default(), |s| s.color) + .into_linear(); + + pack[index * 4] = r; + pack[(index * 4) + 1] = g; + pack[(index * 4) + 2] = b; + pack[(index * 4) + 3] = a; + + offsets[index] = stop.map_or(2.0, |s| s.offset); + } + + pack[32] = offsets[0]; + pack[33] = offsets[1]; + pack[34] = offsets[2]; + pack[35] = offsets[3]; + pack[36] = offsets[4]; + pack[37] = offsets[5]; + pack[38] = offsets[6]; + pack[39] = offsets[7]; + + pack[40] = linear.start.x; + pack[41] = linear.start.y; + pack[42] = linear.end.x; + pack[43] = linear.end.y; + + pack + } + } +} diff --git a/wgpu/src/image.rs b/wgpu/src/image.rs index 263bcfa2..6fe02b91 100644 --- a/wgpu/src/image.rs +++ b/wgpu/src/image.rs @@ -8,10 +8,10 @@ mod vector; use atlas::Atlas; +use crate::buffer::Buffer; use crate::core::{Rectangle, Size}; use crate::graphics::Transformation; -use crate::layer; -use crate::Buffer; +use crate::{layer, quad}; use std::cell::RefCell; use std::mem; @@ -121,7 +121,7 @@ impl Layer { ); let _ = self.instances.resize(device, instances.len()); - self.instances.write(queue, 0, instances); + let _ = self.instances.write(queue, 0, instances); self.instance_count = instances.len(); } @@ -131,7 +131,7 @@ impl Layer { render_pass.set_vertex_buffer(1, self.instances.slice(..)); render_pass.draw_indexed( - 0..QUAD_INDICES.len() as u32, + 0..quad::INDICES.len() as u32, 0, 0..self.instance_count as u32, ); @@ -244,22 +244,7 @@ impl Pipeline { fragment: Some(wgpu::FragmentState { module: &shader, entry_point: "fs_main", - targets: &[Some(wgpu::ColorTargetState { - format, - blend: Some(wgpu::BlendState { - color: wgpu::BlendComponent { - src_factor: wgpu::BlendFactor::SrcAlpha, - dst_factor: wgpu::BlendFactor::OneMinusSrcAlpha, - operation: wgpu::BlendOperation::Add, - }, - alpha: wgpu::BlendComponent { - src_factor: wgpu::BlendFactor::One, - dst_factor: wgpu::BlendFactor::OneMinusSrcAlpha, - operation: wgpu::BlendOperation::Add, - }, - }), - write_mask: wgpu::ColorWrites::ALL, - })], + targets: &quad::color_target_state(format), }), primitive: wgpu::PrimitiveState { topology: wgpu::PrimitiveTopology::TriangleList, @@ -278,14 +263,14 @@ impl Pipeline { let vertices = device.create_buffer_init(&wgpu::util::BufferInitDescriptor { label: Some("iced_wgpu::image vertex buffer"), - contents: bytemuck::cast_slice(&QUAD_VERTS), + contents: bytemuck::cast_slice(&quad::VERTICES), usage: wgpu::BufferUsages::VERTEX, }); let indices = device.create_buffer_init(&wgpu::util::BufferInitDescriptor { label: Some("iced_wgpu::image index buffer"), - contents: bytemuck::cast_slice(&QUAD_INDICES), + contents: bytemuck::cast_slice(&quad::INDICES), usage: wgpu::BufferUsages::INDEX, }); @@ -498,23 +483,6 @@ 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(Debug, Clone, Copy, Zeroable, Pod)] struct Instance { diff --git a/wgpu/src/layer.rs b/wgpu/src/layer.rs index 8af72b9d..b3ee4739 100644 --- a/wgpu/src/layer.rs +++ b/wgpu/src/layer.rs @@ -1,13 +1,13 @@ //! Organize rendering primitives into a flattened list of layers. mod image; -mod quad; mod text; pub mod mesh; +pub mod quad; pub use image::Image; pub use mesh::Mesh; -pub use quad::Quad; +use quad::Quad; pub use text::Text; use crate::core; @@ -22,7 +22,7 @@ pub struct Layer<'a> { pub bounds: Rectangle, /// The quads of the [`Layer`]. - pub quads: Vec, + pub quads: Quads, /// The triangle meshes of the [`Layer`]. pub meshes: Vec>, @@ -34,12 +34,29 @@ pub struct Layer<'a> { pub images: Vec, } +/// The quads of the [`Layer`]. +#[derive(Default, Debug)] +pub struct Quads { + /// The solid quads of the [`Layer`]. + pub solids: Vec, + + /// The gradient quads of the [`Layer`]. + pub gradients: Vec, +} + +impl Quads { + /// Returns true if there are no quads of any type in [`Quads`]. + pub fn is_empty(&self) -> bool { + self.solids.is_empty() && self.gradients.is_empty() + } +} + impl<'a> Layer<'a> { /// Creates a new [`Layer`] with the given clipping bounds. pub fn new(bounds: Rectangle) -> Self { Self { bounds, - quads: Vec::new(), + quads: Quads::default(), meshes: Vec::new(), text: Vec::new(), images: Vec::new(), @@ -145,20 +162,39 @@ impl<'a> Layer<'a> { } => { let layer = &mut layers[current_layer]; - // TODO: Move some of these computations to the GPU (?) - layer.quads.push(Quad { + let quad = Quad { position: [ bounds.x + translation.x, bounds.y + translation.y, ], size: [bounds.width, bounds.height], - color: match background { - Background::Color(color) => color.into_linear(), - }, + border_color: border_color.into_linear(), border_radius: *border_radius, border_width: *border_width, - border_color: border_color.into_linear(), - }); + }; + + match background { + Background::Color(color) => { + layer.quads.solids.push(quad::Solid { + color: color.into_linear(), + quad, + }); + } + Background::Gradient(gradient) => { + let quad = quad::Gradient { + gradient: pack_gradient( + gradient, + Rectangle::new( + quad.position.into(), + quad.size.into(), + ), + ), + quad, + }; + + layer.quads.gradients.push(quad); + } + }; } Primitive::Image { handle, bounds } => { let layer = &mut layers[current_layer]; @@ -198,11 +234,7 @@ impl<'a> Layer<'a> { }); } } - Primitive::GradientMesh { - buffers, - size, - gradient, - } => { + Primitive::GradientMesh { buffers, size } => { let layer = &mut layers[current_layer]; let bounds = Rectangle::new( @@ -216,7 +248,6 @@ impl<'a> Layer<'a> { origin: Point::new(translation.x, translation.y), buffers, clip_bounds, - gradient, }); } } @@ -279,3 +310,32 @@ impl<'a> Layer<'a> { } } } + +/// Packs the [`Gradient`] for use in shader code. +fn pack_gradient(gradient: &core::Gradient, bounds: Rectangle) -> [f32; 44] { + match gradient { + core::Gradient::Linear(linear) => { + let mut pack: [f32; 44] = [0.0; 44]; + + for (index, stop) in linear.color_stops.iter().enumerate() { + let [r, g, b, a] = + stop.map_or(Color::default(), |s| s.color).into_linear(); + + pack[index * 4] = r; + pack[(index * 4) + 1] = g; + pack[(index * 4) + 2] = b; + pack[(index * 4) + 3] = a; + pack[32 + index] = stop.map_or(2.0, |s| s.offset); + } + + let (start, end) = linear.angle.to_distance(&bounds); + + pack[40] = start.x; + pack[41] = start.y; + pack[42] = end.x; + pack[43] = end.y; + + pack + } + } +} diff --git a/wgpu/src/layer/mesh.rs b/wgpu/src/layer/mesh.rs index 9dd14391..b7dd9a0b 100644 --- a/wgpu/src/layer/mesh.rs +++ b/wgpu/src/layer/mesh.rs @@ -1,5 +1,5 @@ //! A collection of triangle primitives. -use crate::core::{Gradient, Point, Rectangle}; +use crate::core::{Point, Rectangle}; use crate::graphics::primitive; /// A mesh of triangles. @@ -22,13 +22,10 @@ pub enum Mesh<'a> { origin: Point, /// The vertex and index buffers of the [`Mesh`]. - buffers: &'a primitive::Mesh2D, + buffers: &'a primitive::Mesh2D, /// The clipping bounds of the [`Mesh`]. clip_bounds: Rectangle, - - /// The gradient to apply to the [`Mesh`]. - gradient: &'a Gradient, }, } @@ -65,9 +62,15 @@ pub struct AttributeCount { /// The total amount of solid vertices. pub solid_vertices: usize, + /// The total amount of solid meshes. + pub solids: usize, + /// The total amount of gradient vertices. pub gradient_vertices: usize, + /// The total amount of gradient meshes. + pub gradients: usize, + /// The total amount of indices. pub indices: usize, } @@ -79,10 +82,12 @@ pub fn attribute_count_of<'a>(meshes: &'a [Mesh<'a>]) -> AttributeCount { .fold(AttributeCount::default(), |mut count, mesh| { match mesh { Mesh::Solid { buffers, .. } => { + count.solids += 1; count.solid_vertices += buffers.vertices.len(); count.indices += buffers.indices.len(); } Mesh::Gradient { buffers, .. } => { + count.gradients += 1; count.gradient_vertices += buffers.vertices.len(); count.indices += buffers.indices.len(); } diff --git a/wgpu/src/layer/quad.rs b/wgpu/src/layer/quad.rs index 0d8bde9d..9913cfe0 100644 --- a/wgpu/src/layer/quad.rs +++ b/wgpu/src/layer/quad.rs @@ -1,7 +1,9 @@ -/// A colored rectangle with a border. -/// -/// This type can be directly uploaded to GPU memory. -#[derive(Debug, Clone, Copy)] +//! A rectangle with certain styled properties. + +use bytemuck::{Pod, Zeroable}; + +/// The properties of a quad. +#[derive(Clone, Copy, Debug, Pod, Zeroable)] #[repr(C)] pub struct Quad { /// The position of the [`Quad`]. @@ -10,21 +12,40 @@ pub struct Quad { /// The size of the [`Quad`]. pub size: [f32; 2], - /// The color of the [`Quad`], in __linear RGB__. - pub color: [f32; 4], - /// The border color of the [`Quad`], in __linear RGB__. pub border_color: [f32; 4], - /// The border radius of the [`Quad`]. + /// The border radii of the [`Quad`]. pub border_radius: [f32; 4], /// The border width of the [`Quad`]. pub border_width: f32, } +/// A quad filled with a solid color. +#[derive(Clone, Copy, Debug, Pod, Zeroable)] +#[repr(C)] +pub struct Solid { + /// The background color data of the quad. + pub color: [f32; 4], + + /// The [`Quad`] data of the [`Solid`]. + pub quad: Quad, +} + +/// A quad filled with interpolated colors. +#[derive(Clone, Copy, Debug)] +#[repr(C)] +pub struct Gradient { + /// The background gradient data of the quad. + pub gradient: [f32; 44], + + /// The [`Quad`] data of the [`Gradient`]. + pub quad: Quad, +} + #[allow(unsafe_code)] -unsafe impl bytemuck::Zeroable for Quad {} +unsafe impl Pod for Gradient {} #[allow(unsafe_code)] -unsafe impl bytemuck::Pod for Quad {} +unsafe impl Zeroable for Gradient {} diff --git a/wgpu/src/lib.rs b/wgpu/src/lib.rs index 4a92c345..c05280a6 100644 --- a/wgpu/src/lib.rs +++ b/wgpu/src/lib.rs @@ -59,8 +59,6 @@ pub use backend::Backend; pub use layer::Layer; pub use settings::Settings; -use buffer::Buffer; - #[cfg(any(feature = "image", feature = "svg"))] mod image; diff --git a/wgpu/src/quad.rs b/wgpu/src/quad.rs index 8fa7359e..31bf2b85 100644 --- a/wgpu/src/quad.rs +++ b/wgpu/src/quad.rs @@ -1,18 +1,19 @@ use crate::core::Rectangle; use crate::graphics::Transformation; use crate::layer; -use crate::Buffer; -use bytemuck::{Pod, Zeroable}; use std::mem; use wgpu::util::DeviceExt; #[cfg(feature = "tracing")] use tracing::info_span; +const INITIAL_INSTANCES: usize = 10_000; + #[derive(Debug)] pub struct Pipeline { - pipeline: wgpu::RenderPipeline, + solid: solid::Pipeline, + gradient: gradient::Pipeline, constant_layout: wgpu::BindGroupLayout, vertices: wgpu::Buffer, indices: wgpu::Buffer, @@ -39,107 +40,28 @@ impl Pipeline { }], }); - let layout = - device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor { - label: Some("iced_wgpu::quad pipeline layout"), - push_constant_ranges: &[], - bind_group_layouts: &[&constant_layout], - }); - - let shader = - device.create_shader_module(wgpu::ShaderModuleDescriptor { - label: Some("iced_wgpu quad shader"), - source: wgpu::ShaderSource::Wgsl(std::borrow::Cow::Borrowed( - include_str!("shader/quad.wgsl"), - )), - }); - - let pipeline = - device.create_render_pipeline(&wgpu::RenderPipelineDescriptor { - label: Some("iced_wgpu::quad pipeline"), - layout: Some(&layout), - vertex: wgpu::VertexState { - module: &shader, - entry_point: "vs_main", - buffers: &[ - wgpu::VertexBufferLayout { - array_stride: mem::size_of::() as u64, - step_mode: wgpu::VertexStepMode::Vertex, - attributes: &[wgpu::VertexAttribute { - shader_location: 0, - format: wgpu::VertexFormat::Float32x2, - offset: 0, - }], - }, - wgpu::VertexBufferLayout { - array_stride: mem::size_of::() as u64, - step_mode: wgpu::VertexStepMode::Instance, - attributes: &wgpu::vertex_attr_array!( - 1 => Float32x2, - 2 => Float32x2, - 3 => Float32x4, - 4 => Float32x4, - 5 => Float32x4, - 6 => Float32, - ), - }, - ], - }, - fragment: Some(wgpu::FragmentState { - module: &shader, - entry_point: "fs_main", - targets: &[Some(wgpu::ColorTargetState { - format, - blend: Some(wgpu::BlendState { - color: wgpu::BlendComponent { - src_factor: wgpu::BlendFactor::SrcAlpha, - dst_factor: wgpu::BlendFactor::OneMinusSrcAlpha, - operation: wgpu::BlendOperation::Add, - }, - alpha: wgpu::BlendComponent { - src_factor: wgpu::BlendFactor::One, - dst_factor: wgpu::BlendFactor::OneMinusSrcAlpha, - operation: wgpu::BlendOperation::Add, - }, - }), - write_mask: wgpu::ColorWrites::ALL, - })], - }), - primitive: wgpu::PrimitiveState { - topology: wgpu::PrimitiveTopology::TriangleList, - front_face: wgpu::FrontFace::Cw, - ..Default::default() - }, - depth_stencil: None, - multisample: wgpu::MultisampleState { - count: 1, - mask: !0, - alpha_to_coverage_enabled: false, - }, - multiview: None, - }); - let vertices = device.create_buffer_init(&wgpu::util::BufferInitDescriptor { label: Some("iced_wgpu::quad vertex buffer"), - contents: bytemuck::cast_slice(&QUAD_VERTS), + contents: bytemuck::cast_slice(&VERTICES), usage: wgpu::BufferUsages::VERTEX, }); let indices = device.create_buffer_init(&wgpu::util::BufferInitDescriptor { label: Some("iced_wgpu::quad index buffer"), - contents: bytemuck::cast_slice(&QUAD_INDICES), + contents: bytemuck::cast_slice(&INDICES), usage: wgpu::BufferUsages::INDEX, }); - Pipeline { - pipeline, - constant_layout, + Self { vertices, indices, + solid: solid::Pipeline::new(device, format, &constant_layout), + gradient: gradient::Pipeline::new(device, format, &constant_layout), layers: Vec::new(), prepare_layer: 0, + constant_layout, } } @@ -147,7 +69,7 @@ impl Pipeline { &mut self, device: &wgpu::Device, queue: &wgpu::Queue, - instances: &[layer::Quad], + instances: &layer::Quads, transformation: Transformation, scale: f32, ) { @@ -168,22 +90,27 @@ impl Pipeline { render_pass: &mut wgpu::RenderPass<'a>, ) { if let Some(layer) = self.layers.get(layer) { - render_pass.set_pipeline(&self.pipeline); - render_pass.set_scissor_rect( bounds.x, bounds.y, bounds.width, bounds.height, ); - render_pass.set_index_buffer( self.indices.slice(..), wgpu::IndexFormat::Uint16, ); render_pass.set_vertex_buffer(0, self.vertices.slice(..)); - layer.draw(render_pass); + if layer.solid.instance_count > 0 { + render_pass.set_pipeline(&self.solid.pipeline); + layer.solid.draw(&layer.constants, render_pass); + } + + if layer.gradient.instance_count > 0 { + render_pass.set_pipeline(&self.gradient.pipeline); + layer.gradient.draw(&layer.constants, render_pass); + } } } @@ -196,8 +123,8 @@ impl Pipeline { struct Layer { constants: wgpu::BindGroup, constants_buffer: wgpu::Buffer, - instances: Buffer, - instance_count: usize, + solid: solid::Layer, + gradient: gradient::Layer, } impl Layer { @@ -221,18 +148,11 @@ impl Layer { }], }); - let instances = Buffer::new( - device, - "iced_wgpu::quad instance buffer", - INITIAL_INSTANCES, - wgpu::BufferUsages::VERTEX | wgpu::BufferUsages::COPY_DST, - ); - Self { constants, constants_buffer, - instances, - instance_count: 0, + solid: solid::Layer::new(device), + gradient: gradient::Layer::new(device), } } @@ -240,7 +160,7 @@ impl Layer { &mut self, device: &wgpu::Device, queue: &wgpu::Queue, - instances: &[layer::Quad], + instances: &layer::Quads, transformation: Transformation, scale: f32, ) { @@ -255,35 +175,350 @@ impl Layer { bytemuck::bytes_of(&uniforms), ); - let _ = self.instances.resize(device, instances.len()); - self.instances.write(queue, 0, instances); - self.instance_count = instances.len(); + let _ = self.solid.instances.resize(device, instances.solids.len()); + let _ = self + .gradient + .instances + .resize(device, instances.gradients.len()); + let _ = + self.solid + .instances + .write(queue, 0, instances.solids.as_slice()); + self.solid.instance_count = instances.solids.len(); + let _ = self.gradient.instances.write( + queue, + 0, + instances.gradients.as_slice(), + ); + self.gradient.instance_count = instances.gradients.len(); } +} - pub fn draw<'a>(&'a self, render_pass: &mut wgpu::RenderPass<'a>) { - #[cfg(feature = "tracing")] - let _ = info_span!("Wgpu::Quad", "DRAW").entered(); +mod solid { + use crate::buffer::Buffer; + use crate::layer::quad; + use crate::quad::{color_target_state, Vertex, INDICES, INITIAL_INSTANCES}; - render_pass.set_bind_group(0, &self.constants, &[]); - render_pass.set_vertex_buffer(1, self.instances.slice(..)); + #[derive(Debug)] + pub struct Pipeline { + pub pipeline: wgpu::RenderPipeline, + } - render_pass.draw_indexed( - 0..QUAD_INDICES.len() as u32, - 0, - 0..self.instance_count as u32, - ); + #[derive(Debug)] + pub struct Layer { + pub instances: Buffer, + pub instance_count: usize, + } + + impl Layer { + pub fn new(device: &wgpu::Device) -> Self { + let instances = Buffer::new( + device, + "iced_wgpu.quad.solid.buffer", + INITIAL_INSTANCES, + wgpu::BufferUsages::VERTEX | wgpu::BufferUsages::COPY_DST, + ); + + Self { + instances, + instance_count: 0, + } + } + + pub fn draw<'a>( + &'a self, + constants: &'a wgpu::BindGroup, + render_pass: &mut wgpu::RenderPass<'a>, + ) { + #[cfg(feature = "tracing")] + let _ = tracing::info_span!("Wgpu::Quad::Solid", "DRAW").entered(); + + render_pass.set_bind_group(0, constants, &[]); + render_pass.set_vertex_buffer(1, self.instances.slice(..)); + + render_pass.draw_indexed( + 0..INDICES.len() as u32, + 0, + 0..self.instance_count as u32, + ); + } + } + + impl Pipeline { + pub fn new( + device: &wgpu::Device, + format: wgpu::TextureFormat, + constants_layout: &wgpu::BindGroupLayout, + ) -> Self { + let layout = device.create_pipeline_layout( + &wgpu::PipelineLayoutDescriptor { + label: Some("iced_wgpu.quad.solid.pipeline"), + push_constant_ranges: &[], + bind_group_layouts: &[constants_layout], + }, + ); + + let shader = + device.create_shader_module(wgpu::ShaderModuleDescriptor { + label: Some("iced_wgpu.quad.solid.shader"), + source: wgpu::ShaderSource::Wgsl( + std::borrow::Cow::Borrowed(include_str!( + "shader/quad.wgsl" + )), + ), + }); + + let pipeline = device.create_render_pipeline( + &wgpu::RenderPipelineDescriptor { + label: Some("iced_wgpu.quad.solid.pipeline"), + layout: Some(&layout), + vertex: wgpu::VertexState { + module: &shader, + entry_point: "solid_vs_main", + buffers: &[ + Vertex::buffer_layout(), + wgpu::VertexBufferLayout { + array_stride: std::mem::size_of::() + as u64, + step_mode: wgpu::VertexStepMode::Instance, + attributes: &wgpu::vertex_attr_array!( + // Color + 1 => Float32x4, + // Position + 2 => Float32x2, + // Size + 3 => Float32x2, + // Border color + 4 => Float32x4, + // Border radius + 5 => Float32x4, + // Border width + 6 => Float32, + ), + }, + ], + }, + fragment: Some(wgpu::FragmentState { + module: &shader, + entry_point: "solid_fs_main", + targets: &color_target_state(format), + }), + primitive: wgpu::PrimitiveState { + topology: wgpu::PrimitiveTopology::TriangleList, + front_face: wgpu::FrontFace::Cw, + ..Default::default() + }, + depth_stencil: None, + multisample: wgpu::MultisampleState { + count: 1, + mask: !0, + alpha_to_coverage_enabled: false, + }, + multiview: None, + }, + ); + + Self { pipeline } + } } } +mod gradient { + use crate::buffer::Buffer; + use crate::layer::quad; + use crate::quad::{color_target_state, Vertex, INDICES, INITIAL_INSTANCES}; + + #[derive(Debug)] + pub struct Pipeline { + pub pipeline: wgpu::RenderPipeline, + } + + #[derive(Debug)] + pub struct Layer { + pub instances: Buffer, + pub instance_count: usize, + } + + impl Layer { + pub fn new(device: &wgpu::Device) -> Self { + let instances = Buffer::new( + device, + "iced_wgpu.quad.gradient.buffer", + INITIAL_INSTANCES, + wgpu::BufferUsages::VERTEX | wgpu::BufferUsages::COPY_DST, + ); + + Self { + instances, + instance_count: 0, + } + } + + pub fn draw<'a>( + &'a self, + constants: &'a wgpu::BindGroup, + render_pass: &mut wgpu::RenderPass<'a>, + ) { + #[cfg(feature = "tracing")] + let _ = + tracing::info_span!("Wgpu::Quad::Gradient", "DRAW").entered(); + + render_pass.set_bind_group(0, constants, &[]); + render_pass.set_vertex_buffer(1, self.instances.slice(..)); + + render_pass.draw_indexed( + 0..INDICES.len() as u32, + 0, + 0..self.instance_count as u32, + ); + } + } + + impl Pipeline { + pub fn new( + device: &wgpu::Device, + format: wgpu::TextureFormat, + constants_layout: &wgpu::BindGroupLayout, + ) -> Self { + let layout = device.create_pipeline_layout( + &wgpu::PipelineLayoutDescriptor { + label: Some("iced_wgpu.quad.gradient.pipeline"), + push_constant_ranges: &[], + bind_group_layouts: &[constants_layout], + }, + ); + + let shader = + device.create_shader_module(wgpu::ShaderModuleDescriptor { + label: Some("iced_wgpu.quad.gradient.shader"), + source: wgpu::ShaderSource::Wgsl( + std::borrow::Cow::Borrowed(include_str!( + "shader/quad.wgsl" + )), + ), + }); + + let pipeline = + device.create_render_pipeline( + &wgpu::RenderPipelineDescriptor { + label: Some("iced_wgpu.quad.gradient.pipeline"), + layout: Some(&layout), + vertex: wgpu::VertexState { + module: &shader, + entry_point: "gradient_vs_main", + buffers: &[ + Vertex::buffer_layout(), + wgpu::VertexBufferLayout { + array_stride: std::mem::size_of::< + quad::Gradient, + >( + ) + as u64, + step_mode: wgpu::VertexStepMode::Instance, + attributes: &wgpu::vertex_attr_array!( + // Color 1 + 1 => Float32x4, + // Color 2 + 2 => Float32x4, + // Color 3 + 3 => Float32x4, + // Color 4 + 4 => Float32x4, + // Color 5 + 5 => Float32x4, + // Color 6 + 6 => Float32x4, + // Color 7 + 7 => Float32x4, + // Color 8 + 8 => Float32x4, + // Offsets 1-4 + 9 => Float32x4, + // Offsets 5-8 + 10 => Float32x4, + // Direction + 11 => Float32x4, + // Position & Scale + 12 => Float32x4, + // Border color + 13 => Float32x4, + // Border radius + 14 => Float32x4, + // Border width + 15 => Float32 + ), + }, + ], + }, + fragment: Some(wgpu::FragmentState { + module: &shader, + entry_point: "gradient_fs_main", + targets: &color_target_state(format), + }), + primitive: wgpu::PrimitiveState { + topology: wgpu::PrimitiveTopology::TriangleList, + front_face: wgpu::FrontFace::Cw, + ..Default::default() + }, + depth_stencil: None, + multisample: wgpu::MultisampleState { + count: 1, + mask: !0, + alpha_to_coverage_enabled: false, + }, + multiview: None, + }, + ); + + Self { pipeline } + } + } +} + +pub(crate) fn color_target_state( + format: wgpu::TextureFormat, +) -> [Option; 1] { + [Some(wgpu::ColorTargetState { + format, + blend: Some(wgpu::BlendState { + color: wgpu::BlendComponent { + src_factor: wgpu::BlendFactor::SrcAlpha, + dst_factor: wgpu::BlendFactor::OneMinusSrcAlpha, + operation: wgpu::BlendOperation::Add, + }, + alpha: wgpu::BlendComponent { + src_factor: wgpu::BlendFactor::One, + dst_factor: wgpu::BlendFactor::OneMinusSrcAlpha, + operation: wgpu::BlendOperation::Add, + }, + }), + write_mask: wgpu::ColorWrites::ALL, + })] +} + #[repr(C)] -#[derive(Clone, Copy, Zeroable, Pod)] +#[derive(Clone, Copy, bytemuck::Zeroable, bytemuck::Pod)] pub struct Vertex { _position: [f32; 2], } -const QUAD_INDICES: [u16; 6] = [0, 1, 2, 0, 2, 3]; +impl Vertex { + fn buffer_layout<'a>() -> wgpu::VertexBufferLayout<'a> { + wgpu::VertexBufferLayout { + array_stride: mem::size_of::() as u64, + step_mode: wgpu::VertexStepMode::Vertex, + attributes: &[wgpu::VertexAttribute { + shader_location: 0, + format: wgpu::VertexFormat::Float32x2, + offset: 0, + }], + } + } +} + +pub(crate) const INDICES: [u16; 6] = [0, 1, 2, 0, 2, 3]; -const QUAD_VERTS: [Vertex; 4] = [ +pub(crate) const VERTICES: [Vertex; 4] = [ Vertex { _position: [0.0, 0.0], }, @@ -298,10 +533,8 @@ const QUAD_VERTS: [Vertex; 4] = [ }, ]; -const INITIAL_INSTANCES: usize = 10_000; - #[repr(C)] -#[derive(Debug, Clone, Copy, Zeroable, Pod)] +#[derive(Debug, Clone, Copy, bytemuck::Zeroable, bytemuck::Pod)] struct Uniforms { transform: [f32; 16], scale: f32, diff --git a/wgpu/src/shader/gradient.wgsl b/wgpu/src/shader/gradient.wgsl deleted file mode 100644 index 63825aec..00000000 --- a/wgpu/src/shader/gradient.wgsl +++ /dev/null @@ -1,88 +0,0 @@ -struct Uniforms { - transform: mat4x4, - //xy = start, wz = end - position: vec4, - //x = start stop, y = end stop, zw = padding - stop_range: vec4, -} - -struct Stop { - color: vec4, - offset: f32, -}; - -@group(0) @binding(0) -var uniforms: Uniforms; - -@group(0) @binding(1) -var color_stops: array; - -struct VertexOutput { - @builtin(position) position: vec4, - @location(0) raw_position: vec2 -} - -@vertex -fn vs_main(@location(0) input: vec2) -> VertexOutput { - var output: VertexOutput; - output.position = uniforms.transform * vec4(input.xy, 0.0, 1.0); - output.raw_position = input; - - return output; -} - -//TODO: rewrite without branching -@fragment -fn fs_main(input: VertexOutput) -> @location(0) vec4 { - let start = uniforms.position.xy; - let end = uniforms.position.zw; - let start_stop = uniforms.stop_range.x; - let end_stop = uniforms.stop_range.y; - - let v1 = end - start; - let v2 = input.raw_position.xy - start; - let unit = normalize(v1); - let offset = dot(unit, v2) / length(v1); - - let min_stop = color_stops[start_stop]; - let max_stop = color_stops[end_stop]; - - var color: vec4; - - if (offset <= min_stop.offset) { - color = min_stop.color; - } else if (offset >= max_stop.offset) { - color = max_stop.color; - } else { - var min = min_stop; - var max = max_stop; - var min_index = start_stop; - var max_index = end_stop; - - loop { - if (min_index >= max_index - 1) { - break; - } - - let index = min_index + (max_index - min_index) / 2; - - let stop = color_stops[index]; - - if (offset <= stop.offset) { - max = stop; - max_index = index; - } else { - min = stop; - min_index = index; - } - } - - color = mix(min.color, max.color, smoothstep( - min.offset, - max.offset, - offset - )); - } - - return color; -} diff --git a/wgpu/src/shader/quad.wgsl b/wgpu/src/shader/quad.wgsl index cf4f7e4d..3232bdbe 100644 --- a/wgpu/src/shader/quad.wgsl +++ b/wgpu/src/shader/quad.wgsl @@ -5,17 +5,50 @@ struct Globals { @group(0) @binding(0) var globals: Globals; -struct VertexInput { +fn distance_alg( + frag_coord: vec2, + position: vec2, + size: vec2, + radius: f32 +) -> f32 { + var inner_size: vec2 = size - vec2(radius, radius) * 2.0; + var top_left: vec2 = position + vec2(radius, radius); + var bottom_right: vec2 = top_left + inner_size; + + var top_left_distance: vec2 = top_left - frag_coord; + var bottom_right_distance: vec2 = frag_coord - bottom_right; + + var dist: vec2 = vec2( + max(max(top_left_distance.x, bottom_right_distance.x), 0.0), + max(max(top_left_distance.y, bottom_right_distance.y), 0.0) + ); + + return sqrt(dist.x * dist.x + dist.y * dist.y); +} + +// Based on the fragement position and the center of the quad, select one of the 4 radi. +// Order matches CSS border radius attribute: +// radi.x = top-left, radi.y = top-right, radi.z = bottom-right, radi.w = bottom-left +fn select_border_radius(radi: vec4, position: vec2, center: vec2) -> f32 { + var rx = radi.x; + var ry = radi.y; + rx = select(radi.x, radi.y, position.x > center.x); + ry = select(radi.w, radi.z, position.x > center.x); + rx = select(rx, ry, position.y > center.y); + return rx; +} + +struct SolidVertexInput { @location(0) v_pos: vec2, - @location(1) pos: vec2, - @location(2) scale: vec2, - @location(3) color: vec4, + @location(1) color: vec4, + @location(2) pos: vec2, + @location(3) scale: vec2, @location(4) border_color: vec4, @location(5) border_radius: vec4, @location(6) border_width: f32, } -struct VertexOutput { +struct SolidVertexOutput { @builtin(position) position: vec4, @location(0) color: vec4, @location(1) border_color: vec4, @@ -26,8 +59,8 @@ struct VertexOutput { } @vertex -fn vs_main(input: VertexInput) -> VertexOutput { - var out: VertexOutput; +fn solid_vs_main(input: SolidVertexInput) -> SolidVertexOutput { + var out: SolidVertexOutput; var pos: vec2 = input.pos * globals.scale; var scale: vec2 = input.scale * globals.scale; @@ -47,54 +80,20 @@ fn vs_main(input: VertexInput) -> VertexOutput { vec4(pos - vec2(0.5, 0.5), 0.0, 1.0) ); + out.position = globals.transform * transform * vec4(input.v_pos, 0.0, 1.0); out.color = input.color; out.border_color = input.border_color; out.pos = pos; out.scale = scale; out.border_radius = border_radius * globals.scale; out.border_width = input.border_width * globals.scale; - out.position = globals.transform * transform * vec4(input.v_pos, 0.0, 1.0); return out; } -fn distance_alg( - frag_coord: vec2, - position: vec2, - size: vec2, - radius: f32 -) -> f32 { - var inner_size: vec2 = size - vec2(radius, radius) * 2.0; - var top_left: vec2 = position + vec2(radius, radius); - var bottom_right: vec2 = top_left + inner_size; - - var top_left_distance: vec2 = top_left - frag_coord; - var bottom_right_distance: vec2 = frag_coord - bottom_right; - - var dist: vec2 = vec2( - max(max(top_left_distance.x, bottom_right_distance.x), 0.0), - max(max(top_left_distance.y, bottom_right_distance.y), 0.0) - ); - - return sqrt(dist.x * dist.x + dist.y * dist.y); -} - -// Based on the fragement position and the center of the quad, select one of the 4 radi. -// Order matches CSS border radius attribute: -// radi.x = top-left, radi.y = top-right, radi.z = bottom-right, radi.w = bottom-left -fn select_border_radius(radi: vec4, position: vec2, center: vec2) -> f32 { - var rx = radi.x; - var ry = radi.y; - rx = select(radi.x, radi.y, position.x > center.x); - ry = select(radi.w, radi.z, position.x > center.x); - rx = select(rx, ry, position.y > center.y); - return rx; -} - - @fragment -fn fs_main( - input: VertexOutput +fn solid_fs_main( + input: SolidVertexOutput ) -> @location(0) vec4 { var mixed_color: vec4 = input.color; @@ -138,3 +137,214 @@ fn fs_main( return vec4(mixed_color.x, mixed_color.y, mixed_color.z, mixed_color.w * radius_alpha); } + +struct GradientVertexInput { + @location(0) v_pos: vec2, + @location(1) color_1: vec4, + @location(2) color_2: vec4, + @location(3) color_3: vec4, + @location(4) color_4: vec4, + @location(5) color_5: vec4, + @location(6) color_6: vec4, + @location(7) color_7: vec4, + @location(8) color_8: vec4, + @location(9) offsets_1: vec4, + @location(10) offsets_2: vec4, + @location(11) direction: vec4, + @location(12) position_and_scale: vec4, + @location(13) border_color: vec4, + @location(14) border_radius: vec4, + @location(15) border_width: f32 +} + +struct GradientVertexOutput { + @builtin(position) position: vec4, + @location(1) color_1: vec4, + @location(2) color_2: vec4, + @location(3) color_3: vec4, + @location(4) color_4: vec4, + @location(5) color_5: vec4, + @location(6) color_6: vec4, + @location(7) color_7: vec4, + @location(8) color_8: vec4, + @location(9) offsets_1: vec4, + @location(10) offsets_2: vec4, + @location(11) direction: vec4, + @location(12) position_and_scale: vec4, + @location(13) border_color: vec4, + @location(14) border_radius: vec4, + @location(15) border_width: f32 +} + +@vertex +fn gradient_vs_main(input: GradientVertexInput) -> GradientVertexOutput { + var out: GradientVertexOutput; + + var pos: vec2 = input.position_and_scale.xy * globals.scale; + var scale: vec2 = input.position_and_scale.zw * globals.scale; + + var min_border_radius = min(input.position_and_scale.z, input.position_and_scale.w) * 0.5; + var border_radius: vec4 = vec4( + min(input.border_radius.x, min_border_radius), + min(input.border_radius.y, min_border_radius), + min(input.border_radius.z, min_border_radius), + min(input.border_radius.w, min_border_radius) + ); + + var transform: mat4x4 = mat4x4( + vec4(scale.x + 1.0, 0.0, 0.0, 0.0), + vec4(0.0, scale.y + 1.0, 0.0, 0.0), + vec4(0.0, 0.0, 1.0, 0.0), + vec4(pos - vec2(0.5, 0.5), 0.0, 1.0) + ); + + out.position = globals.transform * transform * vec4(input.v_pos, 0.0, 1.0); + out.color_1 = input.color_1; + out.color_2 = input.color_2; + out.color_3 = input.color_3; + out.color_4 = input.color_4; + out.color_5 = input.color_5; + out.color_6 = input.color_6; + out.color_7 = input.color_7; + out.color_8 = input.color_8; + out.offsets_1 = input.offsets_1; + out.offsets_2 = input.offsets_2; + out.direction = input.direction * globals.scale; + out.position_and_scale = vec4(pos, scale); + out.border_color = input.border_color; + out.border_radius = border_radius * globals.scale; + out.border_width = input.border_width * globals.scale; + + return out; +} + +fn random(coords: vec2) -> f32 { + return fract(sin(dot(coords, vec2(12.9898,78.233))) * 43758.5453); +} + +/// Returns the current interpolated color with a max 8-stop gradient +fn gradient( + raw_position: vec2, + direction: vec4, + colors: array, 8>, + offsets: array, + last_index: i32 +) -> vec4 { + let start = direction.xy; + let end = direction.zw; + + let v1 = end - start; + let v2 = raw_position - start; + let unit = normalize(v1); + let coord_offset = dot(unit, v2) / length(v1); + + //need to store these as a var to use dynamic indexing in a loop + //this is already added to wgsl spec but not in wgpu yet + var colors_arr = colors; + var offsets_arr = offsets; + + var color: vec4; + + let noise_granularity: f32 = 0.3/255.0; + + for (var i: i32 = 0; i < last_index; i++) { + let curr_offset = offsets_arr[i]; + let next_offset = offsets_arr[i+1]; + + if (coord_offset <= offsets_arr[0]) { + color = colors_arr[0]; + } + + if (curr_offset <= coord_offset && coord_offset <= next_offset) { + color = mix(colors_arr[i], colors_arr[i+1], smoothstep( + curr_offset, + next_offset, + coord_offset, + )); + } + + if (coord_offset >= offsets_arr[last_index]) { + color = colors_arr[last_index]; + } + } + + return color + mix(-noise_granularity, noise_granularity, random(raw_position)); +} + +@fragment +fn gradient_fs_main(input: GradientVertexOutput) -> @location(0) vec4 { + let colors = array, 8>( + input.color_1, + input.color_2, + input.color_3, + input.color_4, + input.color_5, + input.color_6, + input.color_7, + input.color_8, + ); + + var offsets = array( + input.offsets_1.x, + input.offsets_1.y, + input.offsets_1.z, + input.offsets_1.w, + input.offsets_2.x, + input.offsets_2.y, + input.offsets_2.z, + input.offsets_2.w, + ); + + //TODO could just pass this in to the shader but is probably more performant to just check it here + var last_index = 7; + for (var i: i32 = 0; i <= 7; i++) { + if (offsets[i] > 1.0) { + last_index = i - 1; + break; + } + } + + var mixed_color: vec4 = gradient(input.position.xy, input.direction, colors, offsets, last_index); + + let pos = input.position_and_scale.xy; + let scale = input.position_and_scale.zw; + + var border_radius = select_border_radius( + input.border_radius, + input.position.xy, + (pos + scale * 0.5).xy + ); + + if (input.border_width > 0.0) { + var internal_border: f32 = max(border_radius - input.border_width, 0.0); + + var internal_distance: f32 = distance_alg( + input.position.xy, + pos + vec2(input.border_width, input.border_width), + scale - vec2(input.border_width * 2.0, input.border_width * 2.0), + internal_border + ); + + var border_mix: f32 = smoothstep( + max(internal_border - 0.5, 0.0), + internal_border + 0.5, + internal_distance + ); + + mixed_color = mix(mixed_color, input.border_color, vec4(border_mix, border_mix, border_mix, border_mix)); + } + + var dist: f32 = distance_alg( + input.position.xy, + pos, + scale, + border_radius + ); + + var radius_alpha: f32 = 1.0 - smoothstep( + max(border_radius - 0.5, 0.0), + border_radius + 0.5, + dist); + + return vec4(mixed_color.x, mixed_color.y, mixed_color.z, mixed_color.w * radius_alpha); +} diff --git a/wgpu/src/shader/solid.wgsl b/wgpu/src/shader/solid.wgsl deleted file mode 100644 index b24402f8..00000000 --- a/wgpu/src/shader/solid.wgsl +++ /dev/null @@ -1,30 +0,0 @@ -struct Globals { - transform: mat4x4, -} - -@group(0) @binding(0) var globals: Globals; - -struct VertexInput { - @location(0) position: vec2, - @location(1) color: vec4, -} - -struct VertexOutput { - @builtin(position) position: vec4, - @location(0) color: vec4, -} - -@vertex -fn vs_main(input: VertexInput) -> VertexOutput { - var out: VertexOutput; - - out.color = input.color; - out.position = globals.transform * vec4(input.position, 0.0, 1.0); - - return out; -} - -@fragment -fn fs_main(input: VertexOutput) -> @location(0) vec4 { - return input.color; -} diff --git a/wgpu/src/shader/triangle.wgsl b/wgpu/src/shader/triangle.wgsl new file mode 100644 index 00000000..625fa46e --- /dev/null +++ b/wgpu/src/shader/triangle.wgsl @@ -0,0 +1,168 @@ +struct Globals { + transform: mat4x4, +} + +@group(0) @binding(0) var globals: Globals; + +struct SolidVertexInput { + @location(0) position: vec2, + @location(1) color: vec4, +} + +struct SolidVertexOutput { + @builtin(position) position: vec4, + @location(0) color: vec4, +} + +@vertex +fn solid_vs_main(input: SolidVertexInput) -> SolidVertexOutput { + var out: SolidVertexOutput; + + out.color = input.color; + out.position = globals.transform * vec4(input.position, 0.0, 1.0); + + return out; +} + +@fragment +fn solid_fs_main(input: SolidVertexOutput) -> @location(0) vec4 { + return input.color; +} + +struct GradientVertexOutput { + @builtin(position) position: vec4, + @location(0) raw_position: vec2, + @location(1) color_1: vec4, + @location(2) color_2: vec4, + @location(3) color_3: vec4, + @location(4) color_4: vec4, + @location(5) color_5: vec4, + @location(6) color_6: vec4, + @location(7) color_7: vec4, + @location(8) color_8: vec4, + @location(9) offsets_1: vec4, + @location(10) offsets_2: vec4, + @location(11) direction: vec4, +} + +@vertex +fn gradient_vs_main( + @location(0) input: vec2, + @location(1) color_1: vec4, + @location(2) color_2: vec4, + @location(3) color_3: vec4, + @location(4) color_4: vec4, + @location(5) color_5: vec4, + @location(6) color_6: vec4, + @location(7) color_7: vec4, + @location(8) color_8: vec4, + @location(9) offsets_1: vec4, + @location(10) offsets_2: vec4, + @location(11) direction: vec4, +) -> GradientVertexOutput { + var output: GradientVertexOutput; + + output.position = globals.transform * vec4(input.xy, 0.0, 1.0); + output.raw_position = input; + output.color_1 = color_1; + output.color_2 = color_2; + output.color_3 = color_3; + output.color_4 = color_4; + output.color_5 = color_5; + output.color_6 = color_6; + output.color_7 = color_7; + output.color_8 = color_8; + output.offsets_1 = offsets_1; + output.offsets_2 = offsets_2; + output.direction = direction; + + return output; +} + +fn random(coords: vec2) -> f32 { + return fract(sin(dot(coords, vec2(12.9898,78.233))) * 43758.5453); +} + +/// Returns the current interpolated color with a max 8-stop gradient +fn gradient( + raw_position: vec2, + direction: vec4, + colors: array, 8>, + offsets: array, + last_index: i32 +) -> vec4 { + let start = direction.xy; + let end = direction.zw; + + let v1 = end - start; + let v2 = raw_position - start; + let unit = normalize(v1); + let coord_offset = dot(unit, v2) / length(v1); + + //need to store these as a var to use dynamic indexing in a loop + //this is already added to wgsl spec but not in wgpu yet + var colors_arr = colors; + var offsets_arr = offsets; + + var color: vec4; + + let noise_granularity: f32 = 0.3/255.0; + + for (var i: i32 = 0; i < last_index; i++) { + let curr_offset = offsets_arr[i]; + let next_offset = offsets_arr[i+1]; + + if (coord_offset <= offsets_arr[0]) { + color = colors_arr[0]; + } + + if (curr_offset <= coord_offset && coord_offset <= next_offset) { + color = mix(colors_arr[i], colors_arr[i+1], smoothstep( + curr_offset, + next_offset, + coord_offset, + )); + } + + if (coord_offset >= offsets_arr[last_index]) { + color = colors_arr[last_index]; + } + } + + return color + mix(-noise_granularity, noise_granularity, random(raw_position)); +} + +@fragment +fn gradient_fs_main(input: GradientVertexOutput) -> @location(0) vec4 { + let colors = array, 8>( + input.color_1, + input.color_2, + input.color_3, + input.color_4, + input.color_5, + input.color_6, + input.color_7, + input.color_8, + ); + + var offsets = array( + input.offsets_1.x, + input.offsets_1.y, + input.offsets_1.z, + input.offsets_1.w, + input.offsets_2.x, + input.offsets_2.y, + input.offsets_2.z, + input.offsets_2.w, + ); + + var last_index = 7; + for (var i: i32 = 0; i <= 7; i++) { + if (offsets[i] >= 1.0) { + last_index = i; + break; + } + } + + return gradient(input.raw_position, input.direction, colors, offsets, last_index); +} diff --git a/wgpu/src/triangle.rs b/wgpu/src/triangle.rs index eb15a458..0d1fead1 100644 --- a/wgpu/src/triangle.rs +++ b/wgpu/src/triangle.rs @@ -1,26 +1,19 @@ //! Draw meshes of triangles. mod msaa; -use crate::buffer::r#static::Buffer; +use crate::buffer::Buffer; use crate::core::Size; use crate::graphics::{Antialiasing, Transformation}; use crate::layer::mesh::{self, Mesh}; -#[cfg(not(target_arch = "wasm32"))] -use crate::core::Gradient; - -#[cfg(feature = "tracing")] -use tracing::info_span; +const INITIAL_INDEX_COUNT: usize = 1_000; +const INITIAL_VERTEX_COUNT: usize = 1_000; #[derive(Debug)] pub struct Pipeline { blit: Option, solid: solid::Pipeline, - - /// Gradients are currently not supported on WASM targets due to their need of storage buffers. - #[cfg(not(target_arch = "wasm32"))] gradient: gradient::Pipeline, - layers: Vec, prepare_layer: usize, } @@ -30,8 +23,6 @@ struct Layer { index_buffer: Buffer, index_strides: Vec, solid: solid::Layer, - - #[cfg(not(target_arch = "wasm32"))] gradient: gradient::Layer, } @@ -39,18 +30,17 @@ impl Layer { fn new( device: &wgpu::Device, solid: &solid::Pipeline, - #[cfg(not(target_arch = "wasm32"))] gradient: &gradient::Pipeline, + gradient: &gradient::Pipeline, ) -> Self { Self { index_buffer: Buffer::new( device, - "iced_wgpu::triangle index buffer", + "iced_wgpu.triangle.index_buffer", + INITIAL_INDEX_COUNT, wgpu::BufferUsages::INDEX | wgpu::BufferUsages::COPY_DST, ), index_strides: Vec::new(), solid: solid::Layer::new(device, &solid.constants_layout), - - #[cfg(not(target_arch = "wasm32"))] gradient: gradient::Layer::new(device, &gradient.constants_layout), } } @@ -60,7 +50,7 @@ impl Layer { device: &wgpu::Device, queue: &wgpu::Queue, solid: &solid::Pipeline, - #[cfg(not(target_arch = "wasm32"))] gradient: &gradient::Pipeline, + gradient: &gradient::Pipeline, meshes: &[Mesh<'_>], transformation: Transformation, ) { @@ -73,177 +63,92 @@ impl Layer { // the majority of use cases. Therefore we will write GPU data every frame (for now). let _ = self.index_buffer.resize(device, count.indices); let _ = self.solid.vertices.resize(device, count.solid_vertices); - - #[cfg(not(target_arch = "wasm32"))] let _ = self .gradient .vertices .resize(device, count.gradient_vertices); - // Prepare dynamic buffers & data store for writing - self.index_buffer.clear(); + if self.solid.uniforms.resize(device, count.solids) { + self.solid.constants = solid::Layer::bind_group( + device, + &self.solid.uniforms.raw, + &solid.constants_layout, + ); + } + + if self.gradient.uniforms.resize(device, count.gradients) { + self.gradient.constants = gradient::Layer::bind_group( + device, + &self.gradient.uniforms.raw, + &gradient.constants_layout, + ); + } + self.index_strides.clear(); + self.index_buffer.clear(); self.solid.vertices.clear(); self.solid.uniforms.clear(); - - #[cfg(not(target_arch = "wasm32"))] - { - self.gradient.uniforms.clear(); - self.gradient.vertices.clear(); - self.gradient.storage.clear(); - } + self.gradient.vertices.clear(); + self.gradient.uniforms.clear(); let mut solid_vertex_offset = 0; - let mut index_offset = 0; - - #[cfg(not(target_arch = "wasm32"))] + let mut solid_uniform_offset = 0; let mut gradient_vertex_offset = 0; + let mut gradient_uniform_offset = 0; + let mut index_offset = 0; for mesh in meshes { let origin = mesh.origin(); let indices = mesh.indices(); - let transform = - transformation * Transformation::translate(origin.x, origin.y); + let uniforms = Uniforms::new( + transformation * Transformation::translate(origin.x, origin.y), + ); - let new_index_offset = + index_offset += self.index_buffer.write(queue, index_offset, indices); - - index_offset += new_index_offset; self.index_strides.push(indices.len() as u32); - //push uniform data to CPU buffers match mesh { Mesh::Solid { buffers, .. } => { - self.solid.uniforms.push(&solid::Uniforms::new(transform)); - - let written_bytes = self.solid.vertices.write( + solid_vertex_offset += self.solid.vertices.write( queue, solid_vertex_offset, &buffers.vertices, ); - solid_vertex_offset += written_bytes; + solid_uniform_offset += self.solid.uniforms.write( + queue, + solid_uniform_offset, + &[uniforms], + ); } - #[cfg(not(target_arch = "wasm32"))] - Mesh::Gradient { - buffers, gradient, .. - } => { - let written_bytes = self.gradient.vertices.write( + Mesh::Gradient { buffers, .. } => { + gradient_vertex_offset += self.gradient.vertices.write( queue, gradient_vertex_offset, &buffers.vertices, ); - gradient_vertex_offset += written_bytes; - - match gradient { - Gradient::Linear(linear) => { - use glam::{IVec4, Vec4}; - - let start_offset = self.gradient.color_stop_offset; - let end_offset = (linear.color_stops.len() as i32) - + start_offset - - 1; - - self.gradient.uniforms.push(&gradient::Uniforms { - transform: transform.into(), - direction: Vec4::new( - linear.start.x, - linear.start.y, - linear.end.x, - linear.end.y, - ), - stop_range: IVec4::new( - start_offset, - end_offset, - 0, - 0, - ), - }); - - self.gradient.color_stop_offset = end_offset + 1; - - let stops: Vec = linear - .color_stops - .iter() - .map(|stop| { - let [r, g, b, a] = stop.color.into_linear(); - - gradient::ColorStop { - offset: stop.offset, - color: Vec4::new(r, g, b, a), - } - }) - .collect(); - - self.gradient - .color_stops_pending_write - .color_stops - .extend(stops); - } - } + gradient_uniform_offset += self.gradient.uniforms.write( + queue, + gradient_uniform_offset, + &[uniforms], + ); } - #[cfg(target_arch = "wasm32")] - Mesh::Gradient { .. } => {} } } - - // Write uniform data to GPU - if count.solid_vertices > 0 { - let uniforms_resized = self.solid.uniforms.resize(device); - - if uniforms_resized { - self.solid.constants = solid::Layer::bind_group( - device, - self.solid.uniforms.raw(), - &solid.constants_layout, - ) - } - - self.solid.uniforms.write(queue); - } - - #[cfg(not(target_arch = "wasm32"))] - if count.gradient_vertices > 0 { - // First write the pending color stops to the CPU buffer - self.gradient - .storage - .push(&self.gradient.color_stops_pending_write); - - // Resize buffers if needed - let uniforms_resized = self.gradient.uniforms.resize(device); - let storage_resized = self.gradient.storage.resize(device); - - if uniforms_resized || storage_resized { - self.gradient.constants = gradient::Layer::bind_group( - device, - self.gradient.uniforms.raw(), - self.gradient.storage.raw(), - &gradient.constants_layout, - ); - } - - // Write to GPU - self.gradient.uniforms.write(queue); - self.gradient.storage.write(queue); - - // Cleanup - self.gradient.color_stop_offset = 0; - self.gradient.color_stops_pending_write.color_stops.clear(); - } } fn render<'a>( &'a self, solid: &'a solid::Pipeline, - #[cfg(not(target_arch = "wasm32"))] gradient: &'a gradient::Pipeline, + gradient: &'a gradient::Pipeline, meshes: &[Mesh<'_>], scale_factor: f32, render_pass: &mut wgpu::RenderPass<'a>, ) { let mut num_solids = 0; - #[cfg(not(target_arch = "wasm32"))] let mut num_gradients = 0; let mut last_is_solid = None; @@ -268,7 +173,8 @@ impl Layer { render_pass.set_bind_group( 0, &self.solid.constants, - &[self.solid.uniforms.offset_at_index(num_solids)], + &[(num_solids * std::mem::size_of::()) + as u32], ); render_pass.set_vertex_buffer( @@ -278,7 +184,6 @@ impl Layer { num_solids += 1; } - #[cfg(not(target_arch = "wasm32"))] Mesh::Gradient { .. } => { if last_is_solid.unwrap_or(true) { render_pass.set_pipeline(&gradient.pipeline); @@ -289,10 +194,8 @@ impl Layer { render_pass.set_bind_group( 0, &self.gradient.constants, - &[self - .gradient - .uniforms - .offset_at_index(num_gradients)], + &[(num_gradients * std::mem::size_of::()) + as u32], ); render_pass.set_vertex_buffer( @@ -302,8 +205,6 @@ impl Layer { num_gradients += 1; } - #[cfg(target_arch = "wasm32")] - Mesh::Gradient { .. } => {} }; render_pass.set_index_buffer( @@ -325,10 +226,7 @@ impl Pipeline { Pipeline { blit: antialiasing.map(|a| msaa::Blit::new(device, format, a)), solid: solid::Pipeline::new(device, format, antialiasing), - - #[cfg(not(target_arch = "wasm32"))] gradient: gradient::Pipeline::new(device, format, antialiasing), - layers: Vec::new(), prepare_layer: 0, } @@ -342,15 +240,11 @@ impl Pipeline { transformation: Transformation, ) { #[cfg(feature = "tracing")] - let _ = info_span!("Wgpu::Triangle", "PREPARE").entered(); + let _ = tracing::info_span!("Wgpu::Triangle", "PREPARE").entered(); if self.layers.len() <= self.prepare_layer { - self.layers.push(Layer::new( - device, - &self.solid, - #[cfg(not(target_arch = "wasm32"))] - &self.gradient, - )); + self.layers + .push(Layer::new(device, &self.solid, &self.gradient)); } let layer = &mut self.layers[self.prepare_layer]; @@ -358,7 +252,6 @@ impl Pipeline { device, queue, &self.solid, - #[cfg(not(target_arch = "wasm32"))] &self.gradient, meshes, transformation, @@ -378,9 +271,8 @@ impl Pipeline { scale_factor: f32, ) { #[cfg(feature = "tracing")] - let _ = info_span!("Wgpu::Triangle", "DRAW").entered(); + let _ = tracing::info_span!("Wgpu::Triangle", "DRAW").entered(); - // Configure render pass { let (attachment, resolve_target, load) = if let Some(blit) = &mut self.blit @@ -397,12 +289,9 @@ impl Pipeline { (target, None, wgpu::LoadOp::Load) }; - #[cfg(feature = "tracing")] - let _ = info_span!("Wgpu::Triangle", "BEGIN_RENDER_PASS").enter(); - let mut render_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor { - label: Some("iced_wgpu::triangle render pass"), + label: Some("iced_wgpu.triangle.render_pass"), color_attachments: &[Some( wgpu::RenderPassColorAttachment { view: attachment, @@ -417,7 +306,6 @@ impl Pipeline { layer.render( &self.solid, - #[cfg(not(target_arch = "wasm32"))] &self.gradient, meshes, scale_factor, @@ -463,15 +351,49 @@ fn multisample_state( } } +#[derive(Debug, Clone, Copy, bytemuck::Pod, bytemuck::Zeroable)] +#[repr(C)] +pub struct Uniforms { + transform: [f32; 16], + /// Uniform values must be 256-aligned; + /// see: [`wgpu::Limits`] `min_uniform_buffer_offset_alignment`. + _padding: [f32; 48], +} + +impl Uniforms { + pub fn new(transform: Transformation) -> Self { + Self { + transform: transform.into(), + _padding: [0.0; 48], + } + } + + pub fn entry() -> wgpu::BindGroupLayoutEntry { + wgpu::BindGroupLayoutEntry { + binding: 0, + visibility: wgpu::ShaderStages::VERTEX_FRAGMENT, + ty: wgpu::BindingType::Buffer { + ty: wgpu::BufferBindingType::Uniform, + has_dynamic_offset: true, + min_binding_size: wgpu::BufferSize::new( + std::mem::size_of::() as u64, + ), + }, + count: None, + } + } + + pub fn min_size() -> Option { + wgpu::BufferSize::new(std::mem::size_of::() as u64) + } +} + mod solid { - use crate::buffer::dynamic; - use crate::buffer::r#static::Buffer; + use crate::buffer::Buffer; use crate::graphics::primitive; - use crate::graphics::{Antialiasing, Transformation}; + use crate::graphics::Antialiasing; use crate::triangle; - use encase::ShaderType; - #[derive(Debug)] pub struct Pipeline { pub pipeline: wgpu::RenderPipeline, @@ -481,7 +403,7 @@ mod solid { #[derive(Debug)] pub struct Layer { pub vertices: Buffer, - pub uniforms: dynamic::Buffer, + pub uniforms: Buffer, pub constants: wgpu::BindGroup, } @@ -492,17 +414,20 @@ mod solid { ) -> Self { let vertices = Buffer::new( device, - "iced_wgpu::triangle::solid vertex buffer", + "iced_wgpu.triangle.solid.vertex_buffer", + triangle::INITIAL_VERTEX_COUNT, wgpu::BufferUsages::VERTEX | wgpu::BufferUsages::COPY_DST, ); - let uniforms = dynamic::Buffer::uniform( + let uniforms = Buffer::new( device, - "iced_wgpu::triangle::solid uniforms", + "iced_wgpu.triangle.solid.uniforms", + 1, + wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST, ); let constants = - Self::bind_group(device, uniforms.raw(), constants_layout); + Self::bind_group(device, &uniforms.raw, constants_layout); Self { vertices, @@ -517,7 +442,7 @@ mod solid { layout: &wgpu::BindGroupLayout, ) -> wgpu::BindGroup { device.create_bind_group(&wgpu::BindGroupDescriptor { - label: Some("iced_wgpu::triangle::solid bind group"), + label: Some("iced_wgpu.triangle.solid.bind_group"), layout, entries: &[wgpu::BindGroupEntry { binding: 0, @@ -525,7 +450,7 @@ mod solid { wgpu::BufferBinding { buffer, offset: 0, - size: Some(Uniforms::min_size()), + size: triangle::Uniforms::min_size(), }, ), }], @@ -533,21 +458,7 @@ mod solid { } } - #[derive(Debug, Clone, Copy, ShaderType)] - pub struct Uniforms { - transform: glam::Mat4, - } - - impl Uniforms { - pub fn new(transform: Transformation) -> Self { - Self { - transform: transform.into(), - } - } - } - impl Pipeline { - /// Creates a new [SolidPipeline] using `solid.wgsl` shader. pub fn new( device: &wgpu::Device, format: wgpu::TextureFormat, @@ -555,23 +466,14 @@ mod solid { ) -> Self { let constants_layout = device.create_bind_group_layout( &wgpu::BindGroupLayoutDescriptor { - label: Some("iced_wgpu::triangle::solid bind group layout"), - entries: &[wgpu::BindGroupLayoutEntry { - binding: 0, - visibility: wgpu::ShaderStages::VERTEX_FRAGMENT, - ty: wgpu::BindingType::Buffer { - ty: wgpu::BufferBindingType::Uniform, - has_dynamic_offset: true, - min_binding_size: Some(Uniforms::min_size()), - }, - count: None, - }], + label: Some("iced_wgpu.triangle.solid.bind_group_layout"), + entries: &[triangle::Uniforms::entry()], }, ); let layout = device.create_pipeline_layout( &wgpu::PipelineLayoutDescriptor { - label: Some("iced_wgpu::triangle::solid pipeline layout"), + label: Some("iced_wgpu.triangle.solid.pipeline_layout"), bind_group_layouts: &[&constants_layout], push_constant_ranges: &[], }, @@ -579,12 +481,10 @@ mod solid { let shader = device.create_shader_module(wgpu::ShaderModuleDescriptor { - label: Some( - "iced_wgpu triangle solid create shader module", - ), + label: Some("iced_wgpu.triangle.solid.shader"), source: wgpu::ShaderSource::Wgsl( std::borrow::Cow::Borrowed(include_str!( - "shader/solid.wgsl" + "shader/triangle.wgsl" )), ), }); @@ -595,7 +495,7 @@ mod solid { layout: Some(&layout), vertex: wgpu::VertexState { module: &shader, - entry_point: "vs_main", + entry_point: "solid_vs_main", buffers: &[wgpu::VertexBufferLayout { array_stride: std::mem::size_of::< primitive::ColoredVertex2D, @@ -612,7 +512,7 @@ mod solid { }, fragment: Some(wgpu::FragmentState { module: &shader, - entry_point: "fs_main", + entry_point: "solid_fs_main", targets: &[triangle::fragment_target(format)], }), primitive: triangle::primitive_state(), @@ -630,16 +530,11 @@ mod solid { } } -#[cfg(not(target_arch = "wasm32"))] mod gradient { - use crate::buffer::dynamic; - use crate::buffer::r#static::Buffer; - use crate::graphics::Antialiasing; + use crate::graphics::{primitive, Antialiasing}; use crate::triangle; - use encase::ShaderType; - use glam::{IVec4, Vec4}; - use iced_graphics::primitive; + use crate::buffer::Buffer; #[derive(Debug)] pub struct Pipeline { @@ -649,14 +544,9 @@ mod gradient { #[derive(Debug)] pub struct Layer { - pub vertices: Buffer, - pub uniforms: dynamic::Buffer, - pub storage: dynamic::Buffer, + pub vertices: Buffer, + pub uniforms: Buffer, pub constants: wgpu::BindGroup, - pub color_stop_offset: i32, - //Need to store these and then write them all at once - //or else they will be padded to 256 and cause gaps in the storage buffer - pub color_stops_pending_write: Storage, } impl Layer { @@ -666,94 +556,52 @@ mod gradient { ) -> Self { let vertices = Buffer::new( device, - "iced_wgpu::triangle::gradient vertex buffer", + "iced_wgpu.triangle.gradient.vertex_buffer", + triangle::INITIAL_VERTEX_COUNT, wgpu::BufferUsages::VERTEX | wgpu::BufferUsages::COPY_DST, ); - let uniforms = dynamic::Buffer::uniform( + let uniforms = Buffer::new( device, - "iced_wgpu::triangle::gradient uniforms", + "iced_wgpu.triangle.gradient.uniforms", + 1, + wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST, ); - // Note: with a WASM target storage buffers are not supported. Will need to use UBOs & static - // sized array (eg like the 32-sized array on OpenGL side right now) to make gradients work - let storage = dynamic::Buffer::storage( - device, - "iced_wgpu::triangle::gradient storage", - ); - - let constants = Self::bind_group( - device, - uniforms.raw(), - storage.raw(), - constants_layout, - ); + let constants = + Self::bind_group(device, &uniforms.raw, constants_layout); Self { vertices, uniforms, - storage, constants, - color_stop_offset: 0, - color_stops_pending_write: Storage { - color_stops: vec![], - }, } } pub fn bind_group( device: &wgpu::Device, uniform_buffer: &wgpu::Buffer, - storage_buffer: &wgpu::Buffer, layout: &wgpu::BindGroupLayout, ) -> wgpu::BindGroup { device.create_bind_group(&wgpu::BindGroupDescriptor { - label: Some("iced_wgpu::triangle::gradient bind group"), + label: Some("iced_wgpu.triangle.gradient.bind_group"), layout, - entries: &[ - wgpu::BindGroupEntry { - binding: 0, - resource: wgpu::BindingResource::Buffer( - wgpu::BufferBinding { - buffer: uniform_buffer, - offset: 0, - size: Some(Uniforms::min_size()), - }, - ), - }, - wgpu::BindGroupEntry { - binding: 1, - resource: storage_buffer.as_entire_binding(), - }, - ], + entries: &[wgpu::BindGroupEntry { + binding: 0, + resource: wgpu::BindingResource::Buffer( + wgpu::BufferBinding { + buffer: uniform_buffer, + offset: 0, + size: triangle::Uniforms::min_size(), + }, + ), + }], }) } } - #[derive(Debug, ShaderType)] - pub struct Uniforms { - pub transform: glam::Mat4, - //xy = start, zw = end - pub direction: Vec4, - //x = start stop, y = end stop, zw = padding - pub stop_range: IVec4, - } - - #[derive(Debug, ShaderType)] - pub struct ColorStop { - pub color: Vec4, - pub offset: f32, - } - - #[derive(Debug, ShaderType)] - pub struct Storage { - #[size(runtime)] - pub color_stops: Vec, - } - impl Pipeline { - /// Creates a new [GradientPipeline] using `gradient.wgsl` shader. - pub(super) fn new( + pub fn new( device: &wgpu::Device, format: wgpu::TextureFormat, antialiasing: Option, @@ -761,40 +609,15 @@ mod gradient { let constants_layout = device.create_bind_group_layout( &wgpu::BindGroupLayoutDescriptor { label: Some( - "iced_wgpu::triangle::gradient bind group layout", + "iced_wgpu.triangle.gradient.bind_group_layout", ), - entries: &[ - wgpu::BindGroupLayoutEntry { - binding: 0, - visibility: wgpu::ShaderStages::VERTEX_FRAGMENT, - ty: wgpu::BindingType::Buffer { - ty: wgpu::BufferBindingType::Uniform, - has_dynamic_offset: true, - min_binding_size: Some(Uniforms::min_size()), - }, - count: None, - }, - wgpu::BindGroupLayoutEntry { - binding: 1, - visibility: wgpu::ShaderStages::FRAGMENT, - ty: wgpu::BindingType::Buffer { - ty: wgpu::BufferBindingType::Storage { - read_only: true, - }, - has_dynamic_offset: false, - min_binding_size: Some(Storage::min_size()), - }, - count: None, - }, - ], + entries: &[triangle::Uniforms::entry()], }, ); let layout = device.create_pipeline_layout( &wgpu::PipelineLayoutDescriptor { - label: Some( - "iced_wgpu::triangle::gradient pipeline layout", - ), + label: Some("iced_wgpu.triangle.gradient.pipeline_layout"), bind_group_layouts: &[&constants_layout], push_constant_ranges: &[], }, @@ -802,48 +625,66 @@ mod gradient { let shader = device.create_shader_module(wgpu::ShaderModuleDescriptor { - label: Some( - "iced_wgpu::triangle::gradient create shader module", - ), + label: Some("iced_wgpu.triangle.gradient.shader"), source: wgpu::ShaderSource::Wgsl( std::borrow::Cow::Borrowed(include_str!( - "shader/gradient.wgsl" + "shader/triangle.wgsl" )), ), }); - let pipeline = - device.create_render_pipeline( - &wgpu::RenderPipelineDescriptor { - label: Some("iced_wgpu::triangle::gradient pipeline"), - layout: Some(&layout), - vertex: wgpu::VertexState { - module: &shader, - entry_point: "vs_main", - buffers: &[wgpu::VertexBufferLayout { - array_stride: std::mem::size_of::< - primitive::Vertex2D, - >( - ) - as u64, - step_mode: wgpu::VertexStepMode::Vertex, - attributes: &wgpu::vertex_attr_array!( - // Position - 0 => Float32x2, - ), - }], - }, - fragment: Some(wgpu::FragmentState { - module: &shader, - entry_point: "fs_main", - targets: &[triangle::fragment_target(format)], - }), - primitive: triangle::primitive_state(), - depth_stencil: None, - multisample: triangle::multisample_state(antialiasing), - multiview: None, + let pipeline = device.create_render_pipeline( + &wgpu::RenderPipelineDescriptor { + label: Some("iced_wgpu.triangle.gradient.pipeline"), + layout: Some(&layout), + vertex: wgpu::VertexState { + module: &shader, + entry_point: "gradient_vs_main", + buffers: &[wgpu::VertexBufferLayout { + array_stride: std::mem::size_of::< + primitive::GradientVertex2D, + >() + as u64, + step_mode: wgpu::VertexStepMode::Vertex, + attributes: &wgpu::vertex_attr_array!( + // Position + 0 => Float32x2, + // Color 1 + 1 => Float32x4, + // Color 2 + 2 => Float32x4, + // Color 3 + 3 => Float32x4, + // Color 4 + 4 => Float32x4, + // Color 5 + 5 => Float32x4, + // Color 6 + 6 => Float32x4, + // Color 7 + 7 => Float32x4, + // Color 8 + 8 => Float32x4, + // Offsets 1-4 + 9 => Float32x4, + // Offsets 5-8 + 10 => Float32x4, + // Direction + 11 => Float32x4 + ), + }], }, - ); + fragment: Some(wgpu::FragmentState { + module: &shader, + entry_point: "gradient_fs_main", + targets: &[triangle::fragment_target(format)], + }), + primitive: triangle::primitive_state(), + depth_stencil: None, + multisample: triangle::multisample_state(antialiasing), + multiview: None, + }, + ); Self { pipeline, -- cgit From f02f0c01ea9b46b3b303c805b4e001a3f10be748 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Thu, 11 May 2023 20:18:36 +0200 Subject: Fix race condition when growing an `image::Atlas` --- wgpu/src/image.rs | 2 -- wgpu/src/image/atlas.rs | 38 +++++++++++++++++++++++++++----------- wgpu/src/image/raster.rs | 4 +--- wgpu/src/image/vector.rs | 5 ++--- 4 files changed, 30 insertions(+), 19 deletions(-) (limited to 'wgpu') diff --git a/wgpu/src/image.rs b/wgpu/src/image.rs index 263bcfa2..dd297a79 100644 --- a/wgpu/src/image.rs +++ b/wgpu/src/image.rs @@ -369,7 +369,6 @@ impl Pipeline { layer::Image::Raster { handle, bounds } => { if let Some(atlas_entry) = raster_cache.upload( device, - queue, encoder, handle, &mut self.texture_atlas, @@ -395,7 +394,6 @@ impl Pipeline { if let Some(atlas_entry) = vector_cache.upload( device, - queue, encoder, handle, *color, diff --git a/wgpu/src/image/atlas.rs b/wgpu/src/image/atlas.rs index 366fe623..2296710b 100644 --- a/wgpu/src/image/atlas.rs +++ b/wgpu/src/image/atlas.rs @@ -65,7 +65,6 @@ impl Atlas { pub fn upload( &mut self, device: &wgpu::Device, - queue: &wgpu::Queue, encoder: &mut wgpu::CommandEncoder, width: u32, height: u32, @@ -75,6 +74,9 @@ impl Atlas { let current_size = self.layers.len(); let entry = self.allocate(width, height)?; + dbg!(&entry); + dbg!(&self.layers); + // We grow the internal texture after allocating if necessary let new_layers = self.layers.len() - current_size; self.grow(new_layers, device, encoder); @@ -112,7 +114,8 @@ impl Atlas { padding, 0, allocation, - queue, + device, + encoder, ); } Entry::Fragmented { fragments, .. } => { @@ -127,7 +130,8 @@ impl Atlas { padding, offset, &fragment.allocation, - queue, + device, + encoder, ); } } @@ -280,8 +284,11 @@ impl Atlas { padding: u32, offset: usize, allocation: &Allocation, - queue: &wgpu::Queue, + device: &wgpu::Device, + encoder: &mut wgpu::CommandEncoder, ) { + use wgpu::util::DeviceExt; + let (x, y) = allocation.position(); let Size { width, height } = allocation.size(); let layer = allocation.layer(); @@ -292,7 +299,22 @@ impl Atlas { depth_or_array_layers: 1, }; - queue.write_texture( + let buffer = + device.create_buffer_init(&wgpu::util::BufferInitDescriptor { + label: Some("image upload buffer"), + contents: data, + usage: wgpu::BufferUsages::COPY_SRC, + }); + + encoder.copy_buffer_to_texture( + wgpu::ImageCopyBuffer { + buffer: &buffer, + layout: wgpu::ImageDataLayout { + offset: offset as u64, + bytes_per_row: Some(4 * image_width + padding), + rows_per_image: Some(image_height), + }, + }, wgpu::ImageCopyTexture { texture: &self.texture, mip_level: 0, @@ -303,12 +325,6 @@ impl Atlas { }, aspect: wgpu::TextureAspect::default(), }, - data, - wgpu::ImageDataLayout { - offset: offset as u64, - bytes_per_row: Some(4 * image_width + padding), - rows_per_image: Some(image_height), - }, extent, ); } diff --git a/wgpu/src/image/raster.rs b/wgpu/src/image/raster.rs index 9b38dce4..a6cba76a 100644 --- a/wgpu/src/image/raster.rs +++ b/wgpu/src/image/raster.rs @@ -63,7 +63,6 @@ impl Cache { pub fn upload( &mut self, device: &wgpu::Device, - queue: &wgpu::Queue, encoder: &mut wgpu::CommandEncoder, handle: &image::Handle, atlas: &mut Atlas, @@ -73,8 +72,7 @@ impl Cache { if let Memory::Host(image) = memory { let (width, height) = image.dimensions(); - let entry = - atlas.upload(device, queue, encoder, width, height, image)?; + let entry = atlas.upload(device, encoder, width, height, image)?; *memory = Memory::Device(entry); } diff --git a/wgpu/src/image/vector.rs b/wgpu/src/image/vector.rs index 58bdf64a..6b9be651 100644 --- a/wgpu/src/image/vector.rs +++ b/wgpu/src/image/vector.rs @@ -74,7 +74,6 @@ impl Cache { pub fn upload( &mut self, device: &wgpu::Device, - queue: &wgpu::Queue, encoder: &mut wgpu::CommandEncoder, handle: &svg::Handle, color: Option, @@ -138,8 +137,8 @@ impl Cache { }); } - let allocation = atlas - .upload(device, queue, encoder, width, height, &rgba)?; + let allocation = + atlas.upload(device, encoder, width, height, &rgba)?; log::debug!("allocating {} {}x{}", id, width, height); -- cgit From 0ef5ab6c84643784d37801598c68a259f0ca64ff Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Thu, 11 May 2023 20:19:37 +0200 Subject: Remove `dbg!` leftovers in `image::atlas` --- wgpu/src/image/atlas.rs | 3 --- 1 file changed, 3 deletions(-) (limited to 'wgpu') diff --git a/wgpu/src/image/atlas.rs b/wgpu/src/image/atlas.rs index 2296710b..9b6dcc46 100644 --- a/wgpu/src/image/atlas.rs +++ b/wgpu/src/image/atlas.rs @@ -74,9 +74,6 @@ impl Atlas { let current_size = self.layers.len(); let entry = self.allocate(width, height)?; - dbg!(&entry); - dbg!(&self.layers); - // We grow the internal texture after allocating if necessary let new_layers = self.layers.len() - current_size; self.grow(new_layers, device, encoder); -- cgit From 4c1a082f0468a59099bbf8aa8991420a41234948 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Fri, 19 May 2023 03:32:21 +0200 Subject: Remove `Builder` abstractions for gradients --- wgpu/src/geometry.rs | 2 +- wgpu/src/layer.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) (limited to 'wgpu') diff --git a/wgpu/src/geometry.rs b/wgpu/src/geometry.rs index e26d9278..b8a75c33 100644 --- a/wgpu/src/geometry.rs +++ b/wgpu/src/geometry.rs @@ -631,7 +631,7 @@ fn pack_gradient(gradient: &Gradient) -> [f32; 44] { let mut pack: [f32; 44] = [0.0; 44]; let mut offsets: [f32; 8] = [2.0; 8]; - for (index, stop) in linear.color_stops.iter().enumerate() { + for (index, stop) in linear.stops.iter().enumerate() { let [r, g, b, a] = stop .map_or(crate::core::Color::default(), |s| s.color) .into_linear(); diff --git a/wgpu/src/layer.rs b/wgpu/src/layer.rs index b3ee4739..08c0004a 100644 --- a/wgpu/src/layer.rs +++ b/wgpu/src/layer.rs @@ -317,7 +317,7 @@ fn pack_gradient(gradient: &core::Gradient, bounds: Rectangle) -> [f32; 44] { core::Gradient::Linear(linear) => { let mut pack: [f32; 44] = [0.0; 44]; - for (index, stop) in linear.color_stops.iter().enumerate() { + for (index, stop) in linear.stops.iter().enumerate() { let [r, g, b, a] = stop.map_or(Color::default(), |s| s.color).into_linear(); -- cgit From 59663d2e45c3c4487fb64f781eb0d0f422763467 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Fri, 19 May 2023 03:37:36 +0200 Subject: Avoid packing gradient data for every vertex in `iced_wgpu` --- wgpu/src/geometry.rs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) (limited to 'wgpu') diff --git a/wgpu/src/geometry.rs b/wgpu/src/geometry.rs index b8a75c33..d1d4fd3c 100644 --- a/wgpu/src/geometry.rs +++ b/wgpu/src/geometry.rs @@ -74,7 +74,7 @@ impl BufferStack { Box::new(tessellation::BuffersBuilder::new( buffer, GradientVertex2DBuilder { - gradient: gradient.clone(), + gradient: pack_gradient(gradient), }, )) } @@ -97,7 +97,7 @@ impl BufferStack { Box::new(tessellation::BuffersBuilder::new( buffer, GradientVertex2DBuilder { - gradient: gradient.clone(), + gradient: pack_gradient(gradient), }, )) } @@ -490,7 +490,7 @@ impl Frame { } struct GradientVertex2DBuilder { - gradient: Gradient, + gradient: [f32; 44], } impl tessellation::FillVertexConstructor @@ -504,7 +504,7 @@ impl tessellation::FillVertexConstructor primitive::GradientVertex2D { position: [position.x, position.y], - gradient: pack_gradient(&self.gradient), + gradient: self.gradient, } } } @@ -520,7 +520,7 @@ impl tessellation::StrokeVertexConstructor primitive::GradientVertex2D { position: [position.x, position.y], - gradient: pack_gradient(&self.gradient), + gradient: self.gradient, } } } -- cgit From f557b810f5931e69a9a35353b20fff1b07480715 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Fri, 19 May 2023 03:58:25 +0200 Subject: Keep `image` pipeline decoupled from `quad` in `iced_wgpu` --- wgpu/src/image.rs | 42 +++++++++++++++++++++++++++++++++++++----- wgpu/src/quad.rs | 6 +++--- 2 files changed, 40 insertions(+), 8 deletions(-) (limited to 'wgpu') diff --git a/wgpu/src/image.rs b/wgpu/src/image.rs index 6fe02b91..c3479652 100644 --- a/wgpu/src/image.rs +++ b/wgpu/src/image.rs @@ -11,7 +11,7 @@ use atlas::Atlas; use crate::buffer::Buffer; use crate::core::{Rectangle, Size}; use crate::graphics::Transformation; -use crate::{layer, quad}; +use crate::layer; use std::cell::RefCell; use std::mem; @@ -131,7 +131,7 @@ impl Layer { render_pass.set_vertex_buffer(1, self.instances.slice(..)); render_pass.draw_indexed( - 0..quad::INDICES.len() as u32, + 0..QUAD_INDICES.len() as u32, 0, 0..self.instance_count as u32, ); @@ -244,7 +244,22 @@ impl Pipeline { fragment: Some(wgpu::FragmentState { module: &shader, entry_point: "fs_main", - targets: &quad::color_target_state(format), + targets: &[Some(wgpu::ColorTargetState { + format, + blend: Some(wgpu::BlendState { + color: wgpu::BlendComponent { + src_factor: wgpu::BlendFactor::SrcAlpha, + dst_factor: wgpu::BlendFactor::OneMinusSrcAlpha, + operation: wgpu::BlendOperation::Add, + }, + alpha: wgpu::BlendComponent { + src_factor: wgpu::BlendFactor::One, + dst_factor: wgpu::BlendFactor::OneMinusSrcAlpha, + operation: wgpu::BlendOperation::Add, + }, + }), + write_mask: wgpu::ColorWrites::ALL, + })], }), primitive: wgpu::PrimitiveState { topology: wgpu::PrimitiveTopology::TriangleList, @@ -263,14 +278,14 @@ impl Pipeline { let vertices = device.create_buffer_init(&wgpu::util::BufferInitDescriptor { label: Some("iced_wgpu::image vertex buffer"), - contents: bytemuck::cast_slice(&quad::VERTICES), + contents: bytemuck::cast_slice(&QUAD_VERTICES), usage: wgpu::BufferUsages::VERTEX, }); let indices = device.create_buffer_init(&wgpu::util::BufferInitDescriptor { label: Some("iced_wgpu::image index buffer"), - contents: bytemuck::cast_slice(&quad::INDICES), + contents: bytemuck::cast_slice(&QUAD_INDICES), usage: wgpu::BufferUsages::INDEX, }); @@ -483,6 +498,23 @@ pub struct Vertex { _position: [f32; 2], } +const QUAD_INDICES: [u16; 6] = [0, 1, 2, 0, 2, 3]; + +const QUAD_VERTICES: [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(Debug, Clone, Copy, Zeroable, Pod)] struct Instance { diff --git a/wgpu/src/quad.rs b/wgpu/src/quad.rs index 31bf2b85..83ea8e1f 100644 --- a/wgpu/src/quad.rs +++ b/wgpu/src/quad.rs @@ -475,7 +475,7 @@ mod gradient { } } -pub(crate) fn color_target_state( +fn color_target_state( format: wgpu::TextureFormat, ) -> [Option; 1] { [Some(wgpu::ColorTargetState { @@ -516,9 +516,9 @@ impl Vertex { } } -pub(crate) const INDICES: [u16; 6] = [0, 1, 2, 0, 2, 3]; +const INDICES: [u16; 6] = [0, 1, 2, 0, 2, 3]; -pub(crate) const VERTICES: [Vertex; 4] = [ +const VERTICES: [Vertex; 4] = [ Vertex { _position: [0.0, 0.0], }, -- cgit From e267e075ccd35ae3ca357ae4143796e68c1ad392 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Fri, 19 May 2023 04:02:18 +0200 Subject: Avoid redundant `buffer::Buffer` import --- wgpu/src/image.rs | 2 +- wgpu/src/lib.rs | 2 ++ wgpu/src/quad.rs | 4 ++-- wgpu/src/triangle.rs | 7 +++---- 4 files changed, 8 insertions(+), 7 deletions(-) (limited to 'wgpu') diff --git a/wgpu/src/image.rs b/wgpu/src/image.rs index c3479652..3407aa92 100644 --- a/wgpu/src/image.rs +++ b/wgpu/src/image.rs @@ -8,10 +8,10 @@ mod vector; use atlas::Atlas; -use crate::buffer::Buffer; use crate::core::{Rectangle, Size}; use crate::graphics::Transformation; use crate::layer; +use crate::Buffer; use std::cell::RefCell; use std::mem; diff --git a/wgpu/src/lib.rs b/wgpu/src/lib.rs index c05280a6..571d2718 100644 --- a/wgpu/src/lib.rs +++ b/wgpu/src/lib.rs @@ -50,6 +50,8 @@ mod quad; mod text; mod triangle; +use buffer::Buffer; + pub use iced_graphics as graphics; pub use iced_graphics::core; diff --git a/wgpu/src/quad.rs b/wgpu/src/quad.rs index 83ea8e1f..e35aebc9 100644 --- a/wgpu/src/quad.rs +++ b/wgpu/src/quad.rs @@ -195,9 +195,9 @@ impl Layer { } mod solid { - use crate::buffer::Buffer; use crate::layer::quad; use crate::quad::{color_target_state, Vertex, INDICES, INITIAL_INSTANCES}; + use crate::Buffer; #[derive(Debug)] pub struct Pipeline { @@ -324,9 +324,9 @@ mod solid { } mod gradient { - use crate::buffer::Buffer; use crate::layer::quad; use crate::quad::{color_target_state, Vertex, INDICES, INITIAL_INSTANCES}; + use crate::Buffer; #[derive(Debug)] pub struct Pipeline { diff --git a/wgpu/src/triangle.rs b/wgpu/src/triangle.rs index 0d1fead1..6cd54ef7 100644 --- a/wgpu/src/triangle.rs +++ b/wgpu/src/triangle.rs @@ -1,10 +1,10 @@ //! Draw meshes of triangles. mod msaa; -use crate::buffer::Buffer; use crate::core::Size; use crate::graphics::{Antialiasing, Transformation}; use crate::layer::mesh::{self, Mesh}; +use crate::Buffer; const INITIAL_INDEX_COUNT: usize = 1_000; const INITIAL_VERTEX_COUNT: usize = 1_000; @@ -389,10 +389,10 @@ impl Uniforms { } mod solid { - use crate::buffer::Buffer; use crate::graphics::primitive; use crate::graphics::Antialiasing; use crate::triangle; + use crate::Buffer; #[derive(Debug)] pub struct Pipeline { @@ -533,8 +533,7 @@ mod solid { mod gradient { use crate::graphics::{primitive, Antialiasing}; use crate::triangle; - - use crate::buffer::Buffer; + use crate::Buffer; #[derive(Debug)] pub struct Pipeline { -- cgit From c888d0679bcaca13cd0dd5963e65449fef69ec01 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Fri, 19 May 2023 04:04:16 +0200 Subject: Fix inconsistent `pub use` in `wgpu::layer` --- wgpu/src/layer.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'wgpu') diff --git a/wgpu/src/layer.rs b/wgpu/src/layer.rs index 08c0004a..980d807b 100644 --- a/wgpu/src/layer.rs +++ b/wgpu/src/layer.rs @@ -7,7 +7,7 @@ pub mod quad; pub use image::Image; pub use mesh::Mesh; -use quad::Quad; +pub use quad::Quad; pub use text::Text; use crate::core; -- cgit From 9d25f98f0f658819048e2455df0d48aea79fb4cc Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Fri, 19 May 2023 04:07:53 +0200 Subject: Reduce `INITIAL_INSTANCES` in `wgpu::quad` to `2_000` --- wgpu/src/quad.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'wgpu') diff --git a/wgpu/src/quad.rs b/wgpu/src/quad.rs index e35aebc9..0125ec0b 100644 --- a/wgpu/src/quad.rs +++ b/wgpu/src/quad.rs @@ -8,7 +8,7 @@ use wgpu::util::DeviceExt; #[cfg(feature = "tracing")] use tracing::info_span; -const INITIAL_INSTANCES: usize = 10_000; +const INITIAL_INSTANCES: usize = 2_000; #[derive(Debug)] pub struct Pipeline { -- cgit From dee8ede5be04d0c32d24929b255e431821b69d16 Mon Sep 17 00:00:00 2001 From: Benoît du Garreau Date: Tue, 9 May 2023 14:14:29 +0200 Subject: Update `glam` to `0.24` --- wgpu/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'wgpu') diff --git a/wgpu/Cargo.toml b/wgpu/Cargo.toml index 1b71d6ec..f21bf7e0 100644 --- a/wgpu/Cargo.toml +++ b/wgpu/Cargo.toml @@ -47,7 +47,7 @@ git = "https://github.com/hecrj/glyphon.git" rev = "f145067d292082abdd1f2b2481812d4a52c394ec" [dependencies.glam] -version = "0.21.3" +version = "0.24" [dependencies.lyon] version = "1.0" -- cgit From a395e78596d0711a50108cb6654121541337ceb5 Mon Sep 17 00:00:00 2001 From: Bingus Date: Wed, 24 May 2023 13:08:59 -0700 Subject: Made gradient pack public for iced_graphics::gradient mod for use with GradientVertex2D. --- wgpu/src/geometry.rs | 43 ++----------------------------------------- 1 file changed, 2 insertions(+), 41 deletions(-) (limited to 'wgpu') diff --git a/wgpu/src/geometry.rs b/wgpu/src/geometry.rs index d1d4fd3c..13ce2359 100644 --- a/wgpu/src/geometry.rs +++ b/wgpu/src/geometry.rs @@ -74,7 +74,7 @@ impl BufferStack { Box::new(tessellation::BuffersBuilder::new( buffer, GradientVertex2DBuilder { - gradient: pack_gradient(gradient), + gradient: gradient.pack(), }, )) } @@ -97,7 +97,7 @@ impl BufferStack { Box::new(tessellation::BuffersBuilder::new( buffer, GradientVertex2DBuilder { - gradient: pack_gradient(gradient), + gradient: gradient.pack(), }, )) } @@ -623,42 +623,3 @@ pub(super) fn dashed(path: &Path, line_dash: LineDash<'_>) -> Path { ); }) } - -/// Packs the [`Gradient`] for use in shader code. -fn pack_gradient(gradient: &Gradient) -> [f32; 44] { - match gradient { - Gradient::Linear(linear) => { - let mut pack: [f32; 44] = [0.0; 44]; - let mut offsets: [f32; 8] = [2.0; 8]; - - for (index, stop) in linear.stops.iter().enumerate() { - let [r, g, b, a] = stop - .map_or(crate::core::Color::default(), |s| s.color) - .into_linear(); - - pack[index * 4] = r; - pack[(index * 4) + 1] = g; - pack[(index * 4) + 2] = b; - pack[(index * 4) + 3] = a; - - offsets[index] = stop.map_or(2.0, |s| s.offset); - } - - pack[32] = offsets[0]; - pack[33] = offsets[1]; - pack[34] = offsets[2]; - pack[35] = offsets[3]; - pack[36] = offsets[4]; - pack[37] = offsets[5]; - pack[38] = offsets[6]; - pack[39] = offsets[7]; - - pack[40] = linear.start.x; - pack[41] = linear.start.y; - pack[42] = linear.end.x; - pack[43] = linear.end.y; - - pack - } - } -} -- cgit From 413526ad09d006853eb9659efabee168f4a0e0a4 Mon Sep 17 00:00:00 2001 From: Bingus Date: Thu, 25 May 2023 10:49:26 -0700 Subject: Created "Packed" data structure for gradient data. --- wgpu/src/geometry.rs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) (limited to 'wgpu') diff --git a/wgpu/src/geometry.rs b/wgpu/src/geometry.rs index 13ce2359..f3fed7ae 100644 --- a/wgpu/src/geometry.rs +++ b/wgpu/src/geometry.rs @@ -7,6 +7,7 @@ use crate::graphics::geometry::{ use crate::graphics::primitive::{self, Primitive}; use crate::graphics::Gradient; +use iced_graphics::gradient; use lyon::geom::euclid; use lyon::tessellation; use std::borrow::Cow; @@ -490,7 +491,7 @@ impl Frame { } struct GradientVertex2DBuilder { - gradient: [f32; 44], + gradient: gradient::Packed, } impl tessellation::FillVertexConstructor @@ -504,7 +505,7 @@ impl tessellation::FillVertexConstructor primitive::GradientVertex2D { position: [position.x, position.y], - gradient: self.gradient, + gradient: self.gradient.data, } } } @@ -520,7 +521,7 @@ impl tessellation::StrokeVertexConstructor primitive::GradientVertex2D { position: [position.x, position.y], - gradient: self.gradient, + gradient: self.gradient.data, } } } -- cgit From 902e333148a1ceed85aba36262a849aaed8d3ac9 Mon Sep 17 00:00:00 2001 From: Bingus Date: Fri, 26 May 2023 10:07:52 -0700 Subject: Changed gradient::Packed to be `repr(C)` for direct gpu upload. --- wgpu/src/geometry.rs | 4 ++-- wgpu/src/layer.rs | 28 ++++++++++++++++------------ wgpu/src/layer/quad.rs | 4 ++-- 3 files changed, 20 insertions(+), 16 deletions(-) (limited to 'wgpu') diff --git a/wgpu/src/geometry.rs b/wgpu/src/geometry.rs index f3fed7ae..740ec20c 100644 --- a/wgpu/src/geometry.rs +++ b/wgpu/src/geometry.rs @@ -505,7 +505,7 @@ impl tessellation::FillVertexConstructor primitive::GradientVertex2D { position: [position.x, position.y], - gradient: self.gradient.data, + gradient: self.gradient, } } } @@ -521,7 +521,7 @@ impl tessellation::StrokeVertexConstructor primitive::GradientVertex2D { position: [position.x, position.y], - gradient: self.gradient.data, + gradient: self.gradient, } } } diff --git a/wgpu/src/layer.rs b/wgpu/src/layer.rs index 980d807b..9d7f9f2a 100644 --- a/wgpu/src/layer.rs +++ b/wgpu/src/layer.rs @@ -13,6 +13,7 @@ pub use text::Text; use crate::core; use crate::core::alignment; use crate::core::{Background, Color, Font, Point, Rectangle, Size, Vector}; +use crate::graphics::gradient; use crate::graphics::{Primitive, Viewport}; /// A group of primitives that should be clipped together. @@ -312,30 +313,33 @@ impl<'a> Layer<'a> { } /// Packs the [`Gradient`] for use in shader code. -fn pack_gradient(gradient: &core::Gradient, bounds: Rectangle) -> [f32; 44] { +fn pack_gradient( + gradient: &core::Gradient, + bounds: Rectangle, +) -> gradient::Packed { match gradient { core::Gradient::Linear(linear) => { - let mut pack: [f32; 44] = [0.0; 44]; + let mut data: [f32; 44] = [0.0; 44]; for (index, stop) in linear.stops.iter().enumerate() { let [r, g, b, a] = stop.map_or(Color::default(), |s| s.color).into_linear(); - pack[index * 4] = r; - pack[(index * 4) + 1] = g; - pack[(index * 4) + 2] = b; - pack[(index * 4) + 3] = a; - pack[32 + index] = stop.map_or(2.0, |s| s.offset); + data[index * 4] = r; + data[(index * 4) + 1] = g; + data[(index * 4) + 2] = b; + data[(index * 4) + 3] = a; + data[32 + index] = stop.map_or(2.0, |s| s.offset); } let (start, end) = linear.angle.to_distance(&bounds); - pack[40] = start.x; - pack[41] = start.y; - pack[42] = end.x; - pack[43] = end.y; + data[40] = start.x; + data[41] = start.y; + data[42] = end.x; + data[43] = end.y; - pack + data.into() } } } diff --git a/wgpu/src/layer/quad.rs b/wgpu/src/layer/quad.rs index 9913cfe0..0bf7837a 100644 --- a/wgpu/src/layer/quad.rs +++ b/wgpu/src/layer/quad.rs @@ -1,5 +1,5 @@ //! A rectangle with certain styled properties. - +use crate::graphics::gradient; use bytemuck::{Pod, Zeroable}; /// The properties of a quad. @@ -38,7 +38,7 @@ pub struct Solid { #[repr(C)] pub struct Gradient { /// The background gradient data of the quad. - pub gradient: [f32; 44], + pub gradient: gradient::Packed, /// The [`Quad`] data of the [`Gradient`]. pub quad: Quad, -- cgit From 556f3e89d3a0a7a6b3a2783a618c0bb5de52be4b Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Mon, 29 May 2023 20:47:47 +0200 Subject: Skip missing glyphs instead of panicking in `glyphon` --- wgpu/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'wgpu') diff --git a/wgpu/Cargo.toml b/wgpu/Cargo.toml index f21bf7e0..b5401626 100644 --- a/wgpu/Cargo.toml +++ b/wgpu/Cargo.toml @@ -44,7 +44,7 @@ path = "../graphics" [dependencies.glyphon] version = "0.2" git = "https://github.com/hecrj/glyphon.git" -rev = "f145067d292082abdd1f2b2481812d4a52c394ec" +rev = "cf7fe9df00499b868a6a94fa5fdb0a4ca368c9f9" [dependencies.glam] version = "0.24" -- cgit From 8ca7b884c0695e4e7a031ea1359ee733bdcaa8a4 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Mon, 29 May 2023 20:56:51 +0200 Subject: Make `Packed` fully opaque ... by only allowing direct conversion from our `Gradient` types --- wgpu/src/layer.rs | 34 +--------------------------------- 1 file changed, 1 insertion(+), 33 deletions(-) (limited to 'wgpu') diff --git a/wgpu/src/layer.rs b/wgpu/src/layer.rs index 9d7f9f2a..bf5c4c0a 100644 --- a/wgpu/src/layer.rs +++ b/wgpu/src/layer.rs @@ -183,7 +183,7 @@ impl<'a> Layer<'a> { } Background::Gradient(gradient) => { let quad = quad::Gradient { - gradient: pack_gradient( + gradient: gradient::pack( gradient, Rectangle::new( quad.position.into(), @@ -311,35 +311,3 @@ impl<'a> Layer<'a> { } } } - -/// Packs the [`Gradient`] for use in shader code. -fn pack_gradient( - gradient: &core::Gradient, - bounds: Rectangle, -) -> gradient::Packed { - match gradient { - core::Gradient::Linear(linear) => { - let mut data: [f32; 44] = [0.0; 44]; - - for (index, stop) in linear.stops.iter().enumerate() { - let [r, g, b, a] = - stop.map_or(Color::default(), |s| s.color).into_linear(); - - data[index * 4] = r; - data[(index * 4) + 1] = g; - data[(index * 4) + 2] = b; - data[(index * 4) + 3] = a; - data[32 + index] = stop.map_or(2.0, |s| s.offset); - } - - let (start, end) = linear.angle.to_distance(&bounds); - - data[40] = start.x; - data[41] = start.y; - data[42] = end.x; - data[43] = end.y; - - data.into() - } - } -} -- cgit From 3f141459a66fe66e6dc25d579d0cda80662f0895 Mon Sep 17 00:00:00 2001 From: Bingus Date: Thu, 25 May 2023 10:27:27 -0700 Subject: Fixed issue where quads of different types were not ordered. --- wgpu/src/backend.rs | 8 ++++++-- wgpu/src/layer.rs | 42 +++++++++++++++++++++++++++++++++++++++++- wgpu/src/layer/quad.rs | 9 +++++++++ wgpu/src/quad.rs | 43 ++++++++++++++++++++++++++++++++----------- 4 files changed, 88 insertions(+), 14 deletions(-) (limited to 'wgpu') diff --git a/wgpu/src/backend.rs b/wgpu/src/backend.rs index def80a81..764a033b 100644 --- a/wgpu/src/backend.rs +++ b/wgpu/src/backend.rs @@ -265,8 +265,12 @@ impl Backend { } if !layer.quads.is_empty() { - self.quad_pipeline - .render(quad_layer, bounds, &mut render_pass); + self.quad_pipeline.render( + quad_layer, + bounds, + &layer.quads.order, + &mut render_pass, + ); quad_layer += 1; } diff --git a/wgpu/src/layer.rs b/wgpu/src/layer.rs index bf5c4c0a..f5c4b576 100644 --- a/wgpu/src/layer.rs +++ b/wgpu/src/layer.rs @@ -43,6 +43,12 @@ pub struct Quads { /// The gradient quads of the [`Layer`]. pub gradients: Vec, + + /// The quad order of the [`Layer`]; stored as a tuple of the quad type & its count. + pub order: Vec<(quad::Order, usize)>, + + // The last index of quad ordering. + index: usize, } impl Quads { @@ -174,12 +180,13 @@ impl<'a> Layer<'a> { border_width: *border_width, }; - match background { + let quad_order = match background { Background::Color(color) => { layer.quads.solids.push(quad::Solid { color: color.into_linear(), quad, }); + quad::Order::Solid } Background::Gradient(gradient) => { let quad = quad::Gradient { @@ -194,8 +201,41 @@ impl<'a> Layer<'a> { }; layer.quads.gradients.push(quad); + quad::Order::Gradient } }; + + match (layer.quads.order.get_mut(layer.quads.index), quad_order) + { + (Some((quad_order, count)), quad::Order::Solid) => { + match quad_order { + quad::Order::Solid => { + *count += 1; + } + quad::Order::Gradient => { + layer.quads.order.push((quad::Order::Solid, 1)); + layer.quads.index += 1; + } + } + } + (Some((quad_order, count)), quad::Order::Gradient) => { + match quad_order { + quad::Order::Solid => { + layer + .quads + .order + .push((quad::Order::Gradient, 1)); + layer.quads.index += 1; + } + quad::Order::Gradient => { + *count += 1; + } + } + } + (None, _) => { + layer.quads.order.push((quad_order, 1)); + } + } } Primitive::Image { handle, bounds } => { let layer = &mut layers[current_layer]; diff --git a/wgpu/src/layer/quad.rs b/wgpu/src/layer/quad.rs index 0bf7837a..aaaebd5b 100644 --- a/wgpu/src/layer/quad.rs +++ b/wgpu/src/layer/quad.rs @@ -49,3 +49,12 @@ unsafe impl Pod for Gradient {} #[allow(unsafe_code)] unsafe impl Zeroable for Gradient {} + +#[derive(Debug, Copy, Clone)] +/// The identifier of a quad, used for ordering. +pub enum Order { + /// A solid quad + Solid, + /// A gradient quad + Gradient, +} diff --git a/wgpu/src/quad.rs b/wgpu/src/quad.rs index 0125ec0b..a05e5468 100644 --- a/wgpu/src/quad.rs +++ b/wgpu/src/quad.rs @@ -1,6 +1,6 @@ use crate::core::Rectangle; use crate::graphics::Transformation; -use crate::layer; +use crate::layer::{self, quad}; use std::mem; use wgpu::util::DeviceExt; @@ -87,6 +87,7 @@ impl Pipeline { &'a self, layer: usize, bounds: Rectangle, + ordering: &Vec<(quad::Order, usize)>, render_pass: &mut wgpu::RenderPass<'a>, ) { if let Some(layer) = self.layers.get(layer) { @@ -102,14 +103,30 @@ impl Pipeline { ); render_pass.set_vertex_buffer(0, self.vertices.slice(..)); - if layer.solid.instance_count > 0 { - render_pass.set_pipeline(&self.solid.pipeline); - layer.solid.draw(&layer.constants, render_pass); - } - - if layer.gradient.instance_count > 0 { - render_pass.set_pipeline(&self.gradient.pipeline); - layer.gradient.draw(&layer.constants, render_pass); + let mut solid_offset = 0; + let mut gradient_offset = 0; + + for (quad_order, count) in ordering { + match quad_order { + quad::Order::Solid => { + render_pass.set_pipeline(&self.solid.pipeline); + layer.solid.draw( + &layer.constants, + render_pass, + solid_offset..(solid_offset + count), + ); + solid_offset += count; + } + quad::Order::Gradient => { + render_pass.set_pipeline(&self.gradient.pipeline); + layer.gradient.draw( + &layer.constants, + render_pass, + gradient_offset..(gradient_offset + count), + ); + gradient_offset += count; + } + } } } } @@ -198,6 +215,7 @@ mod solid { use crate::layer::quad; use crate::quad::{color_target_state, Vertex, INDICES, INITIAL_INSTANCES}; use crate::Buffer; + use std::ops::Range; #[derive(Debug)] pub struct Pipeline { @@ -229,6 +247,7 @@ mod solid { &'a self, constants: &'a wgpu::BindGroup, render_pass: &mut wgpu::RenderPass<'a>, + range: Range, ) { #[cfg(feature = "tracing")] let _ = tracing::info_span!("Wgpu::Quad::Solid", "DRAW").entered(); @@ -239,7 +258,7 @@ mod solid { render_pass.draw_indexed( 0..INDICES.len() as u32, 0, - 0..self.instance_count as u32, + range.start as u32..range.end as u32, ); } } @@ -327,6 +346,7 @@ mod gradient { use crate::layer::quad; use crate::quad::{color_target_state, Vertex, INDICES, INITIAL_INSTANCES}; use crate::Buffer; + use std::ops::Range; #[derive(Debug)] pub struct Pipeline { @@ -358,6 +378,7 @@ mod gradient { &'a self, constants: &'a wgpu::BindGroup, render_pass: &mut wgpu::RenderPass<'a>, + range: Range, ) { #[cfg(feature = "tracing")] let _ = @@ -369,7 +390,7 @@ mod gradient { render_pass.draw_indexed( 0..INDICES.len() as u32, 0, - 0..self.instance_count as u32, + range.start as u32..range.end as u32, ); } } -- cgit From eb6c663420a28e087c91c39e376db3c294b5aea1 Mon Sep 17 00:00:00 2001 From: Bingus Date: Fri, 26 May 2023 09:55:49 -0700 Subject: Adjusted `Quads` struct to be opaque `quad::Layer`. --- wgpu/src/backend.rs | 2 +- wgpu/src/layer.rs | 87 +++--------------------------------------------- wgpu/src/layer/quad.rs | 89 ++++++++++++++++++++++++++++++++++++++++++++++++++ wgpu/src/quad.rs | 37 +++++++++------------ 4 files changed, 109 insertions(+), 106 deletions(-) (limited to 'wgpu') diff --git a/wgpu/src/backend.rs b/wgpu/src/backend.rs index 764a033b..844987f2 100644 --- a/wgpu/src/backend.rs +++ b/wgpu/src/backend.rs @@ -268,7 +268,7 @@ impl Backend { self.quad_pipeline.render( quad_layer, bounds, - &layer.quads.order, + &layer.quads, &mut render_pass, ); diff --git a/wgpu/src/layer.rs b/wgpu/src/layer.rs index f5c4b576..4e028eac 100644 --- a/wgpu/src/layer.rs +++ b/wgpu/src/layer.rs @@ -12,8 +12,7 @@ pub use text::Text; use crate::core; use crate::core::alignment; -use crate::core::{Background, Color, Font, Point, Rectangle, Size, Vector}; -use crate::graphics::gradient; +use crate::core::{Color, Font, Point, Rectangle, Size, Vector}; use crate::graphics::{Primitive, Viewport}; /// A group of primitives that should be clipped together. @@ -23,7 +22,7 @@ pub struct Layer<'a> { pub bounds: Rectangle, /// The quads of the [`Layer`]. - pub quads: Quads, + pub quads: quad::Layer, /// The triangle meshes of the [`Layer`]. pub meshes: Vec>, @@ -35,35 +34,12 @@ pub struct Layer<'a> { pub images: Vec, } -/// The quads of the [`Layer`]. -#[derive(Default, Debug)] -pub struct Quads { - /// The solid quads of the [`Layer`]. - pub solids: Vec, - - /// The gradient quads of the [`Layer`]. - pub gradients: Vec, - - /// The quad order of the [`Layer`]; stored as a tuple of the quad type & its count. - pub order: Vec<(quad::Order, usize)>, - - // The last index of quad ordering. - index: usize, -} - -impl Quads { - /// Returns true if there are no quads of any type in [`Quads`]. - pub fn is_empty(&self) -> bool { - self.solids.is_empty() && self.gradients.is_empty() - } -} - impl<'a> Layer<'a> { /// Creates a new [`Layer`] with the given clipping bounds. pub fn new(bounds: Rectangle) -> Self { Self { bounds, - quads: Quads::default(), + quads: quad::Layer::default(), meshes: Vec::new(), text: Vec::new(), images: Vec::new(), @@ -180,62 +156,7 @@ impl<'a> Layer<'a> { border_width: *border_width, }; - let quad_order = match background { - Background::Color(color) => { - layer.quads.solids.push(quad::Solid { - color: color.into_linear(), - quad, - }); - quad::Order::Solid - } - Background::Gradient(gradient) => { - let quad = quad::Gradient { - gradient: gradient::pack( - gradient, - Rectangle::new( - quad.position.into(), - quad.size.into(), - ), - ), - quad, - }; - - layer.quads.gradients.push(quad); - quad::Order::Gradient - } - }; - - match (layer.quads.order.get_mut(layer.quads.index), quad_order) - { - (Some((quad_order, count)), quad::Order::Solid) => { - match quad_order { - quad::Order::Solid => { - *count += 1; - } - quad::Order::Gradient => { - layer.quads.order.push((quad::Order::Solid, 1)); - layer.quads.index += 1; - } - } - } - (Some((quad_order, count)), quad::Order::Gradient) => { - match quad_order { - quad::Order::Solid => { - layer - .quads - .order - .push((quad::Order::Gradient, 1)); - layer.quads.index += 1; - } - quad::Order::Gradient => { - *count += 1; - } - } - } - (None, _) => { - layer.quads.order.push((quad_order, 1)); - } - } + layer.quads.add(quad, background); } Primitive::Image { handle, bounds } => { let layer = &mut layers[current_layer]; diff --git a/wgpu/src/layer/quad.rs b/wgpu/src/layer/quad.rs index aaaebd5b..284a7618 100644 --- a/wgpu/src/layer/quad.rs +++ b/wgpu/src/layer/quad.rs @@ -1,4 +1,5 @@ //! A rectangle with certain styled properties. +use crate::core::{Background, Rectangle}; use crate::graphics::gradient; use bytemuck::{Pod, Zeroable}; @@ -58,3 +59,91 @@ pub enum Order { /// A gradient quad Gradient, } + +/// A group of [`Quad`]s rendered together. +#[derive(Default, Debug)] +pub struct Layer { + /// The solid quads of the [`Layer`]. + solids: Vec, + + /// The gradient quads of the [`Layer`]. + gradients: Vec, + + /// The quad order of the [`Layer`]; stored as a tuple of the quad type & its count. + order: Vec<(Order, usize)>, + + /// The last index of quad ordering. + index: usize, +} + +impl Layer { + /// Returns true if there are no quads of any type in [`Quads`]. + pub fn is_empty(&self) -> bool { + self.solids.is_empty() && self.gradients.is_empty() + } + + /// The [`Solid`] quads of the [`Layer`]. + pub fn solids(&self) -> &[Solid] { + &self.solids + } + + /// The [`Gradient`] quads of the [`Layer`]. + pub fn gradients(&self) -> &[Gradient] { + &self.gradients + } + + /// The order of quads within the [`Layer`], grouped by (type, count) for rendering in batches. + pub fn ordering(&self) -> &[(Order, usize)] { + &self.order + } + + /// Adds a [`Quad`] with the provided `Background` type to the quad [`Layer`]. + pub fn add(&mut self, quad: Quad, background: &Background) { + let quad_order = match background { + Background::Color(color) => { + self.solids.push(Solid { + color: color.into_linear(), + quad, + }); + + Order::Solid + } + Background::Gradient(gradient) => { + let quad = Gradient { + gradient: gradient::pack( + gradient, + Rectangle::new(quad.position.into(), quad.size.into()), + ), + quad, + }; + + self.gradients.push(quad); + Order::Gradient + } + }; + + match (self.order.get_mut(self.index), quad_order) { + (Some((quad_order, count)), Order::Solid) => match quad_order { + Order::Solid => { + *count += 1; + } + Order::Gradient => { + self.order.push((Order::Solid, 1)); + self.index += 1; + } + }, + (Some((quad_order, count)), Order::Gradient) => match quad_order { + Order::Solid => { + self.order.push((Order::Gradient, 1)); + self.index += 1; + } + Order::Gradient => { + *count += 1; + } + }, + (None, _) => { + self.order.push((quad_order, 1)); + } + } + } +} diff --git a/wgpu/src/quad.rs b/wgpu/src/quad.rs index a05e5468..065da153 100644 --- a/wgpu/src/quad.rs +++ b/wgpu/src/quad.rs @@ -1,6 +1,6 @@ use crate::core::Rectangle; use crate::graphics::Transformation; -use crate::layer::{self, quad}; +use crate::layer::quad; use std::mem; use wgpu::util::DeviceExt; @@ -69,7 +69,7 @@ impl Pipeline { &mut self, device: &wgpu::Device, queue: &wgpu::Queue, - instances: &layer::Quads, + quads: &quad::Layer, transformation: Transformation, scale: f32, ) { @@ -78,7 +78,7 @@ impl Pipeline { } let layer = &mut self.layers[self.prepare_layer]; - layer.prepare(device, queue, instances, transformation, scale); + layer.prepare(device, queue, quads, transformation, scale); self.prepare_layer += 1; } @@ -87,7 +87,7 @@ impl Pipeline { &'a self, layer: usize, bounds: Rectangle, - ordering: &Vec<(quad::Order, usize)>, + quads: &quad::Layer, render_pass: &mut wgpu::RenderPass<'a>, ) { if let Some(layer) = self.layers.get(layer) { @@ -106,7 +106,7 @@ impl Pipeline { let mut solid_offset = 0; let mut gradient_offset = 0; - for (quad_order, count) in ordering { + for (quad_order, count) in quads.ordering() { match quad_order { quad::Order::Solid => { render_pass.set_pipeline(&self.solid.pipeline); @@ -177,7 +177,7 @@ impl Layer { &mut self, device: &wgpu::Device, queue: &wgpu::Queue, - instances: &layer::Quads, + quads: &quad::Layer, transformation: Transformation, scale: f32, ) { @@ -192,22 +192,15 @@ impl Layer { bytemuck::bytes_of(&uniforms), ); - let _ = self.solid.instances.resize(device, instances.solids.len()); - let _ = self - .gradient - .instances - .resize(device, instances.gradients.len()); - let _ = - self.solid - .instances - .write(queue, 0, instances.solids.as_slice()); - self.solid.instance_count = instances.solids.len(); - let _ = self.gradient.instances.write( - queue, - 0, - instances.gradients.as_slice(), - ); - self.gradient.instance_count = instances.gradients.len(); + let solids = quads.solids(); + let gradients = quads.gradients(); + + let _ = self.solid.instances.resize(device, solids.len()); + let _ = self.gradient.instances.resize(device, gradients.len()); + let _ = self.solid.instances.write(queue, 0, solids); + self.solid.instance_count = solids.len(); + let _ = self.gradient.instances.write(queue, 0, gradients); + self.gradient.instance_count = gradients.len(); } } -- cgit From fe9da174cafffbd77eb351c51ba017cf039a4cf4 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Tue, 30 May 2023 00:56:52 +0200 Subject: Move `layer::quad` types to `quad` module Not sure why I split these to begin with! --- wgpu/src/layer.rs | 7 +- wgpu/src/layer/quad.rs | 149 ------------------ wgpu/src/quad.rs | 390 +++++++++++++--------------------------------- wgpu/src/quad/gradient.rs | 161 +++++++++++++++++++ wgpu/src/quad/solid.rs | 136 ++++++++++++++++ 5 files changed, 408 insertions(+), 435 deletions(-) delete mode 100644 wgpu/src/layer/quad.rs create mode 100644 wgpu/src/quad/gradient.rs create mode 100644 wgpu/src/quad/solid.rs (limited to 'wgpu') diff --git a/wgpu/src/layer.rs b/wgpu/src/layer.rs index 4e028eac..1a870c15 100644 --- a/wgpu/src/layer.rs +++ b/wgpu/src/layer.rs @@ -3,17 +3,16 @@ mod image; mod text; pub mod mesh; -pub mod quad; pub use image::Image; pub use mesh::Mesh; -pub use quad::Quad; pub use text::Text; use crate::core; use crate::core::alignment; use crate::core::{Color, Font, Point, Rectangle, Size, Vector}; use crate::graphics::{Primitive, Viewport}; +use crate::quad::{self, Quad}; /// A group of primitives that should be clipped together. #[derive(Debug)] @@ -22,7 +21,7 @@ pub struct Layer<'a> { pub bounds: Rectangle, /// The quads of the [`Layer`]. - pub quads: quad::Layer, + pub quads: quad::Batch, /// The triangle meshes of the [`Layer`]. pub meshes: Vec>, @@ -39,7 +38,7 @@ impl<'a> Layer<'a> { pub fn new(bounds: Rectangle) -> Self { Self { bounds, - quads: quad::Layer::default(), + quads: quad::Batch::default(), meshes: Vec::new(), text: Vec::new(), images: Vec::new(), diff --git a/wgpu/src/layer/quad.rs b/wgpu/src/layer/quad.rs deleted file mode 100644 index 284a7618..00000000 --- a/wgpu/src/layer/quad.rs +++ /dev/null @@ -1,149 +0,0 @@ -//! A rectangle with certain styled properties. -use crate::core::{Background, Rectangle}; -use crate::graphics::gradient; -use bytemuck::{Pod, Zeroable}; - -/// The properties of a quad. -#[derive(Clone, Copy, Debug, Pod, Zeroable)] -#[repr(C)] -pub struct Quad { - /// The position of the [`Quad`]. - pub position: [f32; 2], - - /// The size of the [`Quad`]. - pub size: [f32; 2], - - /// The border color of the [`Quad`], in __linear RGB__. - pub border_color: [f32; 4], - - /// The border radii of the [`Quad`]. - pub border_radius: [f32; 4], - - /// The border width of the [`Quad`]. - pub border_width: f32, -} - -/// A quad filled with a solid color. -#[derive(Clone, Copy, Debug, Pod, Zeroable)] -#[repr(C)] -pub struct Solid { - /// The background color data of the quad. - pub color: [f32; 4], - - /// The [`Quad`] data of the [`Solid`]. - pub quad: Quad, -} - -/// A quad filled with interpolated colors. -#[derive(Clone, Copy, Debug)] -#[repr(C)] -pub struct Gradient { - /// The background gradient data of the quad. - pub gradient: gradient::Packed, - - /// The [`Quad`] data of the [`Gradient`]. - pub quad: Quad, -} - -#[allow(unsafe_code)] -unsafe impl Pod for Gradient {} - -#[allow(unsafe_code)] -unsafe impl Zeroable for Gradient {} - -#[derive(Debug, Copy, Clone)] -/// The identifier of a quad, used for ordering. -pub enum Order { - /// A solid quad - Solid, - /// A gradient quad - Gradient, -} - -/// A group of [`Quad`]s rendered together. -#[derive(Default, Debug)] -pub struct Layer { - /// The solid quads of the [`Layer`]. - solids: Vec, - - /// The gradient quads of the [`Layer`]. - gradients: Vec, - - /// The quad order of the [`Layer`]; stored as a tuple of the quad type & its count. - order: Vec<(Order, usize)>, - - /// The last index of quad ordering. - index: usize, -} - -impl Layer { - /// Returns true if there are no quads of any type in [`Quads`]. - pub fn is_empty(&self) -> bool { - self.solids.is_empty() && self.gradients.is_empty() - } - - /// The [`Solid`] quads of the [`Layer`]. - pub fn solids(&self) -> &[Solid] { - &self.solids - } - - /// The [`Gradient`] quads of the [`Layer`]. - pub fn gradients(&self) -> &[Gradient] { - &self.gradients - } - - /// The order of quads within the [`Layer`], grouped by (type, count) for rendering in batches. - pub fn ordering(&self) -> &[(Order, usize)] { - &self.order - } - - /// Adds a [`Quad`] with the provided `Background` type to the quad [`Layer`]. - pub fn add(&mut self, quad: Quad, background: &Background) { - let quad_order = match background { - Background::Color(color) => { - self.solids.push(Solid { - color: color.into_linear(), - quad, - }); - - Order::Solid - } - Background::Gradient(gradient) => { - let quad = Gradient { - gradient: gradient::pack( - gradient, - Rectangle::new(quad.position.into(), quad.size.into()), - ), - quad, - }; - - self.gradients.push(quad); - Order::Gradient - } - }; - - match (self.order.get_mut(self.index), quad_order) { - (Some((quad_order, count)), Order::Solid) => match quad_order { - Order::Solid => { - *count += 1; - } - Order::Gradient => { - self.order.push((Order::Solid, 1)); - self.index += 1; - } - }, - (Some((quad_order, count)), Order::Gradient) => match quad_order { - Order::Solid => { - self.order.push((Order::Gradient, 1)); - self.index += 1; - } - Order::Gradient => { - *count += 1; - } - }, - (None, _) => { - self.order.push((quad_order, 1)); - } - } - } -} diff --git a/wgpu/src/quad.rs b/wgpu/src/quad.rs index 065da153..375b0cd7 100644 --- a/wgpu/src/quad.rs +++ b/wgpu/src/quad.rs @@ -1,10 +1,17 @@ -use crate::core::Rectangle; -use crate::graphics::Transformation; -use crate::layer::quad; +mod gradient; +mod solid; -use std::mem; +use gradient::Gradient; +use solid::Solid; + +use crate::core::{Background, Rectangle}; +use crate::graphics::{self, Transformation}; + +use bytemuck::{Pod, Zeroable}; use wgpu::util::DeviceExt; +use std::mem; + #[cfg(feature = "tracing")] use tracing::info_span; @@ -69,7 +76,7 @@ impl Pipeline { &mut self, device: &wgpu::Device, queue: &wgpu::Queue, - quads: &quad::Layer, + quads: &Batch, transformation: Transformation, scale: f32, ) { @@ -87,7 +94,7 @@ impl Pipeline { &'a self, layer: usize, bounds: Rectangle, - quads: &quad::Layer, + quads: &Batch, render_pass: &mut wgpu::RenderPass<'a>, ) { if let Some(layer) = self.layers.get(layer) { @@ -106,9 +113,9 @@ impl Pipeline { let mut solid_offset = 0; let mut gradient_offset = 0; - for (quad_order, count) in quads.ordering() { + for (quad_order, count) in &quads.order { match quad_order { - quad::Order::Solid => { + Order::Solid => { render_pass.set_pipeline(&self.solid.pipeline); layer.solid.draw( &layer.constants, @@ -117,7 +124,7 @@ impl Pipeline { ); solid_offset += count; } - quad::Order::Gradient => { + Order::Gradient => { render_pass.set_pipeline(&self.gradient.pipeline); layer.gradient.draw( &layer.constants, @@ -177,7 +184,7 @@ impl Layer { &mut self, device: &wgpu::Device, queue: &wgpu::Queue, - quads: &quad::Layer, + quads: &Batch, transformation: Transformation, scale: f32, ) { @@ -192,303 +199,122 @@ impl Layer { bytemuck::bytes_of(&uniforms), ); - let solids = quads.solids(); - let gradients = quads.gradients(); + let _ = self.solid.instances.resize(device, quads.solids.len()); + let _ = self + .gradient + .instances + .resize(device, quads.gradients.len()); + + let _ = self.solid.instances.write(queue, 0, &quads.solids); + let _ = self.gradient.instances.write(queue, 0, &quads.gradients); - let _ = self.solid.instances.resize(device, solids.len()); - let _ = self.gradient.instances.resize(device, gradients.len()); - let _ = self.solid.instances.write(queue, 0, solids); - self.solid.instance_count = solids.len(); - let _ = self.gradient.instances.write(queue, 0, gradients); - self.gradient.instance_count = gradients.len(); + self.solid.instance_count = quads.solids.len(); + self.gradient.instance_count = quads.gradients.len(); } } -mod solid { - use crate::layer::quad; - use crate::quad::{color_target_state, Vertex, INDICES, INITIAL_INSTANCES}; - use crate::Buffer; - use std::ops::Range; - - #[derive(Debug)] - pub struct Pipeline { - pub pipeline: wgpu::RenderPipeline, - } +/// The properties of a quad. +#[derive(Clone, Copy, Debug, Pod, Zeroable)] +#[repr(C)] +pub struct Quad { + /// The position of the [`Quad`]. + pub position: [f32; 2], - #[derive(Debug)] - pub struct Layer { - pub instances: Buffer, - pub instance_count: usize, - } + /// The size of the [`Quad`]. + pub size: [f32; 2], - impl Layer { - pub fn new(device: &wgpu::Device) -> Self { - let instances = Buffer::new( - device, - "iced_wgpu.quad.solid.buffer", - INITIAL_INSTANCES, - wgpu::BufferUsages::VERTEX | wgpu::BufferUsages::COPY_DST, - ); + /// The border color of the [`Quad`], in __linear RGB__. + pub border_color: [f32; 4], - Self { - instances, - instance_count: 0, - } - } + /// The border radii of the [`Quad`]. + pub border_radius: [f32; 4], - pub fn draw<'a>( - &'a self, - constants: &'a wgpu::BindGroup, - render_pass: &mut wgpu::RenderPass<'a>, - range: Range, - ) { - #[cfg(feature = "tracing")] - let _ = tracing::info_span!("Wgpu::Quad::Solid", "DRAW").entered(); - - render_pass.set_bind_group(0, constants, &[]); - render_pass.set_vertex_buffer(1, self.instances.slice(..)); - - render_pass.draw_indexed( - 0..INDICES.len() as u32, - 0, - range.start as u32..range.end as u32, - ); - } - } + /// The border width of the [`Quad`]. + pub border_width: f32, +} - impl Pipeline { - pub fn new( - device: &wgpu::Device, - format: wgpu::TextureFormat, - constants_layout: &wgpu::BindGroupLayout, - ) -> Self { - let layout = device.create_pipeline_layout( - &wgpu::PipelineLayoutDescriptor { - label: Some("iced_wgpu.quad.solid.pipeline"), - push_constant_ranges: &[], - bind_group_layouts: &[constants_layout], - }, - ); +/// A group of [`Quad`]s rendered together. +#[derive(Default, Debug)] +pub struct Batch { + /// The solid quads of the [`Layer`]. + solids: Vec, - let shader = - device.create_shader_module(wgpu::ShaderModuleDescriptor { - label: Some("iced_wgpu.quad.solid.shader"), - source: wgpu::ShaderSource::Wgsl( - std::borrow::Cow::Borrowed(include_str!( - "shader/quad.wgsl" - )), - ), - }); + /// The gradient quads of the [`Layer`]. + gradients: Vec, - let pipeline = device.create_render_pipeline( - &wgpu::RenderPipelineDescriptor { - label: Some("iced_wgpu.quad.solid.pipeline"), - layout: Some(&layout), - vertex: wgpu::VertexState { - module: &shader, - entry_point: "solid_vs_main", - buffers: &[ - Vertex::buffer_layout(), - wgpu::VertexBufferLayout { - array_stride: std::mem::size_of::() - as u64, - step_mode: wgpu::VertexStepMode::Instance, - attributes: &wgpu::vertex_attr_array!( - // Color - 1 => Float32x4, - // Position - 2 => Float32x2, - // Size - 3 => Float32x2, - // Border color - 4 => Float32x4, - // Border radius - 5 => Float32x4, - // Border width - 6 => Float32, - ), - }, - ], - }, - fragment: Some(wgpu::FragmentState { - module: &shader, - entry_point: "solid_fs_main", - targets: &color_target_state(format), - }), - primitive: wgpu::PrimitiveState { - topology: wgpu::PrimitiveTopology::TriangleList, - front_face: wgpu::FrontFace::Cw, - ..Default::default() - }, - depth_stencil: None, - multisample: wgpu::MultisampleState { - count: 1, - mask: !0, - alpha_to_coverage_enabled: false, - }, - multiview: None, - }, - ); + /// The quad order of the [`Layer`]; stored as a tuple of the quad type & its count. + order: Vec<(Order, usize)>, - Self { pipeline } - } - } + /// The last index of quad ordering. + index: usize, } -mod gradient { - use crate::layer::quad; - use crate::quad::{color_target_state, Vertex, INDICES, INITIAL_INSTANCES}; - use crate::Buffer; - use std::ops::Range; - - #[derive(Debug)] - pub struct Pipeline { - pub pipeline: wgpu::RenderPipeline, - } - - #[derive(Debug)] - pub struct Layer { - pub instances: Buffer, - pub instance_count: usize, +impl Batch { + /// Returns true if there are no quads of any type in [`Quads`]. + pub fn is_empty(&self) -> bool { + self.solids.is_empty() && self.gradients.is_empty() } - impl Layer { - pub fn new(device: &wgpu::Device) -> Self { - let instances = Buffer::new( - device, - "iced_wgpu.quad.gradient.buffer", - INITIAL_INSTANCES, - wgpu::BufferUsages::VERTEX | wgpu::BufferUsages::COPY_DST, - ); + /// Adds a [`Quad`] with the provided `Background` type to the quad [`Layer`]. + pub fn add(&mut self, quad: Quad, background: &Background) { + let quad_order = match background { + Background::Color(color) => { + self.solids.push(Solid { + color: color.into_linear(), + quad, + }); - Self { - instances, - instance_count: 0, + Order::Solid } - } - - pub fn draw<'a>( - &'a self, - constants: &'a wgpu::BindGroup, - render_pass: &mut wgpu::RenderPass<'a>, - range: Range, - ) { - #[cfg(feature = "tracing")] - let _ = - tracing::info_span!("Wgpu::Quad::Gradient", "DRAW").entered(); - - render_pass.set_bind_group(0, constants, &[]); - render_pass.set_vertex_buffer(1, self.instances.slice(..)); - - render_pass.draw_indexed( - 0..INDICES.len() as u32, - 0, - range.start as u32..range.end as u32, - ); - } - } - - impl Pipeline { - pub fn new( - device: &wgpu::Device, - format: wgpu::TextureFormat, - constants_layout: &wgpu::BindGroupLayout, - ) -> Self { - let layout = device.create_pipeline_layout( - &wgpu::PipelineLayoutDescriptor { - label: Some("iced_wgpu.quad.gradient.pipeline"), - push_constant_ranges: &[], - bind_group_layouts: &[constants_layout], - }, - ); - - let shader = - device.create_shader_module(wgpu::ShaderModuleDescriptor { - label: Some("iced_wgpu.quad.gradient.shader"), - source: wgpu::ShaderSource::Wgsl( - std::borrow::Cow::Borrowed(include_str!( - "shader/quad.wgsl" - )), + Background::Gradient(gradient) => { + let quad = Gradient { + gradient: graphics::gradient::pack( + gradient, + Rectangle::new(quad.position.into(), quad.size.into()), ), - }); + quad, + }; - let pipeline = - device.create_render_pipeline( - &wgpu::RenderPipelineDescriptor { - label: Some("iced_wgpu.quad.gradient.pipeline"), - layout: Some(&layout), - vertex: wgpu::VertexState { - module: &shader, - entry_point: "gradient_vs_main", - buffers: &[ - Vertex::buffer_layout(), - wgpu::VertexBufferLayout { - array_stride: std::mem::size_of::< - quad::Gradient, - >( - ) - as u64, - step_mode: wgpu::VertexStepMode::Instance, - attributes: &wgpu::vertex_attr_array!( - // Color 1 - 1 => Float32x4, - // Color 2 - 2 => Float32x4, - // Color 3 - 3 => Float32x4, - // Color 4 - 4 => Float32x4, - // Color 5 - 5 => Float32x4, - // Color 6 - 6 => Float32x4, - // Color 7 - 7 => Float32x4, - // Color 8 - 8 => Float32x4, - // Offsets 1-4 - 9 => Float32x4, - // Offsets 5-8 - 10 => Float32x4, - // Direction - 11 => Float32x4, - // Position & Scale - 12 => Float32x4, - // Border color - 13 => Float32x4, - // Border radius - 14 => Float32x4, - // Border width - 15 => Float32 - ), - }, - ], - }, - fragment: Some(wgpu::FragmentState { - module: &shader, - entry_point: "gradient_fs_main", - targets: &color_target_state(format), - }), - primitive: wgpu::PrimitiveState { - topology: wgpu::PrimitiveTopology::TriangleList, - front_face: wgpu::FrontFace::Cw, - ..Default::default() - }, - depth_stencil: None, - multisample: wgpu::MultisampleState { - count: 1, - mask: !0, - alpha_to_coverage_enabled: false, - }, - multiview: None, - }, - ); + self.gradients.push(quad); + Order::Gradient + } + }; - Self { pipeline } + match (self.order.get_mut(self.index), quad_order) { + (Some((quad_order, count)), Order::Solid) => match quad_order { + Order::Solid => { + *count += 1; + } + Order::Gradient => { + self.order.push((Order::Solid, 1)); + self.index += 1; + } + }, + (Some((quad_order, count)), Order::Gradient) => match quad_order { + Order::Solid => { + self.order.push((Order::Gradient, 1)); + self.index += 1; + } + Order::Gradient => { + *count += 1; + } + }, + (None, _) => { + self.order.push((quad_order, 1)); + } } } } +#[derive(Debug, Copy, Clone)] +/// The identifier of a quad, used for ordering. +enum Order { + /// A solid quad + Solid, + /// A gradient quad + Gradient, +} + fn color_target_state( format: wgpu::TextureFormat, ) -> [Option; 1] { diff --git a/wgpu/src/quad/gradient.rs b/wgpu/src/quad/gradient.rs new file mode 100644 index 00000000..2729afdf --- /dev/null +++ b/wgpu/src/quad/gradient.rs @@ -0,0 +1,161 @@ +use crate::graphics::gradient; +use crate::quad::{self, Quad}; +use crate::Buffer; + +use bytemuck::{Pod, Zeroable}; +use std::ops::Range; + +/// A quad filled with interpolated colors. +#[derive(Clone, Copy, Debug)] +#[repr(C)] +pub struct Gradient { + /// The background gradient data of the quad. + pub gradient: gradient::Packed, + + /// The [`Quad`] data of the [`Gradient`]. + pub quad: Quad, +} + +#[allow(unsafe_code)] +unsafe impl Pod for Gradient {} + +#[allow(unsafe_code)] +unsafe impl Zeroable for Gradient {} + +#[derive(Debug)] +pub struct Pipeline { + pub pipeline: wgpu::RenderPipeline, +} + +#[derive(Debug)] +pub struct Layer { + pub instances: Buffer, + pub instance_count: usize, +} + +impl Layer { + pub fn new(device: &wgpu::Device) -> Self { + let instances = Buffer::new( + device, + "iced_wgpu.quad.gradient.buffer", + quad::INITIAL_INSTANCES, + wgpu::BufferUsages::VERTEX | wgpu::BufferUsages::COPY_DST, + ); + + Self { + instances, + instance_count: 0, + } + } + + pub fn draw<'a>( + &'a self, + constants: &'a wgpu::BindGroup, + render_pass: &mut wgpu::RenderPass<'a>, + range: Range, + ) { + #[cfg(feature = "tracing")] + let _ = tracing::info_span!("Wgpu::Quad::Gradient", "DRAW").entered(); + + render_pass.set_bind_group(0, constants, &[]); + render_pass.set_vertex_buffer(1, self.instances.slice(..)); + + render_pass.draw_indexed( + 0..quad::INDICES.len() as u32, + 0, + range.start as u32..range.end as u32, + ); + } +} + +impl Pipeline { + pub fn new( + device: &wgpu::Device, + format: wgpu::TextureFormat, + constants_layout: &wgpu::BindGroupLayout, + ) -> Self { + let layout = + device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor { + label: Some("iced_wgpu.quad.gradient.pipeline"), + push_constant_ranges: &[], + bind_group_layouts: &[constants_layout], + }); + + let shader = + device.create_shader_module(wgpu::ShaderModuleDescriptor { + label: Some("iced_wgpu.quad.gradient.shader"), + source: wgpu::ShaderSource::Wgsl(std::borrow::Cow::Borrowed( + include_str!("../shader/quad.wgsl"), + )), + }); + + let pipeline = + device.create_render_pipeline(&wgpu::RenderPipelineDescriptor { + label: Some("iced_wgpu.quad.gradient.pipeline"), + layout: Some(&layout), + vertex: wgpu::VertexState { + module: &shader, + entry_point: "gradient_vs_main", + buffers: &[ + quad::Vertex::buffer_layout(), + wgpu::VertexBufferLayout { + array_stride: std::mem::size_of::() + as u64, + step_mode: wgpu::VertexStepMode::Instance, + attributes: &wgpu::vertex_attr_array!( + // Color 1 + 1 => Float32x4, + // Color 2 + 2 => Float32x4, + // Color 3 + 3 => Float32x4, + // Color 4 + 4 => Float32x4, + // Color 5 + 5 => Float32x4, + // Color 6 + 6 => Float32x4, + // Color 7 + 7 => Float32x4, + // Color 8 + 8 => Float32x4, + // Offsets 1-4 + 9 => Float32x4, + // Offsets 5-8 + 10 => Float32x4, + // Direction + 11 => Float32x4, + // Position & Scale + 12 => Float32x4, + // Border color + 13 => Float32x4, + // Border radius + 14 => Float32x4, + // Border width + 15 => Float32 + ), + }, + ], + }, + fragment: Some(wgpu::FragmentState { + module: &shader, + entry_point: "gradient_fs_main", + targets: &quad::color_target_state(format), + }), + primitive: wgpu::PrimitiveState { + topology: wgpu::PrimitiveTopology::TriangleList, + front_face: wgpu::FrontFace::Cw, + ..Default::default() + }, + depth_stencil: None, + multisample: wgpu::MultisampleState { + count: 1, + mask: !0, + alpha_to_coverage_enabled: false, + }, + multiview: None, + }); + + Self { pipeline } + } +} diff --git a/wgpu/src/quad/solid.rs b/wgpu/src/quad/solid.rs new file mode 100644 index 00000000..3540ce3a --- /dev/null +++ b/wgpu/src/quad/solid.rs @@ -0,0 +1,136 @@ +use crate::quad::{self, Quad}; +use crate::Buffer; + +use bytemuck::{Pod, Zeroable}; +use std::ops::Range; + +/// A quad filled with a solid color. +#[derive(Clone, Copy, Debug, Pod, Zeroable)] +#[repr(C)] +pub struct Solid { + /// The background color data of the quad. + pub color: [f32; 4], + + /// The [`Quad`] data of the [`Solid`]. + pub quad: Quad, +} + +#[derive(Debug)] +pub struct Pipeline { + pub pipeline: wgpu::RenderPipeline, +} + +#[derive(Debug)] +pub struct Layer { + pub instances: Buffer, + pub instance_count: usize, +} + +impl Layer { + pub fn new(device: &wgpu::Device) -> Self { + let instances = Buffer::new( + device, + "iced_wgpu.quad.solid.buffer", + quad::INITIAL_INSTANCES, + wgpu::BufferUsages::VERTEX | wgpu::BufferUsages::COPY_DST, + ); + + Self { + instances, + instance_count: 0, + } + } + + pub fn draw<'a>( + &'a self, + constants: &'a wgpu::BindGroup, + render_pass: &mut wgpu::RenderPass<'a>, + range: Range, + ) { + #[cfg(feature = "tracing")] + let _ = tracing::info_span!("Wgpu::Quad::Solid", "DRAW").entered(); + + render_pass.set_bind_group(0, constants, &[]); + render_pass.set_vertex_buffer(1, self.instances.slice(..)); + + render_pass.draw_indexed( + 0..quad::INDICES.len() as u32, + 0, + range.start as u32..range.end as u32, + ); + } +} + +impl Pipeline { + pub fn new( + device: &wgpu::Device, + format: wgpu::TextureFormat, + constants_layout: &wgpu::BindGroupLayout, + ) -> Self { + let layout = + device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor { + label: Some("iced_wgpu.quad.solid.pipeline"), + push_constant_ranges: &[], + bind_group_layouts: &[constants_layout], + }); + + let shader = + device.create_shader_module(wgpu::ShaderModuleDescriptor { + label: Some("iced_wgpu.quad.solid.shader"), + source: wgpu::ShaderSource::Wgsl(std::borrow::Cow::Borrowed( + include_str!("../shader/quad.wgsl"), + )), + }); + + let pipeline = + device.create_render_pipeline(&wgpu::RenderPipelineDescriptor { + label: Some("iced_wgpu.quad.solid.pipeline"), + layout: Some(&layout), + vertex: wgpu::VertexState { + module: &shader, + entry_point: "solid_vs_main", + buffers: &[ + quad::Vertex::buffer_layout(), + wgpu::VertexBufferLayout { + array_stride: std::mem::size_of::() + as u64, + step_mode: wgpu::VertexStepMode::Instance, + attributes: &wgpu::vertex_attr_array!( + // Color + 1 => Float32x4, + // Position + 2 => Float32x2, + // Size + 3 => Float32x2, + // Border color + 4 => Float32x4, + // Border radius + 5 => Float32x4, + // Border width + 6 => Float32, + ), + }, + ], + }, + fragment: Some(wgpu::FragmentState { + module: &shader, + entry_point: "solid_fs_main", + targets: &quad::color_target_state(format), + }), + primitive: wgpu::PrimitiveState { + topology: wgpu::PrimitiveTopology::TriangleList, + front_face: wgpu::FrontFace::Cw, + ..Default::default() + }, + depth_stencil: None, + multisample: wgpu::MultisampleState { + count: 1, + mask: !0, + alpha_to_coverage_enabled: false, + }, + multiview: None, + }); + + Self { pipeline } + } +} -- cgit From 6d650e7f9987aa72913d20c7e538448b518a6150 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Tue, 30 May 2023 00:59:42 +0200 Subject: Rename `quad::Order` to `quad::Kind` --- wgpu/src/quad.rs | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) (limited to 'wgpu') diff --git a/wgpu/src/quad.rs b/wgpu/src/quad.rs index 375b0cd7..48ef565e 100644 --- a/wgpu/src/quad.rs +++ b/wgpu/src/quad.rs @@ -115,7 +115,7 @@ impl Pipeline { for (quad_order, count) in &quads.order { match quad_order { - Order::Solid => { + Kind::Solid => { render_pass.set_pipeline(&self.solid.pipeline); layer.solid.draw( &layer.constants, @@ -124,7 +124,7 @@ impl Pipeline { ); solid_offset += count; } - Order::Gradient => { + Kind::Gradient => { render_pass.set_pipeline(&self.gradient.pipeline); layer.gradient.draw( &layer.constants, @@ -243,7 +243,7 @@ pub struct Batch { gradients: Vec, /// The quad order of the [`Layer`]; stored as a tuple of the quad type & its count. - order: Vec<(Order, usize)>, + order: Vec<(Kind, usize)>, /// The last index of quad ordering. index: usize, @@ -264,7 +264,7 @@ impl Batch { quad, }); - Order::Solid + Kind::Solid } Background::Gradient(gradient) => { let quad = Gradient { @@ -276,26 +276,26 @@ impl Batch { }; self.gradients.push(quad); - Order::Gradient + Kind::Gradient } }; match (self.order.get_mut(self.index), quad_order) { - (Some((quad_order, count)), Order::Solid) => match quad_order { - Order::Solid => { + (Some((quad_order, count)), Kind::Solid) => match quad_order { + Kind::Solid => { *count += 1; } - Order::Gradient => { - self.order.push((Order::Solid, 1)); + Kind::Gradient => { + self.order.push((Kind::Solid, 1)); self.index += 1; } }, - (Some((quad_order, count)), Order::Gradient) => match quad_order { - Order::Solid => { - self.order.push((Order::Gradient, 1)); + (Some((quad_order, count)), Kind::Gradient) => match quad_order { + Kind::Solid => { + self.order.push((Kind::Gradient, 1)); self.index += 1; } - Order::Gradient => { + Kind::Gradient => { *count += 1; } }, @@ -307,8 +307,8 @@ impl Batch { } #[derive(Debug, Copy, Clone)] -/// The identifier of a quad, used for ordering. -enum Order { +/// The kind of a quad. +enum Kind { /// A solid quad Solid, /// A gradient quad -- cgit From cd7d33aa8ebc6fbb6666f34aeda3ee96d0fb51e3 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Tue, 30 May 2023 01:14:41 +0200 Subject: Simplify `order` match statement in `quad::Batch::add` --- wgpu/src/quad.rs | 35 +++++++++-------------------------- 1 file changed, 9 insertions(+), 26 deletions(-) (limited to 'wgpu') diff --git a/wgpu/src/quad.rs b/wgpu/src/quad.rs index 48ef565e..e25d02af 100644 --- a/wgpu/src/quad.rs +++ b/wgpu/src/quad.rs @@ -244,9 +244,6 @@ pub struct Batch { /// The quad order of the [`Layer`]; stored as a tuple of the quad type & its count. order: Vec<(Kind, usize)>, - - /// The last index of quad ordering. - index: usize, } impl Batch { @@ -257,7 +254,7 @@ impl Batch { /// Adds a [`Quad`] with the provided `Background` type to the quad [`Layer`]. pub fn add(&mut self, quad: Quad, background: &Background) { - let quad_order = match background { + let kind = match background { Background::Color(color) => { self.solids.push(Solid { color: color.into_linear(), @@ -276,37 +273,23 @@ impl Batch { }; self.gradients.push(quad); + Kind::Gradient } }; - match (self.order.get_mut(self.index), quad_order) { - (Some((quad_order, count)), Kind::Solid) => match quad_order { - Kind::Solid => { - *count += 1; - } - Kind::Gradient => { - self.order.push((Kind::Solid, 1)); - self.index += 1; - } - }, - (Some((quad_order, count)), Kind::Gradient) => match quad_order { - Kind::Solid => { - self.order.push((Kind::Gradient, 1)); - self.index += 1; - } - Kind::Gradient => { - *count += 1; - } - }, - (None, _) => { - self.order.push((quad_order, 1)); + match self.order.last_mut() { + Some((last_kind, count)) if kind == *last_kind => { + *count += 1; + } + _ => { + self.order.push((kind, 1)); } } } } -#[derive(Debug, Copy, Clone)] +#[derive(Debug, Copy, Clone, PartialEq, Eq)] /// The kind of a quad. enum Kind { /// A solid quad -- cgit From 9659e6a848d76df39c1988e33d248d1c1560271f Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Tue, 30 May 2023 01:16:30 +0200 Subject: Improve consistency of match branches in `quad::Batch::add` --- wgpu/src/quad.rs | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) (limited to 'wgpu') diff --git a/wgpu/src/quad.rs b/wgpu/src/quad.rs index e25d02af..62472126 100644 --- a/wgpu/src/quad.rs +++ b/wgpu/src/quad.rs @@ -264,15 +264,13 @@ impl Batch { Kind::Solid } Background::Gradient(gradient) => { - let quad = Gradient { + self.gradients.push(Gradient { gradient: graphics::gradient::pack( gradient, Rectangle::new(quad.position.into(), quad.size.into()), ), quad, - }; - - self.gradients.push(quad); + }); Kind::Gradient } -- cgit From 5fdc5affceca9345661ea2d213ccbce99721d09b Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Tue, 30 May 2023 01:17:03 +0200 Subject: Rename `quad_order` variable to `kind` in `quad::Pipeline::render` --- wgpu/src/quad.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'wgpu') diff --git a/wgpu/src/quad.rs b/wgpu/src/quad.rs index 62472126..deb6d59a 100644 --- a/wgpu/src/quad.rs +++ b/wgpu/src/quad.rs @@ -113,8 +113,8 @@ impl Pipeline { let mut solid_offset = 0; let mut gradient_offset = 0; - for (quad_order, count) in &quads.order { - match quad_order { + for (kind, count) in &quads.order { + match kind { Kind::Solid => { render_pass.set_pipeline(&self.solid.pipeline); layer.solid.draw( -- cgit From ef547469fdc8fe4faaa9902d9a8c920684f07189 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Tue, 30 May 2023 01:33:50 +0200 Subject: Improve boundaries between `quad` submodules in `iced_wgpu` --- wgpu/src/quad.rs | 27 +++++++++--------------- wgpu/src/quad/gradient.rs | 50 ++++++++++++++++++++++++++++---------------- wgpu/src/quad/solid.rs | 53 +++++++++++++++++++++++++++++------------------ 3 files changed, 75 insertions(+), 55 deletions(-) (limited to 'wgpu') diff --git a/wgpu/src/quad.rs b/wgpu/src/quad.rs index deb6d59a..9c5ed05f 100644 --- a/wgpu/src/quad.rs +++ b/wgpu/src/quad.rs @@ -116,21 +116,23 @@ impl Pipeline { for (kind, count) in &quads.order { match kind { Kind::Solid => { - render_pass.set_pipeline(&self.solid.pipeline); - layer.solid.draw( - &layer.constants, + self.solid.render( render_pass, + &layer.constants, + &layer.solid, solid_offset..(solid_offset + count), ); + solid_offset += count; } Kind::Gradient => { - render_pass.set_pipeline(&self.gradient.pipeline); - layer.gradient.draw( - &layer.constants, + self.gradient.render( render_pass, + &layer.constants, + &layer.gradient, gradient_offset..(gradient_offset + count), ); + gradient_offset += count; } } @@ -199,17 +201,8 @@ impl Layer { bytemuck::bytes_of(&uniforms), ); - let _ = self.solid.instances.resize(device, quads.solids.len()); - let _ = self - .gradient - .instances - .resize(device, quads.gradients.len()); - - let _ = self.solid.instances.write(queue, 0, &quads.solids); - let _ = self.gradient.instances.write(queue, 0, &quads.gradients); - - self.solid.instance_count = quads.solids.len(); - self.gradient.instance_count = quads.gradients.len(); + self.solid.prepare(device, queue, &quads.solids); + self.gradient.prepare(device, queue, &quads.gradients); } } diff --git a/wgpu/src/quad/gradient.rs b/wgpu/src/quad/gradient.rs index 2729afdf..d3884ce1 100644 --- a/wgpu/src/quad/gradient.rs +++ b/wgpu/src/quad/gradient.rs @@ -24,13 +24,13 @@ unsafe impl Zeroable for Gradient {} #[derive(Debug)] pub struct Pipeline { - pub pipeline: wgpu::RenderPipeline, + pipeline: wgpu::RenderPipeline, } #[derive(Debug)] pub struct Layer { - pub instances: Buffer, - pub instance_count: usize, + instances: Buffer, + instance_count: usize, } impl Layer { @@ -48,23 +48,16 @@ impl Layer { } } - pub fn draw<'a>( - &'a self, - constants: &'a wgpu::BindGroup, - render_pass: &mut wgpu::RenderPass<'a>, - range: Range, + pub fn prepare( + &mut self, + device: &wgpu::Device, + queue: &wgpu::Queue, + instances: &[Gradient], ) { - #[cfg(feature = "tracing")] - let _ = tracing::info_span!("Wgpu::Quad::Gradient", "DRAW").entered(); - - render_pass.set_bind_group(0, constants, &[]); - render_pass.set_vertex_buffer(1, self.instances.slice(..)); + let _ = self.instances.resize(device, instances.len()); + let _ = self.instances.write(queue, 0, instances); - render_pass.draw_indexed( - 0..quad::INDICES.len() as u32, - 0, - range.start as u32..range.end as u32, - ); + self.instance_count = instances.len(); } } @@ -158,4 +151,25 @@ impl Pipeline { Self { pipeline } } + + pub fn render<'a>( + &'a self, + render_pass: &mut wgpu::RenderPass<'a>, + constants: &'a wgpu::BindGroup, + layer: &'a Layer, + range: Range, + ) { + #[cfg(feature = "tracing")] + let _ = tracing::info_span!("Wgpu::Quad::Gradient", "DRAW").entered(); + + render_pass.set_pipeline(&self.pipeline); + render_pass.set_bind_group(0, constants, &[]); + render_pass.set_vertex_buffer(1, layer.instances.slice(..)); + + render_pass.draw_indexed( + 0..quad::INDICES.len() as u32, + 0, + range.start as u32..range.end as u32, + ); + } } diff --git a/wgpu/src/quad/solid.rs b/wgpu/src/quad/solid.rs index 3540ce3a..a3a7a0af 100644 --- a/wgpu/src/quad/solid.rs +++ b/wgpu/src/quad/solid.rs @@ -17,13 +17,13 @@ pub struct Solid { #[derive(Debug)] pub struct Pipeline { - pub pipeline: wgpu::RenderPipeline, + pipeline: wgpu::RenderPipeline, } #[derive(Debug)] pub struct Layer { - pub instances: Buffer, - pub instance_count: usize, + instances: Buffer, + instance_count: usize, } impl Layer { @@ -41,23 +41,16 @@ impl Layer { } } - pub fn draw<'a>( - &'a self, - constants: &'a wgpu::BindGroup, - render_pass: &mut wgpu::RenderPass<'a>, - range: Range, + pub fn prepare( + &mut self, + device: &wgpu::Device, + queue: &wgpu::Queue, + instances: &[Solid], ) { - #[cfg(feature = "tracing")] - let _ = tracing::info_span!("Wgpu::Quad::Solid", "DRAW").entered(); - - render_pass.set_bind_group(0, constants, &[]); - render_pass.set_vertex_buffer(1, self.instances.slice(..)); + let _ = self.instances.resize(device, instances.len()); + let _ = self.instances.write(queue, 0, instances); - render_pass.draw_indexed( - 0..quad::INDICES.len() as u32, - 0, - range.start as u32..range.end as u32, - ); + self.instance_count = instances.len(); } } @@ -92,8 +85,7 @@ impl Pipeline { buffers: &[ quad::Vertex::buffer_layout(), wgpu::VertexBufferLayout { - array_stride: std::mem::size_of::() - as u64, + array_stride: std::mem::size_of::() as u64, step_mode: wgpu::VertexStepMode::Instance, attributes: &wgpu::vertex_attr_array!( // Color @@ -133,4 +125,25 @@ impl Pipeline { Self { pipeline } } + + pub fn render<'a>( + &'a self, + render_pass: &mut wgpu::RenderPass<'a>, + constants: &'a wgpu::BindGroup, + layer: &'a Layer, + range: Range, + ) { + #[cfg(feature = "tracing")] + let _ = tracing::info_span!("Wgpu::Quad::Solid", "DRAW").entered(); + + render_pass.set_pipeline(&self.pipeline); + render_pass.set_bind_group(0, constants, &[]); + render_pass.set_vertex_buffer(1, layer.instances.slice(..)); + + render_pass.draw_indexed( + 0..quad::INDICES.len() as u32, + 0, + range.start as u32..range.end as u32, + ); + } } -- cgit From c319f5113b274bedff0d18260eddbd6f9915efc3 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Tue, 30 May 2023 01:35:06 +0200 Subject: Move `Pipeline` struct definition after `Layer` in `quad` submodules --- wgpu/src/quad/gradient.rs | 10 +++++----- wgpu/src/quad/solid.rs | 10 +++++----- 2 files changed, 10 insertions(+), 10 deletions(-) (limited to 'wgpu') diff --git a/wgpu/src/quad/gradient.rs b/wgpu/src/quad/gradient.rs index d3884ce1..2b56d594 100644 --- a/wgpu/src/quad/gradient.rs +++ b/wgpu/src/quad/gradient.rs @@ -22,11 +22,6 @@ unsafe impl Pod for Gradient {} #[allow(unsafe_code)] unsafe impl Zeroable for Gradient {} -#[derive(Debug)] -pub struct Pipeline { - pipeline: wgpu::RenderPipeline, -} - #[derive(Debug)] pub struct Layer { instances: Buffer, @@ -61,6 +56,11 @@ impl Layer { } } +#[derive(Debug)] +pub struct Pipeline { + pipeline: wgpu::RenderPipeline, +} + impl Pipeline { pub fn new( device: &wgpu::Device, diff --git a/wgpu/src/quad/solid.rs b/wgpu/src/quad/solid.rs index a3a7a0af..f667c42c 100644 --- a/wgpu/src/quad/solid.rs +++ b/wgpu/src/quad/solid.rs @@ -15,11 +15,6 @@ pub struct Solid { pub quad: Quad, } -#[derive(Debug)] -pub struct Pipeline { - pipeline: wgpu::RenderPipeline, -} - #[derive(Debug)] pub struct Layer { instances: Buffer, @@ -54,6 +49,11 @@ impl Layer { } } +#[derive(Debug)] +pub struct Pipeline { + pipeline: wgpu::RenderPipeline, +} + impl Pipeline { pub fn new( device: &wgpu::Device, -- cgit From faa7627ea41b1ce372bae7f0d2ae36e9b15a97a3 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Wed, 31 May 2023 21:31:58 +0200 Subject: Introduce `web-colors` feature flag to enable sRGB linear blending This is how browsers perform color management. They treat gamma-corrected sRGB colors as if they were linear RGB. Correctness aside, this mode is introduced for legacy reasons. Most UI/UX tooling uses this color management as well, and many have created an intuition about how color should behave from interacting with a browser. This feature flag should facilitate application development with `iced` in those cases. More details: https://webcolorisstillbroken.com/ --- wgpu/Cargo.toml | 1 + wgpu/src/backend.rs | 3 ++- wgpu/src/geometry.rs | 7 ++++--- wgpu/src/layer.rs | 3 ++- wgpu/src/quad.rs | 5 +++-- wgpu/src/quad/solid.rs | 3 ++- wgpu/src/text.rs | 4 +++- wgpu/src/window/compositor.rs | 22 +++++++++++++--------- 8 files changed, 30 insertions(+), 18 deletions(-) (limited to 'wgpu') diff --git a/wgpu/Cargo.toml b/wgpu/Cargo.toml index b5401626..badd165b 100644 --- a/wgpu/Cargo.toml +++ b/wgpu/Cargo.toml @@ -11,6 +11,7 @@ repository = "https://github.com/iced-rs/iced" geometry = ["iced_graphics/geometry", "lyon"] image = ["iced_graphics/image"] svg = ["resvg"] +web-colors = ["iced_graphics/web-colors"] [dependencies] wgpu = "0.16" diff --git a/wgpu/src/backend.rs b/wgpu/src/backend.rs index 844987f2..b524c615 100644 --- a/wgpu/src/backend.rs +++ b/wgpu/src/backend.rs @@ -1,6 +1,7 @@ use crate::core; use crate::core::{Color, Font, Point, Size}; use crate::graphics::backend; +use crate::graphics::color; use crate::graphics::{Primitive, Transformation, Viewport}; use crate::quad; use crate::text; @@ -239,7 +240,7 @@ impl Backend { load: match clear_color { Some(background_color) => wgpu::LoadOp::Clear({ let [r, g, b, a] = - background_color.into_linear(); + color::pack(background_color).components(); wgpu::Color { r: f64::from(r), diff --git a/wgpu/src/geometry.rs b/wgpu/src/geometry.rs index 740ec20c..f81b5b2f 100644 --- a/wgpu/src/geometry.rs +++ b/wgpu/src/geometry.rs @@ -1,5 +1,6 @@ //! Build and draw geometry. use crate::core::{Point, Rectangle, Size, Vector}; +use crate::graphics::color; use crate::graphics::geometry::fill::{self, Fill}; use crate::graphics::geometry::{ LineCap, LineDash, LineJoin, Path, Stroke, Style, Text, @@ -68,7 +69,7 @@ impl BufferStack { (Style::Solid(color), Buffer::Solid(buffer)) => { Box::new(tessellation::BuffersBuilder::new( buffer, - TriangleVertex2DBuilder(color.into_linear()), + TriangleVertex2DBuilder(color::pack(*color)), )) } (Style::Gradient(gradient), Buffer::Gradient(buffer)) => { @@ -91,7 +92,7 @@ impl BufferStack { (Style::Solid(color), Buffer::Solid(buffer)) => { Box::new(tessellation::BuffersBuilder::new( buffer, - TriangleVertex2DBuilder(color.into_linear()), + TriangleVertex2DBuilder(color::pack(*color)), )) } (Style::Gradient(gradient), Buffer::Gradient(buffer)) => { @@ -526,7 +527,7 @@ impl tessellation::StrokeVertexConstructor } } -struct TriangleVertex2DBuilder([f32; 4]); +struct TriangleVertex2DBuilder(color::Packed); impl tessellation::FillVertexConstructor for TriangleVertex2DBuilder diff --git a/wgpu/src/layer.rs b/wgpu/src/layer.rs index 1a870c15..71570e3d 100644 --- a/wgpu/src/layer.rs +++ b/wgpu/src/layer.rs @@ -11,6 +11,7 @@ pub use text::Text; use crate::core; use crate::core::alignment; use crate::core::{Color, Font, Point, Rectangle, Size, Vector}; +use crate::graphics::color; use crate::graphics::{Primitive, Viewport}; use crate::quad::{self, Quad}; @@ -150,7 +151,7 @@ impl<'a> Layer<'a> { bounds.y + translation.y, ], size: [bounds.width, bounds.height], - border_color: border_color.into_linear(), + border_color: color::pack(*border_color), border_radius: *border_radius, border_width: *border_width, }; diff --git a/wgpu/src/quad.rs b/wgpu/src/quad.rs index 9c5ed05f..37d0c623 100644 --- a/wgpu/src/quad.rs +++ b/wgpu/src/quad.rs @@ -5,6 +5,7 @@ use gradient::Gradient; use solid::Solid; use crate::core::{Background, Rectangle}; +use crate::graphics::color; use crate::graphics::{self, Transformation}; use bytemuck::{Pod, Zeroable}; @@ -217,7 +218,7 @@ pub struct Quad { pub size: [f32; 2], /// The border color of the [`Quad`], in __linear RGB__. - pub border_color: [f32; 4], + pub border_color: color::Packed, /// The border radii of the [`Quad`]. pub border_radius: [f32; 4], @@ -250,7 +251,7 @@ impl Batch { let kind = match background { Background::Color(color) => { self.solids.push(Solid { - color: color.into_linear(), + color: color::pack(*color), quad, }); diff --git a/wgpu/src/quad/solid.rs b/wgpu/src/quad/solid.rs index f667c42c..f8f1e3a5 100644 --- a/wgpu/src/quad/solid.rs +++ b/wgpu/src/quad/solid.rs @@ -1,3 +1,4 @@ +use crate::graphics::color; use crate::quad::{self, Quad}; use crate::Buffer; @@ -9,7 +10,7 @@ use std::ops::Range; #[repr(C)] pub struct Solid { /// The background color data of the quad. - pub color: [f32; 4], + pub color: color::Packed, /// The [`Quad`] data of the [`Solid`]. pub quad: Quad, diff --git a/wgpu/src/text.rs b/wgpu/src/text.rs index 714e0400..a12bc9f7 100644 --- a/wgpu/src/text.rs +++ b/wgpu/src/text.rs @@ -2,6 +2,7 @@ use crate::core::alignment; use crate::core::font::{self, Font}; use crate::core::text::{Hit, LineHeight, Shaping}; use crate::core::{Pixels, Point, Rectangle, Size}; +use crate::graphics::color; use crate::layer::Text; use rustc_hash::{FxHashMap, FxHashSet}; @@ -155,7 +156,8 @@ impl Pipeline { bottom: (clip_bounds.y + clip_bounds.height) as i32, }, default_color: { - let [r, g, b, a] = section.color.into_linear(); + let [r, g, b, a] = + color::pack(section.color).components(); glyphon::Color::rgba( (r * 255.0) as u8, diff --git a/wgpu/src/window/compositor.rs b/wgpu/src/window/compositor.rs index 500458e8..2eaafde0 100644 --- a/wgpu/src/window/compositor.rs +++ b/wgpu/src/window/compositor.rs @@ -1,6 +1,7 @@ //! Connect a window with a renderer. use crate::core::Color; use crate::graphics; +use crate::graphics::color; use crate::graphics::compositor; use crate::graphics::{Error, Primitive, Viewport}; use crate::{Backend, Renderer, Settings}; @@ -69,16 +70,19 @@ impl Compositor { let format = compatible_surface.as_ref().and_then(|surface| { let capabilities = surface.get_capabilities(&adapter); - capabilities - .formats - .iter() - .copied() - .find(wgpu::TextureFormat::is_srgb) - .or_else(|| { - log::warn!("No sRGB format found!"); + let mut formats = capabilities.formats.iter().copied(); - capabilities.formats.first().copied() - }) + let format = if color::GAMMA_CORRECTION { + formats.find(wgpu::TextureFormat::is_srgb) + } else { + formats.find(|format| !wgpu::TextureFormat::is_srgb(format)) + }; + + format.or_else(|| { + log::warn!("No format found!"); + + capabilities.formats.first().copied() + }) })?; log::info!("Selected format: {:?}", format); -- cgit From f1b259a28fdb92ce62ceb6fcea9547b52ecc77d3 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Wed, 31 May 2023 21:42:39 +0200 Subject: Avoid gamma correction when `web-colors` is enabled for images --- wgpu/src/image/atlas.rs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) (limited to 'wgpu') diff --git a/wgpu/src/image/atlas.rs b/wgpu/src/image/atlas.rs index 9b6dcc46..28bc4943 100644 --- a/wgpu/src/image/atlas.rs +++ b/wgpu/src/image/atlas.rs @@ -13,6 +13,7 @@ use allocator::Allocator; pub const SIZE: u32 = 2048; use crate::core::Size; +use crate::graphics::color; #[derive(Debug)] pub struct Atlas { @@ -35,7 +36,11 @@ impl Atlas { mip_level_count: 1, sample_count: 1, dimension: wgpu::TextureDimension::D2, - format: wgpu::TextureFormat::Rgba8UnormSrgb, + format: if color::GAMMA_CORRECTION { + wgpu::TextureFormat::Rgba8UnormSrgb + } else { + wgpu::TextureFormat::Rgba8Unorm + }, usage: wgpu::TextureUsages::COPY_DST | wgpu::TextureUsages::COPY_SRC | wgpu::TextureUsages::TEXTURE_BINDING, -- cgit From c528f2129e5ce3b30e313f731588082c49beb30b Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Wed, 31 May 2023 21:45:12 +0200 Subject: Use proper gamma correction mode in `image::Atlas::grow` --- wgpu/src/image/atlas.rs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) (limited to 'wgpu') diff --git a/wgpu/src/image/atlas.rs b/wgpu/src/image/atlas.rs index 28bc4943..e3de1290 100644 --- a/wgpu/src/image/atlas.rs +++ b/wgpu/src/image/atlas.rs @@ -351,7 +351,11 @@ impl Atlas { mip_level_count: 1, sample_count: 1, dimension: wgpu::TextureDimension::D2, - format: wgpu::TextureFormat::Rgba8UnormSrgb, + format: if color::GAMMA_CORRECTION { + wgpu::TextureFormat::Rgba8UnormSrgb + } else { + wgpu::TextureFormat::Rgba8Unorm + }, usage: wgpu::TextureUsages::COPY_DST | wgpu::TextureUsages::COPY_SRC | wgpu::TextureUsages::TEXTURE_BINDING, -- cgit From b5fc0f4a3aa45d33d81d5799396f0b0770c4dff3 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Thu, 1 Jun 2023 03:10:02 +0200 Subject: Use consistent color strategy in `glyphon` --- wgpu/Cargo.toml | 2 +- wgpu/src/text.rs | 11 ++++++++++- 2 files changed, 11 insertions(+), 2 deletions(-) (limited to 'wgpu') diff --git a/wgpu/Cargo.toml b/wgpu/Cargo.toml index badd165b..7e50dff2 100644 --- a/wgpu/Cargo.toml +++ b/wgpu/Cargo.toml @@ -45,7 +45,7 @@ path = "../graphics" [dependencies.glyphon] version = "0.2" git = "https://github.com/hecrj/glyphon.git" -rev = "cf7fe9df00499b868a6a94fa5fdb0a4ca368c9f9" +rev = "26f92369da3704988e3e27f0b35e705c6b2de203" [dependencies.glam] version = "0.24" diff --git a/wgpu/src/text.rs b/wgpu/src/text.rs index a12bc9f7..0d88865c 100644 --- a/wgpu/src/text.rs +++ b/wgpu/src/text.rs @@ -36,7 +36,16 @@ impl Pipeline { .into_iter(), )), renderers: Vec::new(), - atlas: glyphon::TextAtlas::new(device, queue, format), + atlas: glyphon::TextAtlas::new( + device, + queue, + format, + if color::GAMMA_CORRECTION { + glyphon::ColorMode::Accurate + } else { + glyphon::ColorMode::Web + }, + ), prepare_layer: 0, measurement_cache: RefCell::new(Cache::new()), render_cache: Cache::new(), -- cgit From 166d350dfc6e8397806002ae6b505e54387517d9 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Thu, 1 Jun 2023 17:12:28 +0200 Subject: Fix empty scissor rectangle in `iced_wgpu::triangle` pipeline --- wgpu/src/triangle.rs | 4 ++++ 1 file changed, 4 insertions(+) (limited to 'wgpu') diff --git a/wgpu/src/triangle.rs b/wgpu/src/triangle.rs index 6cd54ef7..6f32f182 100644 --- a/wgpu/src/triangle.rs +++ b/wgpu/src/triangle.rs @@ -155,6 +155,10 @@ impl Layer { for (index, mesh) in meshes.iter().enumerate() { let clip_bounds = (mesh.clip_bounds() * scale_factor).snap(); + if clip_bounds.width < 1 || clip_bounds.height < 1 { + continue; + } + render_pass.set_scissor_rect( clip_bounds.x, clip_bounds.y, -- cgit From 233196eb14b40f8bd5201ea0262571f82136ad53 Mon Sep 17 00:00:00 2001 From: Bingus Date: Sat, 25 Mar 2023 10:45:39 -0700 Subject: Added offscreen rendering support for wgpu & tiny-skia exposed with the window::screenshot command. --- wgpu/src/backend.rs | 61 ++++++++++++++- wgpu/src/lib.rs | 1 + wgpu/src/offscreen.rs | 102 +++++++++++++++++++++++++ wgpu/src/shader/offscreen_blit.wgsl | 22 ++++++ wgpu/src/triangle/msaa.rs | 11 +-- wgpu/src/window/compositor.rs | 143 +++++++++++++++++++++++++++++++++++- 6 files changed, 329 insertions(+), 11 deletions(-) create mode 100644 wgpu/src/offscreen.rs create mode 100644 wgpu/src/shader/offscreen_blit.wgsl (limited to 'wgpu') diff --git a/wgpu/src/backend.rs b/wgpu/src/backend.rs index b524c615..8f37f285 100644 --- a/wgpu/src/backend.rs +++ b/wgpu/src/backend.rs @@ -1,4 +1,3 @@ -use crate::core; use crate::core::{Color, Font, Point, Size}; use crate::graphics::backend; use crate::graphics::color; @@ -6,6 +5,7 @@ use crate::graphics::{Primitive, Transformation, Viewport}; use crate::quad; use crate::text; use crate::triangle; +use crate::{core, offscreen}; use crate::{Layer, Settings}; #[cfg(feature = "tracing")] @@ -123,6 +123,65 @@ impl Backend { self.image_pipeline.end_frame(); } + /// Performs an offscreen render pass. If the `format` selected by WGPU is not + /// `wgpu::TextureFormat::Rgba8UnormSrgb`, a conversion compute pipeline will run. + /// + /// Returns `None` if the `frame` is `Rgba8UnormSrgb`, else returns the newly + /// converted texture view in `Rgba8UnormSrgb`. + pub fn offscreen>( + &mut self, + device: &wgpu::Device, + queue: &wgpu::Queue, + encoder: &mut wgpu::CommandEncoder, + clear_color: Option, + frame: &wgpu::TextureView, + format: wgpu::TextureFormat, + primitives: &[Primitive], + viewport: &Viewport, + overlay_text: &[T], + texture_extent: wgpu::Extent3d, + ) -> Option { + #[cfg(feature = "tracing")] + let _ = info_span!("iced_wgpu::offscreen", "DRAW").entered(); + + self.present( + device, + queue, + encoder, + clear_color, + frame, + primitives, + viewport, + overlay_text, + ); + + if format != wgpu::TextureFormat::Rgba8UnormSrgb { + log::info!("Texture format is {format:?}; performing conversion to rgba8.."); + let pipeline = offscreen::Pipeline::new(device); + + let texture = device.create_texture(&wgpu::TextureDescriptor { + label: Some("iced_wgpu.offscreen.conversion.source_texture"), + size: texture_extent, + mip_level_count: 1, + sample_count: 1, + dimension: wgpu::TextureDimension::D2, + format: wgpu::TextureFormat::Rgba8Unorm, + usage: wgpu::TextureUsages::STORAGE_BINDING + | wgpu::TextureUsages::COPY_SRC, + view_formats: &[], + }); + + let view = + texture.create_view(&wgpu::TextureViewDescriptor::default()); + + pipeline.convert(device, texture_extent, frame, &view, encoder); + + return Some(texture); + } + + None + } + fn prepare_text( &mut self, device: &wgpu::Device, diff --git a/wgpu/src/lib.rs b/wgpu/src/lib.rs index 0a5726b5..827acb89 100644 --- a/wgpu/src/lib.rs +++ b/wgpu/src/lib.rs @@ -46,6 +46,7 @@ pub mod geometry; mod backend; mod buffer; +mod offscreen; mod quad; mod text; mod triangle; diff --git a/wgpu/src/offscreen.rs b/wgpu/src/offscreen.rs new file mode 100644 index 00000000..29913d02 --- /dev/null +++ b/wgpu/src/offscreen.rs @@ -0,0 +1,102 @@ +use std::borrow::Cow; + +/// A simple compute pipeline to convert any texture to Rgba8UnormSrgb. +#[derive(Debug)] +pub struct Pipeline { + pipeline: wgpu::ComputePipeline, + layout: wgpu::BindGroupLayout, +} + +impl Pipeline { + pub fn new(device: &wgpu::Device) -> Self { + let shader = + device.create_shader_module(wgpu::ShaderModuleDescriptor { + label: Some("iced_wgpu.offscreen.blit.shader"), + source: wgpu::ShaderSource::Wgsl(Cow::Borrowed(include_str!( + "shader/offscreen_blit.wgsl" + ))), + }); + + let bind_group_layout = + device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { + label: Some("iced_wgpu.offscreen.blit.bind_group_layout"), + entries: &[ + wgpu::BindGroupLayoutEntry { + binding: 0, + visibility: wgpu::ShaderStages::COMPUTE, + ty: wgpu::BindingType::Texture { + sample_type: wgpu::TextureSampleType::Float { + filterable: false, + }, + view_dimension: wgpu::TextureViewDimension::D2, + multisampled: false, + }, + count: None, + }, + wgpu::BindGroupLayoutEntry { + binding: 1, + visibility: wgpu::ShaderStages::COMPUTE, + ty: wgpu::BindingType::StorageTexture { + access: wgpu::StorageTextureAccess::WriteOnly, + format: wgpu::TextureFormat::Rgba8Unorm, + view_dimension: wgpu::TextureViewDimension::D2, + }, + count: None, + }, + ], + }); + + let pipeline_layout = + device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor { + label: Some("iced_wgpu.offscreen.blit.pipeline_layout"), + bind_group_layouts: &[&bind_group_layout], + push_constant_ranges: &[], + }); + + let pipeline = + device.create_compute_pipeline(&wgpu::ComputePipelineDescriptor { + label: Some("iced_wgpu.offscreen.blit.pipeline"), + layout: Some(&pipeline_layout), + module: &shader, + entry_point: "main", + }); + + Self { + pipeline, + layout: bind_group_layout, + } + } + + pub fn convert( + &self, + device: &wgpu::Device, + extent: wgpu::Extent3d, + frame: &wgpu::TextureView, + view: &wgpu::TextureView, + encoder: &mut wgpu::CommandEncoder, + ) { + let bind = device.create_bind_group(&wgpu::BindGroupDescriptor { + label: Some("iced_wgpu.offscreen.blit.bind_group"), + layout: &self.layout, + entries: &[ + wgpu::BindGroupEntry { + binding: 0, + resource: wgpu::BindingResource::TextureView(frame), + }, + wgpu::BindGroupEntry { + binding: 1, + resource: wgpu::BindingResource::TextureView(view), + }, + ], + }); + + let mut compute_pass = + encoder.begin_compute_pass(&wgpu::ComputePassDescriptor { + label: Some("iced_wgpu.offscreen.blit.compute_pass"), + }); + + compute_pass.set_pipeline(&self.pipeline); + compute_pass.set_bind_group(0, &bind, &[]); + compute_pass.dispatch_workgroups(extent.width, extent.height, 1); + } +} diff --git a/wgpu/src/shader/offscreen_blit.wgsl b/wgpu/src/shader/offscreen_blit.wgsl new file mode 100644 index 00000000..9c764c36 --- /dev/null +++ b/wgpu/src/shader/offscreen_blit.wgsl @@ -0,0 +1,22 @@ +@group(0) @binding(0) var u_texture: texture_2d; +@group(0) @binding(1) var out_texture: texture_storage_2d; + +fn srgb(color: f32) -> f32 { + if (color <= 0.0031308) { + return 12.92 * color; + } else { + return (1.055 * (pow(color, (1.0/2.4)))) - 0.055; + } +} + +@compute @workgroup_size(1) +fn main(@builtin(global_invocation_id) id: vec3) { + // texture coord must be i32 due to a naga bug: + // https://github.com/gfx-rs/naga/issues/1997 + let coords = vec2(i32(id.x), i32(id.y)); + + let src: vec4 = textureLoad(u_texture, coords, 0); + let srgb_color: vec4 = vec4(srgb(src.x), srgb(src.y), srgb(src.z), src.w); + + textureStore(out_texture, coords, srgb_color); +} diff --git a/wgpu/src/triangle/msaa.rs b/wgpu/src/triangle/msaa.rs index 4afbdb32..320b5b12 100644 --- a/wgpu/src/triangle/msaa.rs +++ b/wgpu/src/triangle/msaa.rs @@ -16,15 +16,8 @@ impl Blit { format: wgpu::TextureFormat, antialiasing: graphics::Antialiasing, ) -> Blit { - 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::Nearest, - min_filter: wgpu::FilterMode::Nearest, - mipmap_filter: wgpu::FilterMode::Nearest, - ..Default::default() - }); + let sampler = + device.create_sampler(&wgpu::SamplerDescriptor::default()); let constant_layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { diff --git a/wgpu/src/window/compositor.rs b/wgpu/src/window/compositor.rs index 2eaafde0..43c3dce5 100644 --- a/wgpu/src/window/compositor.rs +++ b/wgpu/src/window/compositor.rs @@ -1,5 +1,5 @@ //! Connect a window with a renderer. -use crate::core::Color; +use crate::core::{Color, Size}; use crate::graphics; use crate::graphics::color; use crate::graphics::compositor; @@ -283,4 +283,145 @@ impl graphics::Compositor for Compositor { ) }) } + + fn screenshot>( + &mut self, + renderer: &mut Self::Renderer, + _surface: &mut Self::Surface, + viewport: &Viewport, + background_color: Color, + overlay: &[T], + ) -> Vec { + renderer.with_primitives(|backend, primitives| { + screenshot( + self, + backend, + primitives, + viewport, + background_color, + overlay, + ) + }) + } +} + +/// Renders the current surface to an offscreen buffer. +/// +/// Returns RGBA bytes of the texture data. +pub fn screenshot>( + compositor: &Compositor, + backend: &mut Backend, + primitives: &[Primitive], + viewport: &Viewport, + background_color: Color, + overlay: &[T], +) -> Vec { + let mut encoder = compositor.device.create_command_encoder( + &wgpu::CommandEncoderDescriptor { + label: Some("iced_wgpu.offscreen.encoder"), + }, + ); + + let dimensions = BufferDimensions::new(viewport.physical_size()); + + let texture_extent = wgpu::Extent3d { + width: dimensions.width, + height: dimensions.height, + depth_or_array_layers: 1, + }; + + let texture = compositor.device.create_texture(&wgpu::TextureDescriptor { + label: Some("iced_wgpu.offscreen.source_texture"), + size: texture_extent, + mip_level_count: 1, + sample_count: 1, + dimension: wgpu::TextureDimension::D2, + format: compositor.format, + usage: wgpu::TextureUsages::RENDER_ATTACHMENT + | wgpu::TextureUsages::COPY_SRC + | wgpu::TextureUsages::TEXTURE_BINDING, + view_formats: &[], + }); + + let view = texture.create_view(&wgpu::TextureViewDescriptor::default()); + + let rgba_texture = backend.offscreen( + &compositor.device, + &compositor.queue, + &mut encoder, + Some(background_color), + &view, + compositor.format, + primitives, + viewport, + overlay, + texture_extent, + ); + + let output_buffer = + compositor.device.create_buffer(&wgpu::BufferDescriptor { + label: Some("iced_wgpu.offscreen.output_texture_buffer"), + size: (dimensions.padded_bytes_per_row * dimensions.height as usize) + as u64, + usage: wgpu::BufferUsages::MAP_READ | wgpu::BufferUsages::COPY_DST, + mapped_at_creation: false, + }); + + encoder.copy_texture_to_buffer( + rgba_texture.unwrap_or(texture).as_image_copy(), + wgpu::ImageCopyBuffer { + buffer: &output_buffer, + layout: wgpu::ImageDataLayout { + offset: 0, + bytes_per_row: Some(dimensions.padded_bytes_per_row as u32), + rows_per_image: None, + }, + }, + texture_extent, + ); + + let index = compositor.queue.submit(Some(encoder.finish())); + + let slice = output_buffer.slice(..); + slice.map_async(wgpu::MapMode::Read, |_| {}); + + let _ = compositor + .device + .poll(wgpu::Maintain::WaitForSubmissionIndex(index)); + + let mapped_buffer = slice.get_mapped_range(); + + mapped_buffer.chunks(dimensions.padded_bytes_per_row).fold( + vec![], + |mut acc, row| { + acc.extend(&row[..dimensions.unpadded_bytes_per_row]); + acc + }, + ) +} + +#[derive(Clone, Copy, Debug)] +struct BufferDimensions { + width: u32, + height: u32, + unpadded_bytes_per_row: usize, + padded_bytes_per_row: usize, +} + +impl BufferDimensions { + fn new(size: Size) -> Self { + let unpadded_bytes_per_row = size.width as usize * 4; //slice of buffer per row; always RGBA + let alignment = wgpu::COPY_BYTES_PER_ROW_ALIGNMENT as usize; //256 + let padded_bytes_per_row_padding = + (alignment - unpadded_bytes_per_row % alignment) % alignment; + let padded_bytes_per_row = + unpadded_bytes_per_row + padded_bytes_per_row_padding; + + Self { + width: size.width, + height: size.height, + unpadded_bytes_per_row, + padded_bytes_per_row, + } + } } -- cgit From ea7f2626b11af249510b27001fb6addd7f9210a9 Mon Sep 17 00:00:00 2001 From: Bingus Date: Mon, 29 May 2023 16:44:56 -0700 Subject: Optimized gradient data packing. --- wgpu/src/quad/gradient.rs | 34 ++++++----------- wgpu/src/shader/quad.wgsl | 87 ++++++++++++++++++++----------------------- wgpu/src/shader/triangle.wgsl | 71 ++++++++++++++++------------------- wgpu/src/triangle.rs | 26 ++++--------- 4 files changed, 92 insertions(+), 126 deletions(-) (limited to 'wgpu') diff --git a/wgpu/src/quad/gradient.rs b/wgpu/src/quad/gradient.rs index 2b56d594..1b45c9f4 100644 --- a/wgpu/src/quad/gradient.rs +++ b/wgpu/src/quad/gradient.rs @@ -96,36 +96,24 @@ impl Pipeline { as u64, step_mode: wgpu::VertexStepMode::Instance, attributes: &wgpu::vertex_attr_array!( - // Color 1 - 1 => Float32x4, - // Color 2 - 2 => Float32x4, - // Color 3 - 3 => Float32x4, - // Color 4 - 4 => Float32x4, - // Color 5 - 5 => Float32x4, - // Color 6 - 6 => Float32x4, - // Color 7 - 7 => Float32x4, - // Color 8 - 8 => Float32x4, + // Colors 1-4 + 1 => Uint32x4, + // Colors 5-8 + 2 => Uint32x4, // Offsets 1-4 - 9 => Float32x4, + 3 => Float32x4, // Offsets 5-8 - 10 => Float32x4, + 4 => Float32x4, // Direction - 11 => Float32x4, + 5 => Float32x4, // Position & Scale - 12 => Float32x4, + 6 => Float32x4, // Border color - 13 => Float32x4, + 7 => Float32x4, // Border radius - 14 => Float32x4, + 8 => Float32x4, // Border width - 15 => Float32 + 9 => Float32 ), }, ], diff --git a/wgpu/src/shader/quad.wgsl b/wgpu/src/shader/quad.wgsl index 3232bdbe..fdcc6743 100644 --- a/wgpu/src/shader/quad.wgsl +++ b/wgpu/src/shader/quad.wgsl @@ -38,6 +38,19 @@ fn select_border_radius(radi: vec4, position: vec2, center: vec2) return rx; } +fn l(c: f32) -> f32 { + if (c < 0.04045) { + return c / 12.92; + } else { + return pow(((c + 0.055) / 1.055), 2.4); + }; +} + +fn to_linear(color: u32) -> vec4 { + let c = unpack4x8unorm(color); //unpacks as a b g r + return vec4(l(c.w), l(c.z), l(c.y), c.x); +} + struct SolidVertexInput { @location(0) v_pos: vec2, @location(1) color: vec4, @@ -140,40 +153,28 @@ fn solid_fs_main( struct GradientVertexInput { @location(0) v_pos: vec2, - @location(1) color_1: vec4, - @location(2) color_2: vec4, - @location(3) color_3: vec4, - @location(4) color_4: vec4, - @location(5) color_5: vec4, - @location(6) color_6: vec4, - @location(7) color_7: vec4, - @location(8) color_8: vec4, - @location(9) offsets_1: vec4, - @location(10) offsets_2: vec4, - @location(11) direction: vec4, - @location(12) position_and_scale: vec4, - @location(13) border_color: vec4, - @location(14) border_radius: vec4, - @location(15) border_width: f32 + @location(1) colors_1: vec4, + @location(2) colors_2: vec4, + @location(3) offsets_1: vec4, + @location(4) offsets_2: vec4, + @location(5) direction: vec4, + @location(6) position_and_scale: vec4, + @location(7) border_color: vec4, + @location(8) border_radius: vec4, + @location(9) border_width: f32, } struct GradientVertexOutput { @builtin(position) position: vec4, - @location(1) color_1: vec4, - @location(2) color_2: vec4, - @location(3) color_3: vec4, - @location(4) color_4: vec4, - @location(5) color_5: vec4, - @location(6) color_6: vec4, - @location(7) color_7: vec4, - @location(8) color_8: vec4, - @location(9) offsets_1: vec4, - @location(10) offsets_2: vec4, - @location(11) direction: vec4, - @location(12) position_and_scale: vec4, - @location(13) border_color: vec4, - @location(14) border_radius: vec4, - @location(15) border_width: f32 + @location(1) colors_1: vec4, + @location(2) colors_2: vec4, + @location(3) offsets_1: vec4, + @location(4) offsets_2: vec4, + @location(5) direction: vec4, + @location(6) position_and_scale: vec4, + @location(7) border_color: vec4, + @location(8) border_radius: vec4, + @location(9) border_width: f32, } @vertex @@ -199,14 +200,8 @@ fn gradient_vs_main(input: GradientVertexInput) -> GradientVertexOutput { ); out.position = globals.transform * transform * vec4(input.v_pos, 0.0, 1.0); - out.color_1 = input.color_1; - out.color_2 = input.color_2; - out.color_3 = input.color_3; - out.color_4 = input.color_4; - out.color_5 = input.color_5; - out.color_6 = input.color_6; - out.color_7 = input.color_7; - out.color_8 = input.color_8; + out.colors_1 = input.colors_1; + out.colors_2 = input.colors_2; out.offsets_1 = input.offsets_1; out.offsets_2 = input.offsets_2; out.direction = input.direction * globals.scale; @@ -274,14 +269,14 @@ fn gradient( @fragment fn gradient_fs_main(input: GradientVertexOutput) -> @location(0) vec4 { let colors = array, 8>( - input.color_1, - input.color_2, - input.color_3, - input.color_4, - input.color_5, - input.color_6, - input.color_7, - input.color_8, + to_linear(input.colors_1.x), + to_linear(input.colors_1.y), + to_linear(input.colors_1.z), + to_linear(input.colors_1.w), + to_linear(input.colors_2.x), + to_linear(input.colors_2.y), + to_linear(input.colors_2.z), + to_linear(input.colors_2.w), ); var offsets = array( diff --git a/wgpu/src/shader/triangle.wgsl b/wgpu/src/shader/triangle.wgsl index 625fa46e..5a73a77f 100644 --- a/wgpu/src/shader/triangle.wgsl +++ b/wgpu/src/shader/triangle.wgsl @@ -4,6 +4,19 @@ struct Globals { @group(0) @binding(0) var globals: Globals; +fn l(c: f32) -> f32 { + if (c < 0.04045) { + return c / 12.92; + } else { + return pow(((c + 0.055) / 1.055), 2.4); + }; +} + +fn to_linear(color: u32) -> vec4 { + let c = unpack4x8unorm(color); //unpacks as a b g r + return vec4(l(c.w), l(c.z), l(c.y), c.x); +} + struct SolidVertexInput { @location(0) position: vec2, @location(1) color: vec4, @@ -32,46 +45,28 @@ fn solid_fs_main(input: SolidVertexOutput) -> @location(0) vec4 { struct GradientVertexOutput { @builtin(position) position: vec4, @location(0) raw_position: vec2, - @location(1) color_1: vec4, - @location(2) color_2: vec4, - @location(3) color_3: vec4, - @location(4) color_4: vec4, - @location(5) color_5: vec4, - @location(6) color_6: vec4, - @location(7) color_7: vec4, - @location(8) color_8: vec4, - @location(9) offsets_1: vec4, - @location(10) offsets_2: vec4, - @location(11) direction: vec4, + @location(1) colors_1: vec4, + @location(2) colors_2: vec4, + @location(3) offsets_1: vec4, + @location(4) offsets_2: vec4, + @location(5) direction: vec4, } @vertex fn gradient_vs_main( @location(0) input: vec2, - @location(1) color_1: vec4, - @location(2) color_2: vec4, - @location(3) color_3: vec4, - @location(4) color_4: vec4, - @location(5) color_5: vec4, - @location(6) color_6: vec4, - @location(7) color_7: vec4, - @location(8) color_8: vec4, - @location(9) offsets_1: vec4, - @location(10) offsets_2: vec4, - @location(11) direction: vec4, + @location(1) colors_1: vec4, + @location(2) colors_2: vec4, + @location(3) offsets_1: vec4, + @location(4) offsets_2: vec4, + @location(5) direction: vec4, ) -> GradientVertexOutput { var output: GradientVertexOutput; output.position = globals.transform * vec4(input.xy, 0.0, 1.0); output.raw_position = input; - output.color_1 = color_1; - output.color_2 = color_2; - output.color_3 = color_3; - output.color_4 = color_4; - output.color_5 = color_5; - output.color_6 = color_6; - output.color_7 = color_7; - output.color_8 = color_8; + output.colors_1 = colors_1; + output.colors_2 = colors_2; output.offsets_1 = offsets_1; output.offsets_2 = offsets_2; output.direction = direction; @@ -135,14 +130,14 @@ fn gradient( @fragment fn gradient_fs_main(input: GradientVertexOutput) -> @location(0) vec4 { let colors = array, 8>( - input.color_1, - input.color_2, - input.color_3, - input.color_4, - input.color_5, - input.color_6, - input.color_7, - input.color_8, + to_linear(input.colors_1.x), + to_linear(input.colors_1.y), + to_linear(input.colors_1.z), + to_linear(input.colors_1.w), + to_linear(input.colors_2.x), + to_linear(input.colors_2.y), + to_linear(input.colors_2.z), + to_linear(input.colors_2.w), ); var offsets = array( diff --git a/wgpu/src/triangle.rs b/wgpu/src/triangle.rs index 6f32f182..3f633e14 100644 --- a/wgpu/src/triangle.rs +++ b/wgpu/src/triangle.rs @@ -652,28 +652,16 @@ mod gradient { attributes: &wgpu::vertex_attr_array!( // Position 0 => Float32x2, - // Color 1 - 1 => Float32x4, - // Color 2 - 2 => Float32x4, - // Color 3 - 3 => Float32x4, - // Color 4 - 4 => Float32x4, - // Color 5 - 5 => Float32x4, - // Color 6 - 6 => Float32x4, - // Color 7 - 7 => Float32x4, - // Color 8 - 8 => Float32x4, + // Colors 1-4 + 1 => Uint32x4, + // Colors 5-8, + 2 => Uint32x4, // Offsets 1-4 - 9 => Float32x4, + 3 => Float32x4, // Offsets 5-8 - 10 => Float32x4, + 4 => Float32x4, // Direction - 11 => Float32x4 + 5 => Float32x4 ), }], }, -- cgit From 9554c78f3adc9846b76e9d3b96af06e98fb69aa0 Mon Sep 17 00:00:00 2001 From: Bingus Date: Tue, 6 Jun 2023 17:06:40 -0700 Subject: Updated color packing into u32 to consider incorrect web-colors. --- wgpu/src/shader/quad.wgsl | 29 +++++++++++------------------ wgpu/src/shader/triangle.wgsl | 29 +++++++++++------------------ 2 files changed, 22 insertions(+), 36 deletions(-) (limited to 'wgpu') diff --git a/wgpu/src/shader/quad.wgsl b/wgpu/src/shader/quad.wgsl index fdcc6743..5a1237e6 100644 --- a/wgpu/src/shader/quad.wgsl +++ b/wgpu/src/shader/quad.wgsl @@ -38,17 +38,10 @@ fn select_border_radius(radi: vec4, position: vec2, center: vec2) return rx; } -fn l(c: f32) -> f32 { - if (c < 0.04045) { - return c / 12.92; - } else { - return pow(((c + 0.055) / 1.055), 2.4); - }; -} +fn unpack_u32(color: u32) -> vec4 { + let u = unpack4x8unorm(color); -fn to_linear(color: u32) -> vec4 { - let c = unpack4x8unorm(color); //unpacks as a b g r - return vec4(l(c.w), l(c.z), l(c.y), c.x); + return vec4(u.w, u.z, u.y, u.x); } struct SolidVertexInput { @@ -269,14 +262,14 @@ fn gradient( @fragment fn gradient_fs_main(input: GradientVertexOutput) -> @location(0) vec4 { let colors = array, 8>( - to_linear(input.colors_1.x), - to_linear(input.colors_1.y), - to_linear(input.colors_1.z), - to_linear(input.colors_1.w), - to_linear(input.colors_2.x), - to_linear(input.colors_2.y), - to_linear(input.colors_2.z), - to_linear(input.colors_2.w), + unpack_u32(input.colors_1.x), + unpack_u32(input.colors_1.y), + unpack_u32(input.colors_1.z), + unpack_u32(input.colors_1.w), + unpack_u32(input.colors_2.x), + unpack_u32(input.colors_2.y), + unpack_u32(input.colors_2.z), + unpack_u32(input.colors_2.w), ); var offsets = array( diff --git a/wgpu/src/shader/triangle.wgsl b/wgpu/src/shader/triangle.wgsl index 5a73a77f..f1bb2733 100644 --- a/wgpu/src/shader/triangle.wgsl +++ b/wgpu/src/shader/triangle.wgsl @@ -4,17 +4,10 @@ struct Globals { @group(0) @binding(0) var globals: Globals; -fn l(c: f32) -> f32 { - if (c < 0.04045) { - return c / 12.92; - } else { - return pow(((c + 0.055) / 1.055), 2.4); - }; -} +fn unpack_u32(color: u32) -> vec4 { + let u = unpack4x8unorm(color); -fn to_linear(color: u32) -> vec4 { - let c = unpack4x8unorm(color); //unpacks as a b g r - return vec4(l(c.w), l(c.z), l(c.y), c.x); + return vec4(u.w, u.z, u.y, u.x); } struct SolidVertexInput { @@ -130,14 +123,14 @@ fn gradient( @fragment fn gradient_fs_main(input: GradientVertexOutput) -> @location(0) vec4 { let colors = array, 8>( - to_linear(input.colors_1.x), - to_linear(input.colors_1.y), - to_linear(input.colors_1.z), - to_linear(input.colors_1.w), - to_linear(input.colors_2.x), - to_linear(input.colors_2.y), - to_linear(input.colors_2.z), - to_linear(input.colors_2.w), + unpack_u32(input.colors_1.x), + unpack_u32(input.colors_1.y), + unpack_u32(input.colors_1.z), + unpack_u32(input.colors_1.w), + unpack_u32(input.colors_2.x), + unpack_u32(input.colors_2.y), + unpack_u32(input.colors_2.z), + unpack_u32(input.colors_2.w), ); var offsets = array( -- cgit From 677f564f087b009842207e6df74aed343454ea17 Mon Sep 17 00:00:00 2001 From: Bingus Date: Wed, 7 Jun 2023 10:47:57 -0700 Subject: Switched to packing using f16s to maintain acceptable precision. --- wgpu/src/quad/gradient.rs | 24 +++++++------ wgpu/src/shader/quad.wgsl | 77 +++++++++++++++++++++------------------ wgpu/src/shader/triangle.wgsl | 83 ++++++++++++++++++++++++------------------- wgpu/src/triangle.rs | 16 +++++---- 4 files changed, 110 insertions(+), 90 deletions(-) (limited to 'wgpu') diff --git a/wgpu/src/quad/gradient.rs b/wgpu/src/quad/gradient.rs index 1b45c9f4..6db37252 100644 --- a/wgpu/src/quad/gradient.rs +++ b/wgpu/src/quad/gradient.rs @@ -96,24 +96,26 @@ impl Pipeline { as u64, step_mode: wgpu::VertexStepMode::Instance, attributes: &wgpu::vertex_attr_array!( - // Colors 1-4 + // Colors 1-2 1 => Uint32x4, - // Colors 5-8 + // Colors 3-4 2 => Uint32x4, - // Offsets 1-4 - 3 => Float32x4, - // Offsets 5-8 - 4 => Float32x4, + // Colors 5-6 + 3 => Uint32x4, + // Colors 7-8 + 4 => Uint32x4, + // Offsets 1-8 + 5 => Uint32x4, // Direction - 5 => Float32x4, - // Position & Scale 6 => Float32x4, - // Border color + // Position & Scale 7 => Float32x4, - // Border radius + // Border color 8 => Float32x4, + // Border radius + 9 => Float32x4, // Border width - 9 => Float32 + 10 => Float32 ), }, ], diff --git a/wgpu/src/shader/quad.wgsl b/wgpu/src/shader/quad.wgsl index 5a1237e6..fb402158 100644 --- a/wgpu/src/shader/quad.wgsl +++ b/wgpu/src/shader/quad.wgsl @@ -38,10 +38,11 @@ fn select_border_radius(radi: vec4, position: vec2, center: vec2) return rx; } -fn unpack_u32(color: u32) -> vec4 { - let u = unpack4x8unorm(color); +fn unpack_u32(color: vec2) -> vec4 { + let rg: vec2 = unpack2x16float(color.x); + let ba: vec2 = unpack2x16float(color.y); - return vec4(u.w, u.z, u.y, u.x); + return vec4(rg.y, rg.x, ba.y, ba.x); } struct SolidVertexInput { @@ -148,26 +149,28 @@ struct GradientVertexInput { @location(0) v_pos: vec2, @location(1) colors_1: vec4, @location(2) colors_2: vec4, - @location(3) offsets_1: vec4, - @location(4) offsets_2: vec4, - @location(5) direction: vec4, - @location(6) position_and_scale: vec4, - @location(7) border_color: vec4, - @location(8) border_radius: vec4, - @location(9) border_width: f32, + @location(3) colors_3: vec4, + @location(4) colors_4: vec4, + @location(5) offsets: vec4, + @location(6) direction: vec4, + @location(7) position_and_scale: vec4, + @location(8) border_color: vec4, + @location(9) border_radius: vec4, + @location(10) border_width: f32, } struct GradientVertexOutput { @builtin(position) position: vec4, @location(1) colors_1: vec4, @location(2) colors_2: vec4, - @location(3) offsets_1: vec4, - @location(4) offsets_2: vec4, - @location(5) direction: vec4, - @location(6) position_and_scale: vec4, - @location(7) border_color: vec4, - @location(8) border_radius: vec4, - @location(9) border_width: f32, + @location(3) colors_3: vec4, + @location(4) colors_4: vec4, + @location(5) offsets: vec4, + @location(6) direction: vec4, + @location(7) position_and_scale: vec4, + @location(8) border_color: vec4, + @location(9) border_radius: vec4, + @location(10) border_width: f32, } @vertex @@ -195,8 +198,9 @@ fn gradient_vs_main(input: GradientVertexInput) -> GradientVertexOutput { out.position = globals.transform * transform * vec4(input.v_pos, 0.0, 1.0); out.colors_1 = input.colors_1; out.colors_2 = input.colors_2; - out.offsets_1 = input.offsets_1; - out.offsets_2 = input.offsets_2; + out.colors_3 = input.colors_3; + out.colors_4 = input.colors_4; + out.offsets = input.offsets; out.direction = input.direction * globals.scale; out.position_and_scale = vec4(pos, scale); out.border_color = input.border_color; @@ -262,25 +266,28 @@ fn gradient( @fragment fn gradient_fs_main(input: GradientVertexOutput) -> @location(0) vec4 { let colors = array, 8>( - unpack_u32(input.colors_1.x), - unpack_u32(input.colors_1.y), - unpack_u32(input.colors_1.z), - unpack_u32(input.colors_1.w), - unpack_u32(input.colors_2.x), - unpack_u32(input.colors_2.y), - unpack_u32(input.colors_2.z), - unpack_u32(input.colors_2.w), + unpack_u32(input.colors_1.xy), + unpack_u32(input.colors_1.zw), + unpack_u32(input.colors_2.xy), + unpack_u32(input.colors_2.zw), + unpack_u32(input.colors_3.xy), + unpack_u32(input.colors_3.zw), + unpack_u32(input.colors_4.xy), + unpack_u32(input.colors_4.zw), ); + let offsets_1: vec4 = unpack_u32(input.offsets.xy); + let offsets_2: vec4 = unpack_u32(input.offsets.zw); + var offsets = array( - input.offsets_1.x, - input.offsets_1.y, - input.offsets_1.z, - input.offsets_1.w, - input.offsets_2.x, - input.offsets_2.y, - input.offsets_2.z, - input.offsets_2.w, + offsets_1.x, + offsets_1.y, + offsets_1.z, + offsets_1.w, + offsets_2.x, + offsets_2.y, + offsets_2.z, + offsets_2.w, ); //TODO could just pass this in to the shader but is probably more performant to just check it here diff --git a/wgpu/src/shader/triangle.wgsl b/wgpu/src/shader/triangle.wgsl index f1bb2733..9f512d14 100644 --- a/wgpu/src/shader/triangle.wgsl +++ b/wgpu/src/shader/triangle.wgsl @@ -4,10 +4,11 @@ struct Globals { @group(0) @binding(0) var globals: Globals; -fn unpack_u32(color: u32) -> vec4 { - let u = unpack4x8unorm(color); +fn unpack_u32(color: vec2) -> vec4 { + let rg: vec2 = unpack2x16float(color.x); + let ba: vec2 = unpack2x16float(color.y); - return vec4(u.w, u.z, u.y, u.x); + return vec4(rg.y, rg.x, ba.y, ba.x); } struct SolidVertexInput { @@ -35,34 +36,39 @@ fn solid_fs_main(input: SolidVertexOutput) -> @location(0) vec4 { return input.color; } +struct GradientVertexInput { + @location(0) v_pos: vec2, + @location(1) colors_1: vec4, + @location(2) colors_2: vec4, + @location(3) colors_3: vec4, + @location(4) colors_4: vec4, + @location(5) offsets: vec4, + @location(6) direction: vec4, +} + struct GradientVertexOutput { @builtin(position) position: vec4, @location(0) raw_position: vec2, @location(1) colors_1: vec4, @location(2) colors_2: vec4, - @location(3) offsets_1: vec4, - @location(4) offsets_2: vec4, - @location(5) direction: vec4, + @location(3) colors_3: vec4, + @location(4) colors_4: vec4, + @location(5) offsets: vec4, + @location(6) direction: vec4, } @vertex -fn gradient_vs_main( - @location(0) input: vec2, - @location(1) colors_1: vec4, - @location(2) colors_2: vec4, - @location(3) offsets_1: vec4, - @location(4) offsets_2: vec4, - @location(5) direction: vec4, -) -> GradientVertexOutput { +fn gradient_vs_main(input: GradientVertexInput) -> GradientVertexOutput { var output: GradientVertexOutput; - output.position = globals.transform * vec4(input.xy, 0.0, 1.0); - output.raw_position = input; - output.colors_1 = colors_1; - output.colors_2 = colors_2; - output.offsets_1 = offsets_1; - output.offsets_2 = offsets_2; - output.direction = direction; + output.position = globals.transform * vec4(input.v_pos, 0.0, 1.0); + output.raw_position = input.v_pos; + output.colors_1 = input.colors_1; + output.colors_2 = input.colors_2; + output.colors_3 = input.colors_3; + output.colors_4 = input.colors_4; + output.offsets = input.offsets; + output.direction = input.direction; return output; } @@ -123,25 +129,28 @@ fn gradient( @fragment fn gradient_fs_main(input: GradientVertexOutput) -> @location(0) vec4 { let colors = array, 8>( - unpack_u32(input.colors_1.x), - unpack_u32(input.colors_1.y), - unpack_u32(input.colors_1.z), - unpack_u32(input.colors_1.w), - unpack_u32(input.colors_2.x), - unpack_u32(input.colors_2.y), - unpack_u32(input.colors_2.z), - unpack_u32(input.colors_2.w), + unpack_u32(input.colors_1.xy), + unpack_u32(input.colors_1.zw), + unpack_u32(input.colors_2.xy), + unpack_u32(input.colors_2.zw), + unpack_u32(input.colors_3.xy), + unpack_u32(input.colors_3.zw), + unpack_u32(input.colors_4.xy), + unpack_u32(input.colors_4.zw), ); + let offsets_1: vec4 = unpack_u32(input.offsets.xy); + let offsets_2: vec4 = unpack_u32(input.offsets.zw); + var offsets = array( - input.offsets_1.x, - input.offsets_1.y, - input.offsets_1.z, - input.offsets_1.w, - input.offsets_2.x, - input.offsets_2.y, - input.offsets_2.z, - input.offsets_2.w, + offsets_1.x, + offsets_1.y, + offsets_1.z, + offsets_1.w, + offsets_2.x, + offsets_2.y, + offsets_2.z, + offsets_2.w, ); var last_index = 7; diff --git a/wgpu/src/triangle.rs b/wgpu/src/triangle.rs index 3f633e14..3f3635cf 100644 --- a/wgpu/src/triangle.rs +++ b/wgpu/src/triangle.rs @@ -652,16 +652,18 @@ mod gradient { attributes: &wgpu::vertex_attr_array!( // Position 0 => Float32x2, - // Colors 1-4 + // Colors 1-2 1 => Uint32x4, - // Colors 5-8, + // Colors 3-4 2 => Uint32x4, - // Offsets 1-4 - 3 => Float32x4, - // Offsets 5-8 - 4 => Float32x4, + // Colors 5-6 + 3 => Uint32x4, + // Colors 7-8 + 4 => Uint32x4, + // Offsets + 5 => Uint32x4, // Direction - 5 => Float32x4 + 6 => Float32x4 ), }], }, -- cgit From 05e238e9ed5f0c6cade87228f8f3044ee26df756 Mon Sep 17 00:00:00 2001 From: Bingus Date: Thu, 8 Jun 2023 10:10:26 -0700 Subject: Adjusted offscreen pass to be a render pass vs compute for compat with web-colors flag. --- wgpu/src/backend.rs | 21 +---- wgpu/src/offscreen.rs | 164 +++++++++++++++++++++++++++++++----- wgpu/src/shader/offscreen_blit.wgsl | 37 ++++---- 3 files changed, 165 insertions(+), 57 deletions(-) (limited to 'wgpu') diff --git a/wgpu/src/backend.rs b/wgpu/src/backend.rs index 8f37f285..0735f81f 100644 --- a/wgpu/src/backend.rs +++ b/wgpu/src/backend.rs @@ -139,7 +139,7 @@ impl Backend { primitives: &[Primitive], viewport: &Viewport, overlay_text: &[T], - texture_extent: wgpu::Extent3d, + size: wgpu::Extent3d, ) -> Option { #[cfg(feature = "tracing")] let _ = info_span!("iced_wgpu::offscreen", "DRAW").entered(); @@ -159,24 +159,7 @@ impl Backend { log::info!("Texture format is {format:?}; performing conversion to rgba8.."); let pipeline = offscreen::Pipeline::new(device); - let texture = device.create_texture(&wgpu::TextureDescriptor { - label: Some("iced_wgpu.offscreen.conversion.source_texture"), - size: texture_extent, - mip_level_count: 1, - sample_count: 1, - dimension: wgpu::TextureDimension::D2, - format: wgpu::TextureFormat::Rgba8Unorm, - usage: wgpu::TextureUsages::STORAGE_BINDING - | wgpu::TextureUsages::COPY_SRC, - view_formats: &[], - }); - - let view = - texture.create_view(&wgpu::TextureViewDescriptor::default()); - - pipeline.convert(device, texture_extent, frame, &view, encoder); - - return Some(texture); + return Some(pipeline.convert(device, frame, size, encoder)); } None diff --git a/wgpu/src/offscreen.rs b/wgpu/src/offscreen.rs index 29913d02..d0758b66 100644 --- a/wgpu/src/offscreen.rs +++ b/wgpu/src/offscreen.rs @@ -1,12 +1,24 @@ use std::borrow::Cow; +use wgpu::util::DeviceExt; +use wgpu::vertex_attr_array; /// A simple compute pipeline to convert any texture to Rgba8UnormSrgb. #[derive(Debug)] pub struct Pipeline { - pipeline: wgpu::ComputePipeline, + pipeline: wgpu::RenderPipeline, + vertices: wgpu::Buffer, + indices: wgpu::Buffer, + sampler: wgpu::Sampler, layout: wgpu::BindGroupLayout, } +#[derive(Debug, Copy, Clone, bytemuck::Pod, bytemuck::Zeroable)] +#[repr(C)] +struct Vertex { + ndc: [f32; 2], + texel: [f32; 2], +} + impl Pipeline { pub fn new(device: &wgpu::Device) -> Self { let shader = @@ -17,13 +29,53 @@ impl Pipeline { ))), }); + let vertices = + device.create_buffer_init(&wgpu::util::BufferInitDescriptor { + label: Some("iced_wgpu.offscreen.vertex_buffer"), + contents: bytemuck::cast_slice(&[ + //bottom left + Vertex { + ndc: [-1.0, -1.0], + texel: [0.0, 1.0], + }, + //bottom right + Vertex { + ndc: [1.0, -1.0], + texel: [1.0, 1.0], + }, + //top right + Vertex { + ndc: [1.0, 1.0], + texel: [1.0, 0.0], + }, + //top left + Vertex { + ndc: [-1.0, 1.0], + texel: [0.0, 0.0], + }, + ]), + usage: wgpu::BufferUsages::VERTEX, + }); + + let indices = + device.create_buffer_init(&wgpu::util::BufferInitDescriptor { + label: Some("iced_wgpu.offscreen.index_buffer"), + contents: bytemuck::cast_slice(&[0u16, 1, 2, 2, 3, 0]), + usage: wgpu::BufferUsages::INDEX, + }); + + let sampler = device.create_sampler(&wgpu::SamplerDescriptor { + label: Some("iced_wgpu.offscreen.sampler"), + ..Default::default() + }); + let bind_group_layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { label: Some("iced_wgpu.offscreen.blit.bind_group_layout"), entries: &[ wgpu::BindGroupLayoutEntry { binding: 0, - visibility: wgpu::ShaderStages::COMPUTE, + visibility: wgpu::ShaderStages::FRAGMENT, ty: wgpu::BindingType::Texture { sample_type: wgpu::TextureSampleType::Float { filterable: false, @@ -35,12 +87,10 @@ impl Pipeline { }, wgpu::BindGroupLayoutEntry { binding: 1, - visibility: wgpu::ShaderStages::COMPUTE, - ty: wgpu::BindingType::StorageTexture { - access: wgpu::StorageTextureAccess::WriteOnly, - format: wgpu::TextureFormat::Rgba8Unorm, - view_dimension: wgpu::TextureViewDimension::D2, - }, + visibility: wgpu::ShaderStages::FRAGMENT, + ty: wgpu::BindingType::Sampler( + wgpu::SamplerBindingType::NonFiltering, + ), count: None, }, ], @@ -54,15 +104,56 @@ impl Pipeline { }); let pipeline = - device.create_compute_pipeline(&wgpu::ComputePipelineDescriptor { + device.create_render_pipeline(&wgpu::RenderPipelineDescriptor { label: Some("iced_wgpu.offscreen.blit.pipeline"), layout: Some(&pipeline_layout), - module: &shader, - entry_point: "main", + vertex: wgpu::VertexState { + module: &shader, + entry_point: "vs_main", + buffers: &[wgpu::VertexBufferLayout { + array_stride: std::mem::size_of::() as u64, + step_mode: wgpu::VertexStepMode::Vertex, + attributes: &vertex_attr_array![ + 0 => Float32x2, // quad ndc pos + 1 => Float32x2, // texture uv + ], + }], + }, + fragment: Some(wgpu::FragmentState { + module: &shader, + entry_point: "fs_main", + targets: &[Some(wgpu::ColorTargetState { + format: wgpu::TextureFormat::Rgba8UnormSrgb, + blend: Some(wgpu::BlendState { + color: wgpu::BlendComponent { + src_factor: wgpu::BlendFactor::SrcAlpha, + dst_factor: wgpu::BlendFactor::OneMinusSrcAlpha, + operation: wgpu::BlendOperation::Add, + }, + alpha: wgpu::BlendComponent { + src_factor: wgpu::BlendFactor::One, + dst_factor: wgpu::BlendFactor::OneMinusSrcAlpha, + operation: wgpu::BlendOperation::Add, + }, + }), + write_mask: wgpu::ColorWrites::ALL, + })], + }), + primitive: wgpu::PrimitiveState { + topology: wgpu::PrimitiveTopology::TriangleList, + front_face: wgpu::FrontFace::Cw, + ..Default::default() + }, + depth_stencil: None, + multisample: Default::default(), + multiview: None, }); Self { pipeline, + vertices, + indices, + sampler, layout: bind_group_layout, } } @@ -70,11 +161,25 @@ impl Pipeline { pub fn convert( &self, device: &wgpu::Device, - extent: wgpu::Extent3d, frame: &wgpu::TextureView, - view: &wgpu::TextureView, + size: wgpu::Extent3d, encoder: &mut wgpu::CommandEncoder, - ) { + ) -> wgpu::Texture { + let texture = device.create_texture(&wgpu::TextureDescriptor { + label: Some("iced_wgpu.offscreen.conversion.source_texture"), + size, + mip_level_count: 1, + sample_count: 1, + dimension: wgpu::TextureDimension::D2, + format: wgpu::TextureFormat::Rgba8UnormSrgb, + usage: wgpu::TextureUsages::RENDER_ATTACHMENT + | wgpu::TextureUsages::COPY_SRC, + view_formats: &[], + }); + + let view = + &texture.create_view(&wgpu::TextureViewDescriptor::default()); + let bind = device.create_bind_group(&wgpu::BindGroupDescriptor { label: Some("iced_wgpu.offscreen.blit.bind_group"), layout: &self.layout, @@ -85,18 +190,33 @@ impl Pipeline { }, wgpu::BindGroupEntry { binding: 1, - resource: wgpu::BindingResource::TextureView(view), + resource: wgpu::BindingResource::Sampler(&self.sampler), }, ], }); - let mut compute_pass = - encoder.begin_compute_pass(&wgpu::ComputePassDescriptor { - label: Some("iced_wgpu.offscreen.blit.compute_pass"), - }); + let mut pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor { + label: Some("iced_wgpu.offscreen.blit.render_pass"), + color_attachments: &[Some(wgpu::RenderPassColorAttachment { + view, + resolve_target: None, + ops: wgpu::Operations { + load: wgpu::LoadOp::Load, + store: true, + }, + })], + depth_stencil_attachment: None, + }); + + pass.set_pipeline(&self.pipeline); + pass.set_bind_group(0, &bind, &[]); + pass.set_vertex_buffer(0, self.vertices.slice(..)); + pass.set_index_buffer( + self.indices.slice(..), + wgpu::IndexFormat::Uint16, + ); + pass.draw_indexed(0..6u32, 0, 0..1); - compute_pass.set_pipeline(&self.pipeline); - compute_pass.set_bind_group(0, &bind, &[]); - compute_pass.dispatch_workgroups(extent.width, extent.height, 1); + texture } } diff --git a/wgpu/src/shader/offscreen_blit.wgsl b/wgpu/src/shader/offscreen_blit.wgsl index 9c764c36..08952d62 100644 --- a/wgpu/src/shader/offscreen_blit.wgsl +++ b/wgpu/src/shader/offscreen_blit.wgsl @@ -1,22 +1,27 @@ -@group(0) @binding(0) var u_texture: texture_2d; -@group(0) @binding(1) var out_texture: texture_storage_2d; +@group(0) @binding(0) var frame_texture: texture_2d; +@group(0) @binding(1) var frame_sampler: sampler; -fn srgb(color: f32) -> f32 { - if (color <= 0.0031308) { - return 12.92 * color; - } else { - return (1.055 * (pow(color, (1.0/2.4)))) - 0.055; - } +struct VertexInput { + @location(0) v_pos: vec2, + @location(1) texel_coord: vec2, } -@compute @workgroup_size(1) -fn main(@builtin(global_invocation_id) id: vec3) { - // texture coord must be i32 due to a naga bug: - // https://github.com/gfx-rs/naga/issues/1997 - let coords = vec2(i32(id.x), i32(id.y)); +struct VertexOutput { + @builtin(position) clip_pos: vec4, + @location(0) uv: vec2, +} + +@vertex +fn vs_main(input: VertexInput) -> VertexOutput { + var output: VertexOutput; - let src: vec4 = textureLoad(u_texture, coords, 0); - let srgb_color: vec4 = vec4(srgb(src.x), srgb(src.y), srgb(src.z), src.w); + output.clip_pos = vec4(input.v_pos, 0.0, 1.0); + output.uv = input.texel_coord; - textureStore(out_texture, coords, srgb_color); + return output; } + +@fragment +fn fs_main(input: VertexOutput) -> @location(0) vec4 { + return textureSample(frame_texture, frame_sampler, input.uv); +} \ No newline at end of file -- cgit From af099fa6d72d9c3e23c85a36aee14904526d02f0 Mon Sep 17 00:00:00 2001 From: Bingus Date: Thu, 8 Jun 2023 10:17:53 -0700 Subject: Added in check for web-colors. --- wgpu/src/offscreen.rs | 23 ++++++++++++++++------- 1 file changed, 16 insertions(+), 7 deletions(-) (limited to 'wgpu') diff --git a/wgpu/src/offscreen.rs b/wgpu/src/offscreen.rs index d0758b66..baa069e8 100644 --- a/wgpu/src/offscreen.rs +++ b/wgpu/src/offscreen.rs @@ -1,3 +1,4 @@ +use crate::graphics::color; use std::borrow::Cow; use wgpu::util::DeviceExt; use wgpu::vertex_attr_array; @@ -16,7 +17,7 @@ pub struct Pipeline { #[repr(C)] struct Vertex { ndc: [f32; 2], - texel: [f32; 2], + uv: [f32; 2], } impl Pipeline { @@ -36,22 +37,22 @@ impl Pipeline { //bottom left Vertex { ndc: [-1.0, -1.0], - texel: [0.0, 1.0], + uv: [0.0, 1.0], }, //bottom right Vertex { ndc: [1.0, -1.0], - texel: [1.0, 1.0], + uv: [1.0, 1.0], }, //top right Vertex { ndc: [1.0, 1.0], - texel: [1.0, 0.0], + uv: [1.0, 0.0], }, //top left Vertex { ndc: [-1.0, 1.0], - texel: [0.0, 0.0], + uv: [0.0, 0.0], }, ]), usage: wgpu::BufferUsages::VERTEX, @@ -123,7 +124,11 @@ impl Pipeline { module: &shader, entry_point: "fs_main", targets: &[Some(wgpu::ColorTargetState { - format: wgpu::TextureFormat::Rgba8UnormSrgb, + format: if color::GAMMA_CORRECTION { + wgpu::TextureFormat::Rgba8UnormSrgb + } else { + wgpu::TextureFormat::Rgba8Unorm + }, blend: Some(wgpu::BlendState { color: wgpu::BlendComponent { src_factor: wgpu::BlendFactor::SrcAlpha, @@ -171,7 +176,11 @@ impl Pipeline { mip_level_count: 1, sample_count: 1, dimension: wgpu::TextureDimension::D2, - format: wgpu::TextureFormat::Rgba8UnormSrgb, + format: if color::GAMMA_CORRECTION { + wgpu::TextureFormat::Rgba8UnormSrgb + } else { + wgpu::TextureFormat::Rgba8Unorm + }, usage: wgpu::TextureUsages::RENDER_ATTACHMENT | wgpu::TextureUsages::COPY_SRC, view_formats: &[], -- cgit From d955b3444da19e24bf0de6d6f432f06623ed5db2 Mon Sep 17 00:00:00 2001 From: Bingus Date: Wed, 14 Jun 2023 11:10:09 -0700 Subject: Replaced offscreen_blit.wgsl with existing blit.wgsl. --- wgpu/src/offscreen.rs | 163 +++++++++++++----------------------- wgpu/src/shader/offscreen_blit.wgsl | 27 ------ 2 files changed, 60 insertions(+), 130 deletions(-) delete mode 100644 wgpu/src/shader/offscreen_blit.wgsl (limited to 'wgpu') diff --git a/wgpu/src/offscreen.rs b/wgpu/src/offscreen.rs index baa069e8..b25602e4 100644 --- a/wgpu/src/offscreen.rs +++ b/wgpu/src/offscreen.rs @@ -1,16 +1,12 @@ use crate::graphics::color; use std::borrow::Cow; -use wgpu::util::DeviceExt; -use wgpu::vertex_attr_array; /// A simple compute pipeline to convert any texture to Rgba8UnormSrgb. #[derive(Debug)] pub struct Pipeline { pipeline: wgpu::RenderPipeline, - vertices: wgpu::Buffer, - indices: wgpu::Buffer, - sampler: wgpu::Sampler, - layout: wgpu::BindGroupLayout, + sampler_bind_group: wgpu::BindGroup, + texture_layout: wgpu::BindGroupLayout, } #[derive(Debug, Copy, Clone, bytemuck::Pod, bytemuck::Zeroable)] @@ -22,88 +18,67 @@ struct Vertex { impl Pipeline { pub fn new(device: &wgpu::Device) -> Self { - let shader = - device.create_shader_module(wgpu::ShaderModuleDescriptor { - label: Some("iced_wgpu.offscreen.blit.shader"), - source: wgpu::ShaderSource::Wgsl(Cow::Borrowed(include_str!( - "shader/offscreen_blit.wgsl" - ))), - }); - - let vertices = - device.create_buffer_init(&wgpu::util::BufferInitDescriptor { - label: Some("iced_wgpu.offscreen.vertex_buffer"), - contents: bytemuck::cast_slice(&[ - //bottom left - Vertex { - ndc: [-1.0, -1.0], - uv: [0.0, 1.0], - }, - //bottom right - Vertex { - ndc: [1.0, -1.0], - uv: [1.0, 1.0], - }, - //top right - Vertex { - ndc: [1.0, 1.0], - uv: [1.0, 0.0], - }, - //top left - Vertex { - ndc: [-1.0, 1.0], - uv: [0.0, 0.0], - }, - ]), - usage: wgpu::BufferUsages::VERTEX, - }); - - let indices = - device.create_buffer_init(&wgpu::util::BufferInitDescriptor { - label: Some("iced_wgpu.offscreen.index_buffer"), - contents: bytemuck::cast_slice(&[0u16, 1, 2, 2, 3, 0]), - usage: wgpu::BufferUsages::INDEX, - }); - let sampler = device.create_sampler(&wgpu::SamplerDescriptor { label: Some("iced_wgpu.offscreen.sampler"), ..Default::default() }); - let bind_group_layout = + //sampler in 0 + let sampler_layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { - label: Some("iced_wgpu.offscreen.blit.bind_group_layout"), - entries: &[ - wgpu::BindGroupLayoutEntry { - binding: 0, - visibility: wgpu::ShaderStages::FRAGMENT, - ty: wgpu::BindingType::Texture { - sample_type: wgpu::TextureSampleType::Float { - filterable: false, - }, - view_dimension: wgpu::TextureViewDimension::D2, - multisampled: false, + label: Some("iced_wgpu.offscreen.blit.sampler_layout"), + entries: &[wgpu::BindGroupLayoutEntry { + binding: 0, + visibility: wgpu::ShaderStages::FRAGMENT, + ty: wgpu::BindingType::Sampler( + wgpu::SamplerBindingType::NonFiltering, + ), + count: None, + }], + }); + + let sampler_bind_group = + device.create_bind_group(&wgpu::BindGroupDescriptor { + label: Some("iced_wgpu.offscreen.sampler.bind_group"), + layout: &sampler_layout, + entries: &[wgpu::BindGroupEntry { + binding: 0, + resource: wgpu::BindingResource::Sampler(&sampler), + }], + }); + + let texture_layout = + device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { + label: Some("iced_wgpu.offscreen.blit.texture_layout"), + entries: &[wgpu::BindGroupLayoutEntry { + binding: 0, + visibility: wgpu::ShaderStages::FRAGMENT, + ty: wgpu::BindingType::Texture { + sample_type: wgpu::TextureSampleType::Float { + filterable: false, }, - count: None, - }, - wgpu::BindGroupLayoutEntry { - binding: 1, - visibility: wgpu::ShaderStages::FRAGMENT, - ty: wgpu::BindingType::Sampler( - wgpu::SamplerBindingType::NonFiltering, - ), - count: None, + view_dimension: wgpu::TextureViewDimension::D2, + multisampled: false, }, - ], + count: None, + }], }); let pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor { label: Some("iced_wgpu.offscreen.blit.pipeline_layout"), - bind_group_layouts: &[&bind_group_layout], + bind_group_layouts: &[&sampler_layout, &texture_layout], push_constant_ranges: &[], }); + let shader = + device.create_shader_module(wgpu::ShaderModuleDescriptor { + label: Some("iced_wgpu.offscreen.blit.shader"), + source: wgpu::ShaderSource::Wgsl(Cow::Borrowed(include_str!( + "shader/blit.wgsl" + ))), + }); + let pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor { label: Some("iced_wgpu.offscreen.blit.pipeline"), @@ -111,14 +86,7 @@ impl Pipeline { vertex: wgpu::VertexState { module: &shader, entry_point: "vs_main", - buffers: &[wgpu::VertexBufferLayout { - array_stride: std::mem::size_of::() as u64, - step_mode: wgpu::VertexStepMode::Vertex, - attributes: &vertex_attr_array![ - 0 => Float32x2, // quad ndc pos - 1 => Float32x2, // texture uv - ], - }], + buffers: &[], }, fragment: Some(wgpu::FragmentState { module: &shader, @@ -156,10 +124,8 @@ impl Pipeline { Self { pipeline, - vertices, - indices, - sampler, - layout: bind_group_layout, + sampler_bind_group, + texture_layout, } } @@ -189,20 +155,15 @@ impl Pipeline { let view = &texture.create_view(&wgpu::TextureViewDescriptor::default()); - let bind = device.create_bind_group(&wgpu::BindGroupDescriptor { - label: Some("iced_wgpu.offscreen.blit.bind_group"), - layout: &self.layout, - entries: &[ - wgpu::BindGroupEntry { + let texture_bind_group = + device.create_bind_group(&wgpu::BindGroupDescriptor { + label: Some("iced_wgpu.offscreen.blit.texture_bind_group"), + layout: &self.texture_layout, + entries: &[wgpu::BindGroupEntry { binding: 0, resource: wgpu::BindingResource::TextureView(frame), - }, - wgpu::BindGroupEntry { - binding: 1, - resource: wgpu::BindingResource::Sampler(&self.sampler), - }, - ], - }); + }], + }); let mut pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor { label: Some("iced_wgpu.offscreen.blit.render_pass"), @@ -218,13 +179,9 @@ impl Pipeline { }); pass.set_pipeline(&self.pipeline); - pass.set_bind_group(0, &bind, &[]); - pass.set_vertex_buffer(0, self.vertices.slice(..)); - pass.set_index_buffer( - self.indices.slice(..), - wgpu::IndexFormat::Uint16, - ); - pass.draw_indexed(0..6u32, 0, 0..1); + pass.set_bind_group(0, &self.sampler_bind_group, &[]); + pass.set_bind_group(1, &texture_bind_group, &[]); + pass.draw(0..6, 0..1); texture } diff --git a/wgpu/src/shader/offscreen_blit.wgsl b/wgpu/src/shader/offscreen_blit.wgsl deleted file mode 100644 index 08952d62..00000000 --- a/wgpu/src/shader/offscreen_blit.wgsl +++ /dev/null @@ -1,27 +0,0 @@ -@group(0) @binding(0) var frame_texture: texture_2d; -@group(0) @binding(1) var frame_sampler: sampler; - -struct VertexInput { - @location(0) v_pos: vec2, - @location(1) texel_coord: vec2, -} - -struct VertexOutput { - @builtin(position) clip_pos: vec4, - @location(0) uv: vec2, -} - -@vertex -fn vs_main(input: VertexInput) -> VertexOutput { - var output: VertexOutput; - - output.clip_pos = vec4(input.v_pos, 0.0, 1.0); - output.uv = input.texel_coord; - - return output; -} - -@fragment -fn fs_main(input: VertexOutput) -> @location(0) vec4 { - return textureSample(frame_texture, frame_sampler, input.uv); -} \ No newline at end of file -- cgit From 93673836cd2932351b25dea6ca46ae50d0e35ace Mon Sep 17 00:00:00 2001 From: Bingus Date: Wed, 14 Jun 2023 11:45:29 -0700 Subject: Fixed documentation --- wgpu/src/backend.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'wgpu') diff --git a/wgpu/src/backend.rs b/wgpu/src/backend.rs index 0735f81f..7c962308 100644 --- a/wgpu/src/backend.rs +++ b/wgpu/src/backend.rs @@ -124,7 +124,7 @@ impl Backend { } /// Performs an offscreen render pass. If the `format` selected by WGPU is not - /// `wgpu::TextureFormat::Rgba8UnormSrgb`, a conversion compute pipeline will run. + /// `wgpu::TextureFormat::Rgba8UnormSrgb`, it will be run through a blit. /// /// Returns `None` if the `frame` is `Rgba8UnormSrgb`, else returns the newly /// converted texture view in `Rgba8UnormSrgb`. -- cgit From 0c65936664dfba70fe5d22d1347c14cf9f85d0da Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Fri, 16 Jun 2023 15:50:03 +0200 Subject: Update `glyphon` and `cosmic-text` --- wgpu/Cargo.toml | 2 +- wgpu/src/text.rs | 31 +++++++++++++------------------ 2 files changed, 14 insertions(+), 19 deletions(-) (limited to 'wgpu') diff --git a/wgpu/Cargo.toml b/wgpu/Cargo.toml index 7e50dff2..f3a83acb 100644 --- a/wgpu/Cargo.toml +++ b/wgpu/Cargo.toml @@ -45,7 +45,7 @@ path = "../graphics" [dependencies.glyphon] version = "0.2" git = "https://github.com/hecrj/glyphon.git" -rev = "26f92369da3704988e3e27f0b35e705c6b2de203" +rev = "8dbf36020e5759fa9144517b321372266160113e" [dependencies.glam] version = "0.24" diff --git a/wgpu/src/text.rs b/wgpu/src/text.rs index 0d88865c..6a552270 100644 --- a/wgpu/src/text.rs +++ b/wgpu/src/text.rs @@ -53,7 +53,7 @@ impl Pipeline { } pub fn load_font(&mut self, bytes: Cow<'static, [u8]>) { - self.font_system.get_mut().db_mut().load_font_source( + let _ = self.font_system.get_mut().db_mut().load_font_source( glyphon::fontdb::Source::Binary(Arc::new(bytes.into_owned())), ); } @@ -116,15 +116,7 @@ impl Pipeline { let buffer = self.render_cache.get(key).expect("Get cached buffer"); - let (total_lines, max_width) = buffer - .layout_runs() - .enumerate() - .fold((0, 0.0), |(_, max), (i, buffer)| { - (i + 1, buffer.line_w.max(max)) - }); - - let total_height = - total_lines as f32 * buffer.metrics().line_height; + let (max_width, total_height) = measure(buffer); let x = section.bounds.x * scale_factor; let y = section.bounds.y * scale_factor; @@ -265,14 +257,7 @@ impl Pipeline { }, ); - let (total_lines, max_width) = paragraph - .layout_runs() - .enumerate() - .fold((0, 0.0), |(_, max), (i, buffer)| { - (i + 1, buffer.line_w.max(max)) - }); - - (max_width, line_height * total_lines as f32) + measure(paragraph) } pub fn hit_test( @@ -312,6 +297,16 @@ impl Pipeline { } } +fn measure(buffer: &glyphon::Buffer) -> (f32, f32) { + let (width, total_lines) = buffer + .layout_runs() + .fold((0.0, 0usize), |(width, total_lines), run| { + (run.line_w.max(width), total_lines + 1) + }); + + (width, total_lines as f32 * buffer.metrics().line_height) +} + fn to_family(family: font::Family) -> glyphon::Family<'static> { match family { font::Family::Name(name) => glyphon::Family::Name(name), -- cgit From 5bc7cbf5bca039ec3a4cbe82b161c087a4b39680 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Tue, 20 Jun 2023 06:22:17 +0200 Subject: Use subpixel glyph positioning and layout linearity ... for offsetting and scaling text --- wgpu/Cargo.toml | 2 +- wgpu/src/backend.rs | 6 ------ wgpu/src/text.rs | 43 ++++++++++++++++++------------------------- 3 files changed, 19 insertions(+), 32 deletions(-) (limited to 'wgpu') diff --git a/wgpu/Cargo.toml b/wgpu/Cargo.toml index f3a83acb..15db5b5d 100644 --- a/wgpu/Cargo.toml +++ b/wgpu/Cargo.toml @@ -45,7 +45,7 @@ path = "../graphics" [dependencies.glyphon] version = "0.2" git = "https://github.com/hecrj/glyphon.git" -rev = "8dbf36020e5759fa9144517b321372266160113e" +rev = "8324f20158a62f8520bad4ed09f6aa5552f8f2a6" [dependencies.glam] version = "0.24" diff --git a/wgpu/src/backend.rs b/wgpu/src/backend.rs index b524c615..eecba2f1 100644 --- a/wgpu/src/backend.rs +++ b/wgpu/src/backend.rs @@ -334,12 +334,6 @@ impl Backend { } } -impl iced_graphics::Backend for Backend { - fn trim_measurements(&mut self) { - self.text_pipeline.trim_measurement_cache() - } -} - impl backend::Text for Backend { const ICON_FONT: Font = Font::with_name("Iced-Icons"); const CHECKMARK_ICON: char = '\u{f00c}'; diff --git a/wgpu/src/text.rs b/wgpu/src/text.rs index 6a552270..71dcc249 100644 --- a/wgpu/src/text.rs +++ b/wgpu/src/text.rs @@ -18,8 +18,7 @@ pub struct Pipeline { renderers: Vec, atlas: glyphon::TextAtlas, prepare_layer: usize, - measurement_cache: RefCell, - render_cache: Cache, + cache: RefCell, } impl Pipeline { @@ -47,8 +46,7 @@ impl Pipeline { }, ), prepare_layer: 0, - measurement_cache: RefCell::new(Cache::new()), - render_cache: Cache::new(), + cache: RefCell::new(Cache::new()), } } @@ -78,25 +76,25 @@ impl Pipeline { let font_system = self.font_system.get_mut(); let renderer = &mut self.renderers[self.prepare_layer]; + let cache = self.cache.get_mut(); let keys: Vec<_> = sections .iter() .map(|section| { - let (key, _) = self.render_cache.allocate( + let (key, _) = cache.allocate( font_system, Key { content: section.content, - size: section.size * scale_factor, + size: section.size, line_height: f32::from( section .line_height .to_absolute(Pixels(section.size)), - ) * scale_factor, + ), font: section.font, bounds: Size { - width: (section.bounds.width * scale_factor).ceil(), - height: (section.bounds.height * scale_factor) - .ceil(), + width: section.bounds.width, + height: section.bounds.height, }, shaping: section.shaping, }, @@ -113,14 +111,16 @@ impl Pipeline { .iter() .zip(keys.iter()) .filter_map(|(section, key)| { - let buffer = - self.render_cache.get(key).expect("Get cached buffer"); - - let (max_width, total_height) = measure(buffer); + let buffer = cache.get(key).expect("Get cached buffer"); let x = section.bounds.x * scale_factor; let y = section.bounds.y * scale_factor; + let (max_width, total_height) = measure(buffer); + + let max_width = max_width * scale_factor; + let total_height = total_height * scale_factor; + let left = match section.horizontal_alignment { alignment::Horizontal::Left => x, alignment::Horizontal::Center => x - max_width / 2.0, @@ -142,14 +142,11 @@ impl Pipeline { let clip_bounds = bounds.intersection(§ion_bounds)?; - // TODO: Subpixel glyph positioning - let left = left.round() as i32; - let top = top.round() as i32; - Some(glyphon::TextArea { buffer, left, top, + scale: scale_factor, bounds: glyphon::TextBounds { left: clip_bounds.x as i32, top: clip_bounds.y as i32, @@ -227,7 +224,7 @@ impl Pipeline { pub fn end_frame(&mut self) { self.atlas.trim(); - self.render_cache.trim(); + self.cache.get_mut().trim(); self.prepare_layer = 0; } @@ -241,7 +238,7 @@ impl Pipeline { bounds: Size, shaping: Shaping, ) -> (f32, f32) { - let mut measurement_cache = self.measurement_cache.borrow_mut(); + let mut measurement_cache = self.cache.borrow_mut(); let line_height = f32::from(line_height.to_absolute(Pixels(size))); @@ -271,7 +268,7 @@ impl Pipeline { point: Point, _nearest_only: bool, ) -> Option { - let mut measurement_cache = self.measurement_cache.borrow_mut(); + let mut measurement_cache = self.cache.borrow_mut(); let line_height = f32::from(line_height.to_absolute(Pixels(size))); @@ -291,10 +288,6 @@ impl Pipeline { Some(Hit::CharOffset(cursor.index)) } - - pub fn trim_measurement_cache(&mut self) { - self.measurement_cache.borrow_mut().trim(); - } } fn measure(buffer: &glyphon::Buffer) -> (f32, f32) { -- cgit From ef87ff1e8ab24279a551b3eef6bacd8813712530 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Wed, 21 Jun 2023 21:47:29 +0200 Subject: Clear text caches after a font is loaded --- wgpu/src/text.rs | 2 ++ 1 file changed, 2 insertions(+) (limited to 'wgpu') diff --git a/wgpu/src/text.rs b/wgpu/src/text.rs index 71dcc249..c9188bd1 100644 --- a/wgpu/src/text.rs +++ b/wgpu/src/text.rs @@ -54,6 +54,8 @@ impl Pipeline { let _ = self.font_system.get_mut().db_mut().load_font_source( glyphon::fontdb::Source::Binary(Arc::new(bytes.into_owned())), ); + + self.cache = RefCell::new(Cache::new()); } pub fn prepare( -- cgit From 5b6e205e998cbb20b3c8aaff8b515d78315d6703 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Tue, 27 Jun 2023 20:26:13 +0200 Subject: Simplify `offscreen` API as `color` module in `iced_wgpu` --- wgpu/src/backend.rs | 44 +--------- wgpu/src/color.rs | 165 ++++++++++++++++++++++++++++++++++++ wgpu/src/lib.rs | 2 +- wgpu/src/offscreen.rs | 188 ------------------------------------------ wgpu/src/window/compositor.rs | 17 +++- 5 files changed, 180 insertions(+), 236 deletions(-) create mode 100644 wgpu/src/color.rs delete mode 100644 wgpu/src/offscreen.rs (limited to 'wgpu') diff --git a/wgpu/src/backend.rs b/wgpu/src/backend.rs index 7c962308..b524c615 100644 --- a/wgpu/src/backend.rs +++ b/wgpu/src/backend.rs @@ -1,3 +1,4 @@ +use crate::core; use crate::core::{Color, Font, Point, Size}; use crate::graphics::backend; use crate::graphics::color; @@ -5,7 +6,6 @@ use crate::graphics::{Primitive, Transformation, Viewport}; use crate::quad; use crate::text; use crate::triangle; -use crate::{core, offscreen}; use crate::{Layer, Settings}; #[cfg(feature = "tracing")] @@ -123,48 +123,6 @@ impl Backend { self.image_pipeline.end_frame(); } - /// Performs an offscreen render pass. If the `format` selected by WGPU is not - /// `wgpu::TextureFormat::Rgba8UnormSrgb`, it will be run through a blit. - /// - /// Returns `None` if the `frame` is `Rgba8UnormSrgb`, else returns the newly - /// converted texture view in `Rgba8UnormSrgb`. - pub fn offscreen>( - &mut self, - device: &wgpu::Device, - queue: &wgpu::Queue, - encoder: &mut wgpu::CommandEncoder, - clear_color: Option, - frame: &wgpu::TextureView, - format: wgpu::TextureFormat, - primitives: &[Primitive], - viewport: &Viewport, - overlay_text: &[T], - size: wgpu::Extent3d, - ) -> Option { - #[cfg(feature = "tracing")] - let _ = info_span!("iced_wgpu::offscreen", "DRAW").entered(); - - self.present( - device, - queue, - encoder, - clear_color, - frame, - primitives, - viewport, - overlay_text, - ); - - if format != wgpu::TextureFormat::Rgba8UnormSrgb { - log::info!("Texture format is {format:?}; performing conversion to rgba8.."); - let pipeline = offscreen::Pipeline::new(device); - - return Some(pipeline.convert(device, frame, size, encoder)); - } - - None - } - fn prepare_text( &mut self, device: &wgpu::Device, diff --git a/wgpu/src/color.rs b/wgpu/src/color.rs new file mode 100644 index 00000000..a1025601 --- /dev/null +++ b/wgpu/src/color.rs @@ -0,0 +1,165 @@ +use std::borrow::Cow; + +pub fn convert( + device: &wgpu::Device, + encoder: &mut wgpu::CommandEncoder, + source: wgpu::Texture, + format: wgpu::TextureFormat, +) -> wgpu::Texture { + if source.format() == format { + return source; + } + + let sampler = device.create_sampler(&wgpu::SamplerDescriptor { + label: Some("iced_wgpu.offscreen.sampler"), + ..Default::default() + }); + + //sampler in 0 + let sampler_layout = + device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { + label: Some("iced_wgpu.offscreen.blit.sampler_layout"), + entries: &[wgpu::BindGroupLayoutEntry { + binding: 0, + visibility: wgpu::ShaderStages::FRAGMENT, + ty: wgpu::BindingType::Sampler( + wgpu::SamplerBindingType::NonFiltering, + ), + count: None, + }], + }); + + let sampler_bind_group = + device.create_bind_group(&wgpu::BindGroupDescriptor { + label: Some("iced_wgpu.offscreen.sampler.bind_group"), + layout: &sampler_layout, + entries: &[wgpu::BindGroupEntry { + binding: 0, + resource: wgpu::BindingResource::Sampler(&sampler), + }], + }); + + let texture_layout = + device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { + label: Some("iced_wgpu.offscreen.blit.texture_layout"), + entries: &[wgpu::BindGroupLayoutEntry { + binding: 0, + visibility: wgpu::ShaderStages::FRAGMENT, + ty: wgpu::BindingType::Texture { + sample_type: wgpu::TextureSampleType::Float { + filterable: false, + }, + view_dimension: wgpu::TextureViewDimension::D2, + multisampled: false, + }, + count: None, + }], + }); + + let pipeline_layout = + device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor { + label: Some("iced_wgpu.offscreen.blit.pipeline_layout"), + bind_group_layouts: &[&sampler_layout, &texture_layout], + push_constant_ranges: &[], + }); + + let shader = device.create_shader_module(wgpu::ShaderModuleDescriptor { + label: Some("iced_wgpu.offscreen.blit.shader"), + source: wgpu::ShaderSource::Wgsl(Cow::Borrowed(include_str!( + "shader/blit.wgsl" + ))), + }); + + let pipeline = + device.create_render_pipeline(&wgpu::RenderPipelineDescriptor { + label: Some("iced_wgpu.offscreen.blit.pipeline"), + layout: Some(&pipeline_layout), + vertex: wgpu::VertexState { + module: &shader, + entry_point: "vs_main", + buffers: &[], + }, + fragment: Some(wgpu::FragmentState { + module: &shader, + entry_point: "fs_main", + targets: &[Some(wgpu::ColorTargetState { + format, + blend: Some(wgpu::BlendState { + color: wgpu::BlendComponent { + src_factor: wgpu::BlendFactor::SrcAlpha, + dst_factor: wgpu::BlendFactor::OneMinusSrcAlpha, + operation: wgpu::BlendOperation::Add, + }, + alpha: wgpu::BlendComponent { + src_factor: wgpu::BlendFactor::One, + dst_factor: wgpu::BlendFactor::OneMinusSrcAlpha, + operation: wgpu::BlendOperation::Add, + }, + }), + write_mask: wgpu::ColorWrites::ALL, + })], + }), + primitive: wgpu::PrimitiveState { + topology: wgpu::PrimitiveTopology::TriangleList, + front_face: wgpu::FrontFace::Cw, + ..Default::default() + }, + depth_stencil: None, + multisample: Default::default(), + multiview: None, + }); + + let texture = device.create_texture(&wgpu::TextureDescriptor { + label: Some("iced_wgpu.offscreen.conversion.source_texture"), + size: source.size(), + mip_level_count: 1, + sample_count: 1, + dimension: wgpu::TextureDimension::D2, + format, + usage: wgpu::TextureUsages::RENDER_ATTACHMENT + | wgpu::TextureUsages::COPY_SRC, + view_formats: &[], + }); + + let view = &texture.create_view(&wgpu::TextureViewDescriptor::default()); + + let texture_bind_group = + device.create_bind_group(&wgpu::BindGroupDescriptor { + label: Some("iced_wgpu.offscreen.blit.texture_bind_group"), + layout: &texture_layout, + entries: &[wgpu::BindGroupEntry { + binding: 0, + resource: wgpu::BindingResource::TextureView( + &source + .create_view(&wgpu::TextureViewDescriptor::default()), + ), + }], + }); + + let mut pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor { + label: Some("iced_wgpu.offscreen.blit.render_pass"), + color_attachments: &[Some(wgpu::RenderPassColorAttachment { + view, + resolve_target: None, + ops: wgpu::Operations { + load: wgpu::LoadOp::Load, + store: true, + }, + })], + depth_stencil_attachment: None, + }); + + pass.set_pipeline(&pipeline); + pass.set_bind_group(0, &sampler_bind_group, &[]); + pass.set_bind_group(1, &texture_bind_group, &[]); + pass.draw(0..6, 0..1); + + texture +} + +#[derive(Debug, Copy, Clone, bytemuck::Pod, bytemuck::Zeroable)] +#[repr(C)] +struct Vertex { + ndc: [f32; 2], + uv: [f32; 2], +} diff --git a/wgpu/src/lib.rs b/wgpu/src/lib.rs index 827acb89..86a962a5 100644 --- a/wgpu/src/lib.rs +++ b/wgpu/src/lib.rs @@ -46,7 +46,7 @@ pub mod geometry; mod backend; mod buffer; -mod offscreen; +mod color; mod quad; mod text; mod triangle; diff --git a/wgpu/src/offscreen.rs b/wgpu/src/offscreen.rs deleted file mode 100644 index b25602e4..00000000 --- a/wgpu/src/offscreen.rs +++ /dev/null @@ -1,188 +0,0 @@ -use crate::graphics::color; -use std::borrow::Cow; - -/// A simple compute pipeline to convert any texture to Rgba8UnormSrgb. -#[derive(Debug)] -pub struct Pipeline { - pipeline: wgpu::RenderPipeline, - sampler_bind_group: wgpu::BindGroup, - texture_layout: wgpu::BindGroupLayout, -} - -#[derive(Debug, Copy, Clone, bytemuck::Pod, bytemuck::Zeroable)] -#[repr(C)] -struct Vertex { - ndc: [f32; 2], - uv: [f32; 2], -} - -impl Pipeline { - pub fn new(device: &wgpu::Device) -> Self { - let sampler = device.create_sampler(&wgpu::SamplerDescriptor { - label: Some("iced_wgpu.offscreen.sampler"), - ..Default::default() - }); - - //sampler in 0 - let sampler_layout = - device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { - label: Some("iced_wgpu.offscreen.blit.sampler_layout"), - entries: &[wgpu::BindGroupLayoutEntry { - binding: 0, - visibility: wgpu::ShaderStages::FRAGMENT, - ty: wgpu::BindingType::Sampler( - wgpu::SamplerBindingType::NonFiltering, - ), - count: None, - }], - }); - - let sampler_bind_group = - device.create_bind_group(&wgpu::BindGroupDescriptor { - label: Some("iced_wgpu.offscreen.sampler.bind_group"), - layout: &sampler_layout, - entries: &[wgpu::BindGroupEntry { - binding: 0, - resource: wgpu::BindingResource::Sampler(&sampler), - }], - }); - - let texture_layout = - device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { - label: Some("iced_wgpu.offscreen.blit.texture_layout"), - entries: &[wgpu::BindGroupLayoutEntry { - binding: 0, - visibility: wgpu::ShaderStages::FRAGMENT, - ty: wgpu::BindingType::Texture { - sample_type: wgpu::TextureSampleType::Float { - filterable: false, - }, - view_dimension: wgpu::TextureViewDimension::D2, - multisampled: false, - }, - count: None, - }], - }); - - let pipeline_layout = - device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor { - label: Some("iced_wgpu.offscreen.blit.pipeline_layout"), - bind_group_layouts: &[&sampler_layout, &texture_layout], - push_constant_ranges: &[], - }); - - let shader = - device.create_shader_module(wgpu::ShaderModuleDescriptor { - label: Some("iced_wgpu.offscreen.blit.shader"), - source: wgpu::ShaderSource::Wgsl(Cow::Borrowed(include_str!( - "shader/blit.wgsl" - ))), - }); - - let pipeline = - device.create_render_pipeline(&wgpu::RenderPipelineDescriptor { - label: Some("iced_wgpu.offscreen.blit.pipeline"), - layout: Some(&pipeline_layout), - vertex: wgpu::VertexState { - module: &shader, - entry_point: "vs_main", - buffers: &[], - }, - fragment: Some(wgpu::FragmentState { - module: &shader, - entry_point: "fs_main", - targets: &[Some(wgpu::ColorTargetState { - format: if color::GAMMA_CORRECTION { - wgpu::TextureFormat::Rgba8UnormSrgb - } else { - wgpu::TextureFormat::Rgba8Unorm - }, - blend: Some(wgpu::BlendState { - color: wgpu::BlendComponent { - src_factor: wgpu::BlendFactor::SrcAlpha, - dst_factor: wgpu::BlendFactor::OneMinusSrcAlpha, - operation: wgpu::BlendOperation::Add, - }, - alpha: wgpu::BlendComponent { - src_factor: wgpu::BlendFactor::One, - dst_factor: wgpu::BlendFactor::OneMinusSrcAlpha, - operation: wgpu::BlendOperation::Add, - }, - }), - write_mask: wgpu::ColorWrites::ALL, - })], - }), - primitive: wgpu::PrimitiveState { - topology: wgpu::PrimitiveTopology::TriangleList, - front_face: wgpu::FrontFace::Cw, - ..Default::default() - }, - depth_stencil: None, - multisample: Default::default(), - multiview: None, - }); - - Self { - pipeline, - sampler_bind_group, - texture_layout, - } - } - - pub fn convert( - &self, - device: &wgpu::Device, - frame: &wgpu::TextureView, - size: wgpu::Extent3d, - encoder: &mut wgpu::CommandEncoder, - ) -> wgpu::Texture { - let texture = device.create_texture(&wgpu::TextureDescriptor { - label: Some("iced_wgpu.offscreen.conversion.source_texture"), - size, - mip_level_count: 1, - sample_count: 1, - dimension: wgpu::TextureDimension::D2, - format: if color::GAMMA_CORRECTION { - wgpu::TextureFormat::Rgba8UnormSrgb - } else { - wgpu::TextureFormat::Rgba8Unorm - }, - usage: wgpu::TextureUsages::RENDER_ATTACHMENT - | wgpu::TextureUsages::COPY_SRC, - view_formats: &[], - }); - - let view = - &texture.create_view(&wgpu::TextureViewDescriptor::default()); - - let texture_bind_group = - device.create_bind_group(&wgpu::BindGroupDescriptor { - label: Some("iced_wgpu.offscreen.blit.texture_bind_group"), - layout: &self.texture_layout, - entries: &[wgpu::BindGroupEntry { - binding: 0, - resource: wgpu::BindingResource::TextureView(frame), - }], - }); - - let mut pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor { - label: Some("iced_wgpu.offscreen.blit.render_pass"), - color_attachments: &[Some(wgpu::RenderPassColorAttachment { - view, - resolve_target: None, - ops: wgpu::Operations { - load: wgpu::LoadOp::Load, - store: true, - }, - })], - depth_stencil_attachment: None, - }); - - pass.set_pipeline(&self.pipeline); - pass.set_bind_group(0, &self.sampler_bind_group, &[]); - pass.set_bind_group(1, &texture_bind_group, &[]); - pass.draw(0..6, 0..1); - - texture - } -} diff --git a/wgpu/src/window/compositor.rs b/wgpu/src/window/compositor.rs index 43c3dce5..1cfd7b67 100644 --- a/wgpu/src/window/compositor.rs +++ b/wgpu/src/window/compositor.rs @@ -345,17 +345,26 @@ pub fn screenshot>( let view = texture.create_view(&wgpu::TextureViewDescriptor::default()); - let rgba_texture = backend.offscreen( + backend.present( &compositor.device, &compositor.queue, &mut encoder, Some(background_color), &view, - compositor.format, primitives, viewport, overlay, - texture_extent, + ); + + let texture = crate::color::convert( + &compositor.device, + &mut encoder, + texture, + if color::GAMMA_CORRECTION { + wgpu::TextureFormat::Rgba8UnormSrgb + } else { + wgpu::TextureFormat::Rgba8Unorm + }, ); let output_buffer = @@ -368,7 +377,7 @@ pub fn screenshot>( }); encoder.copy_texture_to_buffer( - rgba_texture.unwrap_or(texture).as_image_copy(), + texture.as_image_copy(), wgpu::ImageCopyBuffer { buffer: &output_buffer, layout: wgpu::ImageDataLayout { -- cgit From 78ad365db232e53cbdf12105e40c1dbe87a3238c Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Wed, 28 Jun 2023 00:35:37 +0200 Subject: Reuse entries in `text::Cache` in `iced_wgpu` --- wgpu/src/backend.rs | 2 +- wgpu/src/text.rs | 81 +++++++++++++++++++++++++++++++++++------------------ 2 files changed, 54 insertions(+), 29 deletions(-) (limited to 'wgpu') diff --git a/wgpu/src/backend.rs b/wgpu/src/backend.rs index eecba2f1..f8829e47 100644 --- a/wgpu/src/backend.rs +++ b/wgpu/src/backend.rs @@ -355,7 +355,7 @@ impl backend::Text for Backend { font: Font, bounds: Size, shaping: core::text::Shaping, - ) -> (f32, f32) { + ) -> Size { self.text_pipeline.measure( contents, size, diff --git a/wgpu/src/text.rs b/wgpu/src/text.rs index c9188bd1..04943049 100644 --- a/wgpu/src/text.rs +++ b/wgpu/src/text.rs @@ -113,15 +113,13 @@ impl Pipeline { .iter() .zip(keys.iter()) .filter_map(|(section, key)| { - let buffer = cache.get(key).expect("Get cached buffer"); + let entry = cache.get(key).expect("Get cached buffer"); let x = section.bounds.x * scale_factor; let y = section.bounds.y * scale_factor; - let (max_width, total_height) = measure(buffer); - - let max_width = max_width * scale_factor; - let total_height = total_height * scale_factor; + let max_width = entry.bounds.width * scale_factor; + let total_height = entry.bounds.height * scale_factor; let left = match section.horizontal_alignment { alignment::Horizontal::Left => x, @@ -145,7 +143,7 @@ impl Pipeline { let clip_bounds = bounds.intersection(§ion_bounds)?; Some(glyphon::TextArea { - buffer, + buffer: &entry.buffer, left, top, scale: scale_factor, @@ -239,12 +237,12 @@ impl Pipeline { font: Font, bounds: Size, shaping: Shaping, - ) -> (f32, f32) { + ) -> Size { let mut measurement_cache = self.cache.borrow_mut(); let line_height = f32::from(line_height.to_absolute(Pixels(size))); - let (_, paragraph) = measurement_cache.allocate( + let (_, entry) = measurement_cache.allocate( &mut self.font_system.borrow_mut(), Key { content, @@ -256,7 +254,7 @@ impl Pipeline { }, ); - measure(paragraph) + entry.bounds } pub fn hit_test( @@ -274,7 +272,7 @@ impl Pipeline { let line_height = f32::from(line_height.to_absolute(Pixels(size))); - let (_, paragraph) = measurement_cache.allocate( + let (_, entry) = measurement_cache.allocate( &mut self.font_system.borrow_mut(), Key { content, @@ -286,20 +284,20 @@ impl Pipeline { }, ); - let cursor = paragraph.hit(point.x, point.y)?; + let cursor = entry.buffer.hit(point.x, point.y)?; Some(Hit::CharOffset(cursor.index)) } } -fn measure(buffer: &glyphon::Buffer) -> (f32, f32) { +fn measure(buffer: &glyphon::Buffer) -> Size { let (width, total_lines) = buffer .layout_runs() .fold((0.0, 0usize), |(width, total_lines), run| { (run.line_w.max(width), total_lines + 1) }); - (width, total_lines as f32 * buffer.metrics().line_height) + Size::new(width, total_lines as f32 * buffer.metrics().line_height) } fn to_family(family: font::Family) -> glyphon::Family<'static> { @@ -349,11 +347,17 @@ fn to_shaping(shaping: Shaping) -> glyphon::Shaping { } struct Cache { - entries: FxHashMap, + entries: FxHashMap, + bound_entries: FxHashMap, recently_used: FxHashSet, hasher: HashBuilder, } +struct Entry { + buffer: glyphon::Buffer, + bounds: Size, +} + #[cfg(not(target_arch = "wasm32"))] type HashBuilder = twox_hash::RandomXxHashBuilder64; @@ -364,12 +368,13 @@ impl Cache { fn new() -> Self { Self { entries: FxHashMap::default(), + bound_entries: FxHashMap::default(), recently_used: FxHashSet::default(), hasher: HashBuilder::default(), } } - fn get(&self, key: &KeyHash) -> Option<&glyphon::Buffer> { + fn get(&self, key: &KeyHash) -> Option<&Entry> { self.entries.get(key) } @@ -377,20 +382,15 @@ impl Cache { &mut self, font_system: &mut glyphon::FontSystem, key: Key<'_>, - ) -> (KeyHash, &mut glyphon::Buffer) { - let hash = { - let mut hasher = self.hasher.build_hasher(); + ) -> (KeyHash, &mut Entry) { + let hash = key.hash(self.hasher.build_hasher()); - key.content.hash(&mut hasher); - key.size.to_bits().hash(&mut hasher); - key.line_height.to_bits().hash(&mut hasher); - key.font.hash(&mut hasher); - key.bounds.width.to_bits().hash(&mut hasher); - key.bounds.height.to_bits().hash(&mut hasher); - key.shaping.hash(&mut hasher); + if let Some(bound_hash) = self.bound_entries.get(&hash) { + let _ = self.recently_used.insert(hash); + let _ = self.recently_used.insert(*bound_hash); - hasher.finish() - }; + return (*bound_hash, self.entries.get_mut(&bound_hash).unwrap()); + } if let hash_map::Entry::Vacant(entry) = self.entries.entry(hash) { let metrics = glyphon::Metrics::new(key.size, key.line_height); @@ -411,7 +411,16 @@ impl Cache { to_shaping(key.shaping), ); - let _ = entry.insert(buffer); + let bounds = measure(&buffer); + + let _ = entry.insert(Entry { buffer, bounds }); + + if key.bounds != bounds { + let _ = self.bound_entries.insert( + Key { bounds, ..key }.hash(self.hasher.build_hasher()), + hash, + ); + } } let _ = self.recently_used.insert(hash); @@ -422,6 +431,8 @@ impl Cache { fn trim(&mut self) { self.entries .retain(|key, _| self.recently_used.contains(key)); + self.bound_entries + .retain(|key, _| self.recently_used.contains(key)); self.recently_used.clear(); } @@ -437,4 +448,18 @@ struct Key<'a> { shaping: Shaping, } +impl Key<'_> { + fn hash(self, mut hasher: H) -> KeyHash { + self.content.hash(&mut hasher); + self.size.to_bits().hash(&mut hasher); + self.line_height.to_bits().hash(&mut hasher); + self.font.hash(&mut hasher); + self.bounds.width.to_bits().hash(&mut hasher); + self.bounds.height.to_bits().hash(&mut hasher); + self.shaping.hash(&mut hasher); + + hasher.finish() + } +} + type KeyHash = u64; -- cgit From 73dca5e323756ad29003517587c4092e8b6a246d Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Wed, 28 Jun 2023 00:44:23 +0200 Subject: Reuse entries in `text::Cache` in `iced_tiny_skia` --- wgpu/src/text.rs | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) (limited to 'wgpu') diff --git a/wgpu/src/text.rs b/wgpu/src/text.rs index 04943049..e0484239 100644 --- a/wgpu/src/text.rs +++ b/wgpu/src/text.rs @@ -348,7 +348,7 @@ fn to_shaping(shaping: Shaping) -> glyphon::Shaping { struct Cache { entries: FxHashMap, - bound_entries: FxHashMap, + measurements: FxHashMap, recently_used: FxHashSet, hasher: HashBuilder, } @@ -368,7 +368,7 @@ impl Cache { fn new() -> Self { Self { entries: FxHashMap::default(), - bound_entries: FxHashMap::default(), + measurements: FxHashMap::default(), recently_used: FxHashSet::default(), hasher: HashBuilder::default(), } @@ -385,11 +385,14 @@ impl Cache { ) -> (KeyHash, &mut Entry) { let hash = key.hash(self.hasher.build_hasher()); - if let Some(bound_hash) = self.bound_entries.get(&hash) { + if let Some(measured_hash) = self.measurements.get(&hash) { let _ = self.recently_used.insert(hash); - let _ = self.recently_used.insert(*bound_hash); + let _ = self.recently_used.insert(*measured_hash); - return (*bound_hash, self.entries.get_mut(&bound_hash).unwrap()); + return ( + *measured_hash, + self.entries.get_mut(&measured_hash).unwrap(), + ); } if let hash_map::Entry::Vacant(entry) = self.entries.entry(hash) { @@ -416,7 +419,7 @@ impl Cache { let _ = entry.insert(Entry { buffer, bounds }); if key.bounds != bounds { - let _ = self.bound_entries.insert( + let _ = self.measurements.insert( Key { bounds, ..key }.hash(self.hasher.build_hasher()), hash, ); @@ -431,7 +434,7 @@ impl Cache { fn trim(&mut self) { self.entries .retain(|key, _| self.recently_used.contains(key)); - self.bound_entries + self.measurements .retain(|key, _| self.recently_used.contains(key)); self.recently_used.clear(); -- cgit From c8d79a5cd984c23862c184f437dcbe7639fe72c0 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Wed, 28 Jun 2023 00:45:41 +0200 Subject: Fix needless borrow in `iced_wgpu` and `iced_tiny_skia` --- wgpu/src/text.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'wgpu') diff --git a/wgpu/src/text.rs b/wgpu/src/text.rs index e0484239..ae780c1e 100644 --- a/wgpu/src/text.rs +++ b/wgpu/src/text.rs @@ -391,7 +391,7 @@ impl Cache { return ( *measured_hash, - self.entries.get_mut(&measured_hash).unwrap(), + self.entries.get_mut(measured_hash).unwrap(), ); } -- cgit From 975eebfc6251539d154183f7d3cc69c41a96b156 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Wed, 28 Jun 2023 00:51:40 +0200 Subject: Retain measurements for text entries even if not directly used --- wgpu/src/text.rs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) (limited to 'wgpu') diff --git a/wgpu/src/text.rs b/wgpu/src/text.rs index ae780c1e..b11b91c1 100644 --- a/wgpu/src/text.rs +++ b/wgpu/src/text.rs @@ -434,8 +434,10 @@ impl Cache { fn trim(&mut self) { self.entries .retain(|key, _| self.recently_used.contains(key)); - self.measurements - .retain(|key, _| self.recently_used.contains(key)); + self.measurements.retain(|key, value| { + self.recently_used.contains(key) + || self.recently_used.contains(value) + }); self.recently_used.clear(); } -- cgit From 00859c25f576b399871de74f0b9399d074deea35 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Wed, 28 Jun 2023 01:27:09 +0200 Subject: Retain text measurements as long as original entries --- wgpu/src/text.rs | 35 ++++++++++++++++++----------------- 1 file changed, 18 insertions(+), 17 deletions(-) (limited to 'wgpu') diff --git a/wgpu/src/text.rs b/wgpu/src/text.rs index b11b91c1..1b94edf6 100644 --- a/wgpu/src/text.rs +++ b/wgpu/src/text.rs @@ -385,14 +385,10 @@ impl Cache { ) -> (KeyHash, &mut Entry) { let hash = key.hash(self.hasher.build_hasher()); - if let Some(measured_hash) = self.measurements.get(&hash) { - let _ = self.recently_used.insert(hash); - let _ = self.recently_used.insert(*measured_hash); + if let Some(hash) = self.measurements.get(&hash) { + let _ = self.recently_used.insert(*hash); - return ( - *measured_hash, - self.entries.get_mut(measured_hash).unwrap(), - ); + return (*hash, self.entries.get_mut(hash).unwrap()); } if let hash_map::Entry::Vacant(entry) = self.entries.entry(hash) { @@ -415,14 +411,21 @@ impl Cache { ); let bounds = measure(&buffer); - let _ = entry.insert(Entry { buffer, bounds }); - if key.bounds != bounds { - let _ = self.measurements.insert( - Key { bounds, ..key }.hash(self.hasher.build_hasher()), - hash, - ); + for bounds in [ + bounds, + Size { + width: key.bounds.width, + ..bounds + }, + ] { + if key.bounds != bounds { + let _ = self.measurements.insert( + Key { bounds, ..key }.hash(self.hasher.build_hasher()), + hash, + ); + } } } @@ -434,10 +437,8 @@ impl Cache { fn trim(&mut self) { self.entries .retain(|key, _| self.recently_used.contains(key)); - self.measurements.retain(|key, value| { - self.recently_used.contains(key) - || self.recently_used.contains(value) - }); + self.measurements + .retain(|_, value| self.recently_used.contains(value)); self.recently_used.clear(); } -- cgit From 0ae1baa37bd7b6607f79b33b8a6d8c5daafde0b2 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Thu, 22 Jun 2023 00:38:36 +0200 Subject: Introduce custom backend-specific primitives --- wgpu/src/backend.rs | 7 ++++++- wgpu/src/geometry.rs | 6 +++--- wgpu/src/layer.rs | 11 +++-------- wgpu/src/lib.rs | 6 ++++-- wgpu/src/primitive.rs | 3 +++ wgpu/src/window/compositor.rs | 4 ++-- 6 files changed, 21 insertions(+), 16 deletions(-) create mode 100644 wgpu/src/primitive.rs (limited to 'wgpu') diff --git a/wgpu/src/backend.rs b/wgpu/src/backend.rs index f8829e47..596d43c5 100644 --- a/wgpu/src/backend.rs +++ b/wgpu/src/backend.rs @@ -2,7 +2,8 @@ use crate::core; use crate::core::{Color, Font, Point, Size}; use crate::graphics::backend; use crate::graphics::color; -use crate::graphics::{Primitive, Transformation, Viewport}; +use crate::graphics::{Transformation, Viewport}; +use crate::primitive::{self, Primitive}; use crate::quad; use crate::text; use crate::triangle; @@ -334,6 +335,10 @@ impl Backend { } } +impl crate::graphics::Backend for Backend { + type Primitive = primitive::Custom; +} + impl backend::Text for Backend { const ICON_FONT: Font = Font::with_name("Iced-Icons"); const CHECKMARK_ICON: char = '\u{f00c}'; diff --git a/wgpu/src/geometry.rs b/wgpu/src/geometry.rs index f81b5b2f..d43f1065 100644 --- a/wgpu/src/geometry.rs +++ b/wgpu/src/geometry.rs @@ -5,10 +5,10 @@ use crate::graphics::geometry::fill::{self, Fill}; use crate::graphics::geometry::{ LineCap, LineDash, LineJoin, Path, Stroke, Style, Text, }; -use crate::graphics::primitive::{self, Primitive}; -use crate::graphics::Gradient; +use crate::graphics::gradient::{self, Gradient}; +use crate::graphics::primitive; +use crate::Primitive; -use iced_graphics::gradient; use lyon::geom::euclid; use lyon::tessellation; use std::borrow::Cow; diff --git a/wgpu/src/layer.rs b/wgpu/src/layer.rs index 71570e3d..ef850cd9 100644 --- a/wgpu/src/layer.rs +++ b/wgpu/src/layer.rs @@ -12,8 +12,9 @@ use crate::core; use crate::core::alignment; use crate::core::{Color, Font, Point, Rectangle, Size, Vector}; use crate::graphics::color; -use crate::graphics::{Primitive, Viewport}; +use crate::graphics::Viewport; use crate::quad::{self, Quad}; +use crate::Primitive; /// A group of primitives that should be clipped together. #[derive(Debug)] @@ -262,13 +263,7 @@ impl<'a> Layer<'a> { current_layer, ); } - _ => { - // Not supported! - log::warn!( - "Unsupported primitive in `iced_wgpu`: {:?}", - primitive - ); - } + Primitive::Custom(()) => {} } } } diff --git a/wgpu/src/lib.rs b/wgpu/src/lib.rs index 86a962a5..79dfdd24 100644 --- a/wgpu/src/lib.rs +++ b/wgpu/src/lib.rs @@ -24,8 +24,8 @@ html_logo_url = "https://raw.githubusercontent.com/iced-rs/iced/9ab6923e943f784985e9ef9ca28b10278297225d/docs/logo.svg" )] #![deny( - missing_debug_implementations, - missing_docs, + //missing_debug_implementations, + //missing_docs, unsafe_code, unused_results, clippy::extra_unused_lifetimes, @@ -47,6 +47,7 @@ pub mod geometry; mod backend; mod buffer; mod color; +mod primitive; mod quad; mod text; mod triangle; @@ -60,6 +61,7 @@ pub use wgpu; pub use backend::Backend; pub use layer::Layer; +pub use primitive::Primitive; pub use settings::Settings; #[cfg(any(feature = "image", feature = "svg"))] diff --git a/wgpu/src/primitive.rs b/wgpu/src/primitive.rs new file mode 100644 index 00000000..2e31cd53 --- /dev/null +++ b/wgpu/src/primitive.rs @@ -0,0 +1,3 @@ +pub type Primitive = crate::graphics::Primitive; + +pub type Custom = (); diff --git a/wgpu/src/window/compositor.rs b/wgpu/src/window/compositor.rs index 1cfd7b67..cd5b20cc 100644 --- a/wgpu/src/window/compositor.rs +++ b/wgpu/src/window/compositor.rs @@ -3,8 +3,8 @@ use crate::core::{Color, Size}; use crate::graphics; use crate::graphics::color; use crate::graphics::compositor; -use crate::graphics::{Error, Primitive, Viewport}; -use crate::{Backend, Renderer, Settings}; +use crate::graphics::{Error, Viewport}; +use crate::{Backend, Primitive, Renderer, Settings}; use futures::stream::{self, StreamExt}; -- cgit From fa5650cfd1115e6ccec2ad795cf58fd970d5b43c Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Thu, 29 Jun 2023 07:48:03 +0200 Subject: Decouple `Mesh` primitives from main `Primitive` type --- wgpu/src/geometry.rs | 64 +++++++++++++++++++------------------ wgpu/src/layer.rs | 86 +++++++++++++++++++++++++++++--------------------- wgpu/src/layer/mesh.rs | 6 ++-- wgpu/src/lib.rs | 2 +- wgpu/src/primitive.rs | 16 +++++++++- wgpu/src/triangle.rs | 75 ++++++++++++++++++++++--------------------- 6 files changed, 142 insertions(+), 107 deletions(-) (limited to 'wgpu') diff --git a/wgpu/src/geometry.rs b/wgpu/src/geometry.rs index d43f1065..e421e0b0 100644 --- a/wgpu/src/geometry.rs +++ b/wgpu/src/geometry.rs @@ -6,8 +6,8 @@ use crate::graphics::geometry::{ LineCap, LineDash, LineJoin, Path, Stroke, Style, Text, }; use crate::graphics::gradient::{self, Gradient}; -use crate::graphics::primitive; -use crate::Primitive; +use crate::graphics::mesh::{self, Mesh}; +use crate::primitive::{self, Primitive}; use lyon::geom::euclid; use lyon::tessellation; @@ -25,8 +25,8 @@ pub struct Frame { } enum Buffer { - Solid(tessellation::VertexBuffers), - Gradient(tessellation::VertexBuffers), + Solid(tessellation::VertexBuffers), + Gradient(tessellation::VertexBuffers), } struct BufferStack { @@ -464,24 +464,28 @@ impl Frame { match buffer { Buffer::Solid(buffer) => { if !buffer.indices.is_empty() { - self.primitives.push(Primitive::SolidMesh { - buffers: primitive::Mesh2D { - vertices: buffer.vertices, - indices: buffer.indices, - }, - size: self.size, - }) + self.primitives.push(Primitive::Custom( + primitive::Custom::Mesh(Mesh::Solid { + buffers: mesh::Indexed { + vertices: buffer.vertices, + indices: buffer.indices, + }, + size: self.size, + }), + )) } } Buffer::Gradient(buffer) => { if !buffer.indices.is_empty() { - self.primitives.push(Primitive::GradientMesh { - buffers: primitive::Mesh2D { - vertices: buffer.vertices, - indices: buffer.indices, - }, - size: self.size, - }) + self.primitives.push(Primitive::Custom( + primitive::Custom::Mesh(Mesh::Gradient { + buffers: mesh::Indexed { + vertices: buffer.vertices, + indices: buffer.indices, + }, + size: self.size, + }), + )) } } } @@ -495,32 +499,32 @@ struct GradientVertex2DBuilder { gradient: gradient::Packed, } -impl tessellation::FillVertexConstructor +impl tessellation::FillVertexConstructor for GradientVertex2DBuilder { fn new_vertex( &mut self, vertex: tessellation::FillVertex<'_>, - ) -> primitive::GradientVertex2D { + ) -> mesh::GradientVertex2D { let position = vertex.position(); - primitive::GradientVertex2D { + mesh::GradientVertex2D { position: [position.x, position.y], gradient: self.gradient, } } } -impl tessellation::StrokeVertexConstructor +impl tessellation::StrokeVertexConstructor for GradientVertex2DBuilder { fn new_vertex( &mut self, vertex: tessellation::StrokeVertex<'_, '_>, - ) -> primitive::GradientVertex2D { + ) -> mesh::GradientVertex2D { let position = vertex.position(); - primitive::GradientVertex2D { + mesh::GradientVertex2D { position: [position.x, position.y], gradient: self.gradient, } @@ -529,32 +533,32 @@ impl tessellation::StrokeVertexConstructor struct TriangleVertex2DBuilder(color::Packed); -impl tessellation::FillVertexConstructor +impl tessellation::FillVertexConstructor for TriangleVertex2DBuilder { fn new_vertex( &mut self, vertex: tessellation::FillVertex<'_>, - ) -> primitive::ColoredVertex2D { + ) -> mesh::SolidVertex2D { let position = vertex.position(); - primitive::ColoredVertex2D { + mesh::SolidVertex2D { position: [position.x, position.y], color: self.0, } } } -impl tessellation::StrokeVertexConstructor +impl tessellation::StrokeVertexConstructor for TriangleVertex2DBuilder { fn new_vertex( &mut self, vertex: tessellation::StrokeVertex<'_, '_>, - ) -> primitive::ColoredVertex2D { + ) -> mesh::SolidVertex2D { let position = vertex.position(); - primitive::ColoredVertex2D { + mesh::SolidVertex2D { position: [position.x, position.y], color: self.0, } diff --git a/wgpu/src/layer.rs b/wgpu/src/layer.rs index ef850cd9..b8f32db1 100644 --- a/wgpu/src/layer.rs +++ b/wgpu/src/layer.rs @@ -11,10 +11,11 @@ pub use text::Text; use crate::core; use crate::core::alignment; use crate::core::{Color, Font, Point, Rectangle, Size, Vector}; +use crate::graphics; use crate::graphics::color; use crate::graphics::Viewport; +use crate::primitive::{self, Primitive}; use crate::quad::{self, Quad}; -use crate::Primitive; /// A group of primitives that should be clipped together. #[derive(Debug)] @@ -180,40 +181,6 @@ impl<'a> Layer<'a> { bounds: *bounds + translation, }); } - Primitive::SolidMesh { buffers, size } => { - let layer = &mut layers[current_layer]; - - let bounds = Rectangle::new( - Point::new(translation.x, translation.y), - *size, - ); - - // Only draw visible content - if let Some(clip_bounds) = layer.bounds.intersection(&bounds) { - layer.meshes.push(Mesh::Solid { - origin: Point::new(translation.x, translation.y), - buffers, - clip_bounds, - }); - } - } - Primitive::GradientMesh { buffers, size } => { - let layer = &mut layers[current_layer]; - - let bounds = Rectangle::new( - Point::new(translation.x, translation.y), - *size, - ); - - // Only draw visible content - if let Some(clip_bounds) = layer.bounds.intersection(&bounds) { - layer.meshes.push(Mesh::Gradient { - origin: Point::new(translation.x, translation.y), - buffers, - clip_bounds, - }); - } - } Primitive::Group { primitives } => { // TODO: Inspect a bit and regroup (?) for primitive in primitives { @@ -263,7 +230,54 @@ impl<'a> Layer<'a> { current_layer, ); } - Primitive::Custom(()) => {} + Primitive::Custom(custom) => match custom { + primitive::Custom::Mesh(mesh) => match mesh { + graphics::Mesh::Solid { buffers, size } => { + let layer = &mut layers[current_layer]; + + let bounds = Rectangle::new( + Point::new(translation.x, translation.y), + *size, + ); + + // Only draw visible content + if let Some(clip_bounds) = + layer.bounds.intersection(&bounds) + { + layer.meshes.push(Mesh::Solid { + origin: Point::new( + translation.x, + translation.y, + ), + buffers, + clip_bounds, + }); + } + } + graphics::Mesh::Gradient { buffers, size } => { + let layer = &mut layers[current_layer]; + + let bounds = Rectangle::new( + Point::new(translation.x, translation.y), + *size, + ); + + // Only draw visible content + if let Some(clip_bounds) = + layer.bounds.intersection(&bounds) + { + layer.meshes.push(Mesh::Gradient { + origin: Point::new( + translation.x, + translation.y, + ), + buffers, + clip_bounds, + }); + } + } + }, + }, } } } diff --git a/wgpu/src/layer/mesh.rs b/wgpu/src/layer/mesh.rs index b7dd9a0b..7c6206cd 100644 --- a/wgpu/src/layer/mesh.rs +++ b/wgpu/src/layer/mesh.rs @@ -1,6 +1,6 @@ //! A collection of triangle primitives. use crate::core::{Point, Rectangle}; -use crate::graphics::primitive; +use crate::graphics::mesh; /// A mesh of triangles. #[derive(Debug, Clone, Copy)] @@ -11,7 +11,7 @@ pub enum Mesh<'a> { origin: Point, /// The vertex and index buffers of the [`Mesh`]. - buffers: &'a primitive::Mesh2D, + buffers: &'a mesh::Indexed, /// The clipping bounds of the [`Mesh`]. clip_bounds: Rectangle, @@ -22,7 +22,7 @@ pub enum Mesh<'a> { origin: Point, /// The vertex and index buffers of the [`Mesh`]. - buffers: &'a primitive::Mesh2D, + buffers: &'a mesh::Indexed, /// The clipping bounds of the [`Mesh`]. clip_bounds: Rectangle, diff --git a/wgpu/src/lib.rs b/wgpu/src/lib.rs index 79dfdd24..0ffaeeef 100644 --- a/wgpu/src/lib.rs +++ b/wgpu/src/lib.rs @@ -38,6 +38,7 @@ #![allow(clippy::inherent_to_string, clippy::type_complexity)] #![cfg_attr(docsrs, feature(doc_auto_cfg))] pub mod layer; +pub mod primitive; pub mod settings; pub mod window; @@ -47,7 +48,6 @@ pub mod geometry; mod backend; mod buffer; mod color; -mod primitive; mod quad; mod text; mod triangle; diff --git a/wgpu/src/primitive.rs b/wgpu/src/primitive.rs index 2e31cd53..a8f1119c 100644 --- a/wgpu/src/primitive.rs +++ b/wgpu/src/primitive.rs @@ -1,3 +1,17 @@ +use crate::core::Rectangle; +use crate::graphics::{Damage, Mesh}; + pub type Primitive = crate::graphics::Primitive; -pub type Custom = (); +#[derive(Debug, Clone, PartialEq)] +pub enum Custom { + Mesh(Mesh), +} + +impl Damage for Custom { + fn bounds(&self) -> Rectangle { + match self { + Self::Mesh(mesh) => mesh.bounds(), + } + } +} diff --git a/wgpu/src/triangle.rs b/wgpu/src/triangle.rs index 3f3635cf..d8b23dfe 100644 --- a/wgpu/src/triangle.rs +++ b/wgpu/src/triangle.rs @@ -393,7 +393,7 @@ impl Uniforms { } mod solid { - use crate::graphics::primitive; + use crate::graphics::mesh; use crate::graphics::Antialiasing; use crate::triangle; use crate::Buffer; @@ -406,7 +406,7 @@ mod solid { #[derive(Debug)] pub struct Layer { - pub vertices: Buffer, + pub vertices: Buffer, pub uniforms: Buffer, pub constants: wgpu::BindGroup, } @@ -493,38 +493,40 @@ mod solid { ), }); - let pipeline = device.create_render_pipeline( - &wgpu::RenderPipelineDescriptor { - label: Some("iced_wgpu::triangle::solid pipeline"), - layout: Some(&layout), - vertex: wgpu::VertexState { - module: &shader, - entry_point: "solid_vs_main", - buffers: &[wgpu::VertexBufferLayout { - array_stride: std::mem::size_of::< - primitive::ColoredVertex2D, - >() - as u64, - step_mode: wgpu::VertexStepMode::Vertex, - attributes: &wgpu::vertex_attr_array!( - // Position - 0 => Float32x2, - // Color - 1 => Float32x4, - ), - }], + let pipeline = + device.create_render_pipeline( + &wgpu::RenderPipelineDescriptor { + label: Some("iced_wgpu::triangle::solid pipeline"), + layout: Some(&layout), + vertex: wgpu::VertexState { + module: &shader, + entry_point: "solid_vs_main", + buffers: &[wgpu::VertexBufferLayout { + array_stride: std::mem::size_of::< + mesh::SolidVertex2D, + >( + ) + as u64, + step_mode: wgpu::VertexStepMode::Vertex, + attributes: &wgpu::vertex_attr_array!( + // Position + 0 => Float32x2, + // Color + 1 => Float32x4, + ), + }], + }, + fragment: Some(wgpu::FragmentState { + module: &shader, + entry_point: "solid_fs_main", + targets: &[triangle::fragment_target(format)], + }), + primitive: triangle::primitive_state(), + depth_stencil: None, + multisample: triangle::multisample_state(antialiasing), + multiview: None, }, - fragment: Some(wgpu::FragmentState { - module: &shader, - entry_point: "solid_fs_main", - targets: &[triangle::fragment_target(format)], - }), - primitive: triangle::primitive_state(), - depth_stencil: None, - multisample: triangle::multisample_state(antialiasing), - multiview: None, - }, - ); + ); Self { pipeline, @@ -535,7 +537,8 @@ mod solid { } mod gradient { - use crate::graphics::{primitive, Antialiasing}; + use crate::graphics::mesh; + use crate::graphics::Antialiasing; use crate::triangle; use crate::Buffer; @@ -547,7 +550,7 @@ mod gradient { #[derive(Debug)] pub struct Layer { - pub vertices: Buffer, + pub vertices: Buffer, pub uniforms: Buffer, pub constants: wgpu::BindGroup, } @@ -645,7 +648,7 @@ mod gradient { entry_point: "gradient_vs_main", buffers: &[wgpu::VertexBufferLayout { array_stride: std::mem::size_of::< - primitive::GradientVertex2D, + mesh::GradientVertex2D, >() as u64, step_mode: wgpu::VertexStepMode::Vertex, -- cgit From 6921564c9f66e8103e19ec658099c5f5c32e8cc5 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Thu, 29 Jun 2023 07:55:52 +0200 Subject: Write missing docs in `iced_graphics` and `iced_wgpu` --- wgpu/src/lib.rs | 4 ++-- wgpu/src/primitive.rs | 4 ++++ 2 files changed, 6 insertions(+), 2 deletions(-) (limited to 'wgpu') diff --git a/wgpu/src/lib.rs b/wgpu/src/lib.rs index 0ffaeeef..deb223ef 100644 --- a/wgpu/src/lib.rs +++ b/wgpu/src/lib.rs @@ -24,8 +24,8 @@ html_logo_url = "https://raw.githubusercontent.com/iced-rs/iced/9ab6923e943f784985e9ef9ca28b10278297225d/docs/logo.svg" )] #![deny( - //missing_debug_implementations, - //missing_docs, + missing_debug_implementations, + missing_docs, unsafe_code, unused_results, clippy::extra_unused_lifetimes, diff --git a/wgpu/src/primitive.rs b/wgpu/src/primitive.rs index a8f1119c..8dbf3008 100644 --- a/wgpu/src/primitive.rs +++ b/wgpu/src/primitive.rs @@ -1,10 +1,14 @@ +//! Draw using different graphical primitives. use crate::core::Rectangle; use crate::graphics::{Damage, Mesh}; +/// The graphical primitives supported by `iced_wgpu`. pub type Primitive = crate::graphics::Primitive; +/// The custom primitives supported by `iced_wgpu`. #[derive(Debug, Clone, PartialEq)] pub enum Custom { + /// A mesh primitive. Mesh(Mesh), } -- cgit From 98febd9a428f56e28112257adb72ef603fd2ddc5 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Thu, 29 Jun 2023 17:54:54 +0200 Subject: Introduce `Mode` for `text::Cache` and trim only when switching modes --- wgpu/src/text.rs | 82 +++++++++++++++++++++++++++++++++++++++++++------------- 1 file changed, 64 insertions(+), 18 deletions(-) (limited to 'wgpu') diff --git a/wgpu/src/text.rs b/wgpu/src/text.rs index 1b94edf6..c87b94a8 100644 --- a/wgpu/src/text.rs +++ b/wgpu/src/text.rs @@ -80,6 +80,11 @@ impl Pipeline { let renderer = &mut self.renderers[self.prepare_layer]; let cache = self.cache.get_mut(); + if self.prepare_layer == 0 { + let _ = cache.switch(Mode::Drawing); + cache.trim(); + } + let keys: Vec<_> = sections .iter() .map(|section| { @@ -224,7 +229,6 @@ impl Pipeline { pub fn end_frame(&mut self) { self.atlas.trim(); - self.cache.get_mut().trim(); self.prepare_layer = 0; } @@ -238,11 +242,15 @@ impl Pipeline { bounds: Size, shaping: Shaping, ) -> Size { - let mut measurement_cache = self.cache.borrow_mut(); + let mut cache = self.cache.borrow_mut(); let line_height = f32::from(line_height.to_absolute(Pixels(size))); - let (_, entry) = measurement_cache.allocate( + if cache.switch(Mode::Measuring) { + cache.trim(); + } + + let (_, entry) = cache.allocate( &mut self.font_system.borrow_mut(), Key { content, @@ -268,11 +276,15 @@ impl Pipeline { point: Point, _nearest_only: bool, ) -> Option { - let mut measurement_cache = self.cache.borrow_mut(); + let mut cache = self.cache.borrow_mut(); let line_height = f32::from(line_height.to_absolute(Pixels(size))); - let (_, entry) = measurement_cache.allocate( + if cache.switch(Mode::Measuring) { + cache.trim(); + } + + let (_, entry) = cache.allocate( &mut self.font_system.borrow_mut(), Key { content, @@ -348,9 +360,11 @@ fn to_shaping(shaping: Shaping) -> glyphon::Shaping { struct Cache { entries: FxHashMap, - measurements: FxHashMap, - recently_used: FxHashSet, + aliases: FxHashMap, + recently_measured: FxHashSet, + recently_drawn: FxHashSet, hasher: HashBuilder, + mode: Mode, } struct Entry { @@ -358,6 +372,12 @@ struct Entry { bounds: Size, } +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +enum Mode { + Measuring, + Drawing, +} + #[cfg(not(target_arch = "wasm32"))] type HashBuilder = twox_hash::RandomXxHashBuilder64; @@ -368,9 +388,11 @@ impl Cache { fn new() -> Self { Self { entries: FxHashMap::default(), - measurements: FxHashMap::default(), - recently_used: FxHashSet::default(), + aliases: FxHashMap::default(), + recently_measured: FxHashSet::default(), + recently_drawn: FxHashSet::default(), hasher: HashBuilder::default(), + mode: Mode::Measuring, } } @@ -378,6 +400,14 @@ impl Cache { self.entries.get(key) } + fn switch(&mut self, mode: Mode) -> bool { + let has_changed = self.mode != mode; + + self.mode = mode; + + has_changed + } + fn allocate( &mut self, font_system: &mut glyphon::FontSystem, @@ -385,8 +415,13 @@ impl Cache { ) -> (KeyHash, &mut Entry) { let hash = key.hash(self.hasher.build_hasher()); - if let Some(hash) = self.measurements.get(&hash) { - let _ = self.recently_used.insert(*hash); + let recently_used = match self.mode { + Mode::Measuring => &mut self.recently_measured, + Mode::Drawing => &mut self.recently_drawn, + }; + + if let Some(hash) = self.aliases.get(&hash) { + let _ = recently_used.insert(*hash); return (*hash, self.entries.get_mut(hash).unwrap()); } @@ -421,7 +456,7 @@ impl Cache { }, ] { if key.bounds != bounds { - let _ = self.measurements.insert( + let _ = self.aliases.insert( Key { bounds, ..key }.hash(self.hasher.build_hasher()), hash, ); @@ -429,18 +464,29 @@ impl Cache { } } - let _ = self.recently_used.insert(hash); + let _ = recently_used.insert(hash); (hash, self.entries.get_mut(&hash).unwrap()) } fn trim(&mut self) { - self.entries - .retain(|key, _| self.recently_used.contains(key)); - self.measurements - .retain(|_, value| self.recently_used.contains(value)); + self.entries.retain(|key, _| { + self.recently_measured.contains(key) + || self.recently_drawn.contains(key) + }); + self.aliases.retain(|_, value| { + self.recently_measured.contains(value) + || self.recently_drawn.contains(value) + }); - self.recently_used.clear(); + match self.mode { + Mode::Measuring => { + self.recently_measured.clear(); + } + Mode::Drawing => { + self.recently_drawn.clear(); + } + } } } -- cgit From d666e739cdcc2084c14593888867d40066c232fe Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Thu, 29 Jun 2023 18:23:11 +0200 Subject: Trim text measurements only before `layout` --- wgpu/src/backend.rs | 4 ++++ wgpu/src/text.rs | 45 +++++++++++++++++---------------------------- 2 files changed, 21 insertions(+), 28 deletions(-) (limited to 'wgpu') diff --git a/wgpu/src/backend.rs b/wgpu/src/backend.rs index 596d43c5..4a0c54f0 100644 --- a/wgpu/src/backend.rs +++ b/wgpu/src/backend.rs @@ -337,6 +337,10 @@ impl Backend { impl crate::graphics::Backend for Backend { type Primitive = primitive::Custom; + + fn trim_measurements(&mut self) { + self.text_pipeline.trim_measurements(); + } } impl backend::Text for Backend { diff --git a/wgpu/src/text.rs b/wgpu/src/text.rs index c87b94a8..65d3b818 100644 --- a/wgpu/src/text.rs +++ b/wgpu/src/text.rs @@ -81,8 +81,7 @@ impl Pipeline { let cache = self.cache.get_mut(); if self.prepare_layer == 0 { - let _ = cache.switch(Mode::Drawing); - cache.trim(); + cache.trim(Purpose::Drawing); } let keys: Vec<_> = sections @@ -105,6 +104,7 @@ impl Pipeline { }, shaping: section.shaping, }, + Purpose::Drawing, ); key @@ -233,6 +233,10 @@ impl Pipeline { self.prepare_layer = 0; } + pub fn trim_measurements(&mut self) { + self.cache.get_mut().trim(Purpose::Measuring); + } + pub fn measure( &self, content: &str, @@ -246,10 +250,6 @@ impl Pipeline { let line_height = f32::from(line_height.to_absolute(Pixels(size))); - if cache.switch(Mode::Measuring) { - cache.trim(); - } - let (_, entry) = cache.allocate( &mut self.font_system.borrow_mut(), Key { @@ -260,6 +260,7 @@ impl Pipeline { bounds, shaping, }, + Purpose::Measuring, ); entry.bounds @@ -280,10 +281,6 @@ impl Pipeline { let line_height = f32::from(line_height.to_absolute(Pixels(size))); - if cache.switch(Mode::Measuring) { - cache.trim(); - } - let (_, entry) = cache.allocate( &mut self.font_system.borrow_mut(), Key { @@ -294,6 +291,7 @@ impl Pipeline { bounds, shaping, }, + Purpose::Measuring, ); let cursor = entry.buffer.hit(point.x, point.y)?; @@ -364,7 +362,6 @@ struct Cache { recently_measured: FxHashSet, recently_drawn: FxHashSet, hasher: HashBuilder, - mode: Mode, } struct Entry { @@ -373,7 +370,7 @@ struct Entry { } #[derive(Debug, Clone, Copy, PartialEq, Eq)] -enum Mode { +enum Purpose { Measuring, Drawing, } @@ -392,7 +389,6 @@ impl Cache { recently_measured: FxHashSet::default(), recently_drawn: FxHashSet::default(), hasher: HashBuilder::default(), - mode: Mode::Measuring, } } @@ -400,24 +396,17 @@ impl Cache { self.entries.get(key) } - fn switch(&mut self, mode: Mode) -> bool { - let has_changed = self.mode != mode; - - self.mode = mode; - - has_changed - } - fn allocate( &mut self, font_system: &mut glyphon::FontSystem, key: Key<'_>, + purpose: Purpose, ) -> (KeyHash, &mut Entry) { let hash = key.hash(self.hasher.build_hasher()); - let recently_used = match self.mode { - Mode::Measuring => &mut self.recently_measured, - Mode::Drawing => &mut self.recently_drawn, + let recently_used = match purpose { + Purpose::Measuring => &mut self.recently_measured, + Purpose::Drawing => &mut self.recently_drawn, }; if let Some(hash) = self.aliases.get(&hash) { @@ -469,7 +458,7 @@ impl Cache { (hash, self.entries.get_mut(&hash).unwrap()) } - fn trim(&mut self) { + fn trim(&mut self, purpose: Purpose) { self.entries.retain(|key, _| { self.recently_measured.contains(key) || self.recently_drawn.contains(key) @@ -479,11 +468,11 @@ impl Cache { || self.recently_drawn.contains(value) }); - match self.mode { - Mode::Measuring => { + match purpose { + Purpose::Measuring => { self.recently_measured.clear(); } - Mode::Drawing => { + Purpose::Drawing => { self.recently_drawn.clear(); } } -- cgit From af386fd0a3de432337ee9cdaa4d3661e98bd4105 Mon Sep 17 00:00:00 2001 From: Alec Deason Date: Sat, 10 Jun 2023 13:18:42 -0700 Subject: Upgrade resvg to 0.34 and tiny_skia to 0.10 --- wgpu/Cargo.toml | 2 +- wgpu/src/image/vector.rs | 31 +++++++++++++++++++++---------- 2 files changed, 22 insertions(+), 11 deletions(-) (limited to 'wgpu') diff --git a/wgpu/Cargo.toml b/wgpu/Cargo.toml index 15db5b5d..ecec7d02 100644 --- a/wgpu/Cargo.toml +++ b/wgpu/Cargo.toml @@ -55,7 +55,7 @@ version = "1.0" optional = true [dependencies.resvg] -version = "0.32" +version = "0.34" optional = true [dependencies.tracing] diff --git a/wgpu/src/image/vector.rs b/wgpu/src/image/vector.rs index 6b9be651..5bfc1836 100644 --- a/wgpu/src/image/vector.rs +++ b/wgpu/src/image/vector.rs @@ -114,16 +114,27 @@ impl Cache { // It would be cool to be able to smooth resize the `svg` example. let mut img = tiny_skia::Pixmap::new(width, height)?; - resvg::render( - tree, - if width > height { - resvg::FitTo::Width(width) - } else { - resvg::FitTo::Height(height) - }, - tiny_skia::Transform::default(), - img.as_mut(), - )?; + let tree_size = tree.size.to_int_size(); + let target_size; + if width > height { + target_size = tree_size.scale_to_width(width); + } else { + target_size = tree_size.scale_to_height(height); + } + let transform; + if let Some(target_size) = target_size { + let tree_size = tree_size.to_size(); + let target_size = target_size.to_size(); + transform = tiny_skia::Transform::from_scale( + target_size.width() / tree_size.width(), + target_size.height() / tree_size.height(), + ); + } else { + transform = tiny_skia::Transform::default(); + } + + resvg::Tree::from_usvg(tree) + .render(transform, &mut img.as_mut()); let mut rgba = img.take(); -- cgit From 6502cf1111380c66f96bf5677425a902c4662ef5 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Wed, 12 Jul 2023 09:07:20 +0200 Subject: Improve code style in `vector` modules --- wgpu/src/image/vector.rs | 23 ++++++++++++----------- 1 file changed, 12 insertions(+), 11 deletions(-) (limited to 'wgpu') diff --git a/wgpu/src/image/vector.rs b/wgpu/src/image/vector.rs index 5bfc1836..2c03d36b 100644 --- a/wgpu/src/image/vector.rs +++ b/wgpu/src/image/vector.rs @@ -115,23 +115,24 @@ impl Cache { let mut img = tiny_skia::Pixmap::new(width, height)?; let tree_size = tree.size.to_int_size(); - let target_size; - if width > height { - target_size = tree_size.scale_to_width(width); + + let target_size = if width > height { + tree_size.scale_to_width(width) } else { - target_size = tree_size.scale_to_height(height); - } - let transform; - if let Some(target_size) = target_size { + tree_size.scale_to_height(height) + }; + + let transform = if let Some(target_size) = target_size { let tree_size = tree_size.to_size(); let target_size = target_size.to_size(); - transform = tiny_skia::Transform::from_scale( + + tiny_skia::Transform::from_scale( target_size.width() / tree_size.width(), target_size.height() / tree_size.height(), - ); + ) } else { - transform = tiny_skia::Transform::default(); - } + tiny_skia::Transform::default() + }; resvg::Tree::from_usvg(tree) .render(transform, &mut img.as_mut()); -- cgit From 5dd923402e07578a0002884ac14044fe8762f8b0 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Wed, 12 Jul 2023 09:10:58 +0200 Subject: Update `resvg` dependency to `0.35` --- wgpu/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'wgpu') diff --git a/wgpu/Cargo.toml b/wgpu/Cargo.toml index ecec7d02..22cfad55 100644 --- a/wgpu/Cargo.toml +++ b/wgpu/Cargo.toml @@ -55,7 +55,7 @@ version = "1.0" optional = true [dependencies.resvg] -version = "0.34" +version = "0.35" optional = true [dependencies.tracing] -- cgit