/* Crafter®.Graphics Copyright (C) 2026 Catcrafts® catcrafts.net */ module; #ifndef CRAFTER_GRAPHICS_WINDOW_DOM #include "vulkan/vulkan.h" #endif #include "../lib/stb_truetype.h" module Crafter.Graphics:FontAtlas_impl; import :FontAtlas; import :Font; import :GraphicsTypes; #ifndef CRAFTER_GRAPHICS_WINDOW_DOM import :ImageVulkan; import :Device; #else import :WebGPU; #endif import std; using namespace Crafter; std::uint8_t* FontAtlas::PixelPtr() noexcept { #ifndef CRAFTER_GRAPHICS_WINDOW_DOM return image.buffer.value; #else return staging.data(); #endif } void FontAtlas::Initialize(GraphicsCommandBuffer cmd) { #ifndef CRAFTER_GRAPHICS_WINDOW_DOM image.Create( kAtlasSize, kAtlasSize, /*mipLevels*/ 1, cmd, VK_FORMAT_R8_UNORM, VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_SAMPLED_BIT, 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 } bool FontAtlas::ShelfPlace(int w, int h, int& outX, int& outY) { for (Shelf& s : shelves_) { if (h <= s.height && s.cursorX + w <= kAtlasSize) { outX = s.cursorX; outY = s.y; s.cursorX += w; return true; } } if (nextShelfY_ + h > kAtlasSize) return false; Shelf s{}; s.y = nextShelfY_; s.height = h; s.cursorX = w; outX = 0; outY = s.y; shelves_.push_back(s); nextShelfY_ += h; return true; } bool FontAtlas::Ensure(Font& font, std::uint32_t codepoint) { Key key{&font, codepoint}; if (cache_.contains(key)) return true; float fontScale = stbtt_ScaleForPixelHeight(&font.font, kBaseSize); int advanceUnits = 0, lsb = 0; stbtt_GetCodepointHMetrics(&font.font, static_cast(codepoint), &advanceUnits, &lsb); int sw = 0, sh = 0, xoff = 0, yoff = 0; unsigned char* sdf = stbtt_GetCodepointSDF( &font.font, fontScale, static_cast(codepoint), kPadding, static_cast(kOnEdgeValue), kPixelDistScale, &sw, &sh, &xoff, &yoff ); Glyph g{}; g.advance = advanceUnits * fontScale; g.xoff = static_cast(xoff); g.yoff = static_cast(yoff); if (sdf && sw > 0 && sh > 0) { int px = 0, py = 0; if (!ShelfPlace(sw, sh, px, py)) { stbtt_FreeSDF(sdf, nullptr); return false; } std::uint8_t* dst = PixelPtr(); for (int row = 0; row < sh; ++row) { std::memcpy( dst + (py + row) * kAtlasSize + px, sdf + row * sw, static_cast(sw) ); } stbtt_FreeSDF(sdf, nullptr); g.w = static_cast(sw); g.h = static_cast(sh); g.u0 = static_cast(px) / kAtlasSize; g.v0 = static_cast(py) / kAtlasSize; g.u1 = static_cast(px + sw) / kAtlasSize; g.v1 = static_cast(py + sh) / kAtlasSize; dirty = true; } cache_.emplace(key, g); return true; } const Glyph* FontAtlas::Lookup(Font& font, std::uint32_t codepoint) const { auto it = cache_.find(Key{&font, codepoint}); return it == cache_.end() ? nullptr : &it->second; } void FontAtlas::Update(GraphicsCommandBuffer cmd) { if (!dirty) return; #ifndef CRAFTER_GRAPHICS_WINDOW_DOM image.Update(cmd, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL); #else (void)cmd; // Full-atlas upload. Future: track dirty region. WebGPU::wgpuWriteAtlasRegion( textureHandle, staging.data(), kAtlasSize, kAtlasSize, kAtlasSize, 0, 0, kAtlasSize, kAtlasSize ); #endif dirty = false; }