Merge remote-tracking branch 'origin/master' into claude/issue-47
# Conflicts: # interfaces/Crafter.Graphics-UI.cppm # project.cpp
This commit is contained in:
commit
e8f1c7cff9
15 changed files with 732 additions and 39 deletions
|
|
@ -56,6 +56,23 @@ Font::Font(const std::filesystem::path& fontFilePath) {
|
|||
this->ascent = ascent;
|
||||
this->descent = descent;
|
||||
this->lineGap = lineGap;
|
||||
|
||||
asciiAdvance_.fill(-1);
|
||||
}
|
||||
|
||||
std::int32_t Font::AdvanceUnits(std::uint32_t cp) {
|
||||
if (cp < asciiAdvance_.size()) {
|
||||
if (asciiAdvance_[cp] < 0) {
|
||||
int advance = 0, lsb = 0;
|
||||
stbtt_GetCodepointHMetrics(&font, static_cast<int>(cp), &advance, &lsb);
|
||||
asciiAdvance_[cp] = advance;
|
||||
}
|
||||
return asciiAdvance_[cp];
|
||||
}
|
||||
if (auto it = advanceUnits_.find(cp); it != advanceUnits_.end()) return it->second;
|
||||
int advance = 0, lsb = 0;
|
||||
stbtt_GetCodepointHMetrics(&font, static_cast<int>(cp), &advance, &lsb);
|
||||
return advanceUnits_.emplace(cp, advance).first->second;
|
||||
}
|
||||
|
||||
std::uint32_t Font::GetLineWidth(const std::string_view text, float size) {
|
||||
|
|
@ -65,13 +82,33 @@ std::uint32_t Font::GetLineWidth(const std::string_view text, float size) {
|
|||
while (i < text.size()) {
|
||||
std::uint32_t cp = DecodeUtf8(text, i);
|
||||
if (cp == 0) break;
|
||||
int advance, lsb;
|
||||
stbtt_GetCodepointHMetrics(&font, static_cast<int>(cp), &advance, &lsb);
|
||||
lineWidth += (int)(advance * scale);
|
||||
lineWidth += (int)(AdvanceUnits(cp) * scale);
|
||||
}
|
||||
return lineWidth;
|
||||
}
|
||||
|
||||
std::size_t Font::NearestCursorByte(std::string_view text, float size, float target) {
|
||||
if (target <= 0.0f) return 0;
|
||||
float scale = stbtt_ScaleForPixelHeight(&font, size);
|
||||
std::size_t best = 0; // byte offset 0 → caret before the first glyph
|
||||
float bestDist = target; // |target - 0|, and target > 0 here
|
||||
float cumWidth = 0.0f;
|
||||
std::size_t i = 0;
|
||||
while (i < text.size()) {
|
||||
std::uint32_t cp = DecodeUtf8(text, i); // i advances to the next boundary
|
||||
if (cp == 0) break;
|
||||
cumWidth += (int)(AdvanceUnits(cp) * scale); // match GetLineWidth's per-glyph rounding
|
||||
float d = std::abs(target - cumWidth);
|
||||
if (d < bestDist) {
|
||||
bestDist = d;
|
||||
best = i; // byte offset just past this codepoint
|
||||
} else {
|
||||
break; // monotonic advance ⇒ already past the closest boundary
|
||||
}
|
||||
}
|
||||
return best;
|
||||
}
|
||||
|
||||
float Font::LineHeight(float size) {
|
||||
float scale = stbtt_ScaleForPixelHeight(&font, size);
|
||||
return (ascent - descent + lineGap) * scale;
|
||||
|
|
|
|||
|
|
@ -117,23 +117,12 @@ std::size_t Crafter::InputField_HitTestCursor(const InputField& f,
|
|||
Font& font, float fontSize,
|
||||
const InputFieldColors& colors)
|
||||
{
|
||||
// Single O(n) cumulative-advance pass over the value, keyed at codepoint
|
||||
// byte boundaries — matching cursorPos, which is a byte offset. The old
|
||||
// loop re-walked the prefix for every boundary (O(n^2) glyph metric
|
||||
// lookups) and could even land mid-codepoint on multibyte input.
|
||||
float target = clickX - (rect.x + colors.paddingX);
|
||||
if (target <= 0.0f) return 0;
|
||||
|
||||
std::size_t best = 0;
|
||||
float bestDist = std::abs(target);
|
||||
for (std::size_t i = 1; i <= f.value.size(); ++i) {
|
||||
std::string_view sub(f.value.data(), i);
|
||||
float w = static_cast<float>(font.GetLineWidth(sub, fontSize));
|
||||
float d = std::abs(target - w);
|
||||
if (d < bestDist) {
|
||||
bestDist = d;
|
||||
best = i;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
return best;
|
||||
return font.NearestCursorByte(f.value, fontSize, target);
|
||||
}
|
||||
|
||||
void Crafter::DrawInputField(UIBuffer& buf, const InputField& f, Rect rect,
|
||||
|
|
|
|||
|
|
@ -38,11 +38,27 @@ UIDispatchHeader UIRenderer::FillHeader(std::uint32_t itemBufferSlot,
|
|||
#else
|
||||
h.frameIdx = 0;
|
||||
#endif
|
||||
h.flags = flags;
|
||||
h.flags = ClipFlags(clipRectPx, window_->width, window_->height, flags);
|
||||
h._pad = 0;
|
||||
return h;
|
||||
}
|
||||
|
||||
std::uint32_t UIRenderer::ClipFlags(std::array<float,4> clipRectPx,
|
||||
std::uint32_t surfaceWidth,
|
||||
std::uint32_t surfaceHeight,
|
||||
std::uint32_t flags) noexcept {
|
||||
// The reserved kUIFlagClip bit signals "clip rect is narrower than the
|
||||
// surface". When the rect already covers the whole surface (the common
|
||||
// {0,0,1e9,1e9} default), the four per-pixel clip compares in
|
||||
// uiResolveScreenPixel can never reject an in-surface pixel, so we leave
|
||||
// the bit clear and the shader skips them — pixel-identical output.
|
||||
const bool clipCoversSurface =
|
||||
clipRectPx[0] <= 0.0f && clipRectPx[1] <= 0.0f
|
||||
&& clipRectPx[0] + clipRectPx[2] >= static_cast<float>(surfaceWidth)
|
||||
&& clipRectPx[1] + clipRectPx[3] >= static_cast<float>(surfaceHeight);
|
||||
return clipCoversSurface ? (flags & ~kUIFlagClip) : (flags | kUIFlagClip);
|
||||
}
|
||||
|
||||
std::uint32_t UIRenderer::ShapeText(Font& font, float pxSize,
|
||||
float x, float baselineY,
|
||||
std::string_view utf8,
|
||||
|
|
|
|||
|
|
@ -219,7 +219,15 @@ void UIRenderer::DispatchFused(GraphicsCommandBuffer cmd,
|
|||
pc.outImage = outImageSlot_;
|
||||
pc.fontTexture = fontAtlasImageSlot_;
|
||||
pc.fontSampler = fontAtlasSamplerSlot_;
|
||||
pc.flags = 0;
|
||||
// Per-category clip-active bits, reusing the same surface-coverage test the
|
||||
// standard path uses (ClipFlags). A category whose clip already covers the
|
||||
// surface clears its bit so the kernel skips that category's per-pixel clip
|
||||
// compares (the common full-surface case) — pixel-identical either way.
|
||||
auto clipBit = [&](const FusedBatch& b, std::uint32_t bit) -> std::uint32_t {
|
||||
return (ClipFlags(b.clipRectPx, window_->width, window_->height, 0) & kUIFlagClip) ? bit : 0u;
|
||||
};
|
||||
pc.flags = clipBit(quads, 0x1u) | clipBit(circles, 0x2u)
|
||||
| clipBit(images, 0x4u) | clipBit(text, 0x8u);
|
||||
pc.surfaceWidth = window_->width;
|
||||
pc.surfaceHeight = window_->height;
|
||||
pc.frameIdx = window_->currentBuffer;
|
||||
|
|
@ -239,15 +247,41 @@ void UIRenderer::Dispatch(GraphicsCommandBuffer cmd, const GraphicsComputeShader
|
|||
const void* push, std::uint32_t pushBytes,
|
||||
std::uint32_t gx, std::uint32_t gy, std::uint32_t gz) {
|
||||
if (!firstDispatchThisFrame_) {
|
||||
VkMemoryBarrier mb {
|
||||
.sType = VK_STRUCTURE_TYPE_MEMORY_BARRIER,
|
||||
.srcAccessMask = VK_ACCESS_SHADER_WRITE_BIT,
|
||||
.dstAccessMask = VK_ACCESS_SHADER_READ_BIT | VK_ACCESS_SHADER_WRITE_BIT,
|
||||
// Every UI pass read-modify-writes the same swapchain storage image
|
||||
// (later passes overdraw earlier ones), so each dispatch must observe
|
||||
// the previous dispatch's writes. The only resource the hazard touches
|
||||
// is that one image, so scope the dependency to its subresource with a
|
||||
// VkImageMemoryBarrier rather than a queue-wide VkMemoryBarrier: same
|
||||
// correctness, but only this image's shader caches are flushed —
|
||||
// unrelated buffers/images are no longer invalidated. The image stays
|
||||
// in VK_IMAGE_LAYOUT_GENERAL (bound as a storage image), so this is a
|
||||
// pure memory dependency, no layout transition.
|
||||
//
|
||||
// The execution dependency is still COMPUTE->COMPUTE over the whole
|
||||
// queue, so the passes do NOT overlap — this is a narrower cache flush
|
||||
// only. Eliminating the barriers entirely requires fusing the passes
|
||||
// into one dispatch (tracked in #47); do not attempt that here.
|
||||
VkImageMemoryBarrier ib {
|
||||
.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER,
|
||||
.srcAccessMask = VK_ACCESS_SHADER_WRITE_BIT,
|
||||
.dstAccessMask = VK_ACCESS_SHADER_READ_BIT | VK_ACCESS_SHADER_WRITE_BIT,
|
||||
.oldLayout = VK_IMAGE_LAYOUT_GENERAL,
|
||||
.newLayout = VK_IMAGE_LAYOUT_GENERAL,
|
||||
.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
|
||||
.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
|
||||
.image = window_->images[window_->currentBuffer],
|
||||
.subresourceRange = {
|
||||
.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT,
|
||||
.baseMipLevel = 0,
|
||||
.levelCount = 1,
|
||||
.baseArrayLayer = 0,
|
||||
.layerCount = 1,
|
||||
},
|
||||
};
|
||||
vkCmdPipelineBarrier(cmd,
|
||||
VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT,
|
||||
VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT,
|
||||
0, 1, &mb, 0, nullptr, 0, nullptr);
|
||||
0, 0, nullptr, 0, nullptr, 1, &ib);
|
||||
}
|
||||
firstDispatchThisFrame_ = false;
|
||||
shader.Dispatch(cmd, push, pushBytes, gx, gy, gz);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue