Merge remote-tracking branch 'origin/master' into claude/issue-51

# Conflicts:
#	project.cpp
This commit is contained in:
catbot 2026-06-16 15:43:11 +00:00
commit e4f8b057f3
8 changed files with 396 additions and 42 deletions

View file

@ -90,6 +90,12 @@ export namespace Crafter {
};
static_assert(sizeof(GlyphItem) == 48);
// Where the X coordinate sits relative to the emitted glyph run. Used by
// UIRenderer::ShapeText and the Tier 3 text components (DrawText). Lives
// here (not in :UIComponents) so ShapeText can fold the alignment shift
// into its single pass.
enum class TextAlign : std::uint8_t { Left, Center, Right };
// ─── tiny rect-carving helper ───────────────────────────────────────
struct Rect {
float x = 0, y = 0, w = 0, h = 0;
@ -206,12 +212,30 @@ export namespace Crafter {
#endif
SamplerSlot RegisterLinearClampSampler();
// Shapes `utf8` into `out` (up to `outCapacity` glyphs), positioning
// the run at `(x, baselineY)` with horizontal `align`. Returns the
// number of glyphs written; `*outAdvance` (if non-null) receives the
// full advance width regardless of truncation.
//
// The origin-relative shaped run is memoized in a CPU cache keyed by
// (Font*, pxSize, color, utf8); a hit skips UTF-8 decode, glyph
// lookup, and layout, translating the cached run into `out` instead.
// Caching is byte-equivalent — the glyph buffer is fully rewritten
// and re-flushed every frame regardless. Call InvalidateFont before
// destroying a Font (the cache keys on the Font pointer).
std::uint32_t ShapeText(Font& font, float pxSize,
float x, float baselineY,
std::string_view utf8,
std::array<float,4> color,
GlyphItem* out, std::uint32_t outCapacity,
float* outAdvance = nullptr);
float* outAdvance = nullptr,
TextAlign align = TextAlign::Left);
// Drop every cached shaped run that references `font`. Must be called
// before a Font is destroyed: the run cache (and FontAtlas's glyph
// cache) key on the Font pointer, so a Font freed and a new one
// reallocated at the same address would otherwise alias stale runs.
void InvalidateFont(const Font& font) noexcept;
std::uint16_t FontAtlasImageSlot() const noexcept { return fontAtlasImageSlot_; }
std::uint16_t FontAtlasSamplerSlot() const noexcept { return fontAtlasSamplerSlot_; }
@ -222,6 +246,41 @@ export namespace Crafter {
Window* window_ = nullptr;
GraphicsDescriptorHeap* heap_ = nullptr;
// ─── shaped-run cache ───────────────────────────────────────────
// Glyphs are stored origin-relative (pen at x=0, baseline=0); a hit
// only needs a translate by (x + alignShift, baselineY). The full
// string is part of the key (compared on lookup) so hash collisions
// can't return the wrong run.
struct ShapedRunKey {
const Font* font;
float pxSize;
std::array<float, 4> color;
std::string text;
bool operator==(const ShapedRunKey&) const = default;
};
struct ShapedRunKeyHash {
std::size_t operator()(const ShapedRunKey& k) const noexcept {
std::size_t h = std::hash<const void*>{}(k.font);
auto mix = [&h](std::size_t v) noexcept {
h ^= v + 0x9e3779b97f4a7c15ULL + (h << 6) + (h >> 2);
};
mix(std::hash<std::uint32_t>{}(std::bit_cast<std::uint32_t>(k.pxSize)));
for (float c : k.color)
mix(std::hash<std::uint32_t>{}(std::bit_cast<std::uint32_t>(c)));
mix(std::hash<std::string_view>{}(k.text));
return h;
}
};
struct ShapedRun {
std::vector<GlyphItem> glyphs; // origin-relative
float advance = 0;
};
// Soft cap. A pathological caller drawing a fresh string every frame
// would grow this without bound; on overflow we drop everything and
// rebuild (correctness is unaffected, only the hit rate after a flush).
static constexpr std::size_t kMaxShapedRuns = 8192;
std::unordered_map<ShapedRunKey, ShapedRun, ShapedRunKeyHash> shapedRuns_;
ImageSlot outImageSlot_;
ImageSlot fontAtlasImageSlot_;
SamplerSlot fontAtlasSamplerSlot_;

View file

@ -96,8 +96,7 @@ export namespace Crafter {
float cornerRadius = 0;
};
// Where the X coordinate sits relative to the emitted glyph run.
enum class TextAlign : std::uint8_t { Left, Center, Right };
// TextAlign lives in the :UI partition (used by UIRenderer::ShapeText).
// ─── component functions ───────────────────────────────────────────