Split color and mask atlases apart
This commit is contained in:
		
					parent
					
						
							
								6cf10bba8d
							
						
					
				
			
			
				commit
				
					
						3a0e965675
					
				
			
		
					 4 changed files with 254 additions and 122 deletions
				
			
		
							
								
								
									
										12
									
								
								src/lib.rs
									
										
									
									
									
								
							
							
						
						
									
										12
									
								
								src/lib.rs
									
										
									
									
									
								
							| 
						 | 
				
			
			@ -8,19 +8,24 @@ mod text_render;
 | 
			
		|||
pub use error::{PrepareError, RenderError};
 | 
			
		||||
use recently_used::RecentlyUsedMap;
 | 
			
		||||
pub use text_atlas::TextAtlas;
 | 
			
		||||
use text_render::ContentType;
 | 
			
		||||
pub use text_render::TextRenderer;
 | 
			
		||||
 | 
			
		||||
pub use cosmic_text;
 | 
			
		||||
 | 
			
		||||
pub(crate) enum GpuCache {
 | 
			
		||||
    InAtlas { x: u16, y: u16 },
 | 
			
		||||
pub(crate) enum GpuCacheStatus {
 | 
			
		||||
    InAtlas {
 | 
			
		||||
        x: u16,
 | 
			
		||||
        y: u16,
 | 
			
		||||
        content_type: ContentType,
 | 
			
		||||
    },
 | 
			
		||||
    SkipRasterization,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
pub(crate) struct GlyphDetails {
 | 
			
		||||
    width: u16,
 | 
			
		||||
    height: u16,
 | 
			
		||||
    gpu_cache: GpuCache,
 | 
			
		||||
    gpu_cache: GpuCacheStatus,
 | 
			
		||||
    atlas_id: Option<AllocId>,
 | 
			
		||||
    top: i16,
 | 
			
		||||
    left: i16,
 | 
			
