diff options
Diffstat (limited to 'wgpu/src/backend.rs')
-rw-r--r-- | wgpu/src/backend.rs | 429 |
1 files changed, 265 insertions, 164 deletions
diff --git a/wgpu/src/backend.rs b/wgpu/src/backend.rs index 6a299425..4a0c54f0 100644 --- a/wgpu/src/backend.rs +++ b/wgpu/src/backend.rs @@ -1,14 +1,13 @@ +use crate::core; +use crate::core::{Color, Font, Point, Size}; +use crate::graphics::backend; +use crate::graphics::color; +use crate::graphics::{Transformation, Viewport}; +use crate::primitive::{self, Primitive}; use crate::quad; use crate::text; 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}; +use crate::{Layer, Settings}; #[cfg(feature = "tracing")] use tracing::info_span; @@ -16,11 +15,13 @@ 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 /// [`iced`]: https://github.com/iced-rs/iced -#[derive(Debug)] +#[allow(missing_debug_implementations)] pub struct Backend { quad_pipeline: quad::Pipeline, text_pipeline: text::Pipeline, @@ -29,6 +30,7 @@ pub struct Backend { #[cfg(any(feature = "image", feature = "svg"))] image_pipeline: image::Pipeline, + default_font: Font, default_text_size: f32, } @@ -36,16 +38,11 @@ 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, - 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 +58,7 @@ impl Backend { #[cfg(any(feature = "image", feature = "svg"))] image_pipeline, + default_font: settings.default_font, default_text_size: settings.default_text_size, } } @@ -72,8 +70,9 @@ impl Backend { pub fn present<T: AsRef<str>>( &mut self, device: &wgpu::Device, - staging_belt: &mut wgpu::util::StagingBelt, + queue: &wgpu::Queue, encoder: &mut wgpu::CommandEncoder, + clear_color: Option<Color>, frame: &wgpu::TextureView, primitives: &[Primitive], viewport: &Viewport, @@ -90,180 +89,268 @@ impl Backend { let mut layers = Layer::generate(primitives, viewport); layers.push(Layer::overlay(overlay_text, viewport)); + self.prepare( + device, + queue, + encoder, + scale_factor, + transformation, + &layers, + ); + + while !self.prepare_text( + device, + queue, + scale_factor, + 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(); + self.triangle_pipeline.end_frame(); + + #[cfg(any(feature = "image", feature = "svg"))] + self.image_pipeline.end_frame(); + } + + fn prepare_text( + &mut self, + device: &wgpu::Device, + queue: &wgpu::Queue, + scale_factor: f32, + target_size: Size<u32>, + layers: &[Layer<'_>], + ) -> bool { for layer in layers { - self.flush( - device, - scale_factor, - transformation, - &layer, - staging_belt, - encoder, - frame, - target_size, - ); + let bounds = (layer.bounds * scale_factor).snap(); + + if bounds.width < 1 || bounds.height < 1 { + continue; + } + + if !layer.text.is_empty() + && !self.text_pipeline.prepare( + device, + queue, + &layer.text, + layer.bounds, + scale_factor, + target_size, + ) + { + return false; + } } - #[cfg(any(feature = "image", feature = "svg"))] - self.image_pipeline.trim_cache(device, encoder); + true } - fn flush( + fn prepare( &mut self, device: &wgpu::Device, + queue: &wgpu::Queue, + _encoder: &mut wgpu::CommandEncoder, scale_factor: f32, transformation: Transformation, - layer: &Layer<'_>, - staging_belt: &mut wgpu::util::StagingBelt, + layers: &[Layer<'_>], + ) { + for layer in layers { + let bounds = (layer.bounds * scale_factor).snap(); + + if bounds.width < 1 || bounds.height < 1 { + continue; + } + + if !layer.quads.is_empty() { + self.quad_pipeline.prepare( + device, + queue, + &layer.quads, + transformation, + scale_factor, + ); + } + + if !layer.meshes.is_empty() { + let scaled = transformation + * Transformation::scale(scale_factor, scale_factor); + + self.triangle_pipeline.prepare( + device, + queue, + &layer.meshes, + scaled, + ); + } + + #[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, + ); + } + } + } + } + + fn render( + &mut self, + device: &wgpu::Device, encoder: &mut wgpu::CommandEncoder, target: &wgpu::TextureView, + clear_color: Option<Color>, + scale_factor: f32, target_size: Size<u32>, + layers: &[Layer<'_>], ) { - let bounds = (layer.bounds * scale_factor).snap(); + use std::mem::ManuallyDrop; - if bounds.width < 1 || bounds.height < 1 { - return; - } + let mut quad_layer = 0; + let mut triangle_layer = 0; + #[cfg(any(feature = "image", feature = "svg"))] + 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] = + color::pack(background_color).components(); + + 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, + }, + )); - if !layer.quads.is_empty() { - self.quad_pipeline.draw( - device, - staging_belt, - encoder, - &layer.quads, - transformation, - scale_factor, - bounds, - target, - ); - } + for layer in layers { + let bounds = (layer.bounds * scale_factor).snap(); - if !layer.meshes.is_empty() { - let scaled = transformation - * Transformation::scale(scale_factor, scale_factor); - - self.triangle_pipeline.draw( - device, - staging_belt, - encoder, - target, - target_size, - scaled, - scale_factor, - &layer.meshes, - ); - } + if bounds.width < 1 || bounds.height < 1 { + return; + } - #[cfg(any(feature = "image", feature = "svg"))] - { - if !layer.images.is_empty() { - let scaled = transformation - * Transformation::scale(scale_factor, scale_factor); + if !layer.quads.is_empty() { + self.quad_pipeline.render( + quad_layer, + bounds, + &layer.quads, + &mut render_pass, + ); + + quad_layer += 1; + } - self.image_pipeline.draw( + if !layer.meshes.is_empty() { + let _ = ManuallyDrop::into_inner(render_pass); + + self.triangle_pipeline.render( device, - staging_belt, encoder, - &layer.images, - scaled, - bounds, target, + triangle_layer, + target_size, + &layer.meshes, scale_factor, ); + + 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, + resolve_target: None, + ops: wgpu::Operations { + load: wgpu::LoadOp::Load, + store: true, + }, + }, + )], + depth_stencil_attachment: None, + }, + )); } - } - 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); + #[cfg(any(feature = "image", feature = "svg"))] + { + if !layer.images.is_empty() { + self.image_pipeline.render( + image_layer, + bounds, + &mut render_pass, + ); + + image_layer += 1; + } } - 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, - }, - ); + if !layer.text.is_empty() { + self.text_pipeline + .render(text_layer, bounds, &mut render_pass); + + text_layer += 1; + } } + + let _ = ManuallyDrop::into_inner(render_pass); } } -impl iced_graphics::Backend for Backend { +impl crate::graphics::Backend for Backend { + type Primitive = primitive::Custom; + fn trim_measurements(&mut self) { - self.text_pipeline.trim_measurement_cache() + self.text_pipeline.trim_measurements(); } } 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::with_name("Iced-Icons"); + const CHECKMARK_ICON: char = '\u{f00c}'; + const ARROW_DOWN_ICON: char = '\u{e800}'; + + fn default_font(&self) -> Font { + self.default_font + } fn default_size(&self) -> f32 { self.default_text_size @@ -273,45 +360,59 @@ impl backend::Text for Backend { &self, contents: &str, size: f32, + line_height: core::text::LineHeight, font: Font, bounds: Size, - ) -> (f32, f32) { - self.text_pipeline.measure(contents, size, font, bounds) + shaping: core::text::Shaping, + ) -> Size { + 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, - point: iced_native::Point, + shaping: core::text::Shaping, + point: Point, nearest_only: bool, - ) -> Option<text::Hit> { + ) -> Option<core::text::Hit> { self.text_pipeline.hit_test( contents, size, + line_height, font, bounds, + shaping, point, nearest_only, ) } + + fn load_font(&mut self, font: Cow<'static, [u8]>) { + self.text_pipeline.load_font(font); + } } #[cfg(feature = "image")] impl backend::Image for Backend { - fn dimensions(&self, handle: &iced_native::image::Handle) -> Size<u32> { + fn dimensions(&self, handle: &core::image::Handle) -> Size<u32> { self.image_pipeline.dimensions(handle) } } #[cfg(feature = "svg")] impl backend::Svg for Backend { - fn viewport_dimensions( - &self, - handle: &iced_native::svg::Handle, - ) -> Size<u32> { + fn viewport_dimensions(&self, handle: &core::svg::Handle) -> Size<u32> { self.image_pipeline.viewport_dimensions(handle) } } |