2026-05-01 23:35:37 +02:00
|
|
|
/*
|
|
|
|
|
Crafter®.Graphics
|
|
|
|
|
Copyright (C) 2026 Catcrafts®
|
|
|
|
|
catcrafts.net
|
|
|
|
|
|
|
|
|
|
This library is free software; you can redistribute it and/or
|
|
|
|
|
modify it under the terms of the GNU Lesser General Public
|
|
|
|
|
License version 3.0 as published by the Free Software Foundation;
|
|
|
|
|
|
|
|
|
|
This library is distributed in the hope that it will be useful,
|
|
|
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
|
|
|
Lesser General Public License for more details.
|
|
|
|
|
|
|
|
|
|
You should have received a copy of the GNU Lesser General Public
|
|
|
|
|
License along with this library; if not, write to the Free Software
|
|
|
|
|
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
|
|
|
|
*/
|
|
|
|
|
module;
|
|
|
|
|
#include "vulkan/vulkan.h"
|
2026-05-02 21:08:20 +02:00
|
|
|
export module Crafter.Graphics:FontAtlas;
|
2026-05-01 23:35:37 +02:00
|
|
|
import std;
|
|
|
|
|
import :Font;
|
|
|
|
|
import :ImageVulkan;
|
|
|
|
|
import :Device;
|
|
|
|
|
|
2026-05-02 21:08:20 +02:00
|
|
|
export namespace Crafter {
|
2026-05-01 23:35:37 +02:00
|
|
|
// Per-glyph metrics. UVs are 0..1 in atlas space; on-screen sizes /
|
|
|
|
|
// offsets / advance are in *atlas pixels at the base size* and scale
|
|
|
|
|
// linearly with the requested font size at draw time.
|
|
|
|
|
struct Glyph {
|
|
|
|
|
float u0 = 0, v0 = 0; // top-left UV in the atlas
|
|
|
|
|
float u1 = 0, v1 = 0; // bottom-right UV in the atlas
|
|
|
|
|
float w = 0, h = 0; // glyph quad size in atlas px (= the bitmap size)
|
|
|
|
|
float xoff = 0, yoff = 0; // glyph bearing relative to baseline cursor
|
|
|
|
|
float advance = 0; // horizontal advance at base size, in atlas px
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// Single-channel SDF atlas. Glyphs are rasterised with stb_truetype's
|
|
|
|
|
// GetGlyphSDF at a fixed `kBaseSize` resolution and packed via a simple
|
|
|
|
|
// shelf allocator. Drawing scales the glyph quad linearly; the shader
|
|
|
|
|
// resolves edge AA via screen-space derivatives, so a single atlas
|
|
|
|
|
// serves all sizes and DPI scales without re-bake.
|
|
|
|
|
class FontAtlas {
|
|
|
|
|
public:
|
|
|
|
|
// Build-time constants. Tweak in one place if needed; values picked
|
|
|
|
|
// to give crisp text from ~10pt to ~96pt and leave headroom in the
|
|
|
|
|
// SDF distance band so smoothstep is in the linear regime.
|
|
|
|
|
static constexpr int kAtlasSize = 1024;
|
|
|
|
|
static constexpr float kBaseSize = 32.0f; // pixel-height at which we rasterise
|
|
|
|
|
static constexpr int kPadding = 4; // distance-field padding around each glyph
|
|
|
|
|
static constexpr int kOnEdgeValue = 128; // 8-bit value mapped to "0 distance"
|
|
|
|
|
static constexpr float kPixelDistScale = 32.0f; // how many distance units per pixel — wider = softer AA range
|
|
|
|
|
|
|
|
|
|
ImageVulkan<std::uint8_t> image;
|
|
|
|
|
bool dirty = false; // staging has unflushed writes
|
|
|
|
|
|
|
|
|
|
// Allocate the GPU image and zero-clear it. Must be called once
|
|
|
|
|
// with a one-shot init command buffer.
|
|
|
|
|
void Initialize(VkCommandBuffer cmd);
|
|
|
|
|
|
|
|
|
|
// Rasterise + pack the glyph if it isn't cached yet. Returns
|
|
|
|
|
// false only if the atlas is out of space (V2: grow). After a
|
|
|
|
|
// successful Ensure the bitmap lives in `image.buffer.value` and
|
|
|
|
|
// `dirty` is true; call Update(cmd) before reading on the GPU.
|
|
|
|
|
bool Ensure(Font& font, std::uint32_t codepoint);
|
|
|
|
|
|
|
|
|
|
// Lookup is cheap (hash-table). Returns nullptr if the glyph
|
|
|
|
|
// hasn't been Ensured.
|
|
|
|
|
const Glyph* Lookup(Font& font, std::uint32_t codepoint) const;
|
|
|
|
|
|
|
|
|
|
// If `dirty`, flushes staging into the GPU image and transitions
|
|
|
|
|
// it back to SHADER_READ_ONLY_OPTIMAL. No-op if not dirty.
|
|
|
|
|
void Update(VkCommandBuffer cmd);
|
|
|
|
|
|
|
|
|
|
private:
|
|
|
|
|
// Shelf packer state.
|
|
|
|
|
struct Shelf { int y = 0; int height = 0; int cursorX = 0; };
|
|
|
|
|
std::vector<Shelf> shelves_;
|
|
|
|
|
int nextShelfY_ = 0;
|
|
|
|
|
|
|
|
|
|
// (font*, codepoint) → Glyph cache.
|
|
|
|
|
struct Key {
|
|
|
|
|
const Font* font;
|
|
|
|
|
std::uint32_t cp;
|
|
|
|
|
bool operator==(const Key&) const = default;
|
|
|
|
|
};
|
|
|
|
|
struct KeyHash {
|
|
|
|
|
std::size_t operator()(const Key& k) const noexcept {
|
|
|
|
|
std::size_t h1 = std::hash<const void*>{}(k.font);
|
|
|
|
|
std::size_t h2 = std::hash<std::uint32_t>{}(k.cp);
|
|
|
|
|
return h1 ^ (h2 + 0x9e3779b9 + (h1 << 6) + (h1 >> 2));
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
std::unordered_map<Key, Glyph, KeyHash> cache_;
|
|
|
|
|
|
|
|
|
|
// Place a wxh glyph; returns true + writes top-left into outX/outY.
|
|
|
|
|
bool ShelfPlace(int w, int h, int& outX, int& outY);
|
|
|
|
|
};
|
|
|
|
|
}
|