diff options
Diffstat (limited to 'wgpu/src')
-rw-r--r-- | wgpu/src/backend.rs | 17 | ||||
-rw-r--r-- | wgpu/src/buffer.rs | 43 | ||||
-rw-r--r-- | wgpu/src/buffer/dynamic.rs | 202 | ||||
-rw-r--r-- | wgpu/src/buffer/static.rs | 107 | ||||
-rw-r--r-- | wgpu/src/color.rs | 165 | ||||
-rw-r--r-- | wgpu/src/geometry.rs | 95 | ||||
-rw-r--r-- | wgpu/src/image.rs | 8 | ||||
-rw-r--r-- | wgpu/src/image/atlas.rs | 48 | ||||
-rw-r--r-- | wgpu/src/image/raster.rs | 4 | ||||
-rw-r--r-- | wgpu/src/image/vector.rs | 5 | ||||
-rw-r--r-- | wgpu/src/layer.rs | 29 | ||||
-rw-r--r-- | wgpu/src/layer/mesh.rs | 15 | ||||
-rw-r--r-- | wgpu/src/layer/quad.rs | 30 | ||||
-rw-r--r-- | wgpu/src/lib.rs | 9 | ||||
-rw-r--r-- | wgpu/src/quad.rs | 304 | ||||
-rw-r--r-- | wgpu/src/quad/gradient.rs | 165 | ||||
-rw-r--r-- | wgpu/src/quad/solid.rs | 150 | ||||
-rw-r--r-- | wgpu/src/shader/gradient.wgsl | 88 | ||||
-rw-r--r-- | wgpu/src/shader/quad.wgsl | 293 | ||||
-rw-r--r-- | wgpu/src/shader/solid.wgsl | 30 | ||||
-rw-r--r-- | wgpu/src/shader/triangle.wgsl | 165 | ||||
-rw-r--r-- | wgpu/src/text.rs | 87 | ||||
-rw-r--r-- | wgpu/src/triangle.rs | 538 | ||||
-rw-r--r-- | wgpu/src/triangle/msaa.rs | 11 | ||||
-rw-r--r-- | wgpu/src/window/compositor.rs | 174 |
25 files changed, 1627 insertions, 1155 deletions
diff --git a/wgpu/src/backend.rs b/wgpu/src/backend.rs index def80a81..eecba2f1 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), @@ -265,8 +266,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, + &mut render_pass, + ); quad_layer += 1; } @@ -329,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/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<T> { label: &'static str, size: u64, usage: wgpu::BufferUsages, - raw: wgpu::Buffer, + pub(crate) raw: wgpu::Buffer, + offsets: Vec<wgpu::BufferAddress>, type_: PhantomData<T>, } @@ -35,6 +32,7 @@ impl<T: bytemuck::Pod> Buffer<T> { size, usage, raw, + offsets: Vec::new(), type_: PhantomData, } } @@ -43,6 +41,8 @@ impl<T: bytemuck::Pod> Buffer<T> { let new_size = (std::mem::size_of::<T>() * 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<T: bytemuck::Pod> Buffer<T> { } } + /// 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::<T>() * 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<T: bytemuck::Pod> Buffer<T> { ) -> 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<T>(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<T: ShaderType> { - offsets: Vec<wgpu::DynamicOffset>, - cpu: Internal, - gpu: wgpu::Buffer, - label: &'static str, - size: u64, - _data: PhantomData<T>, -} - -impl<T: ShaderType + WriteInto> Buffer<T> { - /// 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::<T>::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::<T>::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<Vec<u8>>), - #[cfg(not(target_arch = "wasm32"))] - //storage buffers are not supported on wgpu wasm target (yet) - Storage(encase::DynamicStorageBuffer<Vec<u8>>), -} - -impl Internal { - /// Writes the current value to its CPU buffer with proper alignment. - pub(super) fn write<T: ShaderType + WriteInto>( - &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<T> { - //stored sequentially per mesh iteration; refers to the offset index in the GPU buffer - offsets: Vec<wgpu::BufferAddress>, - label: &'static str, - usages: wgpu::BufferUsages, - gpu: wgpu::Buffer, - size: wgpu::BufferAddress, - _data: PhantomData<T>, -} - -impl<T: Pod + Zeroable> Buffer<T> { - /// Initialize a new static buffer. - pub fn new( - device: &wgpu::Device, - label: &'static str, - usages: wgpu::BufferUsages, - ) -> Self { - let size = (mem::size_of::<T>() 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::<T>() * new_count) as u64; - - if self.size < size { - self.size = - (mem::size_of::<T>() * (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/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/geometry.rs b/wgpu/src/geometry.rs index 8cfed1e5..f81b5b2f 100644 --- a/wgpu/src/geometry.rs +++ b/wgpu/src/geometry.rs @@ -1,17 +1,19 @@ -use crate::core::{Gradient, Point, Rectangle, Size, Vector}; +//! 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, }; 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; -/// The frame of a [`Canvas`]. -/// -/// [`Canvas`]: crate::widget::Canvas +/// A frame for drawing some geometry. #[allow(missing_debug_implementations)] pub struct Frame { size: Size, @@ -24,10 +26,7 @@ pub struct Frame { enum Buffer { Solid(tessellation::VertexBuffers<primitive::ColoredVertex2D, u32>), - Gradient( - tessellation::VertexBuffers<primitive::Vertex2D, u32>, - Gradient, - ), + Gradient(tessellation::VertexBuffers<primitive::GradientVertex2D, u32>), } struct BufferStack { @@ -49,12 +48,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(), )); } }, @@ -71,12 +69,17 @@ 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)) => { + Box::new(tessellation::BuffersBuilder::new( + buffer, + GradientVertex2DBuilder { + gradient: gradient.pack(), + }, )) } - (Style::Gradient(_), Buffer::Gradient(buffer, _)) => Box::new( - tessellation::BuffersBuilder::new(buffer, Vertex2DBuilder), - ), _ => unreachable!(), } } @@ -89,12 +92,17 @@ 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)) => { + Box::new(tessellation::BuffersBuilder::new( + buffer, + GradientVertex2DBuilder { + gradient: gradient.pack(), + }, )) } - (Style::Gradient(_), Buffer::Gradient(buffer, _)) => Box::new( - tessellation::BuffersBuilder::new(buffer, Vertex2DBuilder), - ), _ => unreachable!(), } } @@ -132,11 +140,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 } } @@ -353,10 +363,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 +385,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() @@ -459,7 +473,7 @@ impl Frame { }) } } - Buffer::Gradient(buffer, gradient) => { + Buffer::Gradient(buffer) => { if !buffer.indices.is_empty() { self.primitives.push(Primitive::GradientMesh { buffers: primitive::Mesh2D { @@ -467,7 +481,6 @@ impl Frame { indices: buffer.indices, }, size: self.size, - gradient, }) } } @@ -478,39 +491,43 @@ impl Frame { } } -struct Vertex2DBuilder; +struct GradientVertex2DBuilder { + gradient: gradient::Packed, +} -impl tessellation::FillVertexConstructor<primitive::Vertex2D> - for Vertex2DBuilder +impl tessellation::FillVertexConstructor<primitive::GradientVertex2D> + 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: self.gradient, } } } -impl tessellation::StrokeVertexConstructor<primitive::Vertex2D> - for Vertex2DBuilder +impl tessellation::StrokeVertexConstructor<primitive::GradientVertex2D> + 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: self.gradient, } } } -struct TriangleVertex2DBuilder([f32; 4]); +struct TriangleVertex2DBuilder(color::Packed); impl tessellation::FillVertexConstructor<primitive::ColoredVertex2D> for TriangleVertex2DBuilder diff --git a/wgpu/src/image.rs b/wgpu/src/image.rs index 263bcfa2..553ba330 100644 --- a/wgpu/src/image.rs +++ b/wgpu/src/image.rs @@ -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(); } @@ -278,7 +278,7 @@ 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, }); @@ -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, @@ -500,7 +498,7 @@ pub struct Vertex { const QUAD_INDICES: [u16; 6] = [0, 1, 2, 0, 2, 3]; -const QUAD_VERTS: [Vertex; 4] = [ +const QUAD_VERTICES: [Vertex; 4] = [ Vertex { _position: [0.0, 0.0], }, diff --git a/wgpu/src/image/atlas.rs b/wgpu/src/image/atlas.rs index 366fe623..e3de1290 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, @@ -65,7 +70,6 @@ impl Atlas { pub fn upload( &mut self, device: &wgpu::Device, - queue: &wgpu::Queue, encoder: &mut wgpu::CommandEncoder, width: u32, height: u32, @@ -112,7 +116,8 @@ impl Atlas { padding, 0, allocation, - queue, + device, + encoder, ); } Entry::Fragmented { fragments, .. } => { @@ -127,7 +132,8 @@ impl Atlas { padding, offset, &fragment.allocation, - queue, + device, + encoder, ); } } @@ -280,8 +286,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 +301,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 +327,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, ); } @@ -333,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, 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<Color>, @@ -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); diff --git a/wgpu/src/layer.rs b/wgpu/src/layer.rs index 8af72b9d..71570e3d 100644 --- a/wgpu/src/layer.rs +++ b/wgpu/src/layer.rs @@ -1,19 +1,19 @@ //! 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::core; use crate::core::alignment; -use crate::core::{Background, Color, Font, Point, Rectangle, Size, Vector}; +use crate::core::{Color, Font, Point, Rectangle, Size, Vector}; +use crate::graphics::color; use crate::graphics::{Primitive, Viewport}; +use crate::quad::{self, Quad}; /// A group of primitives that should be clipped together. #[derive(Debug)] @@ -22,7 +22,7 @@ pub struct Layer<'a> { pub bounds: Rectangle, /// The quads of the [`Layer`]. - pub quads: Vec<Quad>, + pub quads: quad::Batch, /// The triangle meshes of the [`Layer`]. pub meshes: Vec<Mesh<'a>>, @@ -39,7 +39,7 @@ impl<'a> Layer<'a> { pub fn new(bounds: Rectangle) -> Self { Self { bounds, - quads: Vec::new(), + quads: quad::Batch::default(), meshes: Vec::new(), text: Vec::new(), images: Vec::new(), @@ -145,20 +145,18 @@ 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: color::pack(*border_color), border_radius: *border_radius, border_width: *border_width, - border_color: border_color.into_linear(), - }); + }; + + layer.quads.add(quad, background); } Primitive::Image { handle, bounds } => { let layer = &mut layers[current_layer]; @@ -198,11 +196,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 +210,6 @@ impl<'a> Layer<'a> { origin: Point::new(translation.x, translation.y), buffers, clip_bounds, - gradient, }); } } 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<primitive::Vertex2D>, + buffers: &'a primitive::Mesh2D<primitive::GradientVertex2D>, /// The clipping bounds of the [`Mesh`]. clip_bounds: Rectangle<f32>, - - /// 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 deleted file mode 100644 index 0d8bde9d..00000000 --- a/wgpu/src/layer/quad.rs +++ /dev/null @@ -1,30 +0,0 @@ -/// 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/lib.rs b/wgpu/src/lib.rs index 0b169140..86a962a5 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, @@ -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; @@ -46,10 +46,13 @@ pub mod geometry; mod backend; mod buffer; +mod color; mod quad; mod text; mod triangle; +use buffer::Buffer; + pub use iced_graphics as graphics; pub use iced_graphics::core; @@ -59,8 +62,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..37d0c623 100644 --- a/wgpu/src/quad.rs +++ b/wgpu/src/quad.rs @@ -1,18 +1,27 @@ -use crate::core::Rectangle; -use crate::graphics::Transformation; -use crate::layer; -use crate::Buffer; +mod gradient; +mod solid; + +use gradient::Gradient; +use solid::Solid; + +use crate::core::{Background, Rectangle}; +use crate::graphics::color; +use crate::graphics::{self, Transformation}; use bytemuck::{Pod, Zeroable}; -use std::mem; use wgpu::util::DeviceExt; +use std::mem; + #[cfg(feature = "tracing")] use tracing::info_span; +const INITIAL_INSTANCES: usize = 2_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 +48,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::<Vertex>() 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::<layer::Quad>() 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 +77,7 @@ impl Pipeline { &mut self, device: &wgpu::Device, queue: &wgpu::Queue, - instances: &[layer::Quad], + quads: &Batch, transformation: Transformation, scale: f32, ) { @@ -156,7 +86,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; } @@ -165,25 +95,49 @@ impl Pipeline { &'a self, layer: usize, bounds: Rectangle<u32>, + quads: &Batch, 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); + 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; + } + } + } } } @@ -196,8 +150,8 @@ impl Pipeline { struct Layer { constants: wgpu::BindGroup, constants_buffer: wgpu::Buffer, - instances: Buffer<layer::Quad>, - instance_count: usize, + solid: solid::Layer, + gradient: gradient::Layer, } impl Layer { @@ -221,18 +175,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 +187,7 @@ impl Layer { &mut self, device: &wgpu::Device, queue: &wgpu::Queue, - instances: &[layer::Quad], + quads: &Batch, transformation: Transformation, scale: f32, ) { @@ -255,35 +202,138 @@ impl Layer { bytemuck::bytes_of(&uniforms), ); - let _ = self.instances.resize(device, instances.len()); - self.instances.write(queue, 0, instances); - self.instance_count = instances.len(); + self.solid.prepare(device, queue, &quads.solids); + self.gradient.prepare(device, queue, &quads.gradients); } +} - pub fn draw<'a>(&'a self, render_pass: &mut wgpu::RenderPass<'a>) { - #[cfg(feature = "tracing")] - let _ = info_span!("Wgpu::Quad", "DRAW").entered(); +/// 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], - render_pass.set_bind_group(0, &self.constants, &[]); - render_pass.set_vertex_buffer(1, self.instances.slice(..)); + /// The size of the [`Quad`]. + pub size: [f32; 2], - render_pass.draw_indexed( - 0..QUAD_INDICES.len() as u32, - 0, - 0..self.instance_count as u32, - ); + /// The border color of the [`Quad`], in __linear RGB__. + pub border_color: color::Packed, + + /// The border radii of the [`Quad`]. + pub border_radius: [f32; 4], + + /// The border width of the [`Quad`]. + pub border_width: f32, +} + +/// A group of [`Quad`]s rendered together. +#[derive(Default, Debug)] +pub struct Batch { + /// The solid quads of the [`Layer`]. + solids: Vec<Solid>, + + /// The gradient quads of the [`Layer`]. + gradients: Vec<Gradient>, + + /// The quad order of the [`Layer`]; stored as a tuple of the quad type & its count. + order: Vec<(Kind, 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() + } + + /// Adds a [`Quad`] with the provided `Background` type to the quad [`Layer`]. + pub fn add(&mut self, quad: Quad, background: &Background) { + let kind = match background { + Background::Color(color) => { + self.solids.push(Solid { + color: color::pack(*color), + quad, + }); + + Kind::Solid + } + Background::Gradient(gradient) => { + self.gradients.push(Gradient { + gradient: graphics::gradient::pack( + gradient, + Rectangle::new(quad.position.into(), quad.size.into()), + ), + quad, + }); + + Kind::Gradient + } + }; + + match self.order.last_mut() { + Some((last_kind, count)) if kind == *last_kind => { + *count += 1; + } + _ => { + self.order.push((kind, 1)); + } + } } } +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +/// The kind of a quad. +enum Kind { + /// A solid quad + Solid, + /// A gradient quad + Gradient, +} + +fn color_target_state( + format: wgpu::TextureFormat, +) -> [Option<wgpu::ColorTargetState>; 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::<Self>() as u64, + step_mode: wgpu::VertexStepMode::Vertex, + attributes: &[wgpu::VertexAttribute { + shader_location: 0, + format: wgpu::VertexFormat::Float32x2, + offset: 0, + }], + } + } +} -const QUAD_VERTS: [Vertex; 4] = [ +const INDICES: [u16; 6] = [0, 1, 2, 0, 2, 3]; + +const VERTICES: [Vertex; 4] = [ Vertex { _position: [0.0, 0.0], }, @@ -298,10 +348,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/quad/gradient.rs b/wgpu/src/quad/gradient.rs new file mode 100644 index 00000000..6db37252 --- /dev/null +++ b/wgpu/src/quad/gradient.rs @@ -0,0 +1,165 @@ +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 Layer { + instances: Buffer<Gradient>, + 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 prepare( + &mut self, + device: &wgpu::Device, + queue: &wgpu::Queue, + instances: &[Gradient], + ) { + let _ = self.instances.resize(device, instances.len()); + let _ = self.instances.write(queue, 0, instances); + + self.instance_count = instances.len(); + } +} + +#[derive(Debug)] +pub struct Pipeline { + pipeline: wgpu::RenderPipeline, +} + +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::<Gradient>() + as u64, + step_mode: wgpu::VertexStepMode::Instance, + attributes: &wgpu::vertex_attr_array!( + // Colors 1-2 + 1 => Uint32x4, + // Colors 3-4 + 2 => Uint32x4, + // Colors 5-6 + 3 => Uint32x4, + // Colors 7-8 + 4 => Uint32x4, + // Offsets 1-8 + 5 => Uint32x4, + // Direction + 6 => Float32x4, + // Position & Scale + 7 => Float32x4, + // Border color + 8 => Float32x4, + // Border radius + 9 => Float32x4, + // Border width + 10 => 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 } + } + + pub fn render<'a>( + &'a self, + render_pass: &mut wgpu::RenderPass<'a>, + constants: &'a wgpu::BindGroup, + layer: &'a Layer, + range: Range<usize>, + ) { + #[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 new file mode 100644 index 00000000..f8f1e3a5 --- /dev/null +++ b/wgpu/src/quad/solid.rs @@ -0,0 +1,150 @@ +use crate::graphics::color; +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: color::Packed, + + /// The [`Quad`] data of the [`Solid`]. + pub quad: Quad, +} + +#[derive(Debug)] +pub struct Layer { + instances: Buffer<Solid>, + 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 prepare( + &mut self, + device: &wgpu::Device, + queue: &wgpu::Queue, + instances: &[Solid], + ) { + let _ = self.instances.resize(device, instances.len()); + let _ = self.instances.write(queue, 0, instances); + + self.instance_count = instances.len(); + } +} + +#[derive(Debug)] +pub struct Pipeline { + pipeline: wgpu::RenderPipeline, +} + +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::<Solid>() 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 } + } + + pub fn render<'a>( + &'a self, + render_pass: &mut wgpu::RenderPass<'a>, + constants: &'a wgpu::BindGroup, + layer: &'a Layer, + range: Range<usize>, + ) { + #[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, + ); + } +} 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<f32>, - //xy = start, wz = end - position: vec4<f32>, - //x = start stop, y = end stop, zw = padding - stop_range: vec4<i32>, -} - -struct Stop { - color: vec4<f32>, - offset: f32, -}; - -@group(0) @binding(0) -var<uniform> uniforms: Uniforms; - -@group(0) @binding(1) -var<storage, read> color_stops: array<Stop>; - -struct VertexOutput { - @builtin(position) position: vec4<f32>, - @location(0) raw_position: vec2<f32> -} - -@vertex -fn vs_main(@location(0) input: vec2<f32>) -> VertexOutput { - var output: VertexOutput; - output.position = uniforms.transform * vec4<f32>(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<f32> { - 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<f32>; - - 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..fb402158 100644 --- a/wgpu/src/shader/quad.wgsl +++ b/wgpu/src/shader/quad.wgsl @@ -5,17 +5,57 @@ struct Globals { @group(0) @binding(0) var<uniform> globals: Globals; -struct VertexInput { +fn distance_alg( + frag_coord: vec2<f32>, + position: vec2<f32>, + size: vec2<f32>, + radius: f32 +) -> f32 { + var inner_size: vec2<f32> = size - vec2<f32>(radius, radius) * 2.0; + var top_left: vec2<f32> = position + vec2<f32>(radius, radius); + var bottom_right: vec2<f32> = top_left + inner_size; + + var top_left_distance: vec2<f32> = top_left - frag_coord; + var bottom_right_distance: vec2<f32> = frag_coord - bottom_right; + + var dist: vec2<f32> = vec2<f32>( + 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<f32>, position: vec2<f32>, center: vec2<f32>) -> 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; +} + +fn unpack_u32(color: vec2<u32>) -> vec4<f32> { + let rg: vec2<f32> = unpack2x16float(color.x); + let ba: vec2<f32> = unpack2x16float(color.y); + + return vec4<f32>(rg.y, rg.x, ba.y, ba.x); +} + +struct SolidVertexInput { @location(0) v_pos: vec2<f32>, - @location(1) pos: vec2<f32>, - @location(2) scale: vec2<f32>, - @location(3) color: vec4<f32>, + @location(1) color: vec4<f32>, + @location(2) pos: vec2<f32>, + @location(3) scale: vec2<f32>, @location(4) border_color: vec4<f32>, @location(5) border_radius: vec4<f32>, @location(6) border_width: f32, } -struct VertexOutput { +struct SolidVertexOutput { @builtin(position) position: vec4<f32>, @location(0) color: vec4<f32>, @location(1) border_color: vec4<f32>, @@ -26,8 +66,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<f32> = input.pos * globals.scale; var scale: vec2<f32> = input.scale * globals.scale; @@ -47,54 +87,20 @@ fn vs_main(input: VertexInput) -> VertexOutput { vec4<f32>(pos - vec2<f32>(0.5, 0.5), 0.0, 1.0) ); + out.position = globals.transform * transform * vec4<f32>(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<f32>(input.v_pos, 0.0, 1.0); return out; } -fn distance_alg( - frag_coord: vec2<f32>, - position: vec2<f32>, - size: vec2<f32>, - radius: f32 -) -> f32 { - var inner_size: vec2<f32> = size - vec2<f32>(radius, radius) * 2.0; - var top_left: vec2<f32> = position + vec2<f32>(radius, radius); - var bottom_right: vec2<f32> = top_left + inner_size; - - var top_left_distance: vec2<f32> = top_left - frag_coord; - var bottom_right_distance: vec2<f32> = frag_coord - bottom_right; - - var dist: vec2<f32> = vec2<f32>( - 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<f32>, position: vec2<f32>, center: vec2<f32>) -> 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<f32> { var mixed_color: vec4<f32> = input.color; @@ -138,3 +144,202 @@ fn fs_main( return vec4<f32>(mixed_color.x, mixed_color.y, mixed_color.z, mixed_color.w * radius_alpha); } + +struct GradientVertexInput { + @location(0) v_pos: vec2<f32>, + @location(1) colors_1: vec4<u32>, + @location(2) colors_2: vec4<u32>, + @location(3) colors_3: vec4<u32>, + @location(4) colors_4: vec4<u32>, + @location(5) offsets: vec4<u32>, + @location(6) direction: vec4<f32>, + @location(7) position_and_scale: vec4<f32>, + @location(8) border_color: vec4<f32>, + @location(9) border_radius: vec4<f32>, + @location(10) border_width: f32, +} + +struct GradientVertexOutput { + @builtin(position) position: vec4<f32>, + @location(1) colors_1: vec4<u32>, + @location(2) colors_2: vec4<u32>, + @location(3) colors_3: vec4<u32>, + @location(4) colors_4: vec4<u32>, + @location(5) offsets: vec4<u32>, + @location(6) direction: vec4<f32>, + @location(7) position_and_scale: vec4<f32>, + @location(8) border_color: vec4<f32>, + @location(9) border_radius: vec4<f32>, + @location(10) border_width: f32, +} + +@vertex +fn gradient_vs_main(input: GradientVertexInput) -> GradientVertexOutput { + var out: GradientVertexOutput; + + var pos: vec2<f32> = input.position_and_scale.xy * globals.scale; + var scale: vec2<f32> = 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<f32> = vec4<f32>( + 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<f32> = mat4x4<f32>( + vec4<f32>(scale.x + 1.0, 0.0, 0.0, 0.0), + vec4<f32>(0.0, scale.y + 1.0, 0.0, 0.0), + vec4<f32>(0.0, 0.0, 1.0, 0.0), + vec4<f32>(pos - vec2<f32>(0.5, 0.5), 0.0, 1.0) + ); + + out.position = globals.transform * transform * vec4<f32>(input.v_pos, 0.0, 1.0); + out.colors_1 = input.colors_1; + out.colors_2 = input.colors_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<f32>(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>) -> 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<f32>, + direction: vec4<f32>, + colors: array<vec4<f32>, 8>, + offsets: array<f32, 8>, + last_index: i32 +) -> vec4<f32> { + 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<f32>; + + 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<f32> { + let colors = array<vec4<f32>, 8>( + 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<f32> = unpack_u32(input.offsets.xy); + let offsets_2: vec4<f32> = unpack_u32(input.offsets.zw); + + var offsets = array<f32, 8>( + 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 + 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<f32> = 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<f32>(input.border_width, input.border_width), + scale - vec2<f32>(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<f32>(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<f32>(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<f32>, -} - -@group(0) @binding(0) var<uniform> globals: Globals; - -struct VertexInput { - @location(0) position: vec2<f32>, - @location(1) color: vec4<f32>, -} - -struct VertexOutput { - @builtin(position) position: vec4<f32>, - @location(0) color: vec4<f32>, -} - -@vertex -fn vs_main(input: VertexInput) -> VertexOutput { - var out: VertexOutput; - - out.color = input.color; - out.position = globals.transform * vec4<f32>(input.position, 0.0, 1.0); - - return out; -} - -@fragment -fn fs_main(input: VertexOutput) -> @location(0) vec4<f32> { - return input.color; -} diff --git a/wgpu/src/shader/triangle.wgsl b/wgpu/src/shader/triangle.wgsl new file mode 100644 index 00000000..9f512d14 --- /dev/null +++ b/wgpu/src/shader/triangle.wgsl @@ -0,0 +1,165 @@ +struct Globals { + transform: mat4x4<f32>, +} + +@group(0) @binding(0) var<uniform> globals: Globals; + +fn unpack_u32(color: vec2<u32>) -> vec4<f32> { + let rg: vec2<f32> = unpack2x16float(color.x); + let ba: vec2<f32> = unpack2x16float(color.y); + + return vec4<f32>(rg.y, rg.x, ba.y, ba.x); +} + +struct SolidVertexInput { + @location(0) position: vec2<f32>, + @location(1) color: vec4<f32>, +} + +struct SolidVertexOutput { + @builtin(position) position: vec4<f32>, + @location(0) color: vec4<f32>, +} + +@vertex +fn solid_vs_main(input: SolidVertexInput) -> SolidVertexOutput { + var out: SolidVertexOutput; + + out.color = input.color; + out.position = globals.transform * vec4<f32>(input.position, 0.0, 1.0); + + return out; +} + +@fragment +fn solid_fs_main(input: SolidVertexOutput) -> @location(0) vec4<f32> { + return input.color; +} + +struct GradientVertexInput { + @location(0) v_pos: vec2<f32>, + @location(1) colors_1: vec4<u32>, + @location(2) colors_2: vec4<u32>, + @location(3) colors_3: vec4<u32>, + @location(4) colors_4: vec4<u32>, + @location(5) offsets: vec4<u32>, + @location(6) direction: vec4<f32>, +} + +struct GradientVertexOutput { + @builtin(position) position: vec4<f32>, + @location(0) raw_position: vec2<f32>, + @location(1) colors_1: vec4<u32>, + @location(2) colors_2: vec4<u32>, + @location(3) colors_3: vec4<u32>, + @location(4) colors_4: vec4<u32>, + @location(5) offsets: vec4<u32>, + @location(6) direction: vec4<f32>, +} + +@vertex +fn gradient_vs_main(input: GradientVertexInput) -> GradientVertexOutput { + var output: GradientVertexOutput; + + output.position = globals.transform * vec4<f32>(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; +} + +fn random(coords: vec2<f32>) -> 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<f32>, + direction: vec4<f32>, + colors: array<vec4<f32>, 8>, + offsets: array<f32, 8>, + last_index: i32 +) -> vec4<f32> { + 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<f32>; + + 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<f32> { + let colors = array<vec4<f32>, 8>( + 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<f32> = unpack_u32(input.offsets.xy); + let offsets_2: vec4<f32> = unpack_u32(input.offsets.zw); + + var offsets = array<f32, 8>( + 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; + 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/text.rs b/wgpu/src/text.rs index 714e0400..c9188bd1 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}; @@ -17,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 { @@ -35,17 +35,27 @@ 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(), + 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( @@ -68,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, }, @@ -103,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, @@ -140,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, @@ -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, @@ -224,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; } @@ -238,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))); @@ -254,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( @@ -275,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))); @@ -295,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.rs b/wgpu/src/triangle.rs index eb15a458..3f3635cf 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::core::Size; use crate::graphics::{Antialiasing, Transformation}; use crate::layer::mesh::{self, Mesh}; +use crate::Buffer; -#[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<msaa::Blit>, 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<Layer>, prepare_layer: usize, } @@ -30,8 +23,6 @@ struct Layer { index_buffer: Buffer<u32>, index_strides: Vec<u32>, 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,183 +63,102 @@ 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<gradient::ColorStop> = 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; 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, @@ -268,7 +177,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::<Uniforms>()) + as u32], ); render_pass.set_vertex_buffer( @@ -278,7 +188,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 +198,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::<Uniforms>()) + as u32], ); render_pass.set_vertex_buffer( @@ -302,8 +209,6 @@ impl Layer { num_gradients += 1; } - #[cfg(target_arch = "wasm32")] - Mesh::Gradient { .. } => {} }; render_pass.set_index_buffer( @@ -325,10 +230,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 +244,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 +256,6 @@ impl Pipeline { device, queue, &self.solid, - #[cfg(not(target_arch = "wasm32"))] &self.gradient, meshes, transformation, @@ -378,9 +275,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 +293,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 +310,6 @@ impl Pipeline { layer.render( &self.solid, - #[cfg(not(target_arch = "wasm32"))] &self.gradient, meshes, scale_factor, @@ -463,14 +355,48 @@ 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::<Self>() as u64, + ), + }, + count: None, + } + } + + pub fn min_size() -> Option<wgpu::BufferSize> { + wgpu::BufferSize::new(std::mem::size_of::<Self>() as u64) + } +} + mod solid { - use crate::buffer::dynamic; - use crate::buffer::r#static::Buffer; use crate::graphics::primitive; - use crate::graphics::{Antialiasing, Transformation}; + use crate::graphics::Antialiasing; use crate::triangle; - - use encase::ShaderType; + use crate::Buffer; #[derive(Debug)] pub struct Pipeline { @@ -481,7 +407,7 @@ mod solid { #[derive(Debug)] pub struct Layer { pub vertices: Buffer<primitive::ColoredVertex2D>, - pub uniforms: dynamic::Buffer<Uniforms>, + pub uniforms: Buffer<triangle::Uniforms>, pub constants: wgpu::BindGroup, } @@ -492,17 +418,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 +446,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 +454,7 @@ mod solid { wgpu::BufferBinding { buffer, offset: 0, - size: Some(Uniforms::min_size()), + size: triangle::Uniforms::min_size(), }, ), }], @@ -533,21 +462,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 +470,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 +485,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 +499,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 +516,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 +534,10 @@ 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; #[derive(Debug)] pub struct Pipeline { @@ -649,14 +547,9 @@ mod gradient { #[derive(Debug)] pub struct Layer { - pub vertices: Buffer<primitive::Vertex2D>, - pub uniforms: dynamic::Buffer<Uniforms>, - pub storage: dynamic::Buffer<Storage>, + pub vertices: Buffer<primitive::GradientVertex2D>, + pub uniforms: Buffer<triangle::Uniforms>, 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 +559,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<ColorStop>, - } - 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<Antialiasing>, @@ -761,40 +612,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 +628,56 @@ 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, + // Colors 1-2 + 1 => Uint32x4, + // Colors 3-4 + 2 => Uint32x4, + // Colors 5-6 + 3 => Uint32x4, + // Colors 7-8 + 4 => Uint32x4, + // Offsets + 5 => Uint32x4, + // Direction + 6 => 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, 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 500458e8..1cfd7b67 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::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}; @@ -69,16 +70,19 @@ impl<Theme> Compositor<Theme> { 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); @@ -279,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, + } + } } |