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

# Conflicts:
#	interfaces/Crafter.Graphics-UI.cppm
#	project.cpp
This commit is contained in:
catbot 2026-06-16 17:09:06 +00:00
commit e8f1c7cff9
15 changed files with 732 additions and 39 deletions

View file

@ -60,8 +60,29 @@ namespace Crafter {
stbtt_fontinfo font;
Font(const std::filesystem::path& font);
std::uint32_t GetLineWidth(const std::string_view text, float size);
// Map a target advance (px from the line start) to the nearest UTF-8
// codepoint boundary, returning its BYTE offset into `text`. A single
// left-to-right pass accumulates per-glyph advances (O(n) metric
// lookups), instead of re-walking the prefix for every boundary as a
// naive caller would. Cumulative advance is monotonic (advances are
// non-negative), so the scan stops at the first boundary past the
// closest one. Returned offsets land on codepoint boundaries, which are
// exactly the valid positions for a byte-based text cursor.
std::size_t NearestCursorByte(std::string_view text, float size, float target);
float LineHeight(float size);
float AscentPx(float size);
float ScaleForSize(float size);
// Horizontal advance for `cp` in unscaled font units, cached per Font.
// Stored in font units (not pixels) so it can be rescaled for any
// `size`; Glyph::advance in the FontAtlas is baked at kBaseSize and is
// wrong at other sizes, so it cannot be reused here. ASCII lands in a
// flat array (the common path for caret hit-testing, which rescans the
// whole field per character), everything else in the map.
std::int32_t AdvanceUnits(std::uint32_t cp);
private:
std::array<std::int32_t, 128> asciiAdvance_; // -1 = uncached
std::unordered_map<std::uint32_t, std::int32_t> advanceUnits_;
};
}

View file

@ -59,6 +59,12 @@ export namespace Crafter {
};
static_assert(sizeof(UIDispatchHeader) == 48);
// Reserved `flags` bit (mirrors shaders/ui-shared.glsl::UI_FLAG_CLIP).
// FillHeader sets it when the clip rect is narrower than the surface so
// the standard shaders can skip the per-pixel clip compares otherwise.
// The remaining low bits stay free for user-defined feature flags.
inline constexpr std::uint32_t kUIFlagClip = 0x80000000u;
// ─── fused-dispatch push-constant header (issue #47) ────────────────
// Mirrors the PC block in shaders/ui-fused.comp.glsl byte-for-byte: every
// member is vec4-aligned, no padding holes, exactly 128 bytes (the
@ -71,7 +77,7 @@ export namespace Crafter {
std::uint32_t outImage; // swapchain image heap slot
std::uint32_t fontTexture; // font-atlas image slot (text phase)
std::uint32_t fontSampler; // font-atlas sampler slot (text phase)
std::uint32_t flags; // reserved — keep zeroed
std::uint32_t flags; // per-category clip-active bits (0x1 quads .. 0x8 text)
std::uint32_t surfaceWidth;
std::uint32_t surfaceHeight;
std::uint32_t frameIdx;
@ -200,6 +206,18 @@ export namespace Crafter {
std::array<float,4> clipRectPx = {0.0f, 0.0f, 1e9f, 1e9f},
std::uint32_t flags = 0) const noexcept;
// Builds the header `flags` word: the caller's user flags with the
// reserved kUIFlagClip bit set iff `clipRectPx` does not already cover
// the whole surface. When the clip rect spans the surface, the four
// per-pixel clip compares in uiResolveScreenPixel can never reject an
// in-surface pixel, so the bit stays clear and the shaders skip them.
// Static + dimension-parameterised so the decision is unit-testable
// without a live Window; FillHeader feeds it the current window size.
static std::uint32_t ClipFlags(std::array<float,4> clipRectPx,
std::uint32_t surfaceWidth,
std::uint32_t surfaceHeight,
std::uint32_t flags) noexcept;
void DispatchQuads(GraphicsCommandBuffer cmd, std::uint32_t bufferSlot, std::uint32_t itemCount,
std::array<float,4> clipRectPx = {0.0f, 0.0f, 1e9f, 1e9f});
void DispatchCircles(GraphicsCommandBuffer cmd, std::uint32_t bufferSlot, std::uint32_t itemCount,