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
BIN
tests/FontAdvanceCache/font.ttf
Normal file
BIN
tests/FontAdvanceCache/font.ttf
Normal file
Binary file not shown.
144
tests/FontAdvanceCache/main.cpp
Normal file
144
tests/FontAdvanceCache/main.cpp
Normal file
|
|
@ -0,0 +1,144 @@
|
|||
/*
|
||||
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
|
||||
*/
|
||||
|
||||
// Issue #57: Font::GetLineWidth used to call stbtt_GetCodepointHMetrics for
|
||||
// every glyph on every call. Caret hit-testing (InputField_HitTestCursor)
|
||||
// rescans the whole field once per character, so that is O(n²) HMetrics calls.
|
||||
// Advances are now memoised per Font in *font units* (Font::AdvanceUnits):
|
||||
// ASCII in a flat array, the rest in a map, rescaled per call.
|
||||
//
|
||||
// The cache must be in unscaled units, NOT pixels — the FontAtlas's
|
||||
// Glyph::advance is baked at kBaseSize and is wrong at any other size, so it
|
||||
// cannot be reused here. What is asserted:
|
||||
//
|
||||
// - AdvanceUnits is size-independent: the value for a codepoint is identical
|
||||
// no matter which size was queried first (proves it stores units, not px).
|
||||
// - GetLineWidth equals the documented pipeline: sum of per-glyph
|
||||
// (int)(units * scaleForSize), with the truncation applied PER GLYPH.
|
||||
// - Repeated calls (cache hits) are byte-identical to the first (miss).
|
||||
// - Width scales with size — a 2× size is wider, ruling out a baked-at-one-
|
||||
// size advance that would make every size render the same width.
|
||||
// - The empty string is zero.
|
||||
// - Codepoints above ASCII (the map path) behave like the array path.
|
||||
//
|
||||
// Pure CPU: Font only touches stb_truetype, so no Vulkan device is needed.
|
||||
|
||||
#include <cstdlib>
|
||||
|
||||
import Crafter.Graphics;
|
||||
import std;
|
||||
|
||||
using namespace Crafter;
|
||||
|
||||
namespace {
|
||||
|
||||
int failures = 0;
|
||||
|
||||
void Check(bool ok, std::string_view what) {
|
||||
std::println("{} {}", ok ? "PASS" : "FAIL", what);
|
||||
if (!ok) ++failures;
|
||||
}
|
||||
|
||||
std::filesystem::path FindFont() {
|
||||
for (const char* cand : {
|
||||
"font.ttf",
|
||||
"tests/FontAdvanceCache/font.ttf",
|
||||
"../../examples/HelloUI/font.ttf" }) {
|
||||
if (std::filesystem::exists(cand)) return cand;
|
||||
}
|
||||
return "font.ttf";
|
||||
}
|
||||
|
||||
// The exact contract GetLineWidth must honour: truncate each glyph's scaled
|
||||
// advance to int independently, then accumulate. Rescaling the cached unit
|
||||
// advance per call is what keeps it correct at sizes other than kBaseSize.
|
||||
std::uint32_t ExpectedWidth(Font& font, std::string_view text, float size) {
|
||||
float scale = font.ScaleForSize(size);
|
||||
std::uint32_t w = 0;
|
||||
std::size_t i = 0;
|
||||
while (i < text.size()) {
|
||||
std::uint32_t cp = DecodeUtf8(text, i);
|
||||
if (cp == 0) break;
|
||||
w += static_cast<int>(font.AdvanceUnits(cp) * scale);
|
||||
}
|
||||
return w;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
int main() {
|
||||
Font font(FindFont());
|
||||
|
||||
const std::string_view text = "Hello, World!";
|
||||
|
||||
// ── 1. AdvanceUnits is size-independent (stored in font units). ─────────
|
||||
// Prime the cache via a large size, capture the unit advance, then prime
|
||||
// again via a tiny size. The unit value must not move — if the cache stored
|
||||
// pixels it would differ wildly between the two priming sizes.
|
||||
font.GetLineWidth(text, 96.0f);
|
||||
std::int32_t aBig = font.AdvanceUnits(static_cast<std::uint32_t>('A'));
|
||||
font.GetLineWidth(text, 6.0f);
|
||||
std::int32_t aSmall = font.AdvanceUnits(static_cast<std::uint32_t>('A'));
|
||||
Check(aBig == aSmall && aBig > 0,
|
||||
"AdvanceUnits('A') is a stable, positive font-unit value across sizes");
|
||||
|
||||
// ── 2. GetLineWidth matches the per-glyph-truncated, rescaled pipeline. ─
|
||||
bool pipelineOk = true;
|
||||
for (float size : {6.0f, 11.0f, 18.0f, 32.0f, 64.0f, 100.0f}) {
|
||||
std::uint32_t got = font.GetLineWidth(text, size);
|
||||
std::uint32_t exp = ExpectedWidth(font, text, size);
|
||||
if (got != exp) {
|
||||
pipelineOk = false;
|
||||
std::println(" size {}: got {} expected {}", size, got, exp);
|
||||
}
|
||||
}
|
||||
Check(pipelineOk, "GetLineWidth == sum of per-glyph (int)(units * scale) at every size");
|
||||
|
||||
// ── 3. Cache hits are byte-identical to the first (miss) call. ──────────
|
||||
std::uint32_t first = font.GetLineWidth(text, 18.0f);
|
||||
bool stable = true;
|
||||
for (int k = 0; k < 8; ++k) stable = stable && (font.GetLineWidth(text, 18.0f) == first);
|
||||
Check(stable && first > 0, "repeated GetLineWidth calls are identical (cache hit == miss)");
|
||||
|
||||
// ── 4. Width scales with size — not baked at a single size. ─────────────
|
||||
std::uint32_t w18 = font.GetLineWidth(text, 18.0f);
|
||||
std::uint32_t w36 = font.GetLineWidth(text, 36.0f);
|
||||
Check(w36 > w18 && w36 > w18 + (w18 / 2),
|
||||
"doubling the size roughly doubles the width (advance is rescaled, not baked)");
|
||||
|
||||
// ── 5. Empty string is zero. ────────────────────────────────────────────
|
||||
Check(font.GetLineWidth("", 18.0f) == 0, "empty string has zero width");
|
||||
|
||||
// ── 6. Non-ASCII codepoints (the map path) behave like the array path. ──
|
||||
// U+00E9 'é' (UTF-8 0xC3 0xA9) is > 127 so it lands in advanceUnits_, not
|
||||
// the flat ASCII array. Its advance must be stable and a prefix narrower
|
||||
// than the whole.
|
||||
const std::string_view accented = "café"; // 'é' is two UTF-8 bytes
|
||||
std::uint32_t accWidth = font.GetLineWidth(accented, 24.0f);
|
||||
std::uint32_t accExp = ExpectedWidth(font, accented, 24.0f);
|
||||
Check(accWidth == accExp && accWidth > font.GetLineWidth("caf", 24.0f),
|
||||
"non-ASCII codepoint goes through the map path and widens the line");
|
||||
std::uint32_t eFirst = static_cast<std::uint32_t>(font.AdvanceUnits(0x00E9));
|
||||
Check(static_cast<std::uint32_t>(font.AdvanceUnits(0x00E9)) == eFirst,
|
||||
"AdvanceUnits for a mapped codepoint is stable across calls");
|
||||
|
||||
if (failures == 0) std::println("\nAll font advance-cache checks passed.");
|
||||
else std::println("\n{} font advance-cache check(s) FAILED.", failures);
|
||||
return failures == 0 ? EXIT_SUCCESS : EXIT_FAILURE;
|
||||
}
|
||||
BIN
tests/InputFieldHitTest/font.ttf
Normal file
BIN
tests/InputFieldHitTest/font.ttf
Normal file
Binary file not shown.
212
tests/InputFieldHitTest/main.cpp
Normal file
212
tests/InputFieldHitTest/main.cpp
Normal file
|
|
@ -0,0 +1,212 @@
|
|||
/*
|
||||
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
|
||||
*/
|
||||
|
||||
// Regression test for issue #56: InputField_HitTestCursor used to map a click
|
||||
// x-coord to a cursor byte offset by calling Font::GetLineWidth on every
|
||||
// prefix `value[0..i]` for i = 1..size — each call re-walks from byte 0, so it
|
||||
// was O(n^2) glyph-metric lookups. Worse, it iterated raw byte boundaries, so
|
||||
// on multibyte UTF-8 input it could return an offset that splits a codepoint
|
||||
// (an invalid cursor position, since cursorPos is a byte offset edited via
|
||||
// value.erase(cursorPos, 1)).
|
||||
//
|
||||
// The hit test now delegates to Font::NearestCursorByte: one left-to-right
|
||||
// cumulative-advance pass (O(n) metrics) over codepoint boundaries, returning
|
||||
// the byte offset of the boundary whose cumulative advance is nearest the
|
||||
// target. This test pins:
|
||||
// - the result matches an independent brute-force "nearest codepoint
|
||||
// boundary" reference (built from per-prefix GetLineWidth) for many click
|
||||
// positions across ASCII and multibyte strings,
|
||||
// - clicks at/left of the text start return 0, clicks far past the end
|
||||
// return value.size() (the end-of-text caret),
|
||||
// - every returned offset lands on a codepoint boundary (never mid-sequence),
|
||||
// - clicking exactly on a glyph's left edge lands on that glyph's boundary.
|
||||
//
|
||||
// Pure CPU + a TrueType file — no Vulkan device or window needed. The font is
|
||||
// copied next to the binary; the test also probes the project-root path.
|
||||
|
||||
#include <cstdlib>
|
||||
|
||||
import Crafter.Graphics;
|
||||
import std;
|
||||
|
||||
using namespace Crafter;
|
||||
|
||||
namespace {
|
||||
|
||||
int failures = 0;
|
||||
|
||||
void Check(bool ok, std::string_view what) {
|
||||
std::println("{} {}", ok ? "PASS" : "FAIL", what);
|
||||
if (!ok) ++failures;
|
||||
}
|
||||
|
||||
std::filesystem::path FindFont() {
|
||||
for (const char* cand : {
|
||||
"font.ttf",
|
||||
"tests/InputFieldHitTest/font.ttf",
|
||||
"../../examples/HelloUI/font.ttf" }) {
|
||||
if (std::filesystem::exists(cand)) return cand;
|
||||
}
|
||||
return "font.ttf";
|
||||
}
|
||||
|
||||
// All codepoint-boundary byte offsets of `s`, including 0 and s.size().
|
||||
std::vector<std::size_t> Boundaries(std::string_view s) {
|
||||
std::vector<std::size_t> b{0};
|
||||
std::size_t i = 0;
|
||||
while (i < s.size()) {
|
||||
std::uint32_t cp = DecodeUtf8(s, i); // advances i to the next boundary
|
||||
if (cp == 0) break;
|
||||
b.push_back(i);
|
||||
}
|
||||
return b;
|
||||
}
|
||||
|
||||
// Independent reference: the codepoint boundary whose prefix advance is
|
||||
// nearest `target`, ties resolved to the earlier boundary (matching the
|
||||
// implementation's strict `d < bestDist`). Built with per-prefix GetLineWidth
|
||||
// — the slow path the optimisation replaces, used here only as the oracle.
|
||||
std::size_t NearestBoundaryRef(Font& font, std::string_view s, float size, float target) {
|
||||
if (target <= 0.0f) return 0;
|
||||
auto bounds = Boundaries(s);
|
||||
std::size_t best = 0;
|
||||
float bestDist = std::abs(target); // boundary 0 has width 0
|
||||
for (std::size_t k = 1; k < bounds.size(); ++k) {
|
||||
float w = static_cast<float>(font.GetLineWidth(s.substr(0, bounds[k]), size));
|
||||
float d = std::abs(target - w);
|
||||
if (d < bestDist) {
|
||||
bestDist = d;
|
||||
best = bounds[k];
|
||||
}
|
||||
// No early break: the reference scans every boundary regardless, so a
|
||||
// mismatch would also catch any over-eager break in the implementation.
|
||||
}
|
||||
return best;
|
||||
}
|
||||
|
||||
bool IsBoundary(std::string_view s, std::size_t off) {
|
||||
if (off > s.size()) return false;
|
||||
for (std::size_t b : Boundaries(s)) if (b == off) return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
const InputFieldColors kColors{
|
||||
{0, 0, 0, 1}, {0, 0, 0, 1}, {0, 0, 0, 1}, {0, 0, 0, 1},
|
||||
{1, 1, 1, 1}, {1, 1, 1, 1},
|
||||
};
|
||||
|
||||
// Drive the public hit-test for a click at window-x `clickX`, given a field
|
||||
// rect at `rectX`. paddingX is taken from kColors.
|
||||
std::size_t Hit(const InputField& f, Font& font, float size, float rectX, float clickX) {
|
||||
Rect rect{rectX, 0.0f, 200.0f, 24.0f};
|
||||
return InputField_HitTestCursor(f, rect, clickX, font, size, kColors);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
int main() {
|
||||
Font font(FindFont());
|
||||
|
||||
constexpr float kSize = 18.0f;
|
||||
const float rectX = 30.0f;
|
||||
const float textStart = rectX + kColors.paddingX; // x where the text begins
|
||||
|
||||
const std::array<std::string_view, 4> samples = {
|
||||
"Hello, world",
|
||||
"1234567890",
|
||||
"iiiiWWWWmmmm", // wildly varying advances
|
||||
"caf\xC3\xA9 na\xC3\xAFve \xE2\x9C\x93", // "café naïve ✓" — 2- and 3-byte cps
|
||||
};
|
||||
|
||||
// 1. Across each sample, sweep target px and compare to the oracle.
|
||||
bool sweepOk = true;
|
||||
bool allBoundaries = true;
|
||||
for (std::string_view s : samples) {
|
||||
InputField f;
|
||||
f.type = InputFieldType::Text;
|
||||
f.value = std::string(s);
|
||||
float fullW = static_cast<float>(font.GetLineWidth(s, kSize));
|
||||
// Step finely enough to cross every glyph edge several times, and run
|
||||
// past the end of the text to exercise the end-of-text caret.
|
||||
for (float t = -10.0f; t <= fullW + 40.0f; t += 1.0f) {
|
||||
std::size_t got = Hit(f, font, kSize, rectX, textStart + t);
|
||||
std::size_t ref = NearestBoundaryRef(font, s, kSize, t);
|
||||
if (got != ref) {
|
||||
sweepOk = false;
|
||||
std::println(" mismatch on \"{}\": target={} got={} ref={}",
|
||||
s, t, got, ref);
|
||||
}
|
||||
if (!IsBoundary(s, got)) allBoundaries = false;
|
||||
}
|
||||
}
|
||||
Check(sweepOk, "hit test matches the brute-force nearest-boundary oracle across a px sweep");
|
||||
Check(allBoundaries, "every returned offset lands on a UTF-8 codepoint boundary");
|
||||
|
||||
// 2. Clicks at or left of the text start return 0.
|
||||
{
|
||||
InputField f; f.value = "Hello";
|
||||
Check(Hit(f, font, kSize, rectX, textStart) == 0,
|
||||
"click exactly at the text start returns offset 0");
|
||||
Check(Hit(f, font, kSize, rectX, textStart - 5.0f) == 0,
|
||||
"click left of the text start returns offset 0");
|
||||
Check(Hit(f, font, kSize, rectX, 0.0f) == 0,
|
||||
"click far to the left of the field returns offset 0");
|
||||
}
|
||||
|
||||
// 3. A click far past the end lands on the end-of-text caret (value.size()).
|
||||
{
|
||||
InputField f; f.value = "Hello";
|
||||
float fullW = static_cast<float>(font.GetLineWidth(f.value, kSize));
|
||||
Check(Hit(f, font, kSize, rectX, textStart + fullW + 1000.0f) == f.value.size(),
|
||||
"click far past the end returns value.size()");
|
||||
}
|
||||
|
||||
// 4. An empty field always resolves to offset 0.
|
||||
{
|
||||
InputField f; f.value = "";
|
||||
Check(Hit(f, font, kSize, rectX, textStart + 50.0f) == 0,
|
||||
"empty field returns offset 0 regardless of click x");
|
||||
}
|
||||
|
||||
// 5. Clicking just past a glyph's right edge selects that glyph's boundary
|
||||
// (cursor sits after the glyph). Walk the boundaries of an ASCII string
|
||||
// and click slightly past each cumulative advance.
|
||||
{
|
||||
std::string_view s = "abcXYZ";
|
||||
InputField f; f.value = std::string(s);
|
||||
auto bounds = Boundaries(s);
|
||||
bool edgeOk = true;
|
||||
for (std::size_t k = 1; k < bounds.size(); ++k) {
|
||||
float w = static_cast<float>(font.GetLineWidth(s.substr(0, bounds[k]), kSize));
|
||||
// Click a hair before the glyph's right edge → nearest boundary is
|
||||
// this one (its advance is the closest of all boundaries).
|
||||
std::size_t got = Hit(f, font, kSize, rectX, textStart + w - 0.5f);
|
||||
if (got != bounds[k]) {
|
||||
edgeOk = false;
|
||||
std::println(" edge mismatch at boundary {} (w={}): got {}",
|
||||
bounds[k], w, got);
|
||||
}
|
||||
}
|
||||
Check(edgeOk, "clicking near a glyph's right edge resolves to that glyph's boundary");
|
||||
}
|
||||
|
||||
if (failures == 0) std::println("\nAll InputField hit-test checks passed.");
|
||||
else std::println("\n{} InputField hit-test check(s) FAILED.", failures);
|
||||
return failures == 0 ? EXIT_SUCCESS : EXIT_FAILURE;
|
||||
}
|
||||
105
tests/UIClipFlag/main.cpp
Normal file
105
tests/UIClipFlag/main.cpp
Normal file
|
|
@ -0,0 +1,105 @@
|
|||
/*
|
||||
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
|
||||
*/
|
||||
|
||||
// Regression test for issue #50: uiResolveScreenPixel used to run four float
|
||||
// compares against hdr.clipRectPx for every pixel of every dispatch, even
|
||||
// though the clip rect is the full-surface default {0,0,1e9,1e9} in the
|
||||
// overwhelmingly common case. The compares are now gated on the reserved
|
||||
// kUIFlagClip bit, which UIRenderer::FillHeader sets (via ClipFlags) only when
|
||||
// the clip rect is actually narrower than the surface.
|
||||
//
|
||||
// ClipFlags is the pure CPU decision behind that gate: given a clip rect, the
|
||||
// surface size, and the caller's user flags, it returns the header flag word
|
||||
// with kUIFlagClip set iff the rect does not cover the whole surface. This
|
||||
// test drives it directly with synthetic dimensions — no Window or device.
|
||||
//
|
||||
// The contract it must hold for the shader gate to be correct:
|
||||
// * full-surface (and over-covering) clips must NOT set the bit, so the
|
||||
// shader skips the compares — and the skipped compares would never have
|
||||
// rejected an in-surface pixel anyway, so output is pixel-identical;
|
||||
// * any clip narrower than the surface on ANY edge MUST set the bit;
|
||||
// * the caller's user flag bits are preserved either way; only the reserved
|
||||
// high bit is touched.
|
||||
|
||||
#include <cstdlib>
|
||||
|
||||
import Crafter.Graphics;
|
||||
import std;
|
||||
using namespace Crafter;
|
||||
|
||||
namespace {
|
||||
|
||||
int failures = 0;
|
||||
|
||||
void Check(bool ok, std::string_view what) {
|
||||
std::println("{} {}", ok ? "PASS" : "FAIL", what);
|
||||
if (!ok) ++failures;
|
||||
}
|
||||
|
||||
constexpr std::array<float,4> kFullDefault = {0.0f, 0.0f, 1e9f, 1e9f};
|
||||
|
||||
bool ClipBitSet(std::array<float,4> rect, std::uint32_t w, std::uint32_t h,
|
||||
std::uint32_t userFlags = 0) {
|
||||
return (UIRenderer::ClipFlags(rect, w, h, userFlags) & kUIFlagClip) != 0u;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
int main() {
|
||||
constexpr std::uint32_t W = 1920, H = 1080;
|
||||
|
||||
// --- full-surface clips leave the bit clear (the fast path) -----------
|
||||
Check(!ClipBitSet(kFullDefault, W, H),
|
||||
"the {0,0,1e9,1e9} default does not set the clip bit");
|
||||
Check(!ClipBitSet({0.0f, 0.0f, float(W), float(H)}, W, H),
|
||||
"a clip exactly matching the surface does not set the clip bit");
|
||||
Check(!ClipBitSet({-5.0f, -5.0f, float(W) + 10.0f, float(H) + 10.0f}, W, H),
|
||||
"a clip that over-covers the surface does not set the clip bit");
|
||||
|
||||
// --- any narrower edge sets the bit -----------------------------------
|
||||
Check(ClipBitSet({0.0f, 0.0f, float(W) - 1.0f, float(H)}, W, H),
|
||||
"a clip one pixel short on the right edge sets the clip bit");
|
||||
Check(ClipBitSet({0.0f, 0.0f, float(W), float(H) - 1.0f}, W, H),
|
||||
"a clip one pixel short on the bottom edge sets the clip bit");
|
||||
Check(ClipBitSet({1.0f, 0.0f, float(W), float(H)}, W, H),
|
||||
"a clip inset on the left edge sets the clip bit");
|
||||
Check(ClipBitSet({0.0f, 1.0f, float(W), float(H)}, W, H),
|
||||
"a clip inset on the top edge sets the clip bit");
|
||||
Check(ClipBitSet({100.0f, 100.0f, 200.0f, 200.0f}, W, H),
|
||||
"a small interior clip rect sets the clip bit");
|
||||
|
||||
// --- user flag bits survive the decision ------------------------------
|
||||
constexpr std::uint32_t kUser = 0x0000000Fu; // four low user bits
|
||||
Check(UIRenderer::ClipFlags(kFullDefault, W, H, kUser) == kUser,
|
||||
"full-surface clip preserves user flags and clears the reserved bit");
|
||||
Check(UIRenderer::ClipFlags({0.0f, 0.0f, 200.0f, 200.0f}, W, H, kUser)
|
||||
== (kUser | kUIFlagClip),
|
||||
"narrow clip preserves user flags and sets the reserved bit");
|
||||
// A caller that (wrongly) pre-set the reserved bit must not flip the
|
||||
// decision for a full-surface clip — ClipFlags owns that bit.
|
||||
Check(UIRenderer::ClipFlags(kFullDefault, W, H, kUIFlagClip | kUser) == kUser,
|
||||
"full-surface clip clears a stale reserved bit the caller passed in");
|
||||
|
||||
if (failures != 0) {
|
||||
std::println(std::cerr, "{} case(s) failed", failures);
|
||||
return 1;
|
||||
}
|
||||
std::println(std::cout, "all UI clip-flag cases passed");
|
||||
return 0;
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue