From a59a4c287037f82c24a5b37f8729d450fd6312c5 Mon Sep 17 00:00:00 2001 From: Aleksander Date: Sat, 8 Nov 2025 12:08:48 +0100 Subject: [PATCH] text_renderer: clear VkImage before growing, implement island padding in texture atlas (fix graphical artifacts) --- wgui/src/gfx/cmd.rs | 16 ++++++++++--- wgui/src/renderer_vk/text/text_atlas.rs | 22 ++++++++++++----- wgui/src/renderer_vk/text/text_renderer.rs | 28 +++++++++++++++++----- 3 files changed, 51 insertions(+), 15 deletions(-) diff --git a/wgui/src/gfx/cmd.rs b/wgui/src/gfx/cmd.rs index 55152f6..745ddb3 100644 --- a/wgui/src/gfx/cmd.rs +++ b/wgui/src/gfx/cmd.rs @@ -4,11 +4,11 @@ use vulkano::{ DeviceSize, buffer::{Buffer, BufferCreateInfo, BufferUsage, Subbuffer}, command_buffer::{ - AutoCommandBufferBuilder, CommandBufferExecFuture, CopyBufferToImageInfo, CopyImageInfo, PrimaryAutoCommandBuffer, - PrimaryCommandBufferAbstract, RenderingAttachmentInfo, RenderingInfo, SubpassContents, + AutoCommandBufferBuilder, ClearColorImageInfo, CommandBufferExecFuture, CopyBufferToImageInfo, CopyImageInfo, + PrimaryAutoCommandBuffer, PrimaryCommandBufferAbstract, RenderingAttachmentInfo, RenderingInfo, SubpassContents, }, device::Queue, - format::{ClearValue, Format}, + format::{ClearColorValue, ClearValue, Format}, image::{Image, ImageCreateInfo, ImageType, ImageUsage, view::ImageView}, memory::allocator::{AllocationCreateInfo, MemoryTypeFilter}, render_pass::{AttachmentLoadOp, AttachmentStoreOp}, @@ -128,6 +128,16 @@ impl WCommandBuffer { Ok(image) } + pub fn clear_image(&mut self, image: &Arc) -> anyhow::Result<()> { + let clear_info = ClearColorImageInfo { + clear_value: ClearColorValue::Uint([0, 0, 0, 0]), + ..ClearColorImageInfo::image(image.clone()) + }; + + self.command_buffer.clear_color_image(clear_info)?; + Ok(()) + } + pub fn update_image( &mut self, image: &Arc, diff --git a/wgui/src/renderer_vk/text/text_atlas.rs b/wgui/src/renderer_vk/text/text_atlas.rs index d773489..2b91d9e 100644 --- a/wgui/src/renderer_vk/text/text_atlas.rs +++ b/wgui/src/renderer_vk/text/text_atlas.rs @@ -1,5 +1,5 @@ use cosmic_text::{FontSystem, SwashCache}; -use etagere::{size2, Allocation, BucketedAtlasAllocator}; +use etagere::{Allocation, BucketedAtlasAllocator, size2}; use lru::LruCache; use rustc_hash::FxHasher; use std::{collections::HashSet, hash::BuildHasherDefault, sync::Arc}; @@ -8,20 +8,20 @@ use vulkano::{ command_buffer::CommandBufferUsage, descriptor_set::DescriptorSet, format::Format, - image::{view::ImageView, Image, ImageCreateInfo, ImageType, ImageUsage}, + image::{Image, ImageCreateInfo, ImageType, ImageUsage, view::ImageView}, memory::allocator::AllocationCreateInfo, pipeline::graphics::vertex_input::Vertex, }; use super::{ + GlyphDetails, GpuCacheStatus, custom_glyph::ContentType, shaders::{frag_atlas, vert_atlas}, text_renderer::GlyphonCacheKey, - GlyphDetails, GpuCacheStatus, }; use crate::gfx::{ + BLEND_ALPHA, WGfx, pipeline::{WGfxPipeline, WPipelineCreateInfo}, - WGfx, BLEND_ALPHA, }; /// Pipeline & shaders to be reused between `TextRenderer` instances @@ -77,6 +77,8 @@ pub(super) struct InnerAtlas { common: TextPipeline, } +pub const TEXT_ATLAS_ISLAND_PADDING_PX: u32 = 1; + impl InnerAtlas { const INITIAL_SIZE: u32 = 256; @@ -131,10 +133,15 @@ impl InnerAtlas { } pub(super) fn try_allocate(&mut self, width: usize, height: usize) -> Option { - let size = size2(width as i32, height as i32); + let glyph_size = size2(width as i32, height as i32); + let padded_size = glyph_size + + size2( + (TEXT_ATLAS_ISLAND_PADDING_PX * 2) as i32, + (TEXT_ATLAS_ISLAND_PADDING_PX * 2) as i32, + ); loop { - let allocation = self.packer.allocate(size); + let allocation = self.packer.allocate(padded_size); if allocation.is_some() { return allocation; @@ -200,6 +207,9 @@ impl InnerAtlas { .gfx .create_xfer_command_buffer(CommandBufferUsage::OneTimeSubmit)?; + // clear newly allocated image with zeros + cmd_buf.clear_image(&image)?; + // Re-upload glyphs for (&cache_key, glyph) in &self.glyph_cache { let (x, y) = match glyph.gpu_cache { diff --git a/wgui/src/renderer_vk/text/text_renderer.rs b/wgui/src/renderer_vk/text/text_renderer.rs index 1f485e2..20a2d3d 100644 --- a/wgui/src/renderer_vk/text/text_renderer.rs +++ b/wgui/src/renderer_vk/text/text_renderer.rs @@ -1,14 +1,15 @@ use crate::{ gfx::{cmd::GfxCommandBuffer, pass::WGfxPass}, - renderer_vk::{model_buffer::ModelBuffer, viewport::Viewport}, + renderer_vk::{model_buffer::ModelBuffer, text::text_atlas::TEXT_ATLAS_ISLAND_PADDING_PX, viewport::Viewport}, }; use super::{ + ContentType, FontSystem, GlyphDetails, GpuCacheStatus, SwashCache, TextArea, custom_glyph::{CustomGlyphCacheKey, RasterizeCustomGlyphRequest, RasterizedCustomGlyph}, text_atlas::{GlyphVertex, TextAtlas, TextPipeline}, - ContentType, FontSystem, GlyphDetails, GpuCacheStatus, SwashCache, TextArea, }; use cosmic_text::{Color, SubpixelBin, SwashContent}; +use etagere::size2; use glam::{Mat4, Vec2, Vec3}; use vulkano::{ buffer::{BufferUsage, Subbuffer}, @@ -359,14 +360,29 @@ fn prepare_glyph( inner = par.atlas.inner_for_content_mut(image.content_type); }; - let atlas_min = allocation.rectangle.min; + + let atlas_with_island_min = allocation.rectangle.min; + let size_with_island = allocation.rectangle.size(); + let atlas_glyph_min = + allocation.rectangle.min + size2(TEXT_ATLAS_ISLAND_PADDING_PX as i32, TEXT_ATLAS_ISLAND_PADDING_PX as i32); let mut cmd_buf = gfx.create_xfer_command_buffer(CommandBufferUsage::OneTimeSubmit)?; + // Set data to zeros for the whole glyph island + // TODO: use `vkCmdClearColorImage` with an image subresource (or xywh region?) to omit unnecessary allocation + let zero_bytes_data: Vec = vec![0x00; size_with_island.area() as usize * 4 /* RGBX */]; + cmd_buf.update_image( + inner.image_view.image(), + &zero_bytes_data, + [atlas_with_island_min.x as u32, atlas_with_island_min.y as u32, 0], + Some([size_with_island.width as u32, size_with_island.height as u32, 1]), + )?; + + // Upload glyph itself cmd_buf.update_image( inner.image_view.image(), &image.data, - [atlas_min.x as _, atlas_min.y as _, 0], + [atlas_glyph_min.x as u32, atlas_glyph_min.y as u32, 0], Some([image.width.into(), image.height.into(), 1]), )?; @@ -374,8 +390,8 @@ fn prepare_glyph( ( GpuCacheStatus::InAtlas { - x: atlas_min.x as u16, - y: atlas_min.y as u16, + x: atlas_glyph_min.x as u16, + y: atlas_glyph_min.y as u16, content_type: image.content_type, }, Some(allocation.id),