		||||
| 
						 | 
				
			
			@ -33,6 +38,7 @@ pub(crate) struct GlyphToRender {
 | 
			
		|||
    dim: [u16; 2],
 | 
			
		||||
    uv: [u16; 2],
 | 
			
		||||
    color: u32,
 | 
			
		||||
    content_type: u32,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// The screen resolution to use when rendering text.
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -4,12 +4,14 @@ struct VertexInput {
 | 
			
		|||
    @location(1) dim: u32,
 | 
			
		||||
    @location(2) uv: u32,
 | 
			
		||||
    @location(3) color: u32,
 | 
			
		||||
    @location(4) content_type: u32,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
struct VertexOutput {
 | 
			
		||||
    @builtin(position) position: vec4<f32>,
 | 
			
		||||
    @location(0) color: vec4<f32>,
 | 
			
		||||
    @location(1) uv: vec2<f32>,
 | 
			
		||||
    @location(2) content_type: u32,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
struct Params {
 | 
			
		||||
| 
						 | 
				
			
			@ -21,9 +23,12 @@ struct Params {
 | 
			
		|||
var<uniform> params: Params;
 | 
			
		||||
 | 
			
		||||
@group(0) @binding(1)
 | 
			
		||||
var atlas_texture: texture_2d<f32>;
 | 
			
		||||
var color_atlas_texture: texture_2d<f32>;
 | 
			
		||||
 | 
			
		||||
@group(0) @binding(2)
 | 
			
		||||
var mask_atlas_texture: texture_2d<f32>;
 | 
			
		||||
 | 
			
		||||
@group(0) @binding(3)
 | 
			
		||||
var atlas_sampler: sampler;
 | 
			
		||||
 | 
			
		||||
@vertex
 | 
			
		||||
| 
						 | 
				
			
			@ -70,12 +75,37 @@ fn vs_main(in_vert: VertexInput) -> VertexOutput {
 | 
			
		|||
        f32((color & 0xff000000u) >> 24u),
 | 
			
		||||
    ) / 255.0;
 | 
			
		||||
 | 
			
		||||
    vert_output.uv = vec2<f32>(uv) / vec2<f32>(textureDimensions(atlas_texture).xy);
 | 
			
		||||
    var dim = vec2<u32>(0);
 | 
			
		||||
    switch in_vert.content_type {
 | 
			
		||||
        case 0u: {
 | 
			
		||||
            dim = textureDimensions(color_atlas_texture);
 | 
			
		||||
            break;
 | 
			
		||||
        }
 | 
			
		||||
        case 1u: {
 | 
			
		||||
            dim = textureDimensions(mask_atlas_texture);
 | 
			
		||||
            break;
 | 
			
		||||
        }
 | 
			
		||||
        default: {}
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    vert_output.content_type = in_vert.content_type;
 | 
			
		||||
 | 
			
		||||
    vert_output.uv = vec2<f32>(uv) / vec2<f32>(dim);
 | 
			
		||||
 | 
			
		||||
    return vert_output;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@fragment
 | 
			
		||||
fn fs_main(in_frag: VertexOutput) -> @location(0) vec4<f32> {
 | 
			
		||||
    return in_frag.color * textureSample(atlas_texture, atlas_sampler, in_frag.uv);
 | 
			
		||||
    switch in_frag.content_type {
 | 
			
		||||
        case 0u: {
 | 
			
		||||
            return textureSampleLevel(color_atlas_texture, atlas_sampler, in_frag.uv, 0.0);
 | 
			
		||||
        }
 | 
			
		||||
        case 1u: {
 | 
			
		||||
            return in_frag.color * textureSampleLevel(mask_atlas_texture, atlas_sampler, in_frag.uv, 0.0);
 | 
			
		||||
        }
 | 
			
		||||
        default: {
 | 
			
		||||
            return vec4<f32>(0.0);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,37 +1,32 @@
 | 
			
		|||
use crate::{
 | 
			
		||||
    text_render::ContentType, GlyphDetails, GlyphToRender, Params, RecentlyUsedMap, Resolution,
 | 
			
		||||
};
 | 
			
		||||
use cosmic_text::CacheKey;
 | 
			
		||||
use etagere::{size2, BucketedAtlasAllocator};
 | 
			
		||||
use etagere::{size2, Allocation, BucketedAtlasAllocator};
 | 
			
		||||
use std::{borrow::Cow, mem::size_of, num::NonZeroU64, sync::Arc};
 | 
			
		||||
use wgpu::{
 | 
			
		||||
    BindGroup, BindGroupEntry, BindGroupLayoutEntry, BindingResource, BindingType, BlendState,
 | 
			
		||||
    Buffer, BufferBindingType, BufferDescriptor, BufferUsages, ColorTargetState, ColorWrites,
 | 
			
		||||
    Device, Extent3d, FilterMode, FragmentState, MultisampleState, PipelineLayoutDescriptor,
 | 
			
		||||
    PrimitiveState, Queue, RenderPipeline, RenderPipelineDescriptor, SamplerBindingType,
 | 
			
		||||
    SamplerDescriptor, ShaderModuleDescriptor, ShaderSource, ShaderStages, Texture,
 | 
			
		||||
    TextureDescriptor, TextureDimension, TextureFormat, TextureSampleType, TextureUsages,
 | 
			
		||||
    TextureViewDescriptor, TextureViewDimension, VertexFormat, VertexState,
 | 
			
		||||
    BindGroup, BindGroupDescriptor, BindGroupEntry, BindGroupLayoutEntry, BindingResource,
 | 
			
		||||
    BindingType, BlendState, Buffer, BufferBindingType, BufferDescriptor, BufferUsages,
 | 
			
		||||
    ColorTargetState, ColorWrites, Device, Extent3d, FilterMode, FragmentState, MultisampleState,
 | 
			
		||||
    PipelineLayoutDescriptor, PrimitiveState, Queue, RenderPipeline, RenderPipelineDescriptor,
 | 
			
		||||
    SamplerBindingType, SamplerDescriptor, ShaderModuleDescriptor, ShaderSource, ShaderStages,
 | 
			
		||||
    Texture, TextureDescriptor, TextureDimension, TextureFormat, TextureSampleType, TextureUsages,
 | 
			
		||||
    TextureView, TextureViewDescriptor, TextureViewDimension, VertexFormat, VertexState,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
use crate::{GlyphDetails, GlyphToRender, Params, RecentlyUsedMap, Resolution};
 | 
			
		||||
 | 
			
		||||
pub(crate) const NUM_ATLAS_CHANNELS: usize = 4usize;
 | 
			
		||||
 | 
			
		||||
/// An atlas containing a cache of rasterized glyphs that can be rendered.
 | 
			
		||||
pub struct TextAtlas {
 | 
			
		||||
    pub(crate) texture_pending: Vec<u8>,
 | 
			
		||||
    pub(crate) texture: Texture,
 | 
			
		||||
    pub(crate) packer: BucketedAtlasAllocator,
 | 
			
		||||
    pub(crate) width: u32,
 | 
			
		||||
    pub(crate) height: u32,
 | 
			
		||||
    pub(crate) glyph_cache: RecentlyUsedMap<CacheKey, GlyphDetails>,
 | 
			
		||||
    pub(crate) params: Params,
 | 
			
		||||
    pub(crate) params_buffer: Buffer,
 | 
			
		||||
    pub(crate) pipeline: Arc<RenderPipeline>,
 | 
			
		||||
    pub(crate) bind_group: Arc<BindGroup>,
 | 
			
		||||
pub(crate) struct InnerAtlas {
 | 
			
		||||
    pub texture_pending: Vec<u8>,
 | 
			
		||||
    pub texture: Texture,
 | 
			
		||||
    pub texture_view: TextureView,
 | 
			
		||||
    pub packer: BucketedAtlasAllocator,
 | 
			
		||||
    pub width: u32,
 | 
			
		||||
    pub height: u32,
 | 
			
		||||
    pub glyph_cache: RecentlyUsedMap<CacheKey, GlyphDetails>,
 | 
			
		||||
    pub num_atlas_channels: usize,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl TextAtlas {
 | 
			
		||||
    /// Creates a new `TextAtlas`.
 | 
			
		||||
    pub fn new(device: &Device, _queue: &Queue, format: TextureFormat) -> Self {
 | 
			
		||||
impl InnerAtlas {
 | 
			
		||||
    fn new(device: &Device, _queue: &Queue, num_atlas_channels: usize) -> Self {
 | 
			
		||||
        let max_texture_dimension_2d = device.limits().max_texture_dimension_2d;
 | 
			
		||||
        let width = max_texture_dimension_2d;
 | 
			
		||||
        let height = max_texture_dimension_2d;
 | 
			
		||||
| 
						 | 
				
			
			@ -39,7 +34,7 @@ impl TextAtlas {
 | 
			
		|||
        let packer = BucketedAtlasAllocator::new(size2(width as i32, height as i32));
 | 
			
		||||
 | 
			
		||||
        // Create a texture to use for our atlas
 | 
			
		||||
        let texture_pending = vec![0; (width * height) as usize * NUM_ATLAS_CHANNELS];
 | 
			
		||||
        let texture_pending = vec![0; (width * height) as usize * num_atlas_channels];
 | 
			
		||||
        let texture = device.create_texture(&TextureDescriptor {
 | 
			
		||||
            label: Some("glyphon atlas"),
 | 
			
		||||
            size: Extent3d {
 | 
			
		||||
| 
						 | 
				
			
			@ -50,10 +45,61 @@ impl TextAtlas {
 | 
			
		|||
            mip_level_count: 1,
 | 
			
		||||
            sample_count: 1,
 | 
			
		||||
            dimension: TextureDimension::D2,
 | 
			
		||||
            format: TextureFormat::Rgba8Unorm,
 | 
			
		||||
            format: match num_atlas_channels {
 | 
			
		||||
                1 => TextureFormat::R8Unorm,
 | 
			
		||||
                4 => TextureFormat::Rgba8Unorm,
 | 
			
		||||
                _ => panic!("unexpected number of channels"),
 | 
			
		||||
            },
 | 
			
		||||
            usage: TextureUsages::TEXTURE_BINDING | TextureUsages::COPY_DST,
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        let texture_view = texture.create_view(&TextureViewDescriptor::default());
 | 
			
		||||
 | 
			
		||||
        let glyph_cache = RecentlyUsedMap::new();
 | 
			
		||||
 | 
			
		||||
        Self {
 | 
			
		||||
            texture_pending,
 | 
			
		||||
            texture,
 | 
			
		||||
            texture_view,
 | 
			
		||||
            packer,
 | 
			
		||||
            width,
 | 
			
		||||
            height,
 | 
			
		||||
            glyph_cache,
 | 
			
		||||
            num_atlas_channels,
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    pub(crate) fn try_allocate(&mut self, width: usize, height: usize) -> Option<Allocation> {
 | 
			
		||||
        let size = size2(width as i32, height as i32);
 | 
			
		||||
 | 
			
		||||
        loop {
 | 
			
		||||
            let allocation = self.packer.allocate(size);
 | 
			
		||||
            if allocation.is_some() {
 | 
			
		||||
                return allocation;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            // Try to free least recently used allocation
 | 
			
		||||
            let (key, value) = self.glyph_cache.pop()?;
 | 
			
		||||
            self.packer
 | 
			
		||||
                .deallocate(value.atlas_id.expect("cache corrupt"));
 | 
			
		||||
            self.glyph_cache.remove(&key);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// An atlas containing a cache of rasterized glyphs that can be rendered.
 | 
			
		||||
pub struct TextAtlas {
 | 
			
		||||
    pub(crate) params: Params,
 | 
			
		||||
    pub(crate) params_buffer: Buffer,
 | 
			
		||||
    pub(crate) pipeline: Arc<RenderPipeline>,
 | 
			
		||||
    pub(crate) bind_group: Arc<BindGroup>,
 | 
			
		||||
    pub(crate) color_atlas: InnerAtlas,
 | 
			
		||||
    pub(crate) mask_atlas: InnerAtlas,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl TextAtlas {
 | 
			
		||||
    /// Creates a new `TextAtlas`.
 | 
			
		||||
    pub fn new(device: &Device, queue: &Queue, format: TextureFormat) -> Self {
 | 
			
		||||
        let sampler = device.create_sampler(&SamplerDescriptor {
 | 
			
		||||
            label: Some("glyphon sampler"),
 | 
			
		||||
            min_filter: FilterMode::Nearest,
 | 
			
		||||
| 
						 | 
				
			
			@ -64,8 +110,6 @@ impl TextAtlas {
 | 
			
		|||
            ..Default::default()
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        let glyph_cache = RecentlyUsedMap::new();
 | 
			
		||||
 | 
			
		||||
        // Create a render pipeline to use for rendering later
 | 
			
		||||
        let shader = device.create_shader_module(ShaderModuleDescriptor {
 | 
			
		||||
            label: Some("glyphon shader"),
 | 
			
		||||
| 
						 | 
				
			
			@ -96,6 +140,11 @@ impl TextAtlas {
 | 
			
		|||
                    offset: size_of::<u32>() as u64 * 4,
 | 
			
		||||
                    shader_location: 3,
 | 
			
		||||
                },
 | 
			
		||||
                wgpu::VertexAttribute {
 | 
			
		||||
                    format: VertexFormat::Uint32,
 | 
			
		||||
                    offset: size_of::<u32>() as u64 * 5,
 | 
			
		||||
                    shader_location: 4,
 | 
			
		||||
                }
 | 
			
		||||
            ],
 | 
			
		||||
        }];
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -123,6 +172,16 @@ impl TextAtlas {
 | 
			
		|||
                },
 | 
			
		||||
                BindGroupLayoutEntry {
 | 
			
		||||
                    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,
 | 
			
		||||
                    ty: BindingType::Sampler(SamplerBindingType::Filtering),
 | 
			
		||||
                    count: None,
 | 
			
		||||
| 
						 | 
				
			
			@ -146,7 +205,10 @@ impl TextAtlas {
 | 
			
		|||
            mapped_at_creation: false,
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        let bind_group = Arc::new(device.create_bind_group(&wgpu::BindGroupDescriptor {
 | 
			
		||||
        let color_atlas = InnerAtlas::new(device, queue, 4);
 | 
			
		||||
        let mask_atlas = InnerAtlas::new(device, queue, 1);
 | 
			
		||||
 | 
			
		||||
        let bind_group = Arc::new(device.create_bind_group(&BindGroupDescriptor {
 | 
			
		||||
            layout: &bind_group_layout,
 | 
			
		||||
            entries: &[
 | 
			
		||||
                BindGroupEntry {
 | 
			
		||||
| 
						 | 
				
			
			@ -155,10 +217,14 @@ impl TextAtlas {
 | 
			
		|||
                },
 | 
			
		||||
                BindGroupEntry {
 | 
			
		||||
                    binding: 1,
 | 
			
		||||
                    resource: BindingResource::TextureView(&texture_view),
 | 
			
		||||
                    resource: BindingResource::TextureView(&color_atlas.texture_view),
 | 
			
		||||
                },
 | 
			
		||||
                BindGroupEntry {
 | 
			
		||||
                    binding: 2,
 | 
			
		||||
                    resource: BindingResource::TextureView(&mask_atlas.texture_view),
 | 
			
		||||
                },
 | 
			
		||||
                BindGroupEntry {
 | 
			
		||||
                    binding: 3,
 | 
			
		||||
                    resource: BindingResource::Sampler(&sampler),
 | 
			
		||||
                },
 | 
			
		||||
            ],
 | 
			
		||||
| 
						 | 
				
			
			@ -199,16 +265,38 @@ impl TextAtlas {
 | 
			
		|||
        }));
 | 
			
		||||
 | 
			
		||||
        Self {
 | 
			
		||||
            texture_pending,
 | 
			
		||||
            texture,
 | 
			
		||||
            packer,
 | 
			
		||||
            width,
 | 
			
		||||
            height,
 | 
			
		||||
            glyph_cache,
 | 
			
		||||
            params,
 | 
			
		||||
            params_buffer,
 | 
			
		||||
            pipeline,
 | 
			
		||||
            bind_group,
 | 
			
		||||
            color_atlas,
 | 
			
		||||
            mask_atlas,
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    pub(crate) fn contains_cached_glyph(&self, glyph: &CacheKey) -> bool {
 | 
			
		||||
        self.mask_atlas.glyph_cache.contains_key(glyph)
 | 
			
		||||
            || self.color_atlas.glyph_cache.contains_key(glyph)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    pub(crate) fn glyph(&self, glyph: &CacheKey) -> Option<&GlyphDetails> {
 | 
			
		||||
        self.mask_atlas
 | 
			
		||||
            .glyph_cache
 | 
			
		||||
            .get(glyph)
 | 
			
		||||
            .or_else(|| self.color_atlas.glyph_cache.get(glyph))
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    pub(crate) fn inner_for_content(&self, content_type: ContentType) -> &InnerAtlas {
 | 
			
		||||
        match content_type {
 | 
			
		||||
            ContentType::Color => &self.color_atlas,
 | 
			
		||||
            ContentType::Mask => &self.mask_atlas,
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    pub(crate) fn inner_for_content_mut(&mut self, content_type: ContentType) -> &mut InnerAtlas {
 | 
			
		||||
        match content_type {
 | 
			
		||||
            ContentType::Color => &mut self.color_atlas,
 | 
			
		||||
            ContentType::Mask => &mut self.mask_atlas,
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,17 +1,14 @@
 | 
			
		|||
use crate::{
 | 
			
		||||
    GlyphDetails, GlyphToRender, GpuCacheStatus, Params, PrepareError, RenderError, Resolution,
 | 
			
		||||
    TextAtlas, TextOverflow,
 | 
			
		||||
};
 | 
			
		||||
use cosmic_text::{CacheKey, Color, SwashCache, SwashContent};
 | 
			
		||||
use etagere::{size2, Allocation};
 | 
			
		||||
 | 
			
		||||
use std::{collections::HashSet, iter, mem::size_of, num::NonZeroU32, slice};
 | 
			
		||||
use wgpu::{
 | 
			
		||||
    Buffer, BufferDescriptor, BufferUsages, Device, Extent3d, ImageCopyTexture, ImageDataLayout,
 | 
			
		||||
    IndexFormat, Origin3d, Queue, RenderPass, TextureAspect, COPY_BUFFER_ALIGNMENT,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
use crate::{
 | 
			
		||||
    text_atlas::NUM_ATLAS_CHANNELS, GlyphDetails, GlyphToRender, GpuCache, Params, PrepareError,
 | 
			
		||||
    RenderError, Resolution, TextAtlas, TextOverflow,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
/// A text renderer that uses cached glyphs to render text into an existing render pass.
 | 
			
		||||
pub struct TextRenderer {
 | 
			
		||||
    vertex_buffer: Buffer,
 | 
			
		||||
| 
						 | 
				
			
			@ -87,7 +84,16 @@ impl TextRenderer {
 | 
			
		|||
            y_min: usize,
 | 
			
		||||
            y_max: usize,
 | 
			
		||||
        }
 | 
			
		||||
        let mut upload_bounds = None::<UploadBounds>;
 | 
			
		||||
 | 
			
		||||
        struct BoundsPerAtlas {
 | 
			
		||||
            color: Option<UploadBounds>,
 | 
			
		||||
            mask: Option<UploadBounds>,
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        let mut upload_bounds_per_atlas = BoundsPerAtlas {
 | 
			
		||||
            color: None,
 | 
			
		||||
            mask: None,
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        self.glyphs_in_use.clear();
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -98,37 +104,33 @@ impl TextRenderer {
 | 
			
		|||
                for glyph in run.glyphs.iter() {
 | 
			
		||||
                    self.glyphs_in_use.insert(glyph.cache_key);
 | 
			
		||||
 | 
			
		||||
                    let already_on_gpu = atlas.glyph_cache.contains_key(&glyph.cache_key);
 | 
			
		||||
                    let already_on_gpu = atlas.contains_cached_glyph(&glyph.cache_key);
 | 
			
		||||
 | 
			
		||||
                    if already_on_gpu {
 | 
			
		||||
                        continue;
 | 
			
		||||
                    }
 | 
			
		||||
 | 
			
		||||
                    let image = cache.get_image_uncached(glyph.cache_key).unwrap();
 | 
			
		||||
                    let mut bitmap = image.data;
 | 
			
		||||
 | 
			
		||||
                    match image.content {
 | 
			
		||||
                        SwashContent::Color => {}
 | 
			
		||||
                        SwashContent::Mask => {
 | 
			
		||||
                            // Technically only one channel is needed, but store the mask only in the alpha channel for now.
 | 
			
		||||
                            bitmap = bitmap
 | 
			
		||||
                                .iter()
 | 
			
		||||
                                .flat_map(|color| iter::repeat(255).take(NUM_ATLAS_CHANNELS - 1).chain(iter::once(*color)))
 | 
			
		||||
                                .collect();
 | 
			
		||||
                        }
 | 
			
		||||
                    let content_type = match image.content {
 | 
			
		||||
                        SwashContent::Color => ContentType::Color,
 | 
			
		||||
                        SwashContent::Mask => ContentType::Mask,
 | 
			
		||||
                        SwashContent::SubpixelMask => {
 | 
			
		||||
                            // Not implemented yet, but don't panic if this happens.
 | 
			
		||||
                            ContentType::Mask
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
                    };
 | 
			
		||||
 | 
			
		||||
                    let width = image.placement.width as usize;
 | 
			
		||||
                    let height = image.placement.height as usize;
 | 
			
		||||
 | 
			
		||||
                    let should_rasterize = width > 0 && height > 0;
 | 
			
		||||
 | 
			
		||||
                    let (gpu_cache, atlas_id) = if should_rasterize {
 | 
			
		||||
                    let (gpu_cache, atlas_id, inner) = if should_rasterize {
 | 
			
		||||
                        let inner = atlas.inner_for_content_mut(content_type);
 | 
			
		||||
 | 
			
		||||
                        // Find a position in the packer
 | 
			
		||||
                        let allocation = match try_allocate(atlas, width, height) {
 | 
			
		||||
                        let allocation = match inner.try_allocate(width, height) {
 | 
			
		||||
                            Some(a) => a,
 | 
			
		||||
                            None => return Err(PrepareError::AtlasFull),
 | 
			
		||||
                        };
 | 
			
		||||
| 
						 | 
				
			
			@ -138,14 +140,20 @@ impl TextRenderer {
 | 
			
		|||
                        for row in 0..height {
 | 
			
		||||
                            let y_offset = atlas_min.y as usize;
 | 
			
		||||
                            let x_offset =
 | 
			
		||||
                                (y_offset + row) * atlas.width as usize + atlas_min.x as usize;
 | 
			
		||||
                            let bitmap_row = &bitmap[row * width * NUM_ATLAS_CHANNELS
 | 
			
		||||
                                ..(row + 1) * width * NUM_ATLAS_CHANNELS];
 | 
			
		||||
                            atlas.texture_pending[x_offset * NUM_ATLAS_CHANNELS
 | 
			
		||||
                                ..(x_offset + width) * NUM_ATLAS_CHANNELS]
 | 
			
		||||
                                (y_offset + row) * inner.width as usize + atlas_min.x as usize;
 | 
			
		||||
                            let num_atlas_channels = inner.num_atlas_channels;
 | 
			
		||||
                            let bitmap_row = &image.data[row * width * num_atlas_channels
 | 
			
		||||
                                ..(row + 1) * width * num_atlas_channels];
 | 
			
		||||
                            inner.texture_pending[x_offset * num_atlas_channels
 | 
			
		||||
                                ..(x_offset + width) * num_atlas_channels]
 | 
			
		||||
                                .copy_from_slice(bitmap_row);
 | 
			
		||||
                        }
 | 
			
		||||
 | 
			
		||||
                        let upload_bounds = match content_type {
 | 
			
		||||
                            ContentType::Color => &mut upload_bounds_per_atlas.color,
 | 
			
		||||
                            ContentType::Mask => &mut upload_bounds_per_atlas.mask,
 | 
			
		||||
                        };
 | 
			
		||||
 | 
			
		||||
                        match upload_bounds.as_mut() {
 | 
			
		||||
                            Some(ub) => {
 | 
			
		||||
                                ub.x_min = ub.x_min.min(atlas_min.x as usize);
 | 
			
		||||
| 
						 | 
				
			
			@ -154,7 +162,7 @@ impl TextRenderer {
 | 
			
		|||
                                ub.y_max = ub.y_max.max(atlas_max.y as usize);
 | 
			
		||||
                            }
 | 
			
		||||
                            None => {
 | 
			
		||||
                                upload_bounds = Some(UploadBounds {
 | 
			
		||||
                                *upload_bounds = Some(UploadBounds {
 | 
			
		||||
                                    x_min: atlas_min.x as usize,
 | 
			
		||||
                                    x_max: atlas_max.x as usize,
 | 
			
		||||
                                    y_min: atlas_min.y as usize,
 | 
			
		||||
| 
						 | 
				
			
			@ -164,18 +172,21 @@ impl TextRenderer {
 | 
			
		|||
                        }
 | 
			
		||||
 | 
			
		||||
                        (
 | 
			
		||||
                            GpuCache::InAtlas {
 | 
			
		||||
                            GpuCacheStatus::InAtlas {
 | 
			
		||||
                                x: atlas_min.x as u16,
 | 
			
		||||
                                y: atlas_min.y as u16,
 | 
			
		||||
                                content_type,
 | 
			
		||||
                            },
 | 
			
		||||
                            Some(allocation.id),
 | 
			
		||||
                            inner,
 | 
			
		||||
                        )
 | 
			
		||||
                    } else {
 | 
			
		||||
                        (GpuCache::SkipRasterization, None)
 | 
			
		||||
                        let inner = &mut atlas.color_atlas;
 | 
			
		||||
                        (GpuCacheStatus::SkipRasterization, None, inner)
 | 
			
		||||
                    };
 | 
			
		||||
 | 
			
		||||
                    if !atlas.glyph_cache.contains_key(&glyph.cache_key) {
 | 
			
		||||
                        atlas.glyph_cache.insert(
 | 
			
		||||
                    if !inner.glyph_cache.contains_key(&glyph.cache_key) {
 | 
			
		||||
                        inner.glyph_cache.insert(
 | 
			
		||||
                            glyph.cache_key,
 | 
			
		||||
                            GlyphDetails {
 | 
			
		||||
                                width: width as u16,
 | 
			
		||||
| 
						 | 
				
			
			@ -191,31 +202,38 @@ impl TextRenderer {
 | 
			
		|||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if let Some(ub) = upload_bounds {
 | 
			
		||||
            queue.write_texture(
 | 
			
		||||
                ImageCopyTexture {
 | 
			
		||||
                    texture: &atlas.texture,
 | 
			
		||||
                    mip_level: 0,
 | 
			
		||||
                    origin: Origin3d {
 | 
			
		||||
                        x: ub.x_min as u32,
 | 
			
		||||
                        y: ub.y_min as u32,
 | 
			
		||||
                        z: 0,
 | 
			
		||||
        for (content_type, bounds) in [
 | 
			
		||||
            (ContentType::Color, upload_bounds_per_atlas.color),
 | 
			
		||||
            (ContentType::Mask, upload_bounds_per_atlas.mask),
 | 
			
		||||
        ] {
 | 
			
		||||
            if let Some(ub) = bounds {
 | 
			
		||||
                let inner = atlas.inner_for_content(content_type);
 | 
			
		||||
                let num_atlas_channels = inner.num_atlas_channels;
 | 
			
		||||
                queue.write_texture(
 | 
			
		||||
                    ImageCopyTexture {
 | 
			
		||||
                        texture: &inner.texture,
 | 
			
		||||
                        mip_level: 0,
 | 
			
		||||
                        origin: Origin3d {
 | 
			
		||||
                            x: ub.x_min as u32,
 | 
			
		||||
                            y: ub.y_min as u32,
 | 
			
		||||
                            z: 0,
 | 
			
		||||
                        },
 | 
			
		||||
                        aspect: TextureAspect::All,
 | 
			
		||||
                    },
 | 
			
		||||
                    aspect: TextureAspect::All,
 | 
			
		||||
                },
 | 
			
		||||
                &atlas.texture_pending
 | 
			
		||||
                    [ub.y_min * atlas.width as usize + ub.x_min * NUM_ATLAS_CHANNELS..],
 | 
			
		||||
                ImageDataLayout {
 | 
			
		||||
                    offset: 0,
 | 
			
		||||
                    bytes_per_row: NonZeroU32::new(atlas.width * NUM_ATLAS_CHANNELS as u32),
 | 
			
		||||
                    rows_per_image: NonZeroU32::new(atlas.height),
 | 
			
		||||
                },
 | 
			
		||||
                Extent3d {
 | 
			
		||||
                    width: (ub.x_max - ub.x_min) as u32,
 | 
			
		||||
                    height: (ub.y_max - ub.y_min) as u32,
 | 
			
		||||
                    depth_or_array_layers: 1,
 | 
			
		||||
                },
 | 
			
		||||
            );
 | 
			
		||||
                    &inner.texture_pending
 | 
			
		||||
                        [ub.y_min * inner.width as usize + ub.x_min * num_atlas_channels..],
 | 
			
		||||
                    ImageDataLayout {
 | 
			
		||||
                        offset: 0,
 | 
			
		||||
                        bytes_per_row: NonZeroU32::new(inner.width * num_atlas_channels as u32),
 | 
			
		||||
                        rows_per_image: NonZeroU32::new(inner.height),
 | 
			
		||||
                    },
 | 
			
		||||
                    Extent3d {
 | 
			
		||||
                        width: (ub.x_max - ub.x_min) as u32,
 | 
			
		||||
                        height: (ub.y_max - ub.y_min) as u32,
 | 
			
		||||
                        depth_or_array_layers: 1,
 | 
			
		||||
                    },
 | 
			
		||||
                );
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        let mut glyph_vertices: Vec<GlyphToRender> = Vec::new();
 | 
			
		||||
| 
						 | 
				
			
			@ -239,14 +257,14 @@ impl TextRenderer {
 | 
			
		|||
                        None => default_color,
 | 
			
		||||
                    };
 | 
			
		||||
 | 
			
		||||
                    let details = atlas.glyph_cache.get(&glyph.cache_key).unwrap();
 | 
			
		||||
                    let details = atlas.glyph(&glyph.cache_key).unwrap();
 | 
			
		||||
 | 
			
		||||
                    let mut x = glyph.x_int + details.left as i32;
 | 
			
		||||
                    let mut y = line_y + glyph.y_int - details.top as i32;
 | 
			
		||||
 | 
			
		||||
                    let (mut atlas_x, mut atlas_y) = match details.gpu_cache {
 | 
			
		||||
                        GpuCache::InAtlas { x, y } => (x, y),
 | 
			
		||||
                        GpuCache::SkipRasterization => continue,
 | 
			
		||||
                    let (mut atlas_x, mut atlas_y, content_type) = match details.gpu_cache {
 | 
			
		||||
                        GpuCacheStatus::InAtlas { x, y, content_type } => (x, y, content_type),
 | 
			
		||||
                        GpuCacheStatus::SkipRasterization => continue,
 | 
			
		||||
                    };
 | 
			
		||||
 | 
			
		||||
                    let mut width = details.width as i32;
 | 
			
		||||
| 
						 | 
				
			
			@ -303,6 +321,7 @@ impl TextRenderer {
 | 
			
		|||
                            dim: [width as u16, height as u16],
 | 
			
		||||
                            uv: [atlas_x, atlas_y],
 | 
			
		||||
                            color: color.0,
 | 
			
		||||
                            content_type: content_type as u32,
 | 
			
		||||
                        })
 | 
			
		||||
                        .take(4),
 | 
			
		||||
                    );
 | 
			
		||||
| 
						 | 
				
			
			@ -394,7 +413,7 @@ impl TextRenderer {
 | 
			
		|||
        {
 | 
			
		||||
            // Validate that glyphs haven't been evicted from cache since `prepare`
 | 
			
		||||
            for glyph in self.glyphs_in_use.iter() {
 | 
			
		||||
                if !atlas.glyph_cache.contains_key(glyph) {
 | 
			
		||||
                if !atlas.contains_cached_glyph(glyph) {
 | 
			
		||||
                    return Err(RenderError::RemovedFromAtlas);
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
| 
						 | 
				
			
			@ -415,22 +434,11 @@ impl TextRenderer {
 | 
			
		|||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
fn try_allocate(atlas: &mut TextAtlas, width: usize, height: usize) -> Option<Allocation> {
 | 
			
		||||
    let size = size2(width as i32, height as i32);
 | 
			
		||||
 | 
			
		||||
    loop {
 | 
			
		||||
        let allocation = atlas.packer.allocate(size);
 | 
			
		||||
        if allocation.is_some() {
 | 
			
		||||
            return allocation;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // Try to free least recently used allocation
 | 
			
		||||
        let (key, value) = atlas.glyph_cache.pop()?;
 | 
			
		||||
        atlas
 | 
			
		||||
            .packer
 | 
			
		||||
            .deallocate(value.atlas_id.expect("cache corrupt"));
 | 
			
		||||
        atlas.glyph_cache.remove(&key);
 | 
			
		||||
    }
 | 
			
		||||
#[repr(u32)]
 | 
			
		||||
#[derive(Clone, Copy, Eq, PartialEq)]
 | 
			
		||||
pub(crate) enum ContentType {
 | 
			
		||||
    Color = 0,
 | 
			
		||||
    Mask = 1,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
fn next_copy_buffer_size(size: u64) -> u64 {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue