From fcb74d8fbc735f3c1f481738adec15d52d186c5e Mon Sep 17 00:00:00 2001 From: Nathan Adams Date: Wed, 24 Jan 2024 21:19:44 +0100 Subject: [PATCH] wgpu: Deduplicate common gradients from a single Mesh --- Cargo.lock | 1 + render/Cargo.toml | 1 + render/src/shape_utils.rs | 2 +- render/src/tessellator.rs | 83 +++++++++----- render/webgl/src/lib.rs | 15 ++- render/wgpu/src/backend.rs | 14 ++- render/wgpu/src/mesh.rs | 228 +++++++++++++++++++++---------------- 7 files changed, 203 insertions(+), 141 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 55e8f71f6ac5..574f7d4b201c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4359,6 +4359,7 @@ dependencies = [ "flate2", "gif", "h263-rs-yuv", + "indexmap", "jpeg-decoder", "lru", "lyon", diff --git a/render/Cargo.toml b/render/Cargo.toml index 3b4f5667299f..7206bdeb1af4 100644 --- a/render/Cargo.toml +++ b/render/Cargo.toml @@ -32,6 +32,7 @@ num-traits = "0.2" num-derive = "0.4" byteorder = "1.5" wgpu = { workspace = true, optional = true } +indexmap = "2.1.0" # This crate has a `compile_error!` on apple platforms [target.'cfg(not(target_vendor = "apple"))'.dependencies.renderdoc] diff --git a/render/src/shape_utils.rs b/render/src/shape_utils.rs index 25ef008451cf..0367daf12508 100644 --- a/render/src/shape_utils.rs +++ b/render/src/shape_utils.rs @@ -12,7 +12,7 @@ pub enum FillRule { NonZero, } -#[derive(Copy, Clone, PartialEq, Eq, Debug, Enum)] +#[derive(Copy, Clone, PartialEq, Eq, Debug, Enum, Hash)] pub enum GradientType { Linear, Radial, diff --git a/render/src/tessellator.rs b/render/src/tessellator.rs index efe4029220d2..fa03fd832a73 100644 --- a/render/src/tessellator.rs +++ b/render/src/tessellator.rs @@ -1,5 +1,6 @@ use crate::bitmap::BitmapSource; use crate::shape_utils::{DistilledShape, DrawCommand, DrawPath, GradientType}; +use indexmap::IndexSet; use lyon::path::Path; use lyon::tessellation::{ self, @@ -14,6 +15,7 @@ pub struct ShapeTessellator { fill_tess: FillTessellator, stroke_tess: StrokeTessellator, mesh: Vec, + gradients: IndexSet, lyon_mesh: VertexBuffers, mask_index_count: Option, is_stroke: bool, @@ -25,6 +27,7 @@ impl ShapeTessellator { fill_tess: FillTessellator::new(), stroke_tess: StrokeTessellator::new(), mesh: Vec::new(), + gradients: IndexSet::new(), lyon_mesh: VertexBuffers::new(), mask_index_count: None, is_stroke: false, @@ -38,7 +41,9 @@ impl ShapeTessellator { bitmap_source: &dyn BitmapSource, ) -> Mesh { self.mesh = Vec::new(); + self.gradients = IndexSet::new(); self.lyon_mesh = VertexBuffers::new(); + for path in shape.paths { let (fill_style, lyon_path, next_is_stroke) = match &path { DrawPath::Fill { @@ -59,36 +64,49 @@ impl ShapeTessellator { let (draw, color, needs_flush) = match fill_style { swf::FillStyle::Color(color) => (DrawType::Color, *color, false), - swf::FillStyle::LinearGradient(gradient) => ( - DrawType::Gradient(swf_gradient_to_uniforms( - GradientType::Linear, - gradient, - swf::Fixed8::ZERO, - )), - swf::Color::WHITE, - true, - ), - swf::FillStyle::RadialGradient(gradient) => ( - DrawType::Gradient(swf_gradient_to_uniforms( - GradientType::Radial, - gradient, - swf::Fixed8::ZERO, - )), - swf::Color::WHITE, - true, - ), + swf::FillStyle::LinearGradient(gradient) => { + let uniform = + swf_gradient_to_uniforms(GradientType::Linear, gradient, swf::Fixed8::ZERO); + let (gradient_index, _) = self.gradients.insert_full(uniform); + + ( + DrawType::Gradient { + matrix: swf_to_gl_matrix(gradient.matrix.into()), + gradient: gradient_index, + }, + swf::Color::WHITE, + true, + ) + } + swf::FillStyle::RadialGradient(gradient) => { + let uniform = + swf_gradient_to_uniforms(GradientType::Radial, gradient, swf::Fixed8::ZERO); + let (gradient_index, _) = self.gradients.insert_full(uniform); + ( + DrawType::Gradient { + matrix: swf_to_gl_matrix(gradient.matrix.into()), + gradient: gradient_index, + }, + swf::Color::WHITE, + true, + ) + } swf::FillStyle::FocalGradient { gradient, focal_point, - } => ( - DrawType::Gradient(swf_gradient_to_uniforms( - GradientType::Focal, - gradient, - *focal_point, - )), - swf::Color::WHITE, - true, - ), + } => { + let uniform = + swf_gradient_to_uniforms(GradientType::Focal, gradient, *focal_point); + let (gradient_index, _) = self.gradients.insert_full(uniform); + ( + DrawType::Gradient { + matrix: swf_to_gl_matrix(gradient.matrix.into()), + gradient: gradient_index, + }, + swf::Color::WHITE, + true, + ) + } swf::FillStyle::Bitmap { id, matrix, @@ -197,6 +215,7 @@ impl ShapeTessellator { self.lyon_mesh = VertexBuffers::new(); Mesh { draws: std::mem::take(&mut self.mesh), + gradients: std::mem::take(&mut self.gradients).into_iter().collect(), } } @@ -228,6 +247,7 @@ impl Default for ShapeTessellator { pub struct Mesh { pub draws: Vec, + pub gradients: Vec, } pub struct Draw { @@ -239,7 +259,10 @@ pub struct Draw { pub enum DrawType { Color, - Gradient(Gradient), + Gradient { + matrix: [[f32; 3]; 3], + gradient: usize, + }, Bitmap(Bitmap), } @@ -253,9 +276,8 @@ impl DrawType { } } -#[derive(Clone, Debug)] +#[derive(Clone, Debug, Hash, Eq, PartialEq)] pub struct Gradient { - pub matrix: [[f32; 3]; 3], pub gradient_type: GradientType, pub repeat_mode: swf::GradientSpread, pub focal_point: swf::Fixed8, @@ -390,7 +412,6 @@ fn swf_gradient_to_uniforms( focal_point: swf::Fixed8, ) -> Gradient { Gradient { - matrix: swf_to_gl_matrix(gradient.matrix.into()), records: gradient.records.clone(), gradient_type, repeat_mode: gradient.spread, diff --git a/render/webgl/src/lib.rs b/render/webgl/src/lib.rs index e0bea6e949d8..95e830894014 100644 --- a/render/webgl/src/lib.rs +++ b/render/webgl/src/lib.rs @@ -601,7 +601,7 @@ impl WebGlRenderBackend { let program = match draw.draw_type { TessDrawType::Color => &self.color_program, - TessDrawType::Gradient(_) => &self.gradient_program, + TessDrawType::Gradient { .. } => &self.gradient_program, TessDrawType::Bitmap(_) => &self.bitmap_program, }; @@ -652,8 +652,11 @@ impl WebGlRenderBackend { num_indices, num_mask_indices, }, - TessDrawType::Gradient(gradient) => Draw { - draw_type: DrawType::Gradient(Box::new(Gradient::from(gradient))), + TessDrawType::Gradient { matrix, gradient } => Draw { + draw_type: DrawType::Gradient(Box::new(Gradient::new( + lyon_mesh.gradients[gradient].clone(), // TODO: Gradient deduplication + matrix, + ))), vao, vertex_buffer: Buffer { gl: self.gl.clone(), @@ -1529,8 +1532,8 @@ struct Gradient { interpolation: swf::GradientInterpolation, } -impl From for Gradient { - fn from(gradient: TessGradient) -> Self { +impl Gradient { + fn new(gradient: TessGradient, matrix: [[f32; 3]; 3]) -> Self { // TODO: Support more than MAX_GRADIENT_COLORS. let num_colors = gradient.records.len().min(MAX_GRADIENT_COLORS); let mut ratios = [0.0; MAX_GRADIENT_COLORS]; @@ -1559,7 +1562,7 @@ impl From for Gradient { } Self { - matrix: gradient.matrix, + matrix, gradient_type: match gradient.gradient_type { GradientType::Linear => 0, GradientType::Radial => 1, diff --git a/render/wgpu/src/backend.rs b/render/wgpu/src/backend.rs index 1e32e4069aa1..c32428665d5f 100644 --- a/render/wgpu/src/backend.rs +++ b/render/wgpu/src/backend.rs @@ -3,7 +3,7 @@ use crate::buffer_pool::{BufferPool, TexturePool}; use crate::context3d::WgpuContext3D; use crate::dynamic_transforms::DynamicTransforms; use crate::filters::FilterSource; -use crate::mesh::{Mesh, PendingDraw}; +use crate::mesh::{CommonGradient, Mesh, PendingDraw}; use crate::pixel_bender::{run_pixelbender_shader_impl, ShaderMode}; use crate::surface::{LayerRef, Surface}; use crate::target::{MaybeOwnedBuffer, TextureTarget}; @@ -249,6 +249,16 @@ impl WgpuRenderBackend { let mut uniform_buffer = BufferBuilder::new_for_uniform(&self.descriptors.limits); let mut vertex_buffer = BufferBuilder::new_for_vertices(&self.descriptors.limits); let mut index_buffer = BufferBuilder::new_for_vertices(&self.descriptors.limits); + let mut gradients = Vec::with_capacity(lyon_mesh.gradients.len()); + + for gradient in lyon_mesh.gradients { + gradients.push(CommonGradient::new( + &self.descriptors, + gradient, + &mut uniform_buffer, + )); + } + for draw in lyon_mesh.draws { let draw_id = draws.len(); if let Some(draw) = PendingDraw::new( @@ -283,7 +293,7 @@ impl WgpuRenderBackend { let draws = draws .into_iter() - .map(|d| d.finish(&self.descriptors, &uniform_buffer)) + .map(|d| d.finish(&self.descriptors, &uniform_buffer, &gradients)) .collect(); Mesh { diff --git a/render/wgpu/src/mesh.rs b/render/wgpu/src/mesh.rs index 51f07eb9e8a5..316a39d49180 100644 --- a/render/wgpu/src/mesh.rs +++ b/render/wgpu/src/mesh.rs @@ -38,9 +38,16 @@ pub struct PendingDraw { } impl PendingDraw { - pub fn finish(self, descriptors: &Descriptors, uniform_buffer: &wgpu::Buffer) -> Draw { + pub fn finish( + self, + descriptors: &Descriptors, + uniform_buffer: &wgpu::Buffer, + gradients: &[CommonGradient], + ) -> Draw { Draw { - draw_type: self.draw_type.finish(descriptors, uniform_buffer), + draw_type: self + .draw_type + .finish(descriptors, uniform_buffer, gradients), vertices: self.vertices, indices: self.indices, num_indices: self.num_indices, @@ -93,13 +100,9 @@ impl PendingDraw { let index_count = draw.indices.len() as u32; let draw_type = match draw.draw_type { TessDrawType::Color => PendingDrawType::color(), - TessDrawType::Gradient(gradient) => PendingDrawType::gradient( - backend.descriptors(), - gradient, - shape_id, - draw_id, - uniform_buffer, - ), + TessDrawType::Gradient { matrix, gradient } => { + PendingDrawType::gradient(gradient, matrix, shape_id, draw_id, uniform_buffer) + } TessDrawType::Bitmap(bitmap) => { PendingDrawType::bitmap(bitmap, shape_id, draw_id, source, backend, uniform_buffer)? } @@ -120,9 +123,8 @@ pub enum PendingDrawType { Color, Gradient { texture_transforms_index: wgpu::BufferAddress, - gradient: wgpu::BufferAddress, + gradient_index: usize, bind_group_label: Option, - colors: wgpu::TextureView, }, Bitmap { texture_transforms_index: wgpu::BufferAddress, @@ -152,101 +154,20 @@ impl PendingDrawType { } pub fn gradient( - descriptors: &Descriptors, - gradient: Gradient, + gradient_index: usize, + matrix: [[f32; 3]; 3], shape_id: CharacterId, draw_id: usize, uniform_buffers: &mut BufferBuilder, ) -> Self { - let tex_transforms_index = create_texture_transforms(&gradient.matrix, uniform_buffers); - let colors = if gradient.records.is_empty() { - [0; GRADIENT_SIZE * 4] - } else { - let mut colors = [0; GRADIENT_SIZE * 4]; - - let convert = if gradient.interpolation == GradientInterpolation::LinearRgb { - |c| srgb_to_linear(c / 255.0) * 255.0 - } else { - |c| c - }; - - for t in 0..GRADIENT_SIZE { - let mut last = 0; - let mut next = 0; - - for (i, record) in gradient.records.iter().enumerate().rev() { - if (record.ratio as usize) < t { - last = i; - next = (i + 1).min(gradient.records.len() - 1); - break; - } - } - assert!(last == next || last + 1 == next); - - let last_record = &gradient.records[last]; - let next_record = &gradient.records[next]; - - let a = if next == last { - // this can happen if we are before the first gradient record, or after the last one - 0.0 - } else { - (t as f32 - last_record.ratio as f32) - / (next_record.ratio as f32 - last_record.ratio as f32) - }; - colors[t * 4] = lerp( - convert(last_record.color.r as f32), - convert(next_record.color.r as f32), - a, - ) as u8; - colors[(t * 4) + 1] = lerp( - convert(last_record.color.g as f32), - convert(next_record.color.g as f32), - a, - ) as u8; - colors[(t * 4) + 2] = lerp( - convert(last_record.color.b as f32), - convert(next_record.color.b as f32), - a, - ) as u8; - colors[(t * 4) + 3] = - lerp(last_record.color.a as f32, next_record.color.a as f32, a) as u8; - } - - colors - }; - let texture = descriptors.device.create_texture_with_data( - &descriptors.queue, - &wgpu::TextureDescriptor { - label: None, - size: wgpu::Extent3d { - width: GRADIENT_SIZE as u32, - height: 1, - depth_or_array_layers: 1, - }, - mip_level_count: 1, - sample_count: 1, - dimension: wgpu::TextureDimension::D2, - format: wgpu::TextureFormat::Rgba8Unorm, - usage: wgpu::TextureUsages::TEXTURE_BINDING, - view_formats: &[], - }, - wgpu::util::TextureDataOrder::LayerMajor, - &colors[..], - ); - let view = texture.create_view(&Default::default()); - - let gradient = uniform_buffers - .add(&[GradientUniforms::from(gradient)]) - .expect("Mesh uniform buffer was too large!") - .start; + let tex_transforms_index = create_texture_transforms(&matrix, uniform_buffers); let bind_group_label = create_debug_label!("Shape {} (gradient) draw {} bindgroup", shape_id, draw_id); PendingDrawType::Gradient { texture_transforms_index: tex_transforms_index, - gradient, + gradient_index, bind_group_label, - colors: view, } } @@ -274,15 +195,20 @@ impl PendingDrawType { }) } - pub fn finish(self, descriptors: &Descriptors, uniform_buffer: &wgpu::Buffer) -> DrawType { + pub fn finish( + self, + descriptors: &Descriptors, + uniform_buffer: &wgpu::Buffer, + gradients: &[CommonGradient], + ) -> DrawType { match self { PendingDrawType::Color => DrawType::Color, PendingDrawType::Gradient { texture_transforms_index, - gradient, + gradient_index, bind_group_label, - colors, } => { + let common = &gradients[gradient_index]; let bind_group = descriptors .device .create_bind_group(&wgpu::BindGroupDescriptor { @@ -302,7 +228,7 @@ impl PendingDrawType { binding: 1, resource: wgpu::BindingResource::Buffer(wgpu::BufferBinding { buffer: uniform_buffer, - offset: gradient, + offset: common.buffer_offset, size: wgpu::BufferSize::new( std::mem::size_of::() as u64, ), @@ -310,7 +236,7 @@ impl PendingDrawType { }, wgpu::BindGroupEntry { binding: 2, - resource: wgpu::BindingResource::TextureView(&colors), + resource: wgpu::BindingResource::TextureView(&common.texture_view), }, wgpu::BindGroupEntry { binding: 3, @@ -356,6 +282,106 @@ pub enum DrawType { Bitmap { binds: BitmapBinds }, } +#[derive(Debug)] +pub struct CommonGradient { + texture_view: wgpu::TextureView, + buffer_offset: wgpu::BufferAddress, +} + +impl CommonGradient { + pub fn new( + descriptors: &Descriptors, + gradient: Gradient, + uniform_buffers: &mut BufferBuilder, + ) -> Self { + let colors = if gradient.records.is_empty() { + [0; GRADIENT_SIZE * 4] + } else { + let mut colors = [0; GRADIENT_SIZE * 4]; + + let convert = if gradient.interpolation == GradientInterpolation::LinearRgb { + |c| srgb_to_linear(c / 255.0) * 255.0 + } else { + |c| c + }; + + for t in 0..GRADIENT_SIZE { + let mut last = 0; + let mut next = 0; + + for (i, record) in gradient.records.iter().enumerate().rev() { + if (record.ratio as usize) < t { + last = i; + next = (i + 1).min(gradient.records.len() - 1); + break; + } + } + assert!(last == next || last + 1 == next); + + let last_record = &gradient.records[last]; + let next_record = &gradient.records[next]; + + let a = if next == last { + // this can happen if we are before the first gradient record, or after the last one + 0.0 + } else { + (t as f32 - last_record.ratio as f32) + / (next_record.ratio as f32 - last_record.ratio as f32) + }; + colors[t * 4] = lerp( + convert(last_record.color.r as f32), + convert(next_record.color.r as f32), + a, + ) as u8; + colors[(t * 4) + 1] = lerp( + convert(last_record.color.g as f32), + convert(next_record.color.g as f32), + a, + ) as u8; + colors[(t * 4) + 2] = lerp( + convert(last_record.color.b as f32), + convert(next_record.color.b as f32), + a, + ) as u8; + colors[(t * 4) + 3] = + lerp(last_record.color.a as f32, next_record.color.a as f32, a) as u8; + } + + colors + }; + let texture = descriptors.device.create_texture_with_data( + &descriptors.queue, + &wgpu::TextureDescriptor { + label: None, + size: wgpu::Extent3d { + width: GRADIENT_SIZE as u32, + height: 1, + depth_or_array_layers: 1, + }, + mip_level_count: 1, + sample_count: 1, + dimension: wgpu::TextureDimension::D2, + format: wgpu::TextureFormat::Rgba8Unorm, + usage: wgpu::TextureUsages::TEXTURE_BINDING, + view_formats: &[], + }, + wgpu::util::TextureDataOrder::LayerMajor, + &colors[..], + ); + let view = texture.create_view(&Default::default()); + + let buffer_offset = uniform_buffers + .add(&[GradientUniforms::from(gradient)]) + .expect("Mesh uniform buffer was too large!") + .start; + + Self { + texture_view: view, + buffer_offset, + } + } +} + #[derive(Debug)] pub struct BitmapBinds { pub bind_group: wgpu::BindGroup,