text_renderer: clear VkImage before growing, implement island padding in texture atlas (fix graphical artifacts)

This commit is contained in:
Aleksander
2025-11-08 12:08:48 +01:00
parent 6f41ffe59c
commit a59a4c2870
3 changed files with 51 additions and 15 deletions

View File

@@ -4,11 +4,11 @@ use vulkano::{
DeviceSize, DeviceSize,
buffer::{Buffer, BufferCreateInfo, BufferUsage, Subbuffer}, buffer::{Buffer, BufferCreateInfo, BufferUsage, Subbuffer},
command_buffer::{ command_buffer::{
AutoCommandBufferBuilder, CommandBufferExecFuture, CopyBufferToImageInfo, CopyImageInfo, PrimaryAutoCommandBuffer, AutoCommandBufferBuilder, ClearColorImageInfo, CommandBufferExecFuture, CopyBufferToImageInfo, CopyImageInfo,
PrimaryCommandBufferAbstract, RenderingAttachmentInfo, RenderingInfo, SubpassContents, PrimaryAutoCommandBuffer, PrimaryCommandBufferAbstract, RenderingAttachmentInfo, RenderingInfo, SubpassContents,
}, },
device::Queue, device::Queue,
format::{ClearValue, Format}, format::{ClearColorValue, ClearValue, Format},
image::{Image, ImageCreateInfo, ImageType, ImageUsage, view::ImageView}, image::{Image, ImageCreateInfo, ImageType, ImageUsage, view::ImageView},
memory::allocator::{AllocationCreateInfo, MemoryTypeFilter}, memory::allocator::{AllocationCreateInfo, MemoryTypeFilter},
render_pass::{AttachmentLoadOp, AttachmentStoreOp}, render_pass::{AttachmentLoadOp, AttachmentStoreOp},
@@ -128,6 +128,16 @@ impl WCommandBuffer<CmdBufXfer> {
Ok(image) Ok(image)
} }
pub fn clear_image(&mut self, image: &Arc<Image>) -> 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( pub fn update_image(
&mut self, &mut self,
image: &Arc<Image>, image: &Arc<Image>,

View File

@@ -1,5 +1,5 @@
use cosmic_text::{FontSystem, SwashCache}; use cosmic_text::{FontSystem, SwashCache};
use etagere::{size2, Allocation, BucketedAtlasAllocator}; use etagere::{Allocation, BucketedAtlasAllocator, size2};
use lru::LruCache; use lru::LruCache;
use rustc_hash::FxHasher; use rustc_hash::FxHasher;
use std::{collections::HashSet, hash::BuildHasherDefault, sync::Arc}; use std::{collections::HashSet, hash::BuildHasherDefault, sync::Arc};
@@ -8,20 +8,20 @@ use vulkano::{
command_buffer::CommandBufferUsage, command_buffer::CommandBufferUsage,
descriptor_set::DescriptorSet, descriptor_set::DescriptorSet,
format::Format, format::Format,
image::{view::ImageView, Image, ImageCreateInfo, ImageType, ImageUsage}, image::{Image, ImageCreateInfo, ImageType, ImageUsage, view::ImageView},
memory::allocator::AllocationCreateInfo, memory::allocator::AllocationCreateInfo,
pipeline::graphics::vertex_input::Vertex, pipeline::graphics::vertex_input::Vertex,
}; };
use super::{ use super::{
GlyphDetails, GpuCacheStatus,
custom_glyph::ContentType, custom_glyph::ContentType,
shaders::{frag_atlas, vert_atlas}, shaders::{frag_atlas, vert_atlas},
text_renderer::GlyphonCacheKey, text_renderer::GlyphonCacheKey,
GlyphDetails, GpuCacheStatus,
}; };
use crate::gfx::{ use crate::gfx::{
BLEND_ALPHA, WGfx,
pipeline::{WGfxPipeline, WPipelineCreateInfo}, pipeline::{WGfxPipeline, WPipelineCreateInfo},
WGfx, BLEND_ALPHA,
}; };
/// Pipeline & shaders to be reused between `TextRenderer` instances /// Pipeline & shaders to be reused between `TextRenderer` instances
@@ -77,6 +77,8 @@ pub(super) struct InnerAtlas {
common: TextPipeline, common: TextPipeline,
} }
pub const TEXT_ATLAS_ISLAND_PADDING_PX: u32 = 1;
impl InnerAtlas { impl InnerAtlas {
const INITIAL_SIZE: u32 = 256; const INITIAL_SIZE: u32 = 256;
@@ -131,10 +133,15 @@ impl InnerAtlas {
} }
pub(super) fn try_allocate(&mut self, width: usize, height: usize) -> Option<Allocation> { pub(super) fn try_allocate(&mut self, width: usize, height: usize) -> Option<Allocation> {
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 { loop {
let allocation = self.packer.allocate(size); let allocation = self.packer.allocate(padded_size);
if allocation.is_some() { if allocation.is_some() {
return allocation; return allocation;
@@ -200,6 +207,9 @@ impl InnerAtlas {
.gfx .gfx
.create_xfer_command_buffer(CommandBufferUsage::OneTimeSubmit)?; .create_xfer_command_buffer(CommandBufferUsage::OneTimeSubmit)?;
// clear newly allocated image with zeros
cmd_buf.clear_image(&image)?;
// Re-upload glyphs // Re-upload glyphs
for (&cache_key, glyph) in &self.glyph_cache { for (&cache_key, glyph) in &self.glyph_cache {
let (x, y) = match glyph.gpu_cache { let (x, y) = match glyph.gpu_cache {

View File

@@ -1,14 +1,15 @@
use crate::{ use crate::{
gfx::{cmd::GfxCommandBuffer, pass::WGfxPass}, 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::{ use super::{
ContentType, FontSystem, GlyphDetails, GpuCacheStatus, SwashCache, TextArea,
custom_glyph::{CustomGlyphCacheKey, RasterizeCustomGlyphRequest, RasterizedCustomGlyph}, custom_glyph::{CustomGlyphCacheKey, RasterizeCustomGlyphRequest, RasterizedCustomGlyph},
text_atlas::{GlyphVertex, TextAtlas, TextPipeline}, text_atlas::{GlyphVertex, TextAtlas, TextPipeline},
ContentType, FontSystem, GlyphDetails, GpuCacheStatus, SwashCache, TextArea,
}; };
use cosmic_text::{Color, SubpixelBin, SwashContent}; use cosmic_text::{Color, SubpixelBin, SwashContent};
use etagere::size2;
use glam::{Mat4, Vec2, Vec3}; use glam::{Mat4, Vec2, Vec3};
use vulkano::{ use vulkano::{
buffer::{BufferUsage, Subbuffer}, buffer::{BufferUsage, Subbuffer},
@@ -359,14 +360,29 @@ fn prepare_glyph(
inner = par.atlas.inner_for_content_mut(image.content_type); 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)?; 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<u8> = 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( cmd_buf.update_image(
inner.image_view.image(), inner.image_view.image(),
&image.data, &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]), Some([image.width.into(), image.height.into(), 1]),
)?; )?;
@@ -374,8 +390,8 @@ fn prepare_glyph(
( (
GpuCacheStatus::InAtlas { GpuCacheStatus::InAtlas {
x: atlas_min.x as u16, x: atlas_glyph_min.x as u16,
y: atlas_min.y as u16, y: atlas_glyph_min.y as u16,
content_type: image.content_type, content_type: image.content_type,
}, },
Some(allocation.id), Some(allocation.id),