diff --git a/benches/wgpu.rs b/benches/wgpu.rs index 8d61137e..b400bfa1 100644 --- a/benches/wgpu.rs +++ b/benches/wgpu.rs @@ -80,16 +80,15 @@ fn benchmark<'a>( let format = wgpu::TextureFormat::Bgra8UnormSrgb; - let mut engine = iced_wgpu::Engine::new( + let engine = iced_wgpu::Engine::new( adapter, - device, - queue, + device.clone(), + queue.clone(), format, Some(Antialiasing::MSAAx4), ); - let mut renderer = - Renderer::new(device, &engine, Font::DEFAULT, Pixels::from(16)); + let mut renderer = Renderer::new(engine, Font::DEFAULT, Pixels::from(16)); let viewport = graphics::Viewport::with_physical_size(Size::new(3840, 2160), 2.0); @@ -134,16 +133,7 @@ fn benchmark<'a>( cache = Some(user_interface.into_cache()); - let mut encoder = - device.create_command_encoder(&wgpu::CommandEncoderDescriptor { - label: None, - }); - - renderer.present::<&str>( - &mut engine, - device, - queue, - &mut encoder, + let submission = renderer.present::<&str>( Some(Color::BLACK), format, &texture_view, @@ -151,7 +141,6 @@ fn benchmark<'a>( &[], ); - let submission = engine.submit(queue, encoder); let _ = device.poll(wgpu::Maintain::WaitForSubmissionIndex(submission)); i += 1; diff --git a/core/src/renderer.rs b/core/src/renderer.rs index 68e070e8..c9224970 100644 --- a/core/src/renderer.rs +++ b/core/src/renderer.rs @@ -106,7 +106,12 @@ impl Default for Style { /// a window nor a compositor. pub trait Headless { /// Creates a new [`Headless`] renderer; - fn new(default_font: Font, default_text_size: Pixels) -> Self; + fn new( + default_font: Font, + default_text_size: Pixels, + ) -> impl Future> + where + Self: Sized; /// Draws offscreen into a screenshot, returning a collection of /// bytes representing the rendered pixels in RGBA order. diff --git a/examples/integration/src/main.rs b/examples/integration/src/main.rs index 7c5388ab..6c6cb120 100644 --- a/examples/integration/src/main.rs +++ b/examples/integration/src/main.rs @@ -35,11 +35,9 @@ pub fn main() -> Result<(), winit::error::EventLoopError> { Loading, Ready { window: Arc, - device: wgpu::Device, - queue: wgpu::Queue, surface: wgpu::Surface<'static>, format: wgpu::TextureFormat, - engine: Engine, + device: wgpu::Device, renderer: Renderer, scene: Scene, state: program::State, @@ -146,13 +144,9 @@ pub fn main() -> Result<(), winit::error::EventLoopError> { // Initialize iced let mut debug = Debug::new(); let engine = - Engine::new(&adapter, &device, &queue, format, None); - let mut renderer = Renderer::new( - &device, - &engine, - Font::default(), - Pixels::from(16), - ); + Engine::new(&adapter, device.clone(), queue, format, None); + let mut renderer = + Renderer::new(engine, Font::default(), Pixels::from(16)); let state = program::State::new( controls, @@ -166,11 +160,9 @@ pub fn main() -> Result<(), winit::error::EventLoopError> { *self = Self::Ready { window, - device, - queue, surface, format, - engine, + device, renderer, scene, state, @@ -193,10 +185,8 @@ pub fn main() -> Result<(), winit::error::EventLoopError> { let Self::Ready { window, device, - queue, surface, format, - engine, renderer, scene, state, @@ -264,10 +254,6 @@ pub fn main() -> Result<(), winit::error::EventLoopError> { // And then iced on top renderer.present( - engine, - device, - queue, - &mut encoder, None, frame.texture.format(), &view, @@ -276,7 +262,6 @@ pub fn main() -> Result<(), winit::error::EventLoopError> { ); // Then we submit the work - engine.submit(queue, encoder); frame.present(); // Update the mouse cursor diff --git a/examples/todos/snapshots/creates_a_new_task.sha256 b/examples/todos/snapshots/creates_a_new_task.sha256 index f62b2f69..6dd6ccbe 100644 --- a/examples/todos/snapshots/creates_a_new_task.sha256 +++ b/examples/todos/snapshots/creates_a_new_task.sha256 @@ -1 +1 @@ -0e355b080ad33905145e9f70a3b29e2481197c8fc8f42491acd5358238ebbd5f \ No newline at end of file +804a1bb6d49e3b3158463202960447d9e7820b967280f41dd0c34c00d3edf2c3 \ No newline at end of file diff --git a/graphics/src/compositor.rs b/graphics/src/compositor.rs index 847578cf..e9063678 100644 --- a/graphics/src/compositor.rs +++ b/graphics/src/compositor.rs @@ -87,12 +87,11 @@ pub trait Compositor: Sized { /// the texture ordered as `RGBA` in the `sRGB` color space. /// /// [`Renderer`]: Self::Renderer - fn screenshot>( + fn screenshot( &mut self, renderer: &mut Self::Renderer, viewport: &Viewport, background_color: Color, - overlay: &[T], ) -> Vec; } @@ -199,12 +198,11 @@ impl Compositor for () { Ok(()) } - fn screenshot>( + fn screenshot( &mut self, _renderer: &mut Self::Renderer, _viewport: &Viewport, _background_color: Color, - _overlay: &[T], ) -> Vec { vec![] } diff --git a/renderer/src/compositor.rs b/renderer/src/compositor.rs deleted file mode 100644 index 8b137891..00000000 --- a/renderer/src/compositor.rs +++ /dev/null @@ -1 +0,0 @@ - diff --git a/renderer/src/fallback.rs b/renderer/src/fallback.rs index e8eb3327..8ec49212 100644 --- a/renderer/src/fallback.rs +++ b/renderer/src/fallback.rs @@ -3,7 +3,8 @@ use crate::core::image; use crate::core::renderer; use crate::core::svg; use crate::core::{ - self, Background, Color, Image, Point, Rectangle, Size, Svg, Transformation, + self, Background, Color, Font, Image, Pixels, Point, Rectangle, Size, Svg, + Transformation, }; use crate::graphics; use crate::graphics::compositor; @@ -353,29 +354,18 @@ where } } - fn screenshot>( + fn screenshot( &mut self, renderer: &mut Self::Renderer, viewport: &graphics::Viewport, background_color: Color, - overlay: &[T], ) -> Vec { match (self, renderer) { (Self::Primary(compositor), Renderer::Primary(renderer)) => { - compositor.screenshot( - renderer, - viewport, - background_color, - overlay, - ) + compositor.screenshot(renderer, viewport, background_color) } (Self::Secondary(compositor), Renderer::Secondary(renderer)) => { - compositor.screenshot( - renderer, - viewport, - background_color, - overlay, - ) + compositor.screenshot(renderer, viewport, background_color) } _ => unreachable!(), } @@ -617,6 +607,41 @@ mod geometry { } } +impl renderer::Headless for Renderer +where + A: renderer::Headless, + B: renderer::Headless, +{ + async fn new( + default_font: Font, + default_text_size: Pixels, + ) -> Option { + if let Some(renderer) = A::new(default_font, default_text_size).await { + return Some(Self::Primary(renderer)); + } + + B::new(default_font, default_text_size) + .await + .map(Self::Secondary) + } + + fn screenshot( + &mut self, + size: Size, + scale_factor: f32, + background_color: Color, + ) -> Vec { + match self { + crate::fallback::Renderer::Primary(renderer) => { + renderer.screenshot(size, scale_factor, background_color) + } + crate::fallback::Renderer::Secondary(renderer) => { + renderer.screenshot(size, scale_factor, background_color) + } + } + } +} + impl compositor::Default for Renderer where A: compositor::Default, diff --git a/renderer/src/lib.rs b/renderer/src/lib.rs index ee20a458..220542e1 100644 --- a/renderer/src/lib.rs +++ b/renderer/src/lib.rs @@ -23,9 +23,6 @@ pub type Compositor = renderer::Compositor; #[cfg(all(feature = "wgpu", feature = "tiny-skia"))] mod renderer { - use crate::core::renderer; - use crate::core::{Color, Font, Pixels, Size}; - pub type Renderer = crate::fallback::Renderer< iced_wgpu::Renderer, iced_tiny_skia::Renderer, @@ -35,31 +32,6 @@ mod renderer { iced_wgpu::window::Compositor, iced_tiny_skia::window::Compositor, >; - - impl renderer::Headless for Renderer { - fn new(default_font: Font, default_text_size: Pixels) -> Self { - Self::Secondary(iced_tiny_skia::Renderer::new( - default_font, - default_text_size, - )) - } - - fn screenshot( - &mut self, - size: Size, - scale_factor: f32, - background_color: Color, - ) -> Vec { - match self { - crate::fallback::Renderer::Primary(_) => unreachable!( - "iced_wgpu does not support headless mode yet!" - ), - crate::fallback::Renderer::Secondary(renderer) => { - renderer.screenshot(size, scale_factor, background_color) - } - } - } - } } #[cfg(all(feature = "wgpu", not(feature = "tiny-skia")))] diff --git a/test/src/lib.rs b/test/src/lib.rs index 982cc2c1..c2033f81 100644 --- a/test/src/lib.rs +++ b/test/src/lib.rs @@ -186,8 +186,10 @@ where load_font(font).expect("Font must be valid"); } - let mut renderer = - Renderer::new(default_font, settings.default_text_size); + let mut renderer = iced_runtime::futures::futures::executor::block_on( + Renderer::new(default_font, settings.default_text_size), + ) + .expect("Create new headless renderer"); let raw = UserInterface::build( element, diff --git a/tiny_skia/src/lib.rs b/tiny_skia/src/lib.rs index b73eb842..2442f8fd 100644 --- a/tiny_skia/src/lib.rs +++ b/tiny_skia/src/lib.rs @@ -407,8 +407,11 @@ impl compositor::Default for Renderer { } impl renderer::Headless for Renderer { - fn new(default_font: Font, default_text_size: Pixels) -> Self { - Self::new(default_font, default_text_size) + async fn new( + default_font: Font, + default_text_size: Pixels, + ) -> Option { + Some(Self::new(default_font, default_text_size)) } fn screenshot( @@ -420,11 +423,6 @@ impl renderer::Headless for Renderer { let viewport = Viewport::with_physical_size(size, f64::from(scale_factor)); - window::compositor::screenshot::<&str>( - self, - &viewport, - background_color, - &[], - ) + window::compositor::screenshot(self, &viewport, background_color) } } diff --git a/tiny_skia/src/window/compositor.rs b/tiny_skia/src/window/compositor.rs index b0c369fb..a36be106 100644 --- a/tiny_skia/src/window/compositor.rs +++ b/tiny_skia/src/window/compositor.rs @@ -126,14 +126,13 @@ impl crate::graphics::Compositor for Compositor { ) } - fn screenshot>( + fn screenshot( &mut self, renderer: &mut Self::Renderer, viewport: &Viewport, background_color: Color, - overlay: &[T], ) -> Vec { - screenshot(renderer, viewport, background_color, overlay) + screenshot(renderer, viewport, background_color) } } @@ -219,11 +218,10 @@ pub fn present>( buffer.present().map_err(|_| compositor::SurfaceError::Lost) } -pub fn screenshot>( +pub fn screenshot( renderer: &mut Renderer, viewport: &Viewport, background_color: Color, - overlay: &[T], ) -> Vec { let size = viewport.physical_size(); @@ -233,7 +231,7 @@ pub fn screenshot>( let mut clip_mask = tiny_skia::Mask::new(size.width, size.height) .expect("Create clip mask"); - renderer.draw( + renderer.draw::<&str>( &mut tiny_skia::PixmapMut::from_bytes( bytemuck::cast_slice_mut(&mut offscreen_buffer), size.width, @@ -247,7 +245,7 @@ pub fn screenshot>( size.height as f32, ))], background_color, - overlay, + &[], ); offscreen_buffer.iter().fold( diff --git a/wgpu/src/engine.rs b/wgpu/src/engine.rs index 782fd58c..5125562c 100644 --- a/wgpu/src/engine.rs +++ b/wgpu/src/engine.rs @@ -1,13 +1,16 @@ -use crate::buffer; use crate::graphics::Antialiasing; use crate::primitive; use crate::quad; use crate::text; use crate::triangle; +use std::sync::{Arc, RwLock}; + +#[derive(Clone)] #[allow(missing_debug_implementations)] pub struct Engine { - pub(crate) staging_belt: wgpu::util::StagingBelt, + pub(crate) device: wgpu::Device, + pub(crate) queue: wgpu::Queue, pub(crate) format: wgpu::TextureFormat, pub(crate) quad_pipeline: quad::Pipeline, @@ -15,46 +18,41 @@ pub struct Engine { pub(crate) triangle_pipeline: triangle::Pipeline, #[cfg(any(feature = "image", feature = "svg"))] pub(crate) image_pipeline: crate::image::Pipeline, - pub(crate) primitive_storage: primitive::Storage, + pub(crate) primitive_storage: Arc>, } impl Engine { pub fn new( _adapter: &wgpu::Adapter, - device: &wgpu::Device, - queue: &wgpu::Queue, + device: wgpu::Device, + queue: wgpu::Queue, format: wgpu::TextureFormat, antialiasing: Option, // TODO: Initialize AA pipelines lazily ) -> Self { - 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, antialiasing); - - #[cfg(any(feature = "image", feature = "svg"))] - let image_pipeline = { - let backend = _adapter.get_info().backend; - - crate::image::Pipeline::new(device, format, backend) - }; - Self { - // TODO: Resize belt smartly (?) - // It would be great if the `StagingBelt` API exposed methods - // for introspection to detect when a resize may be worth it. - staging_belt: wgpu::util::StagingBelt::new( - buffer::MAX_WRITE_SIZE as u64, - ), format, - quad_pipeline, - text_pipeline, - triangle_pipeline, + quad_pipeline: quad::Pipeline::new(&device, format), + text_pipeline: text::Pipeline::new(&device, &queue, format), + triangle_pipeline: triangle::Pipeline::new( + &device, + format, + antialiasing, + ), #[cfg(any(feature = "image", feature = "svg"))] - image_pipeline, + image_pipeline: { + let backend = _adapter.get_info().backend; - primitive_storage: primitive::Storage::default(), + crate::image::Pipeline::new(&device, format, backend) + }, + + primitive_storage: Arc::new(RwLock::new( + primitive::Storage::default(), + )), + + device, + queue, } } @@ -65,23 +63,4 @@ impl Engine { ) -> crate::image::Cache { self.image_pipeline.create_cache(device) } - - pub fn submit( - &mut self, - queue: &wgpu::Queue, - encoder: wgpu::CommandEncoder, - ) -> wgpu::SubmissionIndex { - self.staging_belt.finish(); - let index = queue.submit(Some(encoder.finish())); - self.staging_belt.recall(); - - 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(); - - index - } } diff --git a/wgpu/src/image/mod.rs b/wgpu/src/image/mod.rs index 6cb18a07..08cf4ed5 100644 --- a/wgpu/src/image/mod.rs +++ b/wgpu/src/image/mod.rs @@ -21,16 +21,14 @@ pub use crate::graphics::Image; pub type Batch = Vec; -#[derive(Debug)] +#[derive(Debug, Clone)] pub struct Pipeline { - pipeline: wgpu::RenderPipeline, + raw: wgpu::RenderPipeline, backend: wgpu::Backend, nearest_sampler: wgpu::Sampler, linear_sampler: wgpu::Sampler, texture_layout: Arc, constant_layout: wgpu::BindGroupLayout, - layers: Vec, - prepare_layer: usize, } impl Pipeline { @@ -194,26 +192,37 @@ impl Pipeline { }); Pipeline { - pipeline, + raw: pipeline, backend, nearest_sampler, linear_sampler, texture_layout: Arc::new(texture_layout), constant_layout, - layers: Vec::new(), - prepare_layer: 0, } } pub fn create_cache(&self, device: &wgpu::Device) -> Cache { Cache::new(device, self.backend, self.texture_layout.clone()) } +} + +#[derive(Default)] +pub struct State { + layers: Vec, + prepare_layer: usize, +} + +impl State { + pub fn new() -> Self { + Self::default() + } pub fn prepare( &mut self, + pipeline: &Pipeline, device: &wgpu::Device, - encoder: &mut wgpu::CommandEncoder, belt: &mut wgpu::util::StagingBelt, + encoder: &mut wgpu::CommandEncoder, cache: &mut Cache, images: &Batch, transformation: Transformation, @@ -285,9 +294,9 @@ impl Pipeline { if self.layers.len() <= self.prepare_layer { self.layers.push(Layer::new( device, - &self.constant_layout, - &self.nearest_sampler, - &self.linear_sampler, + &pipeline.constant_layout, + &pipeline.nearest_sampler, + &pipeline.linear_sampler, )); } @@ -308,13 +317,14 @@ impl Pipeline { pub fn render<'a>( &'a self, + pipeline: &'a Pipeline, cache: &'a Cache, 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_pipeline(&pipeline.raw); render_pass.set_scissor_rect( bounds.x, diff --git a/wgpu/src/lib.rs b/wgpu/src/lib.rs index ac3210e2..8c5a759a 100644 --- a/wgpu/src/lib.rs +++ b/wgpu/src/lib.rs @@ -60,6 +60,7 @@ pub use settings::Settings; #[cfg(feature = "geometry")] pub use geometry::Geometry; +use crate::core::renderer; use crate::core::{ Background, Color, Font, Pixels, Point, Rectangle, Size, Transformation, Vector, @@ -73,23 +74,30 @@ use crate::graphics::text::{Editor, Paragraph}; /// [`iced`]: https://github.com/iced-rs/iced #[allow(missing_debug_implementations)] pub struct Renderer { + engine: Engine, + default_font: Font, default_text_size: Pixels, layers: layer::Stack, - triangle_storage: triangle::Storage, - text_storage: text::Storage, + quad: quad::State, + triangle: triangle::State, + text: text::State, text_viewport: text::Viewport, + #[cfg(any(feature = "svg", feature = "image"))] + image: image::State, + // TODO: Centralize all the image feature handling #[cfg(any(feature = "svg", feature = "image"))] image_cache: std::cell::RefCell, + + staging_belt: wgpu::util::StagingBelt, } impl Renderer { pub fn new( - device: &wgpu::Device, - engine: &Engine, + engine: Engine, default_font: Font, default_text_size: Pixels, ) -> Self { @@ -98,52 +106,206 @@ impl Renderer { default_text_size, layers: layer::Stack::new(), - triangle_storage: triangle::Storage::new(), - text_storage: text::Storage::new(), - text_viewport: engine.text_pipeline.create_viewport(device), + quad: quad::State::new(), + triangle: triangle::State::new( + &engine.device, + &engine.triangle_pipeline, + ), + text: text::State::new(), + text_viewport: engine.text_pipeline.create_viewport(&engine.device), + + #[cfg(any(feature = "svg", feature = "image"))] + image: image::State::new(), #[cfg(any(feature = "svg", feature = "image"))] image_cache: std::cell::RefCell::new( - engine.create_image_cache(device), + engine.create_image_cache(&engine.device), ), + + // TODO: Resize belt smartly (?) + // It would be great if the `StagingBelt` API exposed methods + // for introspection to detect when a resize may be worth it. + staging_belt: wgpu::util::StagingBelt::new( + buffer::MAX_WRITE_SIZE as u64, + ), + + engine, } } + fn draw( + &mut self, + clear_color: Option, + target: &wgpu::TextureView, + viewport: &Viewport, + ) -> wgpu::CommandEncoder { + let mut encoder = self.engine.device.create_command_encoder( + &wgpu::CommandEncoderDescriptor { + label: Some("iced_wgpu encoder"), + }, + ); + + self.prepare(&mut encoder, viewport); + self.render(&mut encoder, target, clear_color, viewport); + + self.quad.end_frame(); + self.triangle.end_frame(); + self.text.end_frame(); + + #[cfg(any(feature = "svg", feature = "image"))] + { + self.image.end_frame(); + self.image_cache.borrow_mut().trim(); + } + + encoder + } + pub fn present>( &mut self, - engine: &mut Engine, - device: &wgpu::Device, - queue: &wgpu::Queue, - encoder: &mut wgpu::CommandEncoder, clear_color: Option, - format: wgpu::TextureFormat, + _format: wgpu::TextureFormat, frame: &wgpu::TextureView, viewport: &Viewport, overlay: &[T], - ) { + ) -> wgpu::SubmissionIndex { self.draw_overlay(overlay, viewport); - self.prepare(engine, device, queue, format, encoder, viewport); - self.render(engine, encoder, frame, clear_color, viewport); - self.triangle_storage.trim(); - self.text_storage.trim(); + let encoder = self.draw(clear_color, frame, viewport); - #[cfg(any(feature = "svg", feature = "image"))] - self.image_cache.borrow_mut().trim(); + self.staging_belt.finish(); + let submission = self.engine.queue.submit([encoder.finish()]); + self.staging_belt.recall(); + submission + } + + /// Renders the current surface to an offscreen buffer. + /// + /// Returns RGBA bytes of the texture data. + pub fn screenshot( + &mut self, + viewport: &Viewport, + background_color: Color, + ) -> Vec { + #[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, + } + } + } + + 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 = + self.engine.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: self.engine.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 mut encoder = self.draw(Some(background_color), &view, &viewport); + + let texture = crate::color::convert( + &self.engine.device, + &mut encoder, + texture, + if graphics::color::GAMMA_CORRECTION { + wgpu::TextureFormat::Rgba8UnormSrgb + } else { + wgpu::TextureFormat::Rgba8Unorm + }, + ); + + let output_buffer = + self.engine.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( + texture.as_image_copy(), + wgpu::TexelCopyBufferInfo { + buffer: &output_buffer, + layout: wgpu::TexelCopyBufferLayout { + offset: 0, + bytes_per_row: Some(dimensions.padded_bytes_per_row as u32), + rows_per_image: None, + }, + }, + texture_extent, + ); + + self.staging_belt.finish(); + let index = self.engine.queue.submit([encoder.finish()]); + self.staging_belt.recall(); + + let slice = output_buffer.slice(..); + slice.map_async(wgpu::MapMode::Read, |_| {}); + + let _ = self + .engine + .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 + }, + ) } fn prepare( &mut self, - engine: &mut Engine, - device: &wgpu::Device, - queue: &wgpu::Queue, - _format: wgpu::TextureFormat, encoder: &mut wgpu::CommandEncoder, viewport: &Viewport, ) { let scale_factor = viewport.scale_factor() as f32; - self.text_viewport.update(queue, viewport.physical_size()); + self.text_viewport + .update(&self.engine.queue, viewport.physical_size()); let physical_bounds = Rectangle::::from(Rectangle::with_size( viewport.physical_size(), @@ -159,10 +321,11 @@ impl Renderer { } if !layer.quads.is_empty() { - engine.quad_pipeline.prepare( - device, + self.quad.prepare( + &self.engine.quad_pipeline, + &self.engine.device, + &mut self.staging_belt, encoder, - &mut engine.staging_belt, &layer.quads, viewport.projection(), scale_factor, @@ -170,11 +333,11 @@ impl Renderer { } if !layer.triangles.is_empty() { - engine.triangle_pipeline.prepare( - device, + self.triangle.prepare( + &self.engine.triangle_pipeline, + &self.engine.device, + &mut self.staging_belt, encoder, - &mut engine.staging_belt, - &mut self.triangle_storage, &layer.triangles, Transformation::scale(scale_factor), viewport.physical_size(), @@ -182,12 +345,18 @@ impl Renderer { } if !layer.primitives.is_empty() { + let mut primitive_storage = self + .engine + .primitive_storage + .write() + .expect("Write primitive storage"); + for instance in &layer.primitives { instance.primitive.prepare( - device, - queue, - engine.format, - &mut engine.primitive_storage, + &self.engine.device, + &self.engine.queue, + self.engine.format, + &mut primitive_storage, &instance.bounds, viewport, ); @@ -196,10 +365,11 @@ impl Renderer { #[cfg(any(feature = "svg", feature = "image"))] if !layer.images.is_empty() { - engine.image_pipeline.prepare( - device, + self.image.prepare( + &self.engine.image_pipeline, + &self.engine.device, + &mut self.staging_belt, encoder, - &mut engine.staging_belt, &mut self.image_cache.borrow_mut(), &layer.images, viewport.projection(), @@ -208,12 +378,12 @@ impl Renderer { } if !layer.text.is_empty() { - engine.text_pipeline.prepare( - device, - queue, + self.text.prepare( + &self.engine.text_pipeline, + &self.engine.device, + &self.engine.queue, &self.text_viewport, encoder, - &mut self.text_storage, &layer.text, layer.bounds, Transformation::scale(scale_factor), @@ -224,7 +394,6 @@ impl Renderer { fn render( &mut self, - engine: &mut Engine, encoder: &mut wgpu::CommandEncoder, frame: &wgpu::TextureView, clear_color: Option, @@ -291,7 +460,8 @@ impl Renderer { }; if !layer.quads.is_empty() { - engine.quad_pipeline.render( + self.quad.render( + &self.engine.quad_pipeline, quad_layer, scissor_rect, &layer.quads, @@ -304,10 +474,10 @@ impl Renderer { if !layer.triangles.is_empty() { let _ = ManuallyDrop::into_inner(render_pass); - mesh_layer += engine.triangle_pipeline.render( + mesh_layer += self.triangle.render( + &self.engine.triangle_pipeline, encoder, frame, - &self.triangle_storage, mesh_layer, &layer.triangles, physical_bounds, @@ -337,6 +507,12 @@ impl Renderer { if !layer.primitives.is_empty() { let _ = ManuallyDrop::into_inner(render_pass); + let primitive_storage = self + .engine + .primitive_storage + .read() + .expect("Read primitive storage"); + for instance in &layer.primitives { if let Some(clip_bounds) = (instance.bounds * scale) .intersection(&physical_bounds) @@ -344,7 +520,7 @@ impl Renderer { { instance.primitive.render( encoder, - &engine.primitive_storage, + &primitive_storage, frame, &clip_bounds, ); @@ -373,7 +549,8 @@ impl Renderer { #[cfg(any(feature = "svg", feature = "image"))] if !layer.images.is_empty() { - engine.image_pipeline.render( + self.image.render( + &self.engine.image_pipeline, &image_cache, image_layer, scissor_rect, @@ -384,9 +561,9 @@ impl Renderer { } if !layer.text.is_empty() { - text_layer += engine.text_pipeline.render( + text_layer += self.text.render( + &self.engine.text_pipeline, &self.text_viewport, - &self.text_storage, text_layer, &layer.text, scissor_rect, @@ -630,3 +807,67 @@ impl primitive::Renderer for Renderer { impl graphics::compositor::Default for crate::Renderer { type Compositor = window::Compositor; } + +impl renderer::Headless for Renderer { + async fn new( + default_font: Font, + default_text_size: Pixels, + ) -> Option { + let instance = wgpu::Instance::new(&wgpu::InstanceDescriptor { + backends: wgpu::Backends::from_env() + .unwrap_or(wgpu::Backends::PRIMARY), + flags: wgpu::InstanceFlags::empty(), + ..wgpu::InstanceDescriptor::default() + }); + + let adapter = instance + .request_adapter(&wgpu::RequestAdapterOptions { + power_preference: wgpu::PowerPreference::HighPerformance, + force_fallback_adapter: false, + compatible_surface: None, + }) + .await?; + + let (device, queue) = adapter + .request_device( + &wgpu::DeviceDescriptor { + label: Some("iced_wgpu [headless]"), + required_features: wgpu::Features::empty(), + required_limits: wgpu::Limits { + max_bind_groups: 2, + ..wgpu::Limits::default() + }, + memory_hints: wgpu::MemoryHints::MemoryUsage, + }, + None, + ) + .await + .ok()?; + + let engine = Engine::new( + &adapter, + device, + queue, + if graphics::color::GAMMA_CORRECTION { + wgpu::TextureFormat::Rgba8UnormSrgb + } else { + wgpu::TextureFormat::Rgba8Unorm + }, + Some(graphics::Antialiasing::MSAAx4), + ); + + Some(Self::new(engine, default_font, default_text_size)) + } + + fn screenshot( + &mut self, + size: Size, + scale_factor: f32, + background_color: Color, + ) -> Vec { + self.screenshot( + &Viewport::with_physical_size(size, f64::from(scale_factor)), + background_color, + ) + } +} diff --git a/wgpu/src/quad.rs b/wgpu/src/quad.rs index de432d2f..24a538a2 100644 --- a/wgpu/src/quad.rs +++ b/wgpu/src/quad.rs @@ -43,15 +43,96 @@ pub struct Quad { pub shadow_blur_radius: f32, } -#[derive(Debug)] +#[derive(Debug, Clone)] pub struct Pipeline { solid: solid::Pipeline, gradient: gradient::Pipeline, constant_layout: wgpu::BindGroupLayout, +} + +#[derive(Default)] +pub struct State { layers: Vec, prepare_layer: usize, } +impl State { + pub fn new() -> Self { + Self::default() + } + + pub fn prepare( + &mut self, + pipeline: &Pipeline, + device: &wgpu::Device, + belt: &mut wgpu::util::StagingBelt, + encoder: &mut wgpu::CommandEncoder, + quads: &Batch, + transformation: Transformation, + scale: f32, + ) { + if self.layers.len() <= self.prepare_layer { + self.layers + .push(Layer::new(device, &pipeline.constant_layout)); + } + + let layer = &mut self.layers[self.prepare_layer]; + layer.prepare(device, encoder, belt, quads, transformation, scale); + + self.prepare_layer += 1; + } + + pub fn render<'a>( + &'a self, + pipeline: &'a Pipeline, + layer: usize, + bounds: Rectangle, + quads: &Batch, + render_pass: &mut wgpu::RenderPass<'a>, + ) { + if let Some(layer) = self.layers.get(layer) { + render_pass.set_scissor_rect( + bounds.x, + bounds.y, + bounds.width, + bounds.height, + ); + + let mut solid_offset = 0; + let mut gradient_offset = 0; + + for (kind, count) in &quads.order { + match kind { + Kind::Solid => { + pipeline.solid.render( + render_pass, + &layer.constants, + &layer.solid, + solid_offset..(solid_offset + count), + ); + + solid_offset += count; + } + Kind::Gradient => { + pipeline.gradient.render( + render_pass, + &layer.constants, + &layer.gradient, + gradient_offset..(gradient_offset + count), + ); + + gradient_offset += count; + } + } + } + } + } + + pub fn end_frame(&mut self) { + self.prepare_layer = 0; + } +} + impl Pipeline { pub fn new(device: &wgpu::Device, format: wgpu::TextureFormat) -> Pipeline { let constant_layout = @@ -74,79 +155,9 @@ impl Pipeline { Self { solid: solid::Pipeline::new(device, format, &constant_layout), gradient: gradient::Pipeline::new(device, format, &constant_layout), - layers: Vec::new(), - prepare_layer: 0, constant_layout, } } - - pub fn prepare( - &mut self, - device: &wgpu::Device, - encoder: &mut wgpu::CommandEncoder, - belt: &mut wgpu::util::StagingBelt, - quads: &Batch, - transformation: Transformation, - scale: f32, - ) { - if self.layers.len() <= self.prepare_layer { - self.layers.push(Layer::new(device, &self.constant_layout)); - } - - let layer = &mut self.layers[self.prepare_layer]; - layer.prepare(device, encoder, belt, quads, transformation, scale); - - self.prepare_layer += 1; - } - - pub fn render<'a>( - &'a self, - layer: usize, - bounds: Rectangle, - quads: &Batch, - render_pass: &mut wgpu::RenderPass<'a>, - ) { - if let Some(layer) = self.layers.get(layer) { - render_pass.set_scissor_rect( - bounds.x, - bounds.y, - bounds.width, - bounds.height, - ); - - let mut solid_offset = 0; - let mut gradient_offset = 0; - - for (kind, count) in &quads.order { - match kind { - Kind::Solid => { - self.solid.render( - render_pass, - &layer.constants, - &layer.solid, - solid_offset..(solid_offset + count), - ); - - solid_offset += count; - } - Kind::Gradient => { - self.gradient.render( - render_pass, - &layer.constants, - &layer.gradient, - gradient_offset..(gradient_offset + count), - ); - - gradient_offset += count; - } - } - } - } - } - - pub fn end_frame(&mut self) { - self.prepare_layer = 0; - } } #[derive(Debug)] diff --git a/wgpu/src/quad/gradient.rs b/wgpu/src/quad/gradient.rs index 68c11157..3c5fc33f 100644 --- a/wgpu/src/quad/gradient.rs +++ b/wgpu/src/quad/gradient.rs @@ -57,7 +57,7 @@ impl Layer { } } -#[derive(Debug)] +#[derive(Debug, Clone)] pub struct Pipeline { #[cfg(not(target_arch = "wasm32"))] pipeline: wgpu::RenderPipeline, diff --git a/wgpu/src/quad/solid.rs b/wgpu/src/quad/solid.rs index b6d88486..317a248c 100644 --- a/wgpu/src/quad/solid.rs +++ b/wgpu/src/quad/solid.rs @@ -51,7 +51,7 @@ impl Layer { } } -#[derive(Debug)] +#[derive(Debug, Clone)] pub struct Pipeline { pipeline: wgpu::RenderPipeline, } diff --git a/wgpu/src/text.rs b/wgpu/src/text.rs index 9a8f5c0d..7f88cac4 100644 --- a/wgpu/src/text.rs +++ b/wgpu/src/text.rs @@ -9,7 +9,7 @@ use crate::graphics::text::{Editor, Paragraph, font_system, to_color}; use rustc_hash::FxHashMap; use std::collections::hash_map; use std::sync::atomic::{self, AtomicU64}; -use std::sync::{self, Arc}; +use std::sync::{self, Arc, RwLock}; pub use crate::graphics::Text; @@ -94,10 +94,6 @@ struct Group { } impl Storage { - pub fn new() -> Self { - Self::default() - } - fn get(&self, cache: &Cache) -> Option<(&cryoglyph::TextAtlas, &Upload)> { if cache.text.is_empty() { return None; @@ -272,48 +268,40 @@ impl Viewport { } } +#[derive(Clone)] #[allow(missing_debug_implementations)] pub struct Pipeline { - state: cryoglyph::Cache, format: wgpu::TextureFormat, - atlas: cryoglyph::TextAtlas, + cache: cryoglyph::Cache, + atlas: Arc>, +} + +#[derive(Default)] +pub struct State { renderers: Vec, prepare_layer: usize, cache: BufferCache, + storage: Storage, } -impl Pipeline { - pub fn new( - device: &wgpu::Device, - queue: &wgpu::Queue, - format: wgpu::TextureFormat, - ) -> Self { - let state = cryoglyph::Cache::new(device); - let atlas = cryoglyph::TextAtlas::with_color_mode( - device, queue, &state, format, COLOR_MODE, - ); - - Pipeline { - state, - format, - renderers: Vec::new(), - atlas, - prepare_layer: 0, - cache: BufferCache::new(), - } +impl State { + pub fn new() -> Self { + Self::default() } pub fn prepare( &mut self, + pipeline: &Pipeline, device: &wgpu::Device, queue: &wgpu::Queue, viewport: &Viewport, encoder: &mut wgpu::CommandEncoder, - storage: &mut Storage, batch: &Batch, layer_bounds: Rectangle, layer_transformation: Transformation, ) { + let mut atlas = pipeline.atlas.write().expect("Write to text atlas"); + for item in batch { match item { Item::Group { @@ -322,7 +310,7 @@ impl Pipeline { } => { if self.renderers.len() <= self.prepare_layer { self.renderers.push(cryoglyph::TextRenderer::new( - &mut self.atlas, + &mut atlas, device, wgpu::MultisampleState::default(), None, @@ -336,7 +324,7 @@ impl Pipeline { &viewport.0, encoder, renderer, - &mut self.atlas, + &mut atlas, &mut self.cache, text, layer_bounds * layer_transformation, @@ -358,13 +346,13 @@ impl Pipeline { transformation, cache, } => { - storage.prepare( + self.storage.prepare( device, queue, &viewport.0, encoder, - self.format, - &self.state, + pipeline.format, + &pipeline.cache, cache, layer_transformation * *transformation, layer_bounds * layer_transformation, @@ -376,13 +364,14 @@ impl Pipeline { pub fn render<'a>( &'a self, + pipeline: &'a Pipeline, viewport: &'a Viewport, - storage: &'a Storage, start: usize, batch: &'a Batch, bounds: Rectangle, render_pass: &mut wgpu::RenderPass<'a>, ) -> usize { + let atlas = pipeline.atlas.read().expect("Read text atlas"); let mut layer_count = 0; render_pass.set_scissor_rect( @@ -398,13 +387,13 @@ impl Pipeline { let renderer = &self.renderers[start + layer_count]; renderer - .render(&self.atlas, &viewport.0, render_pass) + .render(&atlas, &viewport.0, render_pass) .expect("Render text"); layer_count += 1; } Item::Cached { cache, .. } => { - if let Some((atlas, upload)) = storage.get(cache) { + if let Some((atlas, upload)) = self.storage.get(cache) { upload .renderer .render(atlas, &viewport.0, render_pass) @@ -417,18 +406,37 @@ impl Pipeline { layer_count } - pub fn create_viewport(&self, device: &wgpu::Device) -> Viewport { - Viewport(cryoglyph::Viewport::new(device, &self.state)) - } - pub fn end_frame(&mut self) { - self.atlas.trim(); self.cache.trim(); + self.storage.trim(); self.prepare_layer = 0; } } +impl Pipeline { + pub fn new( + device: &wgpu::Device, + queue: &wgpu::Queue, + format: wgpu::TextureFormat, + ) -> Self { + let cache = cryoglyph::Cache::new(device); + let atlas = cryoglyph::TextAtlas::with_color_mode( + device, queue, &cache, format, COLOR_MODE, + ); + + Pipeline { + format, + cache, + atlas: Arc::new(RwLock::new(atlas)), + } + } + + pub fn create_viewport(&self, device: &wgpu::Device) -> Viewport { + Viewport(cryoglyph::Viewport::new(device, &self.cache)) + } +} + fn prepare( device: &wgpu::Device, queue: &wgpu::Queue, diff --git a/wgpu/src/triangle.rs b/wgpu/src/triangle.rs index a2b976ea..ec3ee309 100644 --- a/wgpu/src/triangle.rs +++ b/wgpu/src/triangle.rs @@ -153,42 +153,47 @@ impl Storage { } } -#[derive(Debug)] +#[derive(Debug, Clone)] pub struct Pipeline { - blit: Option, + msaa: Option, solid: solid::Pipeline, gradient: gradient::Pipeline, - layers: Vec, - prepare_layer: usize, } -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), - gradient: gradient::Pipeline::new(device, format, antialiasing), +pub struct State { + msaa: Option, + layers: Vec, + prepare_layer: usize, + storage: Storage, +} + +impl State { + pub fn new(device: &wgpu::Device, pipeline: &Pipeline) -> Self { + Self { + msaa: pipeline + .msaa + .as_ref() + .map(|pipeline| msaa::State::new(device, pipeline)), layers: Vec::new(), prepare_layer: 0, + storage: Storage::new(), } } pub fn prepare( &mut self, + pipeline: &Pipeline, device: &wgpu::Device, - encoder: &mut wgpu::CommandEncoder, belt: &mut wgpu::util::StagingBelt, - storage: &mut Storage, + encoder: &mut wgpu::CommandEncoder, items: &[Item], scale: Transformation, target_size: Size, ) { - let projection = if let Some(blit) = &mut self.blit { - blit.prepare(device, encoder, belt, target_size) * scale + let projection = if let Some((state, pipeline)) = + self.msaa.as_mut().zip(pipeline.msaa.as_ref()) + { + state.prepare(device, encoder, belt, pipeline, target_size) * scale } else { Transformation::orthographic(target_size.width, target_size.height) * scale @@ -203,8 +208,8 @@ impl Pipeline { if self.layers.len() <= self.prepare_layer { self.layers.push(Layer::new( device, - &self.solid, - &self.gradient, + &pipeline.solid, + &pipeline.gradient, )); } @@ -213,8 +218,8 @@ impl Pipeline { device, encoder, belt, - &self.solid, - &self.gradient, + &pipeline.solid, + &pipeline.gradient, meshes, projection * *transformation, ); @@ -225,12 +230,12 @@ impl Pipeline { transformation, cache, } => { - storage.prepare( + self.storage.prepare( device, encoder, belt, - &self.solid, - &self.gradient, + &pipeline.solid, + &pipeline.gradient, cache, projection * *transformation, ); @@ -241,9 +246,9 @@ impl Pipeline { pub fn render( &mut self, + pipeline: &Pipeline, encoder: &mut wgpu::CommandEncoder, target: &wgpu::TextureView, - storage: &Storage, start: usize, batch: &Batch, bounds: Rectangle, @@ -269,7 +274,7 @@ impl Pipeline { transformation, cache, } => { - let upload = storage.get(cache)?; + let upload = self.storage.get(cache)?; Some(( &upload.layer, @@ -282,9 +287,9 @@ impl Pipeline { render( encoder, target, - self.blit.as_mut(), - &self.solid, - &self.gradient, + self.msaa.as_ref().zip(pipeline.msaa.as_ref()), + &pipeline.solid, + &pipeline.gradient, bounds, items, ); @@ -293,47 +298,54 @@ impl Pipeline { } pub fn end_frame(&mut self) { + self.storage.trim(); + self.prepare_layer = 0; } } +impl Pipeline { + pub fn new( + device: &wgpu::Device, + format: wgpu::TextureFormat, + antialiasing: Option, + ) -> Pipeline { + Pipeline { + msaa: antialiasing.map(|a| msaa::Pipeline::new(device, format, a)), + solid: solid::Pipeline::new(device, format, antialiasing), + gradient: gradient::Pipeline::new(device, format, antialiasing), + } + } +} + fn render<'a>( encoder: &mut wgpu::CommandEncoder, target: &wgpu::TextureView, - mut blit: Option<&mut msaa::Blit>, + mut msaa: Option<(&msaa::State, &msaa::Pipeline)>, solid: &solid::Pipeline, gradient: &gradient::Pipeline, bounds: Rectangle, group: impl Iterator, ) { { - let (attachment, resolve_target, load) = if let Some(blit) = &mut blit { - let (attachment, resolve_target) = blit.targets(); - - ( - attachment, - Some(resolve_target), - wgpu::LoadOp::Clear(wgpu::Color::TRANSPARENT), - ) + let mut render_pass = if let Some((_state, pipeline)) = &mut msaa { + pipeline.render_pass(encoder) } else { - (target, None, wgpu::LoadOp::Load) - }; - - let mut render_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor { label: Some("iced_wgpu.triangle.render_pass"), color_attachments: &[Some(wgpu::RenderPassColorAttachment { - view: attachment, - resolve_target, + view: target, + resolve_target: None, ops: wgpu::Operations { - load, + load: wgpu::LoadOp::Load, store: wgpu::StoreOp::Store, }, })], depth_stencil_attachment: None, timestamp_writes: None, occlusion_query_set: None, - }); + }) + }; for (layer, meshes, transformation) in group { layer.render( @@ -347,8 +359,8 @@ fn render<'a>( } } - if let Some(blit) = blit { - blit.draw(encoder, target); + if let Some((state, pipeline)) = msaa { + state.render(pipeline, encoder, target); } } @@ -649,7 +661,7 @@ mod solid { use crate::graphics::mesh; use crate::triangle; - #[derive(Debug)] + #[derive(Debug, Clone)] pub struct Pipeline { pub pipeline: wgpu::RenderPipeline, pub constants_layout: wgpu::BindGroupLayout, @@ -801,7 +813,7 @@ mod gradient { use crate::graphics::mesh; use crate::triangle; - #[derive(Debug)] + #[derive(Debug, Clone)] pub struct Pipeline { pub pipeline: wgpu::RenderPipeline, pub constants_layout: wgpu::BindGroupLayout, diff --git a/wgpu/src/triangle/msaa.rs b/wgpu/src/triangle/msaa.rs index 0a5b134f..86dddb0b 100644 --- a/wgpu/src/triangle/msaa.rs +++ b/wgpu/src/triangle/msaa.rs @@ -2,35 +2,28 @@ use crate::core::{Size, Transformation}; use crate::graphics; use std::num::NonZeroU64; +use std::sync::{Arc, RwLock}; -#[derive(Debug)] -pub struct Blit { +#[derive(Debug, Clone)] +pub struct Pipeline { format: wgpu::TextureFormat, - pipeline: wgpu::RenderPipeline, - constants: wgpu::BindGroup, - ratio: wgpu::Buffer, + sampler: wgpu::Sampler, + raw: wgpu::RenderPipeline, + constant_layout: wgpu::BindGroupLayout, texture_layout: wgpu::BindGroupLayout, sample_count: u32, - targets: Option, - last_region: Option>, + targets: Arc>>, } -impl Blit { +impl Pipeline { pub fn new( device: &wgpu::Device, format: wgpu::TextureFormat, antialiasing: graphics::Antialiasing, - ) -> Blit { + ) -> Pipeline { let sampler = device.create_sampler(&wgpu::SamplerDescriptor::default()); - let ratio = device.create_buffer(&wgpu::BufferDescriptor { - label: Some("iced-wgpu::triangle::msaa ratio"), - size: std::mem::size_of::() as u64, - usage: wgpu::BufferUsages::COPY_DST | wgpu::BufferUsages::UNIFORM, - mapped_at_creation: false, - }); - let constant_layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { label: Some("iced_wgpu::triangle:msaa uniforms layout"), @@ -56,22 +49,6 @@ impl Blit { ], }); - let constant_bind_group = - device.create_bind_group(&wgpu::BindGroupDescriptor { - label: Some("iced_wgpu::triangle::msaa uniforms bind group"), - layout: &constant_layout, - entries: &[ - wgpu::BindGroupEntry { - binding: 0, - resource: wgpu::BindingResource::Sampler(&sampler), - }, - wgpu::BindGroupEntry { - binding: 1, - resource: ratio.as_entire_binding(), - }, - ], - }); - let texture_layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { label: Some("iced_wgpu::triangle::msaa texture layout"), @@ -143,31 +120,30 @@ impl Blit { cache: None, }); - Blit { + Self { format, - pipeline, - constants: constant_bind_group, - ratio, + sampler, + raw: pipeline, + constant_layout, texture_layout, sample_count: antialiasing.sample_count(), - targets: None, - last_region: None, + targets: Arc::new(RwLock::new(None)), } } - pub fn prepare( - &mut self, + fn targets( + &self, device: &wgpu::Device, - encoder: &mut wgpu::CommandEncoder, - belt: &mut wgpu::util::StagingBelt, region_size: Size, - ) -> Transformation { - match &mut self.targets { + ) -> Targets { + let mut targets = self.targets.write().expect("Write MSAA targets"); + + match targets.as_mut() { Some(targets) if region_size.width <= targets.size.width && region_size.height <= targets.size.height => {} _ => { - self.targets = Some(Targets::new( + *targets = Some(Targets::new( device, self.format, &self.texture_layout, @@ -177,69 +153,34 @@ impl Blit { } } - let targets = self.targets.as_mut().unwrap(); - - if Some(region_size) != self.last_region { - let ratio = Ratio { - u: region_size.width as f32 / targets.size.width as f32, - v: region_size.height as f32 / targets.size.height as f32, - }; - - belt.write_buffer( - encoder, - &self.ratio, - 0, - NonZeroU64::new(std::mem::size_of::() as u64) - .expect("non-empty ratio"), - device, - ) - .copy_from_slice(bytemuck::bytes_of(&ratio)); - - self.last_region = Some(region_size); - } - - Transformation::orthographic(targets.size.width, targets.size.height) + targets.as_ref().unwrap().clone() } - pub fn targets(&self) -> (&wgpu::TextureView, &wgpu::TextureView) { - let targets = self.targets.as_ref().unwrap(); - - (&targets.attachment, &targets.resolve) - } - - pub fn draw( + pub fn render_pass<'a>( &self, - encoder: &mut wgpu::CommandEncoder, - target: &wgpu::TextureView, - ) { - let mut render_pass = - encoder.begin_render_pass(&wgpu::RenderPassDescriptor { - label: Some("iced_wgpu::triangle::msaa render pass"), - color_attachments: &[Some(wgpu::RenderPassColorAttachment { - view: target, - resolve_target: None, - ops: wgpu::Operations { - load: wgpu::LoadOp::Load, - store: wgpu::StoreOp::Store, - }, - })], - depth_stencil_attachment: None, - timestamp_writes: None, - occlusion_query_set: None, - }); + encoder: &'a mut wgpu::CommandEncoder, + ) -> wgpu::RenderPass<'a> { + let targets = self.targets.read().expect("Read MSAA targets"); + let targets = targets.as_ref().unwrap(); - render_pass.set_pipeline(&self.pipeline); - render_pass.set_bind_group(0, &self.constants, &[]); - render_pass.set_bind_group( - 1, - &self.targets.as_ref().unwrap().bind_group, - &[], - ); - render_pass.draw(0..6, 0..1); + encoder.begin_render_pass(&wgpu::RenderPassDescriptor { + label: Some("iced_wgpu.triangle.render_pass"), + color_attachments: &[Some(wgpu::RenderPassColorAttachment { + view: &targets.attachment, + resolve_target: Some(&targets.resolve), + ops: wgpu::Operations { + load: wgpu::LoadOp::Load, + store: wgpu::StoreOp::Store, + }, + })], + depth_stencil_attachment: None, + timestamp_writes: None, + occlusion_query_set: None, + }) } } -#[derive(Debug)] +#[derive(Debug, Clone)] struct Targets { attachment: wgpu::TextureView, resolve: wgpu::TextureView, @@ -308,9 +249,117 @@ impl Targets { } } -#[derive(Debug, Clone, Copy, bytemuck::Pod, bytemuck::Zeroable)] +#[derive(Debug, Clone, Copy, PartialEq, bytemuck::Pod, bytemuck::Zeroable)] #[repr(C)] struct Ratio { u: f32, v: f32, } + +pub struct State { + ratio: wgpu::Buffer, + constants: wgpu::BindGroup, + last_ratio: Option, +} + +impl State { + pub fn new(device: &wgpu::Device, pipeline: &Pipeline) -> Self { + let ratio = device.create_buffer(&wgpu::BufferDescriptor { + label: Some("iced_wgpu::triangle::msaa ratio"), + size: std::mem::size_of::() as u64, + usage: wgpu::BufferUsages::COPY_DST | wgpu::BufferUsages::UNIFORM, + mapped_at_creation: false, + }); + + let constants = device.create_bind_group(&wgpu::BindGroupDescriptor { + label: Some("iced_wgpu::triangle::msaa uniforms bind group"), + layout: &pipeline.constant_layout, + entries: &[ + wgpu::BindGroupEntry { + binding: 0, + resource: wgpu::BindingResource::Sampler(&pipeline.sampler), + }, + wgpu::BindGroupEntry { + binding: 1, + resource: ratio.as_entire_binding(), + }, + ], + }); + + Self { + ratio, + constants, + last_ratio: None, + } + } + + pub fn prepare( + &mut self, + device: &wgpu::Device, + encoder: &mut wgpu::CommandEncoder, + belt: &mut wgpu::util::StagingBelt, + pipeline: &Pipeline, + region_size: Size, + ) -> Transformation { + let targets = pipeline.targets(device, region_size); + + let ratio = Ratio { + u: region_size.width as f32 / targets.size.width as f32, + v: region_size.height as f32 / targets.size.height as f32, + }; + + if Some(ratio) != self.last_ratio { + belt.write_buffer( + encoder, + &self.ratio, + 0, + NonZeroU64::new(std::mem::size_of::() as u64) + .expect("non-empty ratio"), + device, + ) + .copy_from_slice(bytemuck::bytes_of(&ratio)); + + self.last_ratio = Some(ratio); + } + + Transformation::orthographic(targets.size.width, targets.size.height) + } + + pub fn render( + &self, + pipeline: &Pipeline, + encoder: &mut wgpu::CommandEncoder, + target: &wgpu::TextureView, + ) { + let mut render_pass = + encoder.begin_render_pass(&wgpu::RenderPassDescriptor { + label: Some("iced_wgpu::triangle::msaa render pass"), + color_attachments: &[Some(wgpu::RenderPassColorAttachment { + view: target, + resolve_target: None, + ops: wgpu::Operations { + load: wgpu::LoadOp::Load, + store: wgpu::StoreOp::Store, + }, + })], + depth_stencil_attachment: None, + timestamp_writes: None, + occlusion_query_set: None, + }); + + render_pass.set_pipeline(&pipeline.raw); + render_pass.set_bind_group(0, &self.constants, &[]); + render_pass.set_bind_group( + 1, + &pipeline + .targets + .read() + .expect("Read MSAA targets") + .as_ref() + .unwrap() + .bind_group, + &[], + ); + render_pass.draw(0..6, 0..1); + } +} diff --git a/wgpu/src/window/compositor.rs b/wgpu/src/window/compositor.rs index 107aa1f5..053d3a19 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, Size}; +use crate::core::Color; use crate::graphics::color; use crate::graphics::compositor; use crate::graphics::error; @@ -12,8 +12,6 @@ use crate::{Engine, Renderer}; pub struct Compositor { instance: wgpu::Instance, adapter: wgpu::Adapter, - device: wgpu::Device, - queue: wgpu::Queue, format: wgpu::TextureFormat, alpha_mode: wgpu::CompositeAlphaMode, engine: Engine, @@ -178,8 +176,8 @@ impl Compositor { Ok((device, queue)) => { let engine = Engine::new( &adapter, - &device, - &queue, + device, + queue, format, settings.antialiasing, ); @@ -187,8 +185,6 @@ impl Compositor { return Ok(Compositor { instance, adapter, - device, - queue, format, alpha_mode, engine, @@ -215,7 +211,6 @@ pub async fn new( /// Presents the given primitives with the given [`Compositor`]. pub fn present>( - compositor: &mut Compositor, renderer: &mut Renderer, surface: &mut wgpu::Surface<'static>, viewport: &Viewport, @@ -225,21 +220,11 @@ pub fn present>( ) -> 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()); - renderer.present( - &mut compositor.engine, - &compositor.device, - &compositor.queue, - &mut encoder, + let _submission = renderer.present( Some(background_color), frame.texture.format(), view, @@ -247,8 +232,6 @@ pub fn present>( overlay, ); - let _ = compositor.engine.submit(&compositor.queue, encoder); - // Present the frame on_pre_present(); frame.present(); @@ -305,8 +288,7 @@ impl graphics::Compositor for Compositor { fn create_renderer(&self) -> Self::Renderer { Renderer::new( - &self.device, - &self.engine, + self.engine.clone(), self.settings.default_font, self.settings.default_text_size, ) @@ -337,7 +319,7 @@ impl graphics::Compositor for Compositor { height: u32, ) { surface.configure( - &self.device, + &self.engine.device, &wgpu::SurfaceConfiguration { usage: wgpu::TextureUsages::RENDER_ATTACHMENT, format: self.format, @@ -370,7 +352,6 @@ impl graphics::Compositor for Compositor { on_pre_present: impl FnOnce(), ) -> Result<(), compositor::SurfaceError> { present( - self, renderer, surface, viewport, @@ -380,143 +361,12 @@ impl graphics::Compositor for Compositor { ) } - fn screenshot>( + fn screenshot( &mut self, renderer: &mut Self::Renderer, viewport: &Viewport, background_color: Color, - overlay: &[T], ) -> Vec { - screenshot(self, renderer, viewport, background_color, overlay) - } -} - -/// Renders the current surface to an offscreen buffer. -/// -/// Returns RGBA bytes of the texture data. -pub fn screenshot>( - compositor: &mut Compositor, - renderer: &mut Renderer, - viewport: &Viewport, - background_color: Color, - overlay: &[T], -) -> Vec { - 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 mut encoder = compositor.device.create_command_encoder( - &wgpu::CommandEncoderDescriptor { - label: Some("iced_wgpu.offscreen.encoder"), - }, - ); - - renderer.present( - &mut compositor.engine, - &compositor.device, - &compositor.queue, - &mut encoder, - Some(background_color), - texture.format(), - &view, - viewport, - overlay, - ); - - let texture = crate::color::convert( - &compositor.device, - &mut encoder, - texture, - if color::GAMMA_CORRECTION { - wgpu::TextureFormat::Rgba8UnormSrgb - } else { - wgpu::TextureFormat::Rgba8Unorm - }, - ); - - 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( - texture.as_image_copy(), - wgpu::TexelCopyBufferInfo { - buffer: &output_buffer, - layout: wgpu::TexelCopyBufferLayout { - offset: 0, - bytes_per_row: Some(dimensions.padded_bytes_per_row as u32), - rows_per_image: None, - }, - }, - texture_extent, - ); - - let index = compositor.engine.submit(&compositor.queue, encoder); - - 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, - } + renderer.screenshot(viewport, background_color) } } diff --git a/winit/src/program.rs b/winit/src/program.rs index 5a0ebcb1..6904c02a 100644 --- a/winit/src/program.rs +++ b/winit/src/program.rs @@ -773,7 +773,6 @@ async fn run_instance( &mut messages, &mut clipboard, &mut control_sender, - &mut debug, &mut user_interfaces, &mut window_manager, &mut ui_caches, @@ -966,7 +965,6 @@ async fn run_instance( &mut messages, &mut clipboard, &mut control_sender, - &mut debug, &mut user_interfaces, &mut window_manager, &mut ui_caches, @@ -1187,7 +1185,6 @@ fn run_action( messages: &mut Vec, clipboard: &mut Clipboard, control_sender: &mut mpsc::UnboundedSender, - debug: &mut Debug, interfaces: &mut FxHashMap< window::Id, UserInterface<'_, P::Message, P::Theme, P::Renderer>, @@ -1477,7 +1474,6 @@ fn run_action( &mut window.renderer, window.state.viewport(), window.state.background_color(), - &debug.overlay(), ); let _ = channel.send(core::window::Screenshot::new(