From 5a4fc2c3538ebb65706b94fdc4bebe1d1bae3e0b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C4=93teris=20Pakalns?= Date: Tue, 26 Mar 2024 02:25:57 +0200 Subject: [PATCH] Support multi viewport rendering with reusable text atlas (#88) --- src/pipeline.rs | 62 +++++++++++++++++++++++++++------------------- src/shader.wgsl | 10 ++++---- src/text_atlas.rs | 42 +++++++++---------------------- src/text_render.rs | 47 +++++++++++++++++++++-------------- 4 files changed, 82 insertions(+), 79 deletions(-) diff --git a/src/pipeline.rs b/src/pipeline.rs index 7f9548f..054ffb1 100644 --- a/src/pipeline.rs +++ b/src/pipeline.rs @@ -25,7 +25,8 @@ struct Inner { sampler: Sampler, shader: ShaderModule, vertex_buffers: [wgpu::VertexBufferLayout<'static>; 1], - bind_group_layout: BindGroupLayout, + atlas_layout: BindGroupLayout, + uniforms_layout: BindGroupLayout, pipeline_layout: PipelineLayout, cache: RwLock< HashMap<(TextureFormat, MultisampleState, Option), Arc>, @@ -86,20 +87,10 @@ impl Pipeline { ], }; - let bind_group_layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { + let atlas_layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { entries: &[ BindGroupLayoutEntry { binding: 0, - visibility: ShaderStages::VERTEX, - ty: BindingType::Buffer { - ty: BufferBindingType::Uniform, - has_dynamic_offset: false, - min_binding_size: NonZeroU64::new(mem::size_of::() as u64), - }, - count: None, - }, - BindGroupLayoutEntry { - binding: 1, visibility: ShaderStages::VERTEX | ShaderStages::FRAGMENT, ty: BindingType::Texture { multisampled: false, @@ -109,7 +100,7 @@ impl Pipeline { count: None, }, BindGroupLayoutEntry { - binding: 2, + binding: 1, visibility: ShaderStages::VERTEX | ShaderStages::FRAGMENT, ty: BindingType::Texture { multisampled: false, @@ -119,7 +110,7 @@ impl Pipeline { count: None, }, BindGroupLayoutEntry { - binding: 3, + binding: 2, visibility: ShaderStages::FRAGMENT, ty: BindingType::Sampler(SamplerBindingType::Filtering), count: None, @@ -128,9 +119,23 @@ impl Pipeline { label: Some("glyphon bind group layout"), }); + let uniforms_layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { + entries: &[BindGroupLayoutEntry { + binding: 0, + visibility: ShaderStages::VERTEX, + ty: BindingType::Buffer { + ty: BufferBindingType::Uniform, + has_dynamic_offset: false, + min_binding_size: NonZeroU64::new(mem::size_of::() as u64), + }, + count: None, + }], + label: Some("glyphon uniforms bind group layout"), + }); + let pipeline_layout = device.create_pipeline_layout(&PipelineLayoutDescriptor { label: None, - bind_group_layouts: &[&bind_group_layout], + bind_group_layouts: &[&atlas_layout, &uniforms_layout], push_constant_ranges: &[], }); @@ -138,36 +143,32 @@ impl Pipeline { sampler, shader, vertex_buffers: [vertex_buffer_layout], - bind_group_layout, + atlas_layout, + uniforms_layout, pipeline_layout, cache: RwLock::new(HashMap::new()), })) } - pub(crate) fn create_bind_group( + pub(crate) fn create_atlas_bind_group( &self, device: &Device, - params_buffer: &Buffer, color_atlas: &TextureView, mask_atlas: &TextureView, ) -> BindGroup { device.create_bind_group(&BindGroupDescriptor { - layout: &self.0.bind_group_layout, + layout: &self.0.atlas_layout, entries: &[ BindGroupEntry { binding: 0, - resource: params_buffer.as_entire_binding(), - }, - BindGroupEntry { - binding: 1, resource: BindingResource::TextureView(color_atlas), }, BindGroupEntry { - binding: 2, + binding: 1, resource: BindingResource::TextureView(mask_atlas), }, BindGroupEntry { - binding: 3, + binding: 2, resource: BindingResource::Sampler(&self.0.sampler), }, ], @@ -175,6 +176,17 @@ impl Pipeline { }) } + pub(crate) fn create_uniforms_bind_group(&self, device: &Device, buffer: &Buffer) -> BindGroup { + device.create_bind_group(&BindGroupDescriptor { + layout: &self.0.uniforms_layout, + entries: &[BindGroupEntry { + binding: 0, + resource: buffer.as_entire_binding(), + }], + label: Some("glyphon uniforms bind group"), + }) + } + pub(crate) fn get_or_create( &self, device: &Device, diff --git a/src/shader.wgsl b/src/shader.wgsl index 226a52e..1813a66 100644 --- a/src/shader.wgsl +++ b/src/shader.wgsl @@ -21,17 +21,17 @@ struct Params { }; @group(0) @binding(0) -var params: Params; - -@group(0) @binding(1) var color_atlas_texture: texture_2d; -@group(0) @binding(2) +@group(0) @binding(1) var mask_atlas_texture: texture_2d; -@group(0) @binding(3) +@group(0) @binding(2) var atlas_sampler: sampler; +@group(1) @binding(0) +var params: Params; + fn srgb_to_linear(c: f32) -> f32 { if c <= 0.04045 { return c / 12.92; diff --git a/src/text_atlas.rs b/src/text_atlas.rs index ee71e6d..4fbc7d2 100644 --- a/src/text_atlas.rs +++ b/src/text_atlas.rs @@ -1,16 +1,15 @@ use crate::{ - text_render::ContentType, CacheKey, FontSystem, GlyphDetails, GpuCacheStatus, Params, Pipeline, - Resolution, SwashCache, + text_render::ContentType, CacheKey, FontSystem, GlyphDetails, GpuCacheStatus, Pipeline, + SwashCache, }; use etagere::{size2, Allocation, BucketedAtlasAllocator}; use lru::LruCache; use rustc_hash::FxHasher; -use std::{collections::HashSet, hash::BuildHasherDefault, mem::size_of, sync::Arc}; +use std::{collections::HashSet, hash::BuildHasherDefault, sync::Arc}; use wgpu::{ - BindGroup, Buffer, BufferDescriptor, BufferUsages, DepthStencilState, Device, Extent3d, - ImageCopyTexture, ImageDataLayout, MultisampleState, Origin3d, Queue, RenderPipeline, Texture, - TextureAspect, TextureDescriptor, TextureDimension, TextureFormat, TextureUsages, TextureView, - TextureViewDescriptor, + BindGroup, Buffer, DepthStencilState, Device, Extent3d, ImageCopyTexture, ImageDataLayout, + MultisampleState, Origin3d, Queue, RenderPipeline, Texture, TextureAspect, TextureDescriptor, + TextureDimension, TextureFormat, TextureUsages, TextureView, TextureViewDescriptor, }; type Hasher = BuildHasherDefault; @@ -254,8 +253,6 @@ pub enum ColorMode { /// An atlas containing a cache of rasterized glyphs that can be rendered. pub struct TextAtlas { pipeline: Pipeline, - pub(crate) params: Params, - pub(crate) params_buffer: Buffer, pub(crate) bind_group: Arc, pub(crate) color_atlas: InnerAtlas, pub(crate) mask_atlas: InnerAtlas, @@ -277,21 +274,6 @@ impl TextAtlas { format: TextureFormat, color_mode: ColorMode, ) -> Self { - let params = Params { - screen_resolution: Resolution { - width: 0, - height: 0, - }, - _pad: [0, 0], - }; - - let params_buffer = device.create_buffer(&BufferDescriptor { - label: Some("glyphon params"), - size: size_of::() as u64, - usage: BufferUsages::UNIFORM | BufferUsages::COPY_DST, - mapped_at_creation: false, - }); - let color_atlas = InnerAtlas::new( device, queue, @@ -304,17 +286,14 @@ impl TextAtlas { ); let mask_atlas = InnerAtlas::new(device, queue, Kind::Mask); - let bind_group = Arc::new(pipeline.create_bind_group( + let bind_group = Arc::new(pipeline.create_atlas_bind_group( device, - ¶ms_buffer, &color_atlas.texture_view, &mask_atlas.texture_view, )); Self { pipeline: pipeline.clone(), - params, - params_buffer, bind_group, color_atlas, mask_atlas, @@ -372,10 +351,13 @@ impl TextAtlas { .get_or_create(device, self.format, multisample, depth_stencil) } + pub(crate) fn create_uniforms_bind_group(&self, device: &Device, buffer: &Buffer) -> BindGroup { + self.pipeline.create_uniforms_bind_group(device, buffer) + } + fn rebind(&mut self, device: &wgpu::Device) { - self.bind_group = Arc::new(self.pipeline.create_bind_group( + self.bind_group = Arc::new(self.pipeline.create_atlas_bind_group( device, - &self.params_buffer, &self.color_atlas.texture_view, &self.mask_atlas.texture_view, )); diff --git a/src/text_render.rs b/src/text_render.rs index 9931152..71a214c 100644 --- a/src/text_render.rs +++ b/src/text_render.rs @@ -13,9 +13,11 @@ use wgpu::{ /// A text renderer that uses cached glyphs to render text into an existing render pass. pub struct TextRenderer { staging_belt: StagingBelt, + params: Params, + params_buffer: Buffer, + bind_group: wgpu::BindGroup, vertex_buffer: Buffer, vertex_buffer_size: u64, - screen_resolution: Resolution, pipeline: Arc, glyph_vertices: Vec, glyphs_to_render: u32, @@ -39,14 +41,30 @@ impl TextRenderer { let pipeline = atlas.get_or_create_pipeline(device, multisample, depth_stencil); - Self { - staging_belt: StagingBelt::new(vertex_buffer_size), - vertex_buffer, - vertex_buffer_size, + let params = Params { screen_resolution: Resolution { width: 0, height: 0, }, + _pad: [0, 0], + }; + + let params_buffer = device.create_buffer(&BufferDescriptor { + label: Some("glyphon params"), + size: size_of::() as u64, + usage: BufferUsages::UNIFORM | BufferUsages::COPY_DST, + mapped_at_creation: false, + }); + + let bind_group = atlas.create_uniforms_bind_group(device, ¶ms_buffer); + + Self { + staging_belt: StagingBelt::new(vertex_buffer_size), + params, + params_buffer, + bind_group, + vertex_buffer, + vertex_buffer_size, pipeline, glyph_vertices: Vec::new(), glyphs_to_render: 0, @@ -67,15 +85,12 @@ impl TextRenderer { mut metadata_to_depth: impl FnMut(usize) -> f32, ) -> Result<(), PrepareError> { self.staging_belt.recall(); - self.screen_resolution = screen_resolution; - let atlas_current_resolution = { atlas.params.screen_resolution }; - - if screen_resolution != atlas_current_resolution { - atlas.params.screen_resolution = screen_resolution; - queue.write_buffer(&atlas.params_buffer, 0, unsafe { + if self.params.screen_resolution != screen_resolution { + self.params.screen_resolution = screen_resolution; + queue.write_buffer(&self.params_buffer, 0, unsafe { slice::from_raw_parts( - &atlas.params as *const Params as *const u8, + &self.params as *const Params as *const u8, size_of::(), ) }); @@ -362,15 +377,9 @@ impl TextRenderer { return Ok(()); } - { - // Validate that screen resolution hasn't changed since `prepare` - if self.screen_resolution != atlas.params.screen_resolution { - return Err(RenderError::ScreenResolutionChanged); - } - } - pass.set_pipeline(&self.pipeline); pass.set_bind_group(0, &atlas.bind_group, &[]); + pass.set_bind_group(1, &self.bind_group, &[]); pass.set_vertex_buffer(0, self.vertex_buffer.slice(..)); pass.draw(0..4, 0..self.glyphs_to_render);