diff --git a/.envrc b/.envrc deleted file mode 100644 index a5dbbcb..0000000 --- a/.envrc +++ /dev/null @@ -1 +0,0 @@ -use flake . diff --git a/Cargo.toml b/Cargo.toml index ba8558c..e210e48 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -2,14 +2,14 @@ name = "cryoglyph" description = "Fast, simple 2D text rendering for wgpu" version = "0.1.0" -edition = "2024" +edition = "2021" repository = "https://github.com/iced-rs/cryoglyph" license = "MIT OR Apache-2.0 OR Zlib" [dependencies] wgpu = { version = "24", default-features = false, features = ["wgsl"] } etagere = "0.2.10" -cosmic-text = "0.14" +cosmic-text = "0.12" lru = { version = "0.12.1", default-features = false } rustc-hash = "2.0" diff --git a/benches/prepare.rs b/benches/prepare.rs index 0e5669d..ce30b1e 100644 --- a/benches/prepare.rs +++ b/benches/prepare.rs @@ -1,5 +1,5 @@ use cosmic_text::{Attrs, Buffer, Color, Family, FontSystem, Metrics, Shaping, SwashCache}; -use criterion::{Criterion, criterion_group, criterion_main}; +use criterion::{criterion_group, criterion_main, Criterion}; use cryoglyph::{ Cache, ColorMode, Resolution, TextArea, TextAtlas, TextBounds, TextRenderer, Viewport, Weight, }; diff --git a/examples/hello-world.rs b/examples/hello-world.rs index 88afb29..1a13dd1 100644 --- a/examples/hello-world.rs +++ b/examples/hello-world.rs @@ -1,7 +1,6 @@ use cryoglyph::{ - cosmic_text::LetterSpacing, Attrs, AttrsOwned, Buffer, Cache, Color, Family, FontSystem, - Metrics, Resolution, Shaping, SwashCache, TextArea, TextAtlas, TextBounds, TextRenderer, - Viewport, + Attrs, Buffer, Cache, Color, Family, FontSystem, Metrics, Resolution, Shaping, SwashCache, + TextArea, TextAtlas, TextBounds, TextRenderer, Viewport, }; use std::sync::Arc; use wgpu::{ @@ -88,7 +87,7 @@ impl WindowState { Some(physical_width), Some(physical_height), ); - text_buffer.set_text(&mut font_system, "Hello world! 👋\nThis is rendered with 🦅 glyphon 🦁\nThe text below should be partially clipped.\na b c d e f g h i j k l m n o p q r s t u v w x y z", &Attrs::new().family(Family::SansSerif), Shaping::Advanced); + text_buffer.set_text(&mut font_system, "Hello world! 👋\nThis is rendered with 🦅 glyphon 🦁\nThe text below should be partially clipped.\na b c d e f g h i j k l m n o p q r s t u v w x y z", Attrs::new().family(Family::SansSerif), Shaping::Advanced); text_buffer.shape_until_scroll(&mut font_system, false); Self { @@ -170,21 +169,6 @@ impl winit::application::ApplicationHandler for Application { let mut encoder = device.create_command_encoder(&CommandEncoderDescriptor { label: None }); - let text_area = TextArea { - buffer: text_buffer, - left: 10.0, - top: 10.0, - scale: 3.5, - bounds: TextBounds { - left: 0, - top: 0, - right: 600, - bottom: 160, - }, - default_color: Color::rgb(255, 255, 255), - stroke_size: 5.5, - stroke_color: Color::rgb(50, 50, 255), - }; text_renderer .prepare( @@ -194,7 +178,19 @@ impl winit::application::ApplicationHandler for Application { font_system, atlas, viewport, - [text_area], + [TextArea { + buffer: text_buffer, + left: 10.0, + top: 10.0, + scale: 1.0, + bounds: TextBounds { + left: 0, + top: 0, + right: 600, + bottom: 160, + }, + default_color: Color::rgb(255, 255, 255), + }], swash_cache, ) .unwrap(); diff --git a/flake.lock b/flake.lock deleted file mode 100644 index 51620a2..0000000 --- a/flake.lock +++ /dev/null @@ -1,149 +0,0 @@ -{ - "nodes": { - "fenix": { - "inputs": { - "nixpkgs": "nixpkgs", - "rust-analyzer-src": "rust-analyzer-src" - }, - "locked": { - "lastModified": 1746081462, - "narHash": "sha256-WmJBaktb33WwqNn5BwdJghAoiBnvnPhgHSBksTrF5K8=", - "owner": "nix-community", - "repo": "fenix", - "rev": "e3be528e4f03538852ba49b413ec4ac843edeb60", - "type": "github" - }, - "original": { - "owner": "nix-community", - "repo": "fenix", - "type": "github" - } - }, - "flake-utils": { - "inputs": { - "systems": "systems" - }, - "locked": { - "lastModified": 1731533236, - "narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=", - "owner": "numtide", - "repo": "flake-utils", - "rev": "11707dc2f618dd54ca8739b309ec4fc024de578b", - "type": "github" - }, - "original": { - "owner": "numtide", - "repo": "flake-utils", - "type": "github" - } - }, - "naersk": { - "inputs": { - "nixpkgs": "nixpkgs_2" - }, - "locked": { - "lastModified": 1745925850, - "narHash": "sha256-cyAAMal0aPrlb1NgzMxZqeN1mAJ2pJseDhm2m6Um8T0=", - "owner": "nix-community", - "repo": "naersk", - "rev": "38bc60bbc157ae266d4a0c96671c6c742ee17a5f", - "type": "github" - }, - "original": { - "owner": "nix-community", - "repo": "naersk", - "type": "github" - } - }, - "nixpkgs": { - "locked": { - "lastModified": 1745930157, - "narHash": "sha256-y3h3NLnzRSiUkYpnfvnS669zWZLoqqI6NprtLQ+5dck=", - "owner": "nixos", - "repo": "nixpkgs", - "rev": "46e634be05ce9dc6d4db8e664515ba10b78151ae", - "type": "github" - }, - "original": { - "owner": "nixos", - "ref": "nixos-unstable", - "repo": "nixpkgs", - "type": "github" - } - }, - "nixpkgs_2": { - "locked": { - "lastModified": 1746061036, - "narHash": "sha256-OxYwCGJf9VJ2KnUO+w/hVJVTjOgscdDg/lPv8Eus07Y=", - "owner": "NixOS", - "repo": "nixpkgs", - "rev": "3afd19146cac33ed242fc0fc87481c67c758a59e", - "type": "github" - }, - "original": { - "owner": "NixOS", - "ref": "nixpkgs-unstable", - "repo": "nixpkgs", - "type": "github" - } - }, - "nixpkgs_3": { - "locked": { - "lastModified": 1745930157, - "narHash": "sha256-y3h3NLnzRSiUkYpnfvnS669zWZLoqqI6NprtLQ+5dck=", - "owner": "nixos", - "repo": "nixpkgs", - "rev": "46e634be05ce9dc6d4db8e664515ba10b78151ae", - "type": "github" - }, - "original": { - "owner": "nixos", - "ref": "nixos-unstable", - "repo": "nixpkgs", - "type": "github" - } - }, - "root": { - "inputs": { - "fenix": "fenix", - "flake-utils": "flake-utils", - "naersk": "naersk", - "nixpkgs": "nixpkgs_3" - } - }, - "rust-analyzer-src": { - "flake": false, - "locked": { - "lastModified": 1746024678, - "narHash": "sha256-Q5J7+RoTPH4zPeu0Ka7iSXtXty228zKjS0Ed4R+ohpA=", - "owner": "rust-lang", - "repo": "rust-analyzer", - "rev": "5d66d45005fef79751294419ab9a9fa304dfdf5c", - "type": "github" - }, - "original": { - "owner": "rust-lang", - "ref": "nightly", - "repo": "rust-analyzer", - "type": "github" - } - }, - "systems": { - "locked": { - "lastModified": 1681028828, - "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", - "owner": "nix-systems", - "repo": "default", - "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", - "type": "github" - }, - "original": { - "owner": "nix-systems", - "repo": "default", - "type": "github" - } - } - }, - "root": "root", - "version": 7 -} diff --git a/flake.nix b/flake.nix deleted file mode 100644 index 2b2225b..0000000 --- a/flake.nix +++ /dev/null @@ -1,84 +0,0 @@ -{ - description = "A fast, simple 2D text renderer for wgpu"; - - inputs = { - nixpkgs.url = "github:nixos/nixpkgs/nixos-unstable"; - naersk.url = "github:nix-community/naersk"; - flake-utils.url = "github:numtide/flake-utils"; - fenix.url = "github:nix-community/fenix"; - }; - - outputs = inputs: with inputs; - flake-utils.lib.eachDefaultSystem - (system: - let - pkgs = import nixpkgs { - inherit system; - overlays = [fenix.overlays.default]; - # overlays = [cargo2nix.overlays.default]; - }; - naersk' = pkgs.callPackage naersk {}; - nbi = with pkgs; [ - # Rust tools - alejandra - (pkgs.fenix.stable.withComponents [ - "cargo" - "clippy" - "rust-src" - "rustc" - "rustfmt" - ]) - rust-analyzer - vulkan-loader - wayland - wayland-protocols - libxkbcommon - pkg-config - ]; - - bi = with pkgs; [ - gcc - stdenv - gnumake - gdb - cmake - makeWrapper - vulkan-headers - vulkan-loader - vulkan-tools - harfbuzz - libGL - cargo-flamegraph - fontconfig - just - cargo-watch - ]; - in rec - { - devShell = pkgs.mkShell.override { - stdenv = pkgs.stdenvAdapters.useMoldLinker pkgs.clangStdenv; - } { - nativeBuildInputs = nbi; - buildInputs = bi; - LD_LIBRARY_PATH = "$LD_LIBRARY_PATH:${ - with pkgs; - pkgs.lib.makeLibraryPath [ - pkgs.vulkan-loader - pkgs.wayland - pkgs.wayland-protocols - pkgs.libxkbcommon - ] - }"; - DATABASE_URL = "sqlite:///home/chris/.local/share/lumina/library-db.sqlite3"; - }; - defaultPackage = naersk'.buildPackage { - src = ./.; - }; - packages = { - default = naersk'.buildPackage { - src = ./.; - }; - }; - } - ); -} diff --git a/src/cache.rs b/src/cache.rs index 9a1e1dc..c567341 100644 --- a/src/cache.rs +++ b/src/cache.rs @@ -1,4 +1,5 @@ use crate::{GlyphToRender, Params}; + use wgpu::{ BindGroup, BindGroupDescriptor, BindGroupEntry, BindGroupLayout, BindGroupLayoutEntry, BindingResource, BindingType, BlendState, Buffer, BufferBindingType, ColorTargetState, @@ -13,7 +14,7 @@ use std::borrow::Cow; use std::mem; use std::num::NonZeroU64; use std::ops::Deref; -use std::sync::{Arc, Mutex}; +use std::sync::{Arc, RwLock}; #[derive(Debug, Clone)] pub struct Cache(Arc); @@ -26,12 +27,12 @@ struct Inner { atlas_layout: BindGroupLayout, uniforms_layout: BindGroupLayout, pipeline_layout: PipelineLayout, - cache: Mutex< + cache: RwLock< Vec<( TextureFormat, MultisampleState, Option, - RenderPipeline, + Arc, )>, >, } @@ -87,16 +88,6 @@ impl Cache { offset: mem::size_of::() as u64 * 6, shader_location: 5, }, - wgpu::VertexAttribute { - format: VertexFormat::Uint32, - offset: mem::size_of::() as u64 * 7, - shader_location: 6, - }, - wgpu::VertexAttribute { - format: VertexFormat::Float32, - offset: mem::size_of::() as u64 * 8, - shader_location: 7, - }, ], }; @@ -159,7 +150,7 @@ impl Cache { uniforms_layout, atlas_layout, pipeline_layout, - cache: Mutex::new(Vec::new()), + cache: RwLock::new(Vec::new()), })) } @@ -206,7 +197,7 @@ impl Cache { format: TextureFormat, multisample: MultisampleState, depth_stencil: Option, - ) -> RenderPipeline { + ) -> Arc { let Inner { cache, pipeline_layout, @@ -215,14 +206,14 @@ impl Cache { .. } = self.0.deref(); - let mut cache = cache.lock().expect("Write pipeline cache"); + let mut cache = cache.write().expect("Write pipeline cache"); cache .iter() .find(|(fmt, ms, ds, _)| fmt == &format && ms == &multisample && ds == &depth_stencil) - .map(|(_, _, _, p)| p.clone()) + .map(|(_, _, _, p)| Arc::clone(p)) .unwrap_or_else(|| { - let pipeline = device.create_render_pipeline(&RenderPipelineDescriptor { + let pipeline = Arc::new(device.create_render_pipeline(&RenderPipelineDescriptor { label: Some("glyphon pipeline"), layout: Some(pipeline_layout), vertex: VertexState { @@ -249,7 +240,7 @@ impl Cache { multisample, multiview: None, cache: None, - }); + })); cache.push((format, multisample, depth_stencil, pipeline.clone())); diff --git a/src/lib.rs b/src/lib.rs index 31f5ef7..3b31120 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -57,8 +57,6 @@ pub(crate) struct GlyphToRender { color: u32, content_type_with_srgb: [u16; 2], depth: f32, - stroke_color: u32, - stroke_size: f32, } /// The screen resolution to use when rendering text. @@ -119,8 +117,4 @@ pub struct TextArea<'a> { pub bounds: TextBounds, // The default color of the text area. pub default_color: Color, - /// The stroke (outline) size - pub stroke_size: f32, - /// The stroke (outline) color - pub stroke_color: Color, } diff --git a/src/shader.wgsl b/src/shader.wgsl index a88e9f6..1813a66 100644 --- a/src/shader.wgsl +++ b/src/shader.wgsl @@ -6,8 +6,6 @@ struct VertexInput { @location(3) color: u32, @location(4) content_type_with_srgb: u32, @location(5) depth: f32, - @location(6) stroke_color: u32, - @location(7) stroke_size: f32, } struct VertexOutput { @@ -15,8 +13,6 @@ struct VertexOutput { @location(0) color: vec4, @location(1) uv: vec2, @location(2) @interpolate(flat) content_type: u32, - @location(3) stroke_color: vec4, - @location(4) stroke_size: f32, }; struct Params { @@ -46,13 +42,11 @@ fn srgb_to_linear(c: f32) -> f32 { @vertex fn vs_main(in_vert: VertexInput) -> VertexOutput { - var stroke = in_vert.stroke_size; - var width = in_vert.dim & 0xffffu; - var height = (in_vert.dim & 0xffff0000u) >> 16u; - var pos = in_vert.pos; - var uv = vec2(in_vert.uv & 0xffffu, (in_vert.uv & 0xffff0000u) >> 16u); + var pos = in_vert.pos; + let width = in_vert.dim & 0xffffu; + let height = (in_vert.dim & 0xffff0000u) >> 16u; let color = in_vert.color; - let stroke_color = in_vert.stroke_color; + var uv = vec2(in_vert.uv & 0xffffu, (in_vert.uv & 0xffff0000u) >> 16u); let v = in_vert.vertex_idx; let corner_position = vec2( @@ -86,12 +80,6 @@ fn vs_main(in_vert: VertexInput) -> VertexOutput { f32(color & 0x000000ffu) / 255.0, f32((color & 0xff000000u) >> 24u) / 255.0, ); - vert_output.stroke_color = vec4( - f32((stroke_color & 0x00ff0000u) >> 16u) / 255.0, - f32((stroke_color & 0x0000ff00u) >> 8u) / 255.0, - f32(stroke_color & 0x000000ffu) / 255.0, - f32((stroke_color & 0xff000000u) >> 24u) / 255.0, - ); } case 1u: { vert_output.color = vec4( @@ -100,12 +88,6 @@ fn vs_main(in_vert: VertexInput) -> VertexOutput { srgb_to_linear(f32(color & 0x000000ffu) / 255.0), f32((color & 0xff000000u) >> 24u) / 255.0, ); - vert_output.stroke_color = vec4( - srgb_to_linear(f32((stroke_color & 0x00ff0000u) >> 16u) / 255.0), - srgb_to_linear(f32((stroke_color & 0x0000ff00u) >> 8u) / 255.0), - srgb_to_linear(f32(stroke_color & 0x000000ffu) / 255.0), - f32((stroke_color & 0xff000000u) >> 24u) / 255.0, - ); } default: {} } @@ -126,54 +108,20 @@ fn vs_main(in_vert: VertexInput) -> VertexOutput { vert_output.content_type = content_type; vert_output.uv = vec2(uv) / vec2(dim); - vert_output.stroke_size = in_vert.stroke_size; return vert_output; } @fragment fn fs_main(in_frag: VertexOutput) -> @location(0) vec4 { - let dims = vec2(textureDimensions(mask_atlas_texture)); - var bit: u32 = 1; - - let current = textureSample(mask_atlas_texture, atlas_sampler, dims); - var closest_dist = distance(vec2(in_frag.position.xy), vec2(current.xy)); - var result = current; - - for (var step: u32 = 0; step <= 32; step++) { - bit = 1u << step; - for (var dy = -1; dy <= 1; dy++) { - for (var dx = -1; dx <= 1; dx++) { - if (dx == 0 && dy == 0) { - continue; - } - - let neighbour_coord = in_frag.position.xy + vec2(f32(dx * bit), f32(dy * bit)); - let neighbour = textureSample(mask_atlas_texture, atlas_sampler, neighbour_coord / dims); - let dist = distance(vec2(in_frag.position.xy), vec2(neighbour.xy)); - - if (dist < closest_dist) { - closest_dist = dist; - result = vec4(in_frag.stroke_color.rgb, in_frag.stroke_color.a * textureSampleLevel(mask_atlas_texture, atlas_sampler, neighbour.xy, 0.0).x); - } - } - } - } - - switch in_frag.content_type { case 0u: { return textureSampleLevel(color_atlas_texture, atlas_sampler, in_frag.uv, 0.0); } case 1u: { - if in_frag.stroke_size > 0.0 { - return result; - } else { return vec4(in_frag.color.rgb, in_frag.color.a * textureSampleLevel(mask_atlas_texture, atlas_sampler, in_frag.uv, 0.0).x); - } } default: { - // return result; return vec4(0.0); } } diff --git a/src/text_atlas.rs b/src/text_atlas.rs index 8313672..0b90a0f 100644 --- a/src/text_atlas.rs +++ b/src/text_atlas.rs @@ -4,7 +4,7 @@ use crate::{ use etagere::{size2, Allocation, BucketedAtlasAllocator}; use lru::LruCache; use rustc_hash::FxHasher; -use std::{collections::HashSet, hash::BuildHasherDefault}; +use std::{collections::HashSet, hash::BuildHasherDefault, sync::Arc}; use wgpu::{ BindGroup, DepthStencilState, Device, Extent3d, MultisampleState, Origin3d, Queue, RenderPipeline, TexelCopyBufferLayout, TexelCopyTextureInfo, Texture, TextureAspect, @@ -329,7 +329,7 @@ impl TextAtlas { device: &Device, multisample: MultisampleState, depth_stencil: Option, - ) -> RenderPipeline { + ) -> Arc { self.cache .get_or_create_pipeline(device, self.format, multisample, depth_stencil) } diff --git a/src/text_render.rs b/src/text_render.rs index 39cadc6..1d4f802 100644 --- a/src/text_render.rs +++ b/src/text_render.rs @@ -2,8 +2,7 @@ use crate::{ ColorMode, FontSystem, GlyphDetails, GlyphToRender, GpuCacheStatus, PrepareError, RenderError, SwashCache, SwashContent, TextArea, TextAtlas, Viewport, }; -use cosmic_text::LayoutRun; -use std::{num::NonZeroU64, slice}; +use std::{num::NonZeroU64, slice, sync::Arc}; use wgpu::util::StagingBelt; use wgpu::{ Buffer, BufferDescriptor, BufferUsages, CommandEncoder, DepthStencilState, Device, Extent3d, @@ -16,7 +15,7 @@ pub struct TextRenderer { staging_belt: StagingBelt, vertex_buffer: Buffer, vertex_buffer_size: u64, - pipeline: RenderPipeline, + pipeline: Arc, glyph_vertices: Vec, glyphs_to_render: u32, } @@ -74,11 +73,10 @@ impl TextRenderer { let bounds_max_y = text_area.bounds.bottom.min(resolution.height as i32); let is_run_visible = |run: &cosmic_text::LayoutRun| { - let start_y_physical = (text_area.top + (run.line_top * text_area.scale)) as i32; - let end_y_physical = start_y_physical + (run.line_height * text_area.scale) as i32; + let start_y = (text_area.top + run.line_top) as i32; + let end_y = (text_area.top + run.line_top + run.line_height) as i32; - start_y_physical <= text_area.bounds.bottom - && text_area.bounds.top <= end_y_physical + start_y <= bounds_max_y && bounds_min_y <= end_y }; let layout_runs = text_area @@ -88,40 +86,188 @@ impl TextRenderer { .take_while(is_run_visible); for run in layout_runs { - if text_area.stroke_size > 0.0 { - for i in 0..2 { - self.prepare_glyphs( - &run, - &text_area, - atlas, - cache, - font_system, - device, - queue, - bounds_max_x, - bounds_max_y, - bounds_min_y, - bounds_min_x, - i == 0, - &mut metadata_to_depth, - )?; + for glyph in run.glyphs.iter() { + let physical_glyph = + glyph.physical((text_area.left, text_area.top), text_area.scale); + + let cache_key = physical_glyph.cache_key; + + let details = if let Some(details) = + atlas.mask_atlas.glyph_cache.get(&cache_key) + { + atlas.mask_atlas.glyphs_in_use.insert(cache_key); + details + } else if let Some(details) = atlas.color_atlas.glyph_cache.get(&cache_key) { + atlas.color_atlas.glyphs_in_use.insert(cache_key); + details + } else { + let Some(image) = + cache.get_image_uncached(font_system, physical_glyph.cache_key) + else { + continue; + }; + + 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, inner) = if should_rasterize { + let mut inner = atlas.inner_for_content_mut(content_type); + + // Find a position in the packer + let allocation = loop { + match inner.try_allocate(width, height) { + Some(a) => break a, + None => { + if !atlas.grow( + device, + queue, + font_system, + cache, + content_type, + ) { + return Err(PrepareError::AtlasFull); + } + + inner = atlas.inner_for_content_mut(content_type); + } + } + }; + let atlas_min = allocation.rectangle.min; + + queue.write_texture( + TexelCopyTextureInfo { + texture: &inner.texture, + mip_level: 0, + origin: Origin3d { + x: atlas_min.x as u32, + y: atlas_min.y as u32, + z: 0, + }, + aspect: TextureAspect::All, + }, + &image.data, + TexelCopyBufferLayout { + offset: 0, + bytes_per_row: Some(width as u32 * inner.num_channels() as u32), + rows_per_image: None, + }, + Extent3d { + width: width as u32, + height: height as u32, + depth_or_array_layers: 1, + }, + ); + + ( + GpuCacheStatus::InAtlas { + x: atlas_min.x as u16, + y: atlas_min.y as u16, + content_type, + }, + Some(allocation.id), + inner, + ) + } else { + let inner = &mut atlas.color_atlas; + (GpuCacheStatus::SkipRasterization, None, inner) + }; + + inner.glyphs_in_use.insert(cache_key); + // Insert the glyph into the cache and return the details reference + inner.glyph_cache.get_or_insert(cache_key, || GlyphDetails { + width: image.placement.width as u16, + height: image.placement.height as u16, + gpu_cache, + atlas_id, + top: image.placement.top as i16, + left: image.placement.left as i16, + }) + }; + + let mut x = physical_glyph.x + details.left as i32; + let mut y = (run.line_y * text_area.scale).round() as i32 + physical_glyph.y + - details.top as i32; + + 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; + let mut height = details.height as i32; + + // Starts beyond right edge or ends beyond left edge + let max_x = x + width; + if x > bounds_max_x || max_x < bounds_min_x { + continue; } - } else { - self.prepare_glyphs( - &run, - &text_area, - atlas, - cache, - font_system, - device, - queue, - bounds_max_x, - bounds_max_y, - bounds_min_y, - bounds_min_x, - false, - &mut metadata_to_depth, - )?; + + // Starts beyond bottom edge or ends beyond top edge + let max_y = y + height; + if y > bounds_max_y || max_y < bounds_min_y { + continue; + } + + // Clip left ege + if x < bounds_min_x { + let right_shift = bounds_min_x - x; + + x = bounds_min_x; + width = max_x - bounds_min_x; + atlas_x += right_shift as u16; + } + + // Clip right edge + if x + width > bounds_max_x { + width = bounds_max_x - x; + } + + // Clip top edge + if y < bounds_min_y { + let bottom_shift = bounds_min_y - y; + + y = bounds_min_y; + height = max_y - bounds_min_y; + atlas_y += bottom_shift as u16; + } + + // Clip bottom edge + if y + height > bounds_max_y { + height = bounds_max_y - y; + } + + let color = match glyph.color_opt { + Some(some) => some, + None => text_area.default_color, + }; + + let depth = metadata_to_depth(glyph.metadata); + + self.glyph_vertices.push(GlyphToRender { + pos: [x, y], + dim: [width as u16, height as u16], + uv: [atlas_x, atlas_y], + color: color.0, + content_type_with_srgb: [ + content_type as u16, + match atlas.color_mode { + ColorMode::Accurate => TextColorConversion::ConvertToLinear, + ColorMode::Web => TextColorConversion::None, + } as u16, + ], + depth, + }); } } } @@ -216,204 +362,6 @@ impl TextRenderer { Ok(()) } - - fn prepare_glyphs( - &mut self, - run: &LayoutRun, - text_area: &TextArea, - atlas: &mut TextAtlas, - cache: &mut SwashCache, - font_system: &mut FontSystem, - device: &Device, - queue: &Queue, - bounds_max_x: i32, - bounds_max_y: i32, - bounds_min_y: i32, - bounds_min_x: i32, - stroke: bool, - mut metadata_to_depth: impl FnMut(usize) -> f32, - ) -> Result<(), PrepareError> { - for glyph in run.glyphs.iter() { - let physical_glyph = glyph.physical((text_area.left, text_area.top), text_area.scale); - - let cache_key = physical_glyph.cache_key; - - let details = if let Some(details) = atlas.mask_atlas.glyph_cache.get(&cache_key) { - atlas.mask_atlas.glyphs_in_use.insert(cache_key); - details - } else if let Some(details) = atlas.color_atlas.glyph_cache.get(&cache_key) { - atlas.color_atlas.glyphs_in_use.insert(cache_key); - details - } else { - let Some(image) = cache.get_image_uncached(font_system, physical_glyph.cache_key) - else { - continue; - }; - - 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, inner) = if should_rasterize { - let mut inner = atlas.inner_for_content_mut(content_type); - - // Find a position in the packer - let allocation = loop { - match inner.try_allocate(width, height) { - Some(a) => break a, - None => { - if !atlas.grow(device, queue, font_system, cache, content_type) { - return Err(PrepareError::AtlasFull); - } - - inner = atlas.inner_for_content_mut(content_type); - } - } - }; - let atlas_min = allocation.rectangle.min; - - queue.write_texture( - TexelCopyTextureInfo { - texture: &inner.texture, - mip_level: 0, - origin: Origin3d { - x: atlas_min.x as u32, - y: atlas_min.y as u32, - z: 0, - }, - aspect: TextureAspect::All, - }, - &image.data, - TexelCopyBufferLayout { - offset: 0, - bytes_per_row: Some(width as u32 * inner.num_channels() as u32), - rows_per_image: None, - }, - Extent3d { - width: width as u32, - height: height as u32, - depth_or_array_layers: 1, - }, - ); - - ( - GpuCacheStatus::InAtlas { - x: atlas_min.x as u16, - y: atlas_min.y as u16, - content_type, - }, - Some(allocation.id), - inner, - ) - } else { - let inner = &mut atlas.color_atlas; - (GpuCacheStatus::SkipRasterization, None, inner) - }; - - inner.glyphs_in_use.insert(cache_key); - // Insert the glyph into the cache and return the details reference - inner.glyph_cache.get_or_insert(cache_key, || GlyphDetails { - width: image.placement.width as u16, - height: image.placement.height as u16, - gpu_cache, - atlas_id, - top: image.placement.top as i16, - left: image.placement.left as i16, - }) - }; - - let mut x = physical_glyph.x + details.left as i32; - let mut y = (run.line_y * text_area.scale).round() as i32 + physical_glyph.y - - details.top as i32; - - 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; - let mut height = details.height as i32; - - // Starts beyond right edge or ends beyond left edge - let max_x = x + width; - if x > bounds_max_x || max_x < bounds_min_x { - continue; - } - - // Starts beyond bottom edge or ends beyond top edge - let max_y = y + height; - if y > bounds_max_y || max_y < bounds_min_y { - continue; - } - - // Clip left ege - if x < bounds_min_x { - let right_shift = bounds_min_x - x; - - x = bounds_min_x; - width = max_x - bounds_min_x; - atlas_x += right_shift as u16; - } - - // Clip right edge - if x + width > bounds_max_x { - width = bounds_max_x - x; - } - - // Clip top edge - if y < bounds_min_y { - let bottom_shift = bounds_min_y - y; - - y = bounds_min_y; - height = max_y - bounds_min_y; - atlas_y += bottom_shift as u16; - } - - // Clip bottom edge - if y + height > bounds_max_y { - height = bounds_max_y - y; - } - - let color = if stroke { - text_area.stroke_color - } else { - match glyph.color_opt { - Some(some) => some, - None => text_area.default_color, - } - }; - - let depth = metadata_to_depth(glyph.metadata); - - self.glyph_vertices.push(GlyphToRender { - pos: [x, y], - dim: [width as u16, height as u16], - uv: [atlas_x, atlas_y], - color: color.0, - content_type_with_srgb: [ - content_type as u16, - match atlas.color_mode { - ColorMode::Accurate => TextColorConversion::ConvertToLinear, - ColorMode::Web => TextColorConversion::None, - } as u16, - ], - depth, - stroke_color: text_area.stroke_color.0, - stroke_size: if stroke { text_area.stroke_size } else { 0.0 }, - }); - } - Ok(()) - } } #[repr(u16)]