Merge pull request 'perf(font): upload only the dirty sub-rect of the glyph atlas (#51)' (#83) from claude/issue-51 into master

This commit is contained in:
catbot 2026-06-16 17:43:23 +02:00
commit 991a39e094
5 changed files with 276 additions and 6 deletions

View file

@ -39,13 +39,17 @@ void FontAtlas::Initialize(GraphicsCommandBuffer cmd) {
VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL
);
std::memset(image.buffer.value, 0, kAtlasSize * kAtlasSize);
dirty = true;
#else
(void)cmd;
staging.assign(kAtlasSize * kAtlasSize, 0);
textureHandle = WebGPU::wgpuCreateAtlasTexture(kAtlasSize, kAtlasSize);
dirty = true;
#endif
// The freshly-zeroed atlas backs an image whose contents are undefined
// (Vulkan) / unwritten (WebGPU). Mark the whole extent dirty so the
// first Update clears it once; thereafter only glyph sub-rects flip
// dirty, and every untouched texel stays the zero it was uploaded as —
// so partial uploads keep the image byte-identical to the staging copy.
MarkDirty(0, 0, kAtlasSize, kAtlasSize);
}
bool FontAtlas::ShelfPlace(int w, int h, int& outX, int& outY) {
@ -112,7 +116,7 @@ bool FontAtlas::Ensure(Font& font, std::uint32_t codepoint) {
g.v0 = static_cast<float>(py) / kAtlasSize;
g.u1 = static_cast<float>(px + sw) / kAtlasSize;
g.v1 = static_cast<float>(py + sh) / kAtlasSize;
dirty = true;
MarkDirty(px, py, sw, sh);
}
cache_.emplace(key, g);
@ -126,16 +130,28 @@ const Glyph* FontAtlas::Lookup(Font& font, std::uint32_t codepoint) const {
void FontAtlas::Update(GraphicsCommandBuffer cmd) {
if (!dirty) return;
// Clamp the accumulated box to the atlas and convert to (origin,
// extent). Add() works in glyph coordinates that are always in-bounds,
// but clamping keeps the copy extent provably valid. The `dirty` guard
// above guarantees the rect is non-empty, so UploadBox always fills the
// outputs.
std::uint32_t x = 0, y = 0, w = 0, h = 0;
dirtyRect.UploadBox(kAtlasSize, x, y, w, h);
#ifndef CRAFTER_GRAPHICS_WINDOW_DOM
image.Update(cmd, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL);
image.UpdateRegion(cmd, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, x, y, w, h);
#else
(void)cmd;
// Full-atlas upload. Future: track dirty region.
// The staging buffer keeps the full atlas row stride, so srcBytesPerRow
// stays kAtlasSize and (dstX, dstY) index the sub-rect's first texel.
WebGPU::wgpuWriteAtlasRegion(
textureHandle, staging.data(),
kAtlasSize, kAtlasSize, kAtlasSize,
0, 0, kAtlasSize, kAtlasSize
static_cast<std::int32_t>(x), static_cast<std::int32_t>(y),
static_cast<std::int32_t>(w), static_cast<std::int32_t>(h)
);
#endif
dirty = false;
dirtyRect.Reset();
}