Support multi viewport rendering with reusable text atlas (#88)

This commit is contained in:
Pēteris Pakalns 2024-03-26 02:25:57 +02:00 committed by GitHub
parent 4700e54f16
commit f95e66f612
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 78 additions and 83 deletions

View file

@ -23,13 +23,13 @@ struct Params {
@group(0) @binding(0) @group(0) @binding(0)
var<uniform> params: Params; var<uniform> params: Params;
@group(0) @binding(1) @group(1) @binding(0)
var color_atlas_texture: texture_2d<f32>; var color_atlas_texture: texture_2d<f32>;
@group(0) @binding(2) @group(1) @binding(1)
var mask_atlas_texture: texture_2d<f32>; var mask_atlas_texture: texture_2d<f32>;
@group(0) @binding(3) @group(1) @binding(2)
var atlas_sampler: sampler; var atlas_sampler: sampler;
fn srgb_to_linear(c: f32) -> f32 { fn srgb_to_linear(c: f32) -> f32 {

View file

@ -1,20 +1,20 @@
use crate::{ use crate::{
text_render::ContentType, CacheKey, FontSystem, GlyphDetails, GlyphToRender, GpuCacheStatus, text_render::ContentType, CacheKey, FontSystem, GlyphDetails, GlyphToRender, GpuCacheStatus,
Params, Resolution, SwashCache, Params, SwashCache,
}; };
use etagere::{size2, Allocation, BucketedAtlasAllocator}; use etagere::{size2, Allocation, BucketedAtlasAllocator};
use lru::LruCache; use lru::LruCache;
use std::{borrow::Cow, collections::HashSet, mem::size_of, num::NonZeroU64, sync::Arc}; use std::{borrow::Cow, collections::HashSet, mem::size_of, num::NonZeroU64, sync::Arc};
use wgpu::{ use wgpu::{
BindGroup, BindGroupDescriptor, BindGroupEntry, BindGroupLayout, BindGroupLayoutEntry, BindGroup, BindGroupDescriptor, BindGroupEntry, BindGroupLayout, BindGroupLayoutEntry,
BindingResource, BindingType, BlendState, Buffer, BufferBindingType, BufferDescriptor, BindingResource, BindingType, BlendState, BufferBindingType, ColorTargetState, ColorWrites,
BufferUsages, ColorTargetState, ColorWrites, DepthStencilState, Device, Extent3d, FilterMode, DepthStencilState, Device, Extent3d, FilterMode, FragmentState, ImageCopyTexture,
FragmentState, ImageCopyTexture, ImageDataLayout, MultisampleState, Origin3d, PipelineLayout, ImageDataLayout, MultisampleState, Origin3d, PipelineLayout, PipelineLayoutDescriptor,
PipelineLayoutDescriptor, PrimitiveState, Queue, RenderPipeline, RenderPipelineDescriptor, PrimitiveState, Queue, RenderPipeline, RenderPipelineDescriptor, Sampler, SamplerBindingType,
Sampler, SamplerBindingType, SamplerDescriptor, ShaderModule, ShaderModuleDescriptor, SamplerDescriptor, ShaderModule, ShaderModuleDescriptor, ShaderSource, ShaderStages, Texture,
ShaderSource, ShaderStages, Texture, TextureAspect, TextureDescriptor, TextureDimension, TextureAspect, TextureDescriptor, TextureDimension, TextureFormat, TextureSampleType,
TextureFormat, TextureSampleType, TextureUsages, TextureView, TextureViewDescriptor, TextureUsages, TextureView, TextureViewDescriptor, TextureViewDimension, VertexFormat,
TextureViewDimension, VertexFormat, VertexState, VertexState,
}; };
#[allow(dead_code)] #[allow(dead_code)]
@ -255,8 +255,6 @@ pub enum ColorMode {
/// An atlas containing a cache of rasterized glyphs that can be rendered. /// An atlas containing a cache of rasterized glyphs that can be rendered.
pub struct TextAtlas { pub struct TextAtlas {
pub(crate) params: Params,
pub(crate) params_buffer: Buffer,
pub(crate) cached_pipelines: Vec<( pub(crate) cached_pipelines: Vec<(
MultisampleState, MultisampleState,
Option<DepthStencilState>, Option<DepthStencilState>,
@ -264,6 +262,7 @@ pub struct TextAtlas {
)>, )>,
pub(crate) bind_group: Arc<BindGroup>, pub(crate) bind_group: Arc<BindGroup>,
pub(crate) bind_group_layout: BindGroupLayout, pub(crate) bind_group_layout: BindGroupLayout,
pub(crate) text_render_bind_group_layout: BindGroupLayout,
pub(crate) sampler: Sampler, pub(crate) sampler: Sampler,
pub(crate) color_atlas: InnerAtlas, pub(crate) color_atlas: InnerAtlas,
pub(crate) mask_atlas: InnerAtlas, pub(crate) mask_atlas: InnerAtlas,
@ -340,9 +339,9 @@ impl TextAtlas {
], ],
}]; }];
let bind_group_layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { let text_render_bind_group_layout =
entries: &[ device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
BindGroupLayoutEntry { entries: &[BindGroupLayoutEntry {
binding: 0, binding: 0,
visibility: ShaderStages::VERTEX, visibility: ShaderStages::VERTEX,
ty: BindingType::Buffer { ty: BindingType::Buffer {
@ -351,6 +350,21 @@ impl TextAtlas {
min_binding_size: NonZeroU64::new(size_of::<Params>() as u64), min_binding_size: NonZeroU64::new(size_of::<Params>() as u64),
}, },
count: None, count: None,
}],
label: Some("glyphon text render bind group layout"),
});
let bind_group_layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
entries: &[
BindGroupLayoutEntry {
binding: 0,
visibility: ShaderStages::VERTEX | ShaderStages::FRAGMENT,
ty: BindingType::Texture {
multisampled: false,
view_dimension: TextureViewDimension::D2,
sample_type: TextureSampleType::Float { filterable: true },
},
count: None,
}, },
BindGroupLayoutEntry { BindGroupLayoutEntry {
binding: 1, binding: 1,
@ -364,37 +378,12 @@ impl TextAtlas {
}, },
BindGroupLayoutEntry { BindGroupLayoutEntry {
binding: 2, binding: 2,
visibility: ShaderStages::VERTEX | ShaderStages::FRAGMENT,
ty: BindingType::Texture {
multisampled: false,
view_dimension: TextureViewDimension::D2,
sample_type: TextureSampleType::Float { filterable: true },
},
count: None,
},
BindGroupLayoutEntry {
binding: 3,
visibility: ShaderStages::FRAGMENT, visibility: ShaderStages::FRAGMENT,
ty: BindingType::Sampler(SamplerBindingType::Filtering), ty: BindingType::Sampler(SamplerBindingType::Filtering),
count: None, count: None,
}, },
], ],
label: Some("glyphon bind group layout"), label: Some("glyphon text atlas bind group layout"),
});
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::<Params>() as u64,
usage: BufferUsages::UNIFORM | BufferUsages::COPY_DST,
mapped_at_creation: false,
}); });
let color_atlas = InnerAtlas::new( let color_atlas = InnerAtlas::new(
@ -414,34 +403,29 @@ impl TextAtlas {
entries: &[ entries: &[
BindGroupEntry { BindGroupEntry {
binding: 0, binding: 0,
resource: params_buffer.as_entire_binding(),
},
BindGroupEntry {
binding: 1,
resource: BindingResource::TextureView(&color_atlas.texture_view), resource: BindingResource::TextureView(&color_atlas.texture_view),
}, },
BindGroupEntry { BindGroupEntry {
binding: 2, binding: 1,
resource: BindingResource::TextureView(&mask_atlas.texture_view), resource: BindingResource::TextureView(&mask_atlas.texture_view),
}, },
BindGroupEntry { BindGroupEntry {
binding: 3, binding: 2,
resource: BindingResource::Sampler(&sampler), resource: BindingResource::Sampler(&sampler),
}, },
], ],
label: Some("glyphon bind group"), label: Some("glyphon text atlas bind group"),
})); }));
let pipeline_layout = device.create_pipeline_layout(&PipelineLayoutDescriptor { let pipeline_layout = device.create_pipeline_layout(&PipelineLayoutDescriptor {
label: None, label: None,
bind_group_layouts: &[&bind_group_layout], bind_group_layouts: &[&text_render_bind_group_layout, &bind_group_layout],
push_constant_ranges: &[], push_constant_ranges: &[],
}); });
Self { Self {
params,
params_buffer,
cached_pipelines: Vec::new(), cached_pipelines: Vec::new(),
text_render_bind_group_layout,
bind_group, bind_group,
bind_group_layout, bind_group_layout,
sampler, sampler,
@ -540,18 +524,14 @@ impl TextAtlas {
entries: &[ entries: &[
BindGroupEntry { BindGroupEntry {
binding: 0, binding: 0,
resource: self.params_buffer.as_entire_binding(),
},
BindGroupEntry {
binding: 1,
resource: BindingResource::TextureView(&self.color_atlas.texture_view), resource: BindingResource::TextureView(&self.color_atlas.texture_view),
}, },
BindGroupEntry { BindGroupEntry {
binding: 2, binding: 1,
resource: BindingResource::TextureView(&self.mask_atlas.texture_view), resource: BindingResource::TextureView(&self.mask_atlas.texture_view),
}, },
BindGroupEntry { BindGroupEntry {
binding: 3, binding: 2,
resource: BindingResource::Sampler(&self.sampler), resource: BindingResource::Sampler(&self.sampler),
}, },
], ],

View file

@ -4,20 +4,22 @@ use crate::{
}; };
use std::{iter, mem::size_of, slice, sync::Arc}; use std::{iter, mem::size_of, slice, sync::Arc};
use wgpu::{ use wgpu::{
Buffer, BufferDescriptor, BufferUsages, DepthStencilState, Device, Extent3d, ImageCopyTexture, BindGroupDescriptor, BindGroupEntry, Buffer, BufferDescriptor, BufferUsages, DepthStencilState,
ImageDataLayout, IndexFormat, MultisampleState, Origin3d, Queue, RenderPass, RenderPipeline, Device, Extent3d, ImageCopyTexture, ImageDataLayout, IndexFormat, MultisampleState, Origin3d,
TextureAspect, COPY_BUFFER_ALIGNMENT, Queue, RenderPass, RenderPipeline, TextureAspect, COPY_BUFFER_ALIGNMENT,
}; };
/// A text renderer that uses cached glyphs to render text into an existing render pass. /// A text renderer that uses cached glyphs to render text into an existing render pass.
pub struct TextRenderer { pub struct TextRenderer {
params: Params,
params_buffer: Buffer,
vertex_buffer: Buffer, vertex_buffer: Buffer,
vertex_buffer_size: u64, vertex_buffer_size: u64,
index_buffer: Buffer, index_buffer: Buffer,
index_buffer_size: u64, index_buffer_size: u64,
vertices_to_render: u32, vertices_to_render: u32,
screen_resolution: Resolution,
pipeline: Arc<RenderPipeline>, pipeline: Arc<RenderPipeline>,
bind_group: wgpu::BindGroup,
} }
impl TextRenderer { impl TextRenderer {
@ -46,17 +48,40 @@ impl TextRenderer {
let pipeline = atlas.get_or_create_pipeline(device, multisample, depth_stencil); let pipeline = atlas.get_or_create_pipeline(device, multisample, depth_stencil);
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::<Params>() as u64,
usage: BufferUsages::UNIFORM | BufferUsages::COPY_DST,
mapped_at_creation: false,
});
let bind_group = device.create_bind_group(&BindGroupDescriptor {
layout: &atlas.text_render_bind_group_layout,
entries: &[BindGroupEntry {
binding: 0,
resource: params_buffer.as_entire_binding(),
}],
label: Some("glyphon text render bind group"),
});
Self { Self {
params,
params_buffer,
vertex_buffer, vertex_buffer,
vertex_buffer_size, vertex_buffer_size,
index_buffer, index_buffer,
index_buffer_size, index_buffer_size,
vertices_to_render: 0, vertices_to_render: 0,
screen_resolution: Resolution {
width: 0,
height: 0,
},
pipeline, pipeline,
bind_group,
} }
} }
@ -72,15 +97,11 @@ impl TextRenderer {
cache: &mut SwashCache, cache: &mut SwashCache,
mut metadata_to_depth: impl FnMut(usize) -> f32, mut metadata_to_depth: impl FnMut(usize) -> f32,
) -> Result<(), PrepareError> { ) -> Result<(), PrepareError> {
self.screen_resolution = screen_resolution; if self.params.screen_resolution != screen_resolution {
self.params.screen_resolution = screen_resolution;
let atlas_current_resolution = { atlas.params.screen_resolution }; queue.write_buffer(&self.params_buffer, 0, unsafe {
if screen_resolution != atlas_current_resolution {
atlas.params.screen_resolution = screen_resolution;
queue.write_buffer(&atlas.params_buffer, 0, unsafe {
slice::from_raw_parts( slice::from_raw_parts(
&atlas.params as *const Params as *const u8, &self.params as *const Params as *const u8,
size_of::<Params>(), size_of::<Params>(),
) )
}); });
@ -394,15 +415,9 @@ impl TextRenderer {
return Ok(()); 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_pipeline(&self.pipeline);
pass.set_bind_group(0, &atlas.bind_group, &[]); pass.set_bind_group(0, &self.bind_group, &[]);
pass.set_bind_group(1, &atlas.bind_group, &[]);
pass.set_vertex_buffer(0, self.vertex_buffer.slice(..)); pass.set_vertex_buffer(0, self.vertex_buffer.slice(..));
pass.set_index_buffer(self.index_buffer.slice(..), IndexFormat::Uint32); pass.set_index_buffer(self.index_buffer.slice(..), IndexFormat::Uint32);
pass.draw_indexed(0..self.vertices_to_render, 0, 0..1); pass.draw_indexed(0..self.vertices_to_render, 0, 0..1);