From 524fc2e412fb9bc4047049ee86746c8c6ce304f1 Mon Sep 17 00:00:00 2001 From: Armin Sander Date: Fri, 8 Aug 2025 16:00:21 +0200 Subject: [PATCH 1/4] Conversion to instanced rendering (by GPT-5) --- renderer/src/text_layer/atlas_renderer.rs | 133 ++++++++---------- renderer/src/text_layer/renderer.rs | 96 ++++++------- .../src/text_layer/shader/color_atlas.wgsl | 30 ++-- renderer/src/text_layer/shader/sdf_atlas.wgsl | 37 +++-- 4 files changed, 159 insertions(+), 137 deletions(-) diff --git a/renderer/src/text_layer/atlas_renderer.rs b/renderer/src/text_layer/atlas_renderer.rs index 272957ba..986d464c 100644 --- a/renderer/src/text_layer/atlas_renderer.rs +++ b/renderer/src/text_layer/atlas_renderer.rs @@ -21,7 +21,7 @@ pub struct AtlasRenderer { } impl AtlasRenderer { - pub fn new( + pub fn new( device: &wgpu::Device, atlas_format: wgpu::TextureFormat, shader: wgpu::ShaderModuleDescriptor<'_>, @@ -46,7 +46,8 @@ impl AtlasRenderer { write_mask: wgpu::ColorWrites::ALL, })]; - let vertex_layout = [VertexT::layout()]; + // One vertex buffer layout describing instance attributes. + let vertex_layout = [InstanceVertexT::layout()]; let pipeline = create_pipeline( "Atlas Pipeline", @@ -66,20 +67,15 @@ impl AtlasRenderer { } } - // Convert a number of instances to a batch. - pub fn batch( + // Build a batch from instance data, uploading one instance per glyph. + pub fn batch_instances( &self, context: &PreparationContext, - instances: &[InstanceT], + instances: &[InstanceVertexT], ) -> Option { if instances.is_empty() { return None; } - let mut vertices = Vec::with_capacity(instances.len() * 4); - - for instance in instances { - vertices.extend(instance.to_vertices()); - } let device = context.device; @@ -90,8 +86,8 @@ impl AtlasRenderer { ); let vertex_buffer = device.create_buffer_init(&BufferInitDescriptor { - label: Some("Atlas Vertex Buffer"), - contents: bytemuck::cast_slice(&vertices), + label: Some("Atlas Instance Buffer"), + contents: bytemuck::cast_slice(instances), usage: wgpu::BufferUsages::VERTEX, }); @@ -107,75 +103,70 @@ impl AtlasRenderer { } } -pub trait AtlasInstance { - type Vertex: Pod; - - fn to_vertices(&self) -> [Self::Vertex; 4]; -} - +// Instance vertex types used by the shaders pub mod sdf_atlas { - use massive_geometry::{Color, Point3}; - - use super::AtlasInstance; - use crate::{glyph::glyph_atlas, pods::TextureColorVertex}; - - #[derive(Debug)] - pub struct QuadInstance { - pub atlas_rect: glyph_atlas::Rectangle, - pub vertices: [Point3; 4], - pub color: Color, + use bytemuck::{Pod, Zeroable}; + + #[repr(C)] + #[derive(Copy, Clone, Debug, Pod, Zeroable)] + pub struct InstanceVertex { + // pos_lt.xy, pos_rb.xy + pub pos_lt: [f32; 2], + pub pos_rb: [f32; 2], + // uv_lt.xy, uv_rb.xy + pub uv_lt: [f32; 2], + pub uv_rb: [f32; 2], + // color rgba + pub color: [f32; 4], } - impl AtlasInstance for QuadInstance { - type Vertex = TextureColorVertex; - - fn to_vertices(&self) -> [Self::Vertex; 4] { - let r = self.atlas_rect; - // ADR: u/v normalization is done in the shader, because its probably free and we don't - // have to care about the atlas texture growing as long the rects stay the same. - let (ltx, lty) = (r.min.x as f32, r.min.y as f32); - let (rbx, rby) = (r.max.x as f32, r.max.y as f32); - - let v = &self.vertices; - let color = self.color; - [ - TextureColorVertex::new(v[0], (ltx, lty), color), - TextureColorVertex::new(v[1], (ltx, rby), color), - TextureColorVertex::new(v[2], (rbx, rby), color), - TextureColorVertex::new(v[3], (rbx, lty), color), - ] + impl super::VertexLayout for InstanceVertex { + fn layout() -> wgpu::VertexBufferLayout<'static> { + use std::mem::size_of; + use wgpu::{BufferAddress, VertexAttribute, VertexBufferLayout, VertexStepMode}; + const ATTRS: [VertexAttribute; 5] = wgpu::vertex_attr_array![ + 0 => Float32x2, // pos_lt + 1 => Float32x2, // pos_rb + 2 => Float32x2, // uv_lt + 3 => Float32x2, // uv_rb + 4 => Float32x4 // color + ]; + VertexBufferLayout { + array_stride: size_of::() as BufferAddress, + step_mode: VertexStepMode::Instance, + attributes: &ATTRS, + } } } } pub mod color_atlas { - use massive_geometry::Point3; - - use super::AtlasInstance; - use crate::{glyph::glyph_atlas, pods::TextureVertex}; - - #[derive(Debug)] - pub struct QuadInstance { - pub atlas_rect: glyph_atlas::Rectangle, - pub vertices: [Point3; 4], + use bytemuck::{Pod, Zeroable}; + + #[repr(C)] + #[derive(Copy, Clone, Debug, Pod, Zeroable)] + pub struct InstanceVertex { + pub pos_lt: [f32; 2], + pub pos_rb: [f32; 2], + pub uv_lt: [f32; 2], + pub uv_rb: [f32; 2], } - impl AtlasInstance for QuadInstance { - type Vertex = TextureVertex; - - fn to_vertices(&self) -> [Self::Vertex; 4] { - let r = self.atlas_rect; - // ADR: u/v normalization is done in the shader. Its probably free, and we don't have to - // care about the atlas texture growing as long the rects stay the same. - let (ltx, lty) = (r.min.x as f32, r.min.y as f32); - let (rbx, rby) = (r.max.x as f32, r.max.y as f32); - let v = &self.vertices; - [ - TextureVertex::new(v[0], (ltx, lty)), - TextureVertex::new(v[1], (ltx, rby)), - TextureVertex::new(v[2], (rbx, rby)), - TextureVertex::new(v[3], (rbx, lty)), - ] + impl super::VertexLayout for InstanceVertex { + fn layout() -> wgpu::VertexBufferLayout<'static> { + use std::mem::size_of; + use wgpu::{BufferAddress, VertexAttribute, VertexBufferLayout, VertexStepMode}; + const ATTRS: [VertexAttribute; 4] = wgpu::vertex_attr_array![ + 0 => Float32x2, // pos_lt + 1 => Float32x2, // pos_rb + 2 => Float32x2, // uv_lt + 3 => Float32x2 // uv_rb + ]; + VertexBufferLayout { + array_stride: size_of::() as BufferAddress, + step_mode: VertexStepMode::Instance, + attributes: &ATTRS, + } } } } diff --git a/renderer/src/text_layer/renderer.rs b/renderer/src/text_layer/renderer.rs index 2133f548..edbfe5af 100644 --- a/renderer/src/text_layer/renderer.rs +++ b/renderer/src/text_layer/renderer.rs @@ -5,7 +5,6 @@ use std::{ use anyhow::Result; use cosmic_text::{self as text, FontSystem}; -use massive_geometry::{Point, Point3}; use massive_scene::{Change, Id, Matrix, SceneChange, Shape, VisualRenderObj}; use massive_shapes::{GlyphRun, RunGlyph, TextWeight}; use swash::{Weight, scale::ScaleContext}; @@ -17,10 +16,10 @@ use crate::{ GlyphRasterizationParam, RasterizedGlyphKey, SwashRasterizationParam, glyph_atlas, glyph_rasterization::rasterize_glyph_with_padding, }, - pods::{AsBytes, TextureColorVertex, TextureVertex, ToPod}, + pods::{AsBytes, ToPod}, renderer::{PreparationContext, RenderContext}, scene::{IdTable, LocationMatrices}, - text_layer::atlas_renderer::{AtlasRenderer, color_atlas, sdf_atlas}, + text_layer::atlas_renderer::{self, AtlasRenderer}, tools::QuadIndexBuffer, }; @@ -106,13 +105,13 @@ impl TextLayerRenderer { index_buffer: QuadIndexBuffer::new(device), // Instead of specifying all these consts _and_ the vertex type, a trait based spec type // would probably be better. - sdf_renderer: AtlasRenderer::new::( + sdf_renderer: AtlasRenderer::new::( device, wgpu::TextureFormat::R8Unorm, wgpu::include_wgsl!("shader/sdf_atlas.wgsl"), target_format, ), - color_renderer: AtlasRenderer::new::( + color_renderer: AtlasRenderer::new::( device, wgpu::TextureFormat::Rgba8Unorm, wgpu::include_wgsl!("shader/color_atlas.wgsl"), @@ -239,10 +238,11 @@ impl TextLayerRenderer { pass.set_bind_group(0, &batch.fs_bind_group, &[]); pass.set_vertex_buffer(0, batch.vertex_buffer.slice(..)); + // Draw instanced quads: 6 indices for the unit quad, one instance per glyph. pass.draw_indexed( - 0..(batch.quad_count * QuadIndexBuffer::INDICES_PER_QUAD) as u32, + 0..(QuadIndexBuffer::INDICES_PER_QUAD as u32), 0, - 0..1, + 0..(batch.quad_count as u32), ) } @@ -254,10 +254,10 @@ impl TextLayerRenderer { context: &mut PreparationContext, runs: impl Iterator, ) -> Result { - // Step 1: Get all instance data. // Performance: Compute a conservative capacity? - let mut sdf_glyphs = Vec::new(); - let mut color_glyphs = Vec::new(); + // Step 1: Build instance data directly. + let mut sdf_instances: Vec = Vec::new(); + let mut color_instances: Vec = Vec::new(); // Architecture: We should really not lock this for a longer period of time. After initial // renderers, it's usually not used anymore, because it just invokes get_font() on @@ -278,29 +278,51 @@ impl TextLayerRenderer { continue; // Glyph is empty: Not rendered. }; - // Performance: translation might be applied to two points only (lt, rb) - let vertices = - Self::glyph_vertices(run, glyph, &placement).map(|p| p + translation); + // Compute left-top/right-bottom in pixel space once. + let (lt, rb) = run.place_glyph(glyph, &placement); + let left = lt.x as f32 + translation.x as f32; + let top = lt.y as f32 + translation.y as f32; + let right = rb.x as f32 + translation.x as f32; + let bottom = rb.y as f32 + translation.y as f32; + + let pos_lt = [left, top]; + let pos_rb = [right, bottom]; + + // Atlas rect in pixel space; normalization is done in shader. + let uv_lt = [rect.min.x as f32, rect.min.y as f32]; + let uv_rb = [rect.max.x as f32, rect.max.y as f32]; match kind { AtlasKind::Sdf => { - sdf_glyphs.push(sdf_atlas::QuadInstance { - atlas_rect: rect, - vertices, - // OO: Text color is changing per run only. - color: run.text_color, - }) + sdf_instances.push(atlas_renderer::sdf_atlas::InstanceVertex { + pos_lt, + pos_rb, + uv_lt, + uv_rb, + color: [ + run.text_color.red, + run.text_color.green, + run.text_color.blue, + run.text_color.alpha, + ], + }); + } + AtlasKind::Color => { + color_instances.push(atlas_renderer::color_atlas::InstanceVertex { + pos_lt, + pos_rb, + uv_lt, + uv_rb, + }); } - AtlasKind::Color => color_glyphs.push(color_atlas::QuadInstance { - atlas_rect: rect, - vertices, - }), } } } - let sdf_batch = self.sdf_renderer.batch(context, &sdf_glyphs); - let color_batch = self.color_renderer.batch(context, &color_glyphs); + let sdf_batch = self.sdf_renderer.batch_instances(context, &sdf_instances); + let color_batch = self + .color_renderer + .batch_instances(context, &color_instances); Ok(VisualBatches { sdf: sdf_batch, @@ -375,27 +397,5 @@ impl TextLayerRenderer { } } - fn glyph_vertices( - run: &GlyphRun, - glyph: &RunGlyph, - placement: &text::Placement, - ) -> [Point3; 4] { - let (lt, rb) = run.place_glyph(glyph, placement); - - // Convert the pixel rect to 3D Points. - let left = lt.x as f64; - let top = lt.y as f64; - let right = rb.x as f64; - let bottom = rb.y as f64; - - // OO: might use Point3 here. - let points: [Point; 4] = [ - (left, top).into(), - (left, bottom).into(), - (right, bottom).into(), - (right, top).into(), - ]; - - points.map(|f| f.with_z(0.0)) - } + // glyph_vertices removed in instanced rendering path } diff --git a/renderer/src/text_layer/shader/color_atlas.wgsl b/renderer/src/text_layer/shader/color_atlas.wgsl index cbb6bb4b..99cbb060 100644 --- a/renderer/src/text_layer/shader/color_atlas.wgsl +++ b/renderer/src/text_layer/shader/color_atlas.wgsl @@ -1,10 +1,15 @@ -// Vertex shader +// Vertex shader (instanced quads) var view_model: mat4x4; struct VertexInput { - @location(0) position: vec3, - @location(1) tex_coords: vec2, + @builtin(vertex_index) vertex_index: u32, + // Position rectangle in pixels: left-top and right-bottom + @location(0) pos_lt: vec2, + @location(1) pos_rb: vec2, + // Texture rect in atlas pixel space: left-top and right-bottom + @location(2) uv_lt: vec2, + @location(3) uv_rb: vec2, } struct VertexOutput { @@ -14,12 +19,21 @@ struct VertexOutput { } @vertex -fn vs_main( - vertex_input: VertexInput, -) -> VertexOutput { +fn vs_main(input: VertexInput) -> VertexOutput { + let i = input.vertex_index & 3u; + let use_right = (i == 2u) || (i == 3u); + let use_bottom = (i == 1u) || (i == 2u); + + let x = select(input.pos_lt.x, input.pos_rb.x, use_right); + let y = select(input.pos_lt.y, input.pos_rb.y, use_bottom); + let pos = vec3(x, y, 0.0); + + let tu = select(input.uv_lt.x, input.uv_rb.x, use_right); + let tv = select(input.uv_lt.y, input.uv_rb.y, use_bottom); + var out: VertexOutput; - out.tex_coords = vertex_input.tex_coords; - out.clip_position = view_model * vec4(vertex_input.position, 1.0); + out.tex_coords = vec2(tu, tv); + out.clip_position = view_model * vec4(pos, 1.0); return out; } diff --git a/renderer/src/text_layer/shader/sdf_atlas.wgsl b/renderer/src/text_layer/shader/sdf_atlas.wgsl index d7067fe9..13a235a4 100644 --- a/renderer/src/text_layer/shader/sdf_atlas.wgsl +++ b/renderer/src/text_layer/shader/sdf_atlas.wgsl @@ -1,11 +1,18 @@ -// Vertex shader +// Vertex shader (instanced quads) var view_model: mat4x4; struct VertexInput { - @location(0) position: vec3, - @location(1) tex_coords: vec2, - @location(2) color: vec4, + @builtin(vertex_index) vertex_index: u32, + // Instance attributes + // Position rectangle in pixels: left-top and right-bottom + @location(0) pos_lt: vec2, + @location(1) pos_rb: vec2, + // Texture rect in atlas pixel space: left-top and right-bottom + @location(2) uv_lt: vec2, + @location(3) uv_rb: vec2, + // Per-glyph color + @location(4) color: vec4, } struct VertexOutput { @@ -16,13 +23,23 @@ struct VertexOutput { } @vertex -fn vs_main( - vertex_input: VertexInput, -) -> VertexOutput { +fn vs_main(input: VertexInput) -> VertexOutput { + // Compute quad corner (0..3) from vertex_index & 3 for indexed draw [0,1,2,0,2,3] + let i = input.vertex_index & 3u; + let use_right = (i == 2u) || (i == 3u); + let use_bottom = (i == 1u) || (i == 2u); + + let x = select(input.pos_lt.x, input.pos_rb.x, use_right); + let y = select(input.pos_lt.y, input.pos_rb.y, use_bottom); + let pos = vec3(x, y, 0.0); + + let tu = select(input.uv_lt.x, input.uv_rb.x, use_right); + let tv = select(input.uv_lt.y, input.uv_rb.y, use_bottom); + var out: VertexOutput; - out.tex_coords = vertex_input.tex_coords; - out.clip_position = view_model * vec4(vertex_input.position, 1.0); - out.color = vertex_input.color; + out.tex_coords = vec2(tu, tv); + out.clip_position = view_model * vec4(pos, 1.0); + out.color = input.color; return out; } From c9ff1027debb3d66116a53aac7249e5d1d1b9565 Mon Sep 17 00:00:00 2001 From: Armin Sander Date: Fri, 8 Aug 2025 16:06:54 +0200 Subject: [PATCH 2/4] instance renderer: Minor fixes --- renderer/src/pods.rs | 4 +--- renderer/src/text_layer/atlas_renderer.rs | 18 +++++++++++------- renderer/src/text_layer/renderer.rs | 21 ++++++++------------- 3 files changed, 20 insertions(+), 23 deletions(-) diff --git a/renderer/src/pods.rs b/renderer/src/pods.rs index 49cf7cd1..3fc9793f 100644 --- a/renderer/src/pods.rs +++ b/renderer/src/pods.rs @@ -1,3 +1,4 @@ +#![allow(unused)] use std::{ fmt, mem::{self, size_of}, @@ -91,7 +92,6 @@ pub struct TextureVertex { } impl TextureVertex { - #[allow(unused)] pub fn new(position: impl Into, uv: (f32, f32)) -> Self { Self { position: position.into(), @@ -113,7 +113,6 @@ impl VertexLayout for TextureVertex { } } -#[allow(unused)] #[repr(C)] #[derive(Copy, Clone, Debug, Pod, Zeroable)] pub struct ColorVertex { @@ -121,7 +120,6 @@ pub struct ColorVertex { pub color: Color, } -#[allow(unused)] impl ColorVertex { pub fn new(position: impl Into, color: impl Into) -> Self { Self { diff --git a/renderer/src/text_layer/atlas_renderer.rs b/renderer/src/text_layer/atlas_renderer.rs index 986d464c..af52a6dd 100644 --- a/renderer/src/text_layer/atlas_renderer.rs +++ b/renderer/src/text_layer/atlas_renderer.rs @@ -107,9 +107,11 @@ impl AtlasRenderer { pub mod sdf_atlas { use bytemuck::{Pod, Zeroable}; + use crate::pods::Color; + #[repr(C)] #[derive(Copy, Clone, Debug, Pod, Zeroable)] - pub struct InstanceVertex { + pub struct Instance { // pos_lt.xy, pos_rb.xy pub pos_lt: [f32; 2], pub pos_rb: [f32; 2], @@ -117,10 +119,10 @@ pub mod sdf_atlas { pub uv_lt: [f32; 2], pub uv_rb: [f32; 2], // color rgba - pub color: [f32; 4], + pub color: Color, } - impl super::VertexLayout for InstanceVertex { + impl super::VertexLayout for Instance { fn layout() -> wgpu::VertexBufferLayout<'static> { use std::mem::size_of; use wgpu::{BufferAddress, VertexAttribute, VertexBufferLayout, VertexStepMode}; @@ -132,7 +134,7 @@ pub mod sdf_atlas { 4 => Float32x4 // color ]; VertexBufferLayout { - array_stride: size_of::() as BufferAddress, + array_stride: size_of::() as BufferAddress, step_mode: VertexStepMode::Instance, attributes: &ATTRS, } @@ -143,16 +145,18 @@ pub mod sdf_atlas { pub mod color_atlas { use bytemuck::{Pod, Zeroable}; + use crate::pods::VertexLayout; + #[repr(C)] #[derive(Copy, Clone, Debug, Pod, Zeroable)] - pub struct InstanceVertex { + pub struct Instance { pub pos_lt: [f32; 2], pub pos_rb: [f32; 2], pub uv_lt: [f32; 2], pub uv_rb: [f32; 2], } - impl super::VertexLayout for InstanceVertex { + impl VertexLayout for Instance { fn layout() -> wgpu::VertexBufferLayout<'static> { use std::mem::size_of; use wgpu::{BufferAddress, VertexAttribute, VertexBufferLayout, VertexStepMode}; @@ -163,7 +167,7 @@ pub mod color_atlas { 3 => Float32x2 // uv_rb ]; VertexBufferLayout { - array_stride: size_of::() as BufferAddress, + array_stride: size_of::() as BufferAddress, step_mode: VertexStepMode::Instance, attributes: &ATTRS, } diff --git a/renderer/src/text_layer/renderer.rs b/renderer/src/text_layer/renderer.rs index edbfe5af..98a6e393 100644 --- a/renderer/src/text_layer/renderer.rs +++ b/renderer/src/text_layer/renderer.rs @@ -19,7 +19,7 @@ use crate::{ pods::{AsBytes, ToPod}, renderer::{PreparationContext, RenderContext}, scene::{IdTable, LocationMatrices}, - text_layer::atlas_renderer::{self, AtlasRenderer}, + text_layer::atlas_renderer::{self, AtlasRenderer, color_atlas, sdf_atlas}, tools::QuadIndexBuffer, }; @@ -105,13 +105,13 @@ impl TextLayerRenderer { index_buffer: QuadIndexBuffer::new(device), // Instead of specifying all these consts _and_ the vertex type, a trait based spec type // would probably be better. - sdf_renderer: AtlasRenderer::new::( + sdf_renderer: AtlasRenderer::new::( device, wgpu::TextureFormat::R8Unorm, wgpu::include_wgsl!("shader/sdf_atlas.wgsl"), target_format, ), - color_renderer: AtlasRenderer::new::( + color_renderer: AtlasRenderer::new::( device, wgpu::TextureFormat::Rgba8Unorm, wgpu::include_wgsl!("shader/color_atlas.wgsl"), @@ -256,8 +256,8 @@ impl TextLayerRenderer { ) -> Result { // Performance: Compute a conservative capacity? // Step 1: Build instance data directly. - let mut sdf_instances: Vec = Vec::new(); - let mut color_instances: Vec = Vec::new(); + let mut sdf_instances: Vec = Vec::new(); + let mut color_instances: Vec = Vec::new(); // Architecture: We should really not lock this for a longer period of time. After initial // renderers, it's usually not used anymore, because it just invokes get_font() on @@ -294,21 +294,16 @@ impl TextLayerRenderer { match kind { AtlasKind::Sdf => { - sdf_instances.push(atlas_renderer::sdf_atlas::InstanceVertex { + sdf_instances.push(sdf_atlas::Instance { pos_lt, pos_rb, uv_lt, uv_rb, - color: [ - run.text_color.red, - run.text_color.green, - run.text_color.blue, - run.text_color.alpha, - ], + color: run.text_color.into(), }); } AtlasKind::Color => { - color_instances.push(atlas_renderer::color_atlas::InstanceVertex { + color_instances.push(color_atlas::Instance { pos_lt, pos_rb, uv_lt, From 795e2295e3aaeb48f30d3aa19b3fa76421409fc2 Mon Sep 17 00:00:00 2001 From: Armin Sander Date: Fri, 8 Aug 2025 16:17:45 +0200 Subject: [PATCH 3/4] No need to compute max_quads anymore, because index buffer has a constant size now --- renderer/src/text_layer/renderer.rs | 56 ++++++----------------------- 1 file changed, 10 insertions(+), 46 deletions(-) diff --git a/renderer/src/text_layer/renderer.rs b/renderer/src/text_layer/renderer.rs index 98a6e393..ec860a81 100644 --- a/renderer/src/text_layer/renderer.rs +++ b/renderer/src/text_layer/renderer.rs @@ -5,8 +5,6 @@ use std::{ use anyhow::Result; use cosmic_text::{self as text, FontSystem}; -use massive_scene::{Change, Id, Matrix, SceneChange, Shape, VisualRenderObj}; -use massive_shapes::{GlyphRun, RunGlyph, TextWeight}; use swash::{Weight, scale::ScaleContext}; use text::SwashContent; use wgpu::Device; @@ -22,6 +20,8 @@ use crate::{ text_layer::atlas_renderer::{self, AtlasRenderer, color_atlas, sdf_atlas}, tools::QuadIndexBuffer, }; +use massive_scene::{Change, Id, Matrix, SceneChange, Shape, VisualRenderObj}; +use massive_shapes::{GlyphRun, RunGlyph, TextWeight}; pub struct TextLayerRenderer { // Optimization: This is used for get_font() only, which needs &mut. In the long run, completely @@ -48,9 +48,6 @@ pub struct TextLayerRenderer { // Quads for example? /// Visual Id -> batch table. visuals: IdTable>, - - /// The maximum quads currently in use. This may be more than the index buffer can hold. - max_quads_in_use: usize, } struct Visual { @@ -64,21 +61,6 @@ struct VisualBatches { color: Option, } -impl VisualBatches { - fn max_quads(&self) -> usize { - [ - self.sdf.as_ref().map(|b| b.quad_count).unwrap_or_default(), - self.color - .as_ref() - .map(|b| b.quad_count) - .unwrap_or_default(), - ] - .into_iter() - .max() - .unwrap() - } -} - pub struct QuadBatch { /// Contains texture reference(s) and the sampler configuration. pub fs_bind_group: wgpu::BindGroup, @@ -98,7 +80,7 @@ impl TextLayerRenderer { font_system: Arc>, target_format: wgpu::TextureFormat, ) -> Self { - Self { + let mut renderer = Self { scale_context: ScaleContext::default(), font_system, empty_glyphs: HashSet::new(), @@ -118,8 +100,11 @@ impl TextLayerRenderer { target_format, ), visuals: IdTable::default(), - max_quads_in_use: 0, - } + }; + + // Since we use instance drendering, the index buffer needs to hold only one quad. + renderer.index_buffer.ensure_can_index_num_quads(device, 1); + renderer } // Architecture: Optimization: @@ -175,32 +160,11 @@ impl TextLayerRenderer { locations.into_iter() } - pub fn prepare(&mut self, context: &mut PreparationContext) { - // Optimization: Visuals are iterated 4 times per render (see all_locations(), which could - // also compute max_quads). - // - // Compute only one max_quads value (which is optimal when we use one index buffer only). - let max_quads = self - .visuals - .iter_some() - .map(|v| v.batches.max_quads()) - .max() - .unwrap_or_default(); - - self.index_buffer - .ensure_can_index_num_quads(context.device, max_quads); - - self.max_quads_in_use = max_quads; - } + pub fn prepare(&mut self, _context: &mut PreparationContext) {} pub fn render(&self, matrices: &LocationMatrices, context: &mut RenderContext) { - if self.max_quads_in_use == 0 { - return; - } - // Set the shared index buffer for all quad renderers. - self.index_buffer - .set(&mut context.pass, self.max_quads_in_use); + self.index_buffer.set(&mut context.pass, 1); { context.pass.set_pipeline(self.sdf_renderer.pipeline()); From ee99b32f02ee9db175bee4b566953c32f38758c5 Mon Sep 17 00:00:00 2001 From: Armin Sander Date: Mon, 11 Aug 2025 17:47:00 +0200 Subject: [PATCH 4/4] Readd depth --- renderer/src/text_layer/atlas_renderer.rs | 14 ++++++++++---- renderer/src/text_layer/renderer.rs | 3 +++ renderer/src/text_layer/shader/color_atlas.wgsl | 4 +++- renderer/src/text_layer/shader/sdf_atlas.wgsl | 4 +++- 4 files changed, 19 insertions(+), 6 deletions(-) diff --git a/renderer/src/text_layer/atlas_renderer.rs b/renderer/src/text_layer/atlas_renderer.rs index af52a6dd..7e13dc04 100644 --- a/renderer/src/text_layer/atlas_renderer.rs +++ b/renderer/src/text_layer/atlas_renderer.rs @@ -120,18 +120,21 @@ pub mod sdf_atlas { pub uv_rb: [f32; 2], // color rgba pub color: Color, + // depth (z in pixel space) + pub depth: f32, } impl super::VertexLayout for Instance { fn layout() -> wgpu::VertexBufferLayout<'static> { use std::mem::size_of; use wgpu::{BufferAddress, VertexAttribute, VertexBufferLayout, VertexStepMode}; - const ATTRS: [VertexAttribute; 5] = wgpu::vertex_attr_array![ + const ATTRS: [VertexAttribute; 6] = wgpu::vertex_attr_array![ 0 => Float32x2, // pos_lt 1 => Float32x2, // pos_rb 2 => Float32x2, // uv_lt 3 => Float32x2, // uv_rb - 4 => Float32x4 // color + 4 => Float32x4, // color + 5 => Float32, // depth ]; VertexBufferLayout { array_stride: size_of::() as BufferAddress, @@ -154,17 +157,20 @@ pub mod color_atlas { pub pos_rb: [f32; 2], pub uv_lt: [f32; 2], pub uv_rb: [f32; 2], + // depth (z in pixel space) + pub depth: f32, } impl VertexLayout for Instance { fn layout() -> wgpu::VertexBufferLayout<'static> { use std::mem::size_of; use wgpu::{BufferAddress, VertexAttribute, VertexBufferLayout, VertexStepMode}; - const ATTRS: [VertexAttribute; 4] = wgpu::vertex_attr_array![ + const ATTRS: [VertexAttribute; 5] = wgpu::vertex_attr_array![ 0 => Float32x2, // pos_lt 1 => Float32x2, // pos_rb 2 => Float32x2, // uv_lt - 3 => Float32x2 // uv_rb + 3 => Float32x2, // uv_rb + 4 => Float32, // depth ]; VertexBufferLayout { array_stride: size_of::() as BufferAddress, diff --git a/renderer/src/text_layer/renderer.rs b/renderer/src/text_layer/renderer.rs index ec860a81..d4e06b09 100644 --- a/renderer/src/text_layer/renderer.rs +++ b/renderer/src/text_layer/renderer.rs @@ -248,6 +248,7 @@ impl TextLayerRenderer { let top = lt.y as f32 + translation.y as f32; let right = rb.x as f32 + translation.x as f32; let bottom = rb.y as f32 + translation.y as f32; + let depth = translation.z as f32; let pos_lt = [left, top]; let pos_rb = [right, bottom]; @@ -264,6 +265,7 @@ impl TextLayerRenderer { uv_lt, uv_rb, color: run.text_color.into(), + depth, }); } AtlasKind::Color => { @@ -272,6 +274,7 @@ impl TextLayerRenderer { pos_rb, uv_lt, uv_rb, + depth, }); } } diff --git a/renderer/src/text_layer/shader/color_atlas.wgsl b/renderer/src/text_layer/shader/color_atlas.wgsl index 99cbb060..d3d87af5 100644 --- a/renderer/src/text_layer/shader/color_atlas.wgsl +++ b/renderer/src/text_layer/shader/color_atlas.wgsl @@ -10,6 +10,8 @@ struct VertexInput { // Texture rect in atlas pixel space: left-top and right-bottom @location(2) uv_lt: vec2, @location(3) uv_rb: vec2, + // Depth in pixel space + @location(4) depth: f32, } struct VertexOutput { @@ -26,7 +28,7 @@ fn vs_main(input: VertexInput) -> VertexOutput { let x = select(input.pos_lt.x, input.pos_rb.x, use_right); let y = select(input.pos_lt.y, input.pos_rb.y, use_bottom); - let pos = vec3(x, y, 0.0); + let pos = vec3(x, y, input.depth); let tu = select(input.uv_lt.x, input.uv_rb.x, use_right); let tv = select(input.uv_lt.y, input.uv_rb.y, use_bottom); diff --git a/renderer/src/text_layer/shader/sdf_atlas.wgsl b/renderer/src/text_layer/shader/sdf_atlas.wgsl index 13a235a4..c36892a2 100644 --- a/renderer/src/text_layer/shader/sdf_atlas.wgsl +++ b/renderer/src/text_layer/shader/sdf_atlas.wgsl @@ -13,6 +13,8 @@ struct VertexInput { @location(3) uv_rb: vec2, // Per-glyph color @location(4) color: vec4, + // Depth in pixel space + @location(5) depth: f32, } struct VertexOutput { @@ -31,7 +33,7 @@ fn vs_main(input: VertexInput) -> VertexOutput { let x = select(input.pos_lt.x, input.pos_rb.x, use_right); let y = select(input.pos_lt.y, input.pos_rb.y, use_bottom); - let pos = vec3(x, y, 0.0); + let pos = vec3(x, y, input.depth); let tu = select(input.uv_lt.x, input.uv_rb.x, use_right); let tv = select(input.uv_lt.y, input.uv_rb.y, use_bottom);