//! Draw meshes of triangles. mod msaa; use crate::core::{Rectangle, Size, Transformation}; use crate::graphics::mesh::{self, Mesh}; use crate::graphics::Antialiasing; use crate::Buffer; const INITIAL_INDEX_COUNT: usize = 1_000; const INITIAL_VERTEX_COUNT: usize = 1_000; pub type Batch = Vec; #[derive(Debug)] pub struct Pipeline { blit: Option, solid: solid::Pipeline, gradient: gradient::Pipeline, layers: Vec, prepare_layer: usize, } impl Pipeline { pub fn new( device: &wgpu::Device, format: wgpu::TextureFormat, antialiasing: Option, ) -> Pipeline { Pipeline { blit: antialiasing.map(|a| msaa::Blit::new(device, format, a)), solid: solid::Pipeline::new(device, format, antialiasing), gradient: gradient::Pipeline::new(device, format, antialiasing), layers: Vec::new(), prepare_layer: 0, } } pub fn prepare_batch( &mut self, device: &wgpu::Device, encoder: &mut wgpu::CommandEncoder, belt: &mut wgpu::util::StagingBelt, meshes: &Batch, transformation: Transformation, ) { if self.layers.len() <= self.prepare_layer { self.layers .push(Layer::new(device, &self.solid, &self.gradient)); } let layer = &mut self.layers[self.prepare_layer]; layer.prepare( device, encoder, belt, &self.solid, &self.gradient, meshes, transformation, ); self.prepare_layer += 1; } pub fn prepare_cache( &mut self, device: &wgpu::Device, encoder: &mut wgpu::CommandEncoder, belt: &mut wgpu::util::StagingBelt, cache: &mut Cache, new_projection: Transformation, new_transformation: Transformation, ) { let new_projection = new_projection * new_transformation; match cache { Cache::Staged(_) => { let Cache::Staged(batch) = std::mem::replace(cache, Cache::Staged(Batch::default())) else { unreachable!() }; let mut layer = Layer::new(device, &self.solid, &self.gradient); layer.prepare( device, encoder, belt, &self.solid, &self.gradient, &batch, new_projection, ); *cache = Cache::Uploaded { layer, batch, transformation: new_transformation, projection: new_projection, needs_reupload: false, } } Cache::Uploaded { batch, layer, transformation, projection, needs_reupload, } => { if *needs_reupload || new_projection != *projection { layer.prepare( device, encoder, belt, &self.solid, &self.gradient, batch, new_projection, ); *transformation = new_transformation; *projection = new_projection; *needs_reupload = false; } } } } pub fn render_batch( &mut self, device: &wgpu::Device, encoder: &mut wgpu::CommandEncoder, target: &wgpu::TextureView, layer: usize, target_size: Size, meshes: &Batch, bounds: Rectangle, transformation: &Transformation, ) { Self::render( device, encoder, target, self.blit.as_mut(), &self.solid, &self.gradient, target_size, std::iter::once(( &self.layers[layer], meshes, transformation, bounds, )), ); } #[allow(dead_code)] pub fn render_cache( &mut self, device: &wgpu::Device, encoder: &mut wgpu::CommandEncoder, target: &wgpu::TextureView, target_size: Size, cache: &Cache, bounds: Rectangle, ) { let Cache::Uploaded { batch, layer, transformation, .. } = cache else { return; }; Self::render( device, encoder, target, self.blit.as_mut(), &self.solid, &self.gradient, target_size, std::iter::once((layer, batch, transformation, bounds)), ); } pub fn render_cache_group<'a>( &mut self, device: &wgpu::Device, encoder: &mut wgpu::CommandEncoder, target: &wgpu::TextureView, target_size: Size, group: impl Iterator)>, ) { let group = group.filter_map(|(cache, bounds)| { if let Cache::Uploaded { batch, layer, transformation, .. } = cache { Some((layer, batch, transformation, bounds)) } else { None } }); Self::render( device, encoder, target, self.blit.as_mut(), &self.solid, &self.gradient, target_size, group, ); } fn render<'a>( device: &wgpu::Device, encoder: &mut wgpu::CommandEncoder, target: &wgpu::TextureView, mut blit: Option<&mut msaa::Blit>, solid: &solid::Pipeline, gradient: &gradient::Pipeline, target_size: Size, group: impl Iterator< Item = (&'a Layer, &'a Batch, &'a Transformation, Rectangle), >, ) { { let (attachment, resolve_target, load) = if let Some(blit) = &mut blit { let (attachment, resolve_target) = blit.targets(device, target_size.width, target_size.height); ( attachment, Some(resolve_target), wgpu::LoadOp::Clear(wgpu::Color::TRANSPARENT), ) } else { (target, None, wgpu::LoadOp::Load) }; let mut render_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor { label: Some("iced_wgpu.triangle.render_pass"), color_attachments: &[Some( wgpu::RenderPassColorAttachment { view: attachment, resolve_target, ops: wgpu::Operations { load, store: wgpu::StoreOp::Store, }, }, )], depth_stencil_attachment: None, timestamp_writes: None, occlusion_query_set: None, }); for (layer, meshes, transformation, bounds) in group { layer.render( solid, gradient, meshes, bounds, *transformation, &mut render_pass, ); } } if let Some(blit) = blit { blit.draw(encoder, target); } } pub fn end_frame(&mut self) { self.prepare_layer = 0; } } #[derive(Debug)] pub enum Cache { Staged(Batch), Uploaded { batch: Batch, layer: Layer, transformation: Transformation, projection: Transformation, needs_reupload: bool, }, } impl Cache { pub fn is_empty(&self) -> bool { match self { Cache::Staged(batch) | Cache::Uploaded { batch, .. } => { batch.is_empty() } } } pub fn update(&mut self, new_batch: Batch) { match self { Self::Staged(batch) => { *batch = new_batch; } Self::Uploaded { batch, needs_reupload, .. } => { *batch = new_batch; *needs_reupload = true; } } } } impl Default for Cache { fn default() -> Self { Self::Staged(Batch::default()) } } #[derive(Debug)] pub struct Layer { index_buffer: Buffer, index_strides: Vec, solid: solid::Layer, gradient: gradient::Layer, } impl Layer { fn new( device: &wgpu::Device, solid: &solid::Pipeline, gradient: &gradient::Pipeline, ) -> Self { Self { index_buffer: Buffer::new( device, "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), gradient: gradient::Layer::new(device, &gradient.constants_layout), } } fn prepare( &mut self, device: &wgpu::Device, encoder: &mut wgpu::CommandEncoder, belt: &mut wgpu::util::StagingBelt, solid: &solid::Pipeline, gradient: &gradient::Pipeline, meshes: &Batch, transformation: Transformation, ) { // Count the total amount of vertices & indices we need to handle let count = mesh::attribute_count_of(meshes); // Then we ensure the current attribute buffers are big enough, resizing if necessary. // We are not currently using the return value of these functions as we have no system in // place to calculate mesh diff, or to know whether or not that would be more performant for // 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); let _ = self .gradient .vertices .resize(device, count.gradient_vertices); 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(); self.gradient.vertices.clear(); self.gradient.uniforms.clear(); let mut solid_vertex_offset = 0; 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 indices = mesh.indices(); let uniforms = Uniforms::new(transformation * mesh.transformation()); index_offset += self.index_buffer.write( device, encoder, belt, index_offset, indices, ); self.index_strides.push(indices.len() as u32); match mesh { Mesh::Solid { buffers, .. } => { solid_vertex_offset += self.solid.vertices.write( device, encoder, belt, solid_vertex_offset, &buffers.vertices, ); solid_uniform_offset += self.solid.uniforms.write( device, encoder, belt, solid_uniform_offset, &[uniforms], ); } Mesh::Gradient { buffers, .. } => { gradient_vertex_offset += self.gradient.vertices.write( device, encoder, belt, gradient_vertex_offset, &buffers.vertices, ); gradient_uniform_offset += self.gradient.uniforms.write( device, encoder, belt, gradient_uniform_offset, &[uniforms], ); } } } } fn render<'a>( &'a self, solid: &'a solid::Pipeline, gradient: &'a gradient::Pipeline, meshes: &Batch, layer_bounds: Rectangle, transformation: Transformation, render_pass: &mut wgpu::RenderPass<'a>, ) { let mut num_solids = 0; let mut num_gradients = 0; let mut last_is_solid = None; for (index, mesh) in meshes.iter().enumerate() { let Some(clip_bounds) = Rectangle::::from(layer_bounds) .intersection(&(mesh.clip_bounds() * transformation)) else { continue; }; let clip_bounds = clip_bounds.snap(); render_pass.set_scissor_rect( clip_bounds.x, clip_bounds.y, clip_bounds.width, clip_bounds.height, ); match mesh { Mesh::Solid { .. } => { if !last_is_solid.unwrap_or(false) { render_pass.set_pipeline(&solid.pipeline); last_is_solid = Some(true); } render_pass.set_bind_group( 0, &self.solid.constants, &[(num_solids * std::mem::size_of::()) as u32], ); render_pass.set_vertex_buffer( 0, self.solid.vertices.slice_from_index(num_solids), ); num_solids += 1; } Mesh::Gradient { .. } => { if last_is_solid.unwrap_or(true) { render_pass.set_pipeline(&gradient.pipeline); last_is_solid = Some(false); } render_pass.set_bind_group( 0, &self.gradient.constants, &[(num_gradients * std::mem::size_of::()) as u32], ); render_pass.set_vertex_buffer( 0, self.gradient.vertices.slice_from_index(num_gradients), ); num_gradients += 1; } }; render_pass.set_index_buffer( self.index_buffer.slice_from_index(index), wgpu::IndexFormat::Uint32, ); render_pass.draw_indexed(0..self.index_strides[index], 0, 0..1); } } } fn fragment_target( texture_format: wgpu::TextureFormat, ) -> wgpu::ColorTargetState { wgpu::ColorTargetState { format: texture_format, blend: Some(wgpu::BlendState::ALPHA_BLENDING), write_mask: wgpu::ColorWrites::ALL, } } fn primitive_state() -> wgpu::PrimitiveState { wgpu::PrimitiveState { topology: wgpu::PrimitiveTopology::TriangleList, front_face: wgpu::FrontFace::Cw, ..Default::default() } } fn multisample_state( antialiasing: Option, ) -> wgpu::MultisampleState { wgpu::MultisampleState { count: antialiasing.map(Antialiasing::sample_count).unwrap_or(1), mask: !0, alpha_to_coverage_enabled: false, } } #[derive(Debug, Clone, Copy, bytemuck::Pod, bytemuck::Zeroable)] #[repr(C)] pub struct Uniforms { transform: [f32; 16], /// Uniform values must be 256-aligned; /// see: [`wgpu::Limits`] `min_uniform_buffer_offset_alignment`. _padding: [f32; 48], } impl Uniforms { pub fn new(transform: Transformation) -> Self { Self { transform: transform.into(), _padding: [0.0; 48], } } pub fn entry() -> wgpu::BindGroupLayoutEntry { wgpu::BindGroupLayoutEntry { binding: 0, visibility: wgpu::ShaderStages::VERTEX_FRAGMENT, ty: wgpu::BindingType::Buffer { ty: wgpu::BufferBindingType::Uniform, has_dynamic_offset: true, min_binding_size: wgpu::BufferSize::new( std::mem::size_of::() as u64, ), }, count: None, } } pub fn min_size() -> Option { wgpu::BufferSize::new(std::mem::size_of::() as u64) } } mod solid { use crate::graphics::mesh; use crate::graphics::Antialiasing; use crate::triangle; use crate::Buffer; #[derive(Debug)] pub struct Pipeline { pub pipeline: wgpu::RenderPipeline, pub constants_layout: wgpu::BindGroupLayout, } #[derive(Debug)] pub struct Layer { pub vertices: Buffer, pub uniforms: Buffer, pub constants: wgpu::BindGroup, } impl Layer { pub fn new( device: &wgpu::Device, constants_layout: &wgpu::BindGroupLayout, ) -> Self { let vertices = Buffer::new( device, "iced_wgpu.triangle.solid.vertex_buffer", triangle::INITIAL_VERTEX_COUNT, wgpu::BufferUsages::VERTEX | wgpu::BufferUsages::COPY_DST, ); let uniforms = Buffer::new( device, "iced_wgpu.triangle.solid.uniforms", 1, wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST, ); let constants = Self::bind_group(device, &uniforms.raw, constants_layout); Self { vertices, uniforms, constants, } } pub fn bind_group( device: &wgpu::Device, buffer: &wgpu::Buffer, layout: &wgpu::BindGroupLayout, ) -> wgpu::BindGroup { device.create_bind_group(&wgpu::BindGroupDescriptor { label: Some("iced_wgpu.triangle.solid.bind_group"), layout, entries: &[wgpu::BindGroupEntry { binding: 0, resource: wgpu::BindingResource::Buffer( wgpu::BufferBinding { buffer, offset: 0, size: triangle::Uniforms::min_size(), }, ), }], }) } } impl Pipeline { pub fn new( device: &wgpu::Device, format: wgpu::TextureFormat, antialiasing: Option, ) -> Self { let constants_layout = device.create_bind_group_layout( &wgpu::BindGroupLayoutDescriptor { 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"), bind_group_layouts: &[&constants_layout], push_constant_ranges: &[], }, ); let shader = device.create_shader_module(wgpu::ShaderModuleDescriptor { label: Some("iced_wgpu.triangle.solid.shader"), source: wgpu::ShaderSource::Wgsl( std::borrow::Cow::Borrowed(concat!( include_str!("shader/triangle.wgsl"), "\n", include_str!("shader/triangle/solid.wgsl"), )), ), }); let pipeline = device.create_render_pipeline( &wgpu::RenderPipelineDescriptor { label: Some("iced_wgpu::triangle::solid pipeline"), layout: Some(&layout), vertex: wgpu::VertexState { module: &shader, entry_point: "solid_vs_main", buffers: &[wgpu::VertexBufferLayout { array_stride: std::mem::size_of::< mesh::SolidVertex2D, >( ) as u64, step_mode: wgpu::VertexStepMode::Vertex, attributes: &wgpu::vertex_attr_array!( // Position 0 => Float32x2, // Color 1 => Float32x4, ), }], }, fragment: Some(wgpu::FragmentState { module: &shader, entry_point: "solid_fs_main", targets: &[Some(triangle::fragment_target(format))], }), primitive: triangle::primitive_state(), depth_stencil: None, multisample: triangle::multisample_state(antialiasing), multiview: None, }, ); Self { pipeline, constants_layout, } } } } mod gradient { use crate::graphics::color; use crate::graphics::mesh; use crate::graphics::Antialiasing; use crate::triangle; use crate::Buffer; #[derive(Debug)] pub struct Pipeline { pub pipeline: wgpu::RenderPipeline, pub constants_layout: wgpu::BindGroupLayout, } #[derive(Debug)] pub struct Layer { pub vertices: Buffer, pub uniforms: Buffer, pub constants: wgpu::BindGroup, } impl Layer { pub fn new( device: &wgpu::Device, constants_layout: &wgpu::BindGroupLayout, ) -> Self { let vertices = Buffer::new( device, "iced_wgpu.triangle.gradient.vertex_buffer", triangle::INITIAL_VERTEX_COUNT, wgpu::BufferUsages::VERTEX | wgpu::BufferUsages::COPY_DST, ); let uniforms = Buffer::new( device, "iced_wgpu.triangle.gradient.uniforms", 1, wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST, ); let constants = Self::bind_group(device, &uniforms.raw, constants_layout); Self { vertices, uniforms, constants, } } pub fn bind_group( device: &wgpu::Device, uniform_buffer: &wgpu::Buffer, layout: &wgpu::BindGroupLayout, ) -> wgpu::BindGroup { device.create_bind_group(&wgpu::BindGroupDescriptor { label: Some("iced_wgpu.triangle.gradient.bind_group"), layout, entries: &[wgpu::BindGroupEntry { binding: 0, resource: wgpu::BindingResource::Buffer( wgpu::BufferBinding { buffer: uniform_buffer, offset: 0, size: triangle::Uniforms::min_size(), }, ), }], }) } } impl Pipeline { pub fn new( device: &wgpu::Device, format: wgpu::TextureFormat, antialiasing: Option, ) -> Self { let constants_layout = device.create_bind_group_layout( &wgpu::BindGroupLayoutDescriptor { label: Some( "iced_wgpu.triangle.gradient.bind_group_layout", ), entries: &[triangle::Uniforms::entry()], }, ); let layout = device.create_pipeline_layout( &wgpu::PipelineLayoutDescriptor { label: Some("iced_wgpu.triangle.gradient.pipeline_layout"), bind_group_layouts: &[&constants_layout], push_constant_ranges: &[], }, ); let shader = device.create_shader_module(wgpu::ShaderModuleDescriptor { label: Some("iced_wgpu.triangle.gradient.shader"), source: wgpu::ShaderSource::Wgsl( std::borrow::Cow::Borrowed( if color::GAMMA_CORRECTION { concat!( include_str!("shader/triangle.wgsl"), "\n", include_str!( "shader/triangle/gradient.wgsl" ), "\n", include_str!("shader/color/oklab.wgsl") ) } else { concat!( include_str!("shader/triangle.wgsl"), "\n", include_str!( "shader/triangle/gradient.wgsl" ), "\n", include_str!( "shader/color/linear_rgb.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: "gradient_vs_main", buffers: &[wgpu::VertexBufferLayout { array_stride: std::mem::size_of::< mesh::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: &[Some(triangle::fragment_target(format))], }), primitive: triangle::primitive_state(), depth_stencil: None, multisample: triangle::multisample_state(antialiasing), multiview: None, }, ); Self { pipeline, constants_layout, } } } }