diff options
Diffstat (limited to 'wgpu')
-rw-r--r-- | wgpu/Cargo.toml | 2 | ||||
-rw-r--r-- | wgpu/src/backend.rs | 6 | ||||
-rw-r--r-- | wgpu/src/color.rs | 165 | ||||
-rw-r--r-- | wgpu/src/lib.rs | 1 | ||||
-rw-r--r-- | wgpu/src/text.rs | 72 | ||||
-rw-r--r-- | wgpu/src/triangle/msaa.rs | 11 | ||||
-rw-r--r-- | wgpu/src/window/compositor.rs | 152 |
7 files changed, 351 insertions, 58 deletions
diff --git a/wgpu/Cargo.toml b/wgpu/Cargo.toml index 7e50dff2..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 = "26f92369da3704988e3e27f0b35e705c6b2de203" +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/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 0a5726b5..86a962a5 100644 --- a/wgpu/src/lib.rs +++ b/wgpu/src/lib.rs @@ -46,6 +46,7 @@ pub mod geometry; mod backend; mod buffer; +mod color; mod quad; mod text; mod triangle; diff --git a/wgpu/src/text.rs b/wgpu/src/text.rs index 0d88865c..c9188bd1 100644 --- a/wgpu/src/text.rs +++ b/wgpu/src/text.rs @@ -18,8 +18,7 @@ pub struct Pipeline { renderers: Vec<glyphon::TextRenderer>, atlas: glyphon::TextAtlas, prepare_layer: usize, - measurement_cache: RefCell<Cache>, - render_cache: Cache, + cache: RefCell<Cache>, } impl Pipeline { @@ -47,15 +46,16 @@ impl Pipeline { }, ), prepare_layer: 0, - measurement_cache: RefCell::new(Cache::new()), - render_cache: Cache::new(), + cache: RefCell::new(Cache::new()), } } 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())), ); + + self.cache = RefCell::new(Cache::new()); } pub fn prepare( @@ -78,25 +78,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,22 +113,16 @@ impl Pipeline { .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 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, @@ -150,14 +144,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, @@ -235,7 +226,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; } @@ -249,7 +240,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))); @@ -265,14 +256,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( @@ -286,7 +270,7 @@ impl Pipeline { point: Point, _nearest_only: bool, ) -> Option<Hit> { - 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))); @@ -306,10 +290,16 @@ 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) { + 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> { 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..1cfd7b67 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,154 @@ impl<Theme> graphics::Compositor for Compositor<Theme> { ) }) } + + fn screenshot<T: AsRef<str>>( + &mut self, + renderer: &mut Self::Renderer, + _surface: &mut Self::Surface, + viewport: &Viewport, + background_color: Color, + overlay: &[T], + ) -> Vec<u8> { + 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<Theme, T: AsRef<str>>( + compositor: &Compositor<Theme>, + backend: &mut Backend, + primitives: &[Primitive], + viewport: &Viewport, + background_color: Color, + overlay: &[T], +) -> Vec<u8> { + 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()); + + backend.present( + &compositor.device, + &compositor.queue, + &mut encoder, + Some(background_color), + &view, + primitives, + 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::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<u32>) -> 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, + } + } } |