refactor(render): TextBackend owns its atlas, and staged glyphs carry a rotation basis
This commit is contained in:
1 parent
90e17a0a11
commit
e074f57ddf
23 files changed
+1073
-227
No files matched your search
+65
-14
@@ -247,33 +247,84 @@ PLOT_TEST(text_batch_stages_one_quad_per_glyph) {
|
||||
plot::render::BitmapFont f;
|
||||
plot::render::TextBatch b;
|
||||
b.begin();
|
||||
const plot::Vec2 end = b.add_text({0.0f, 0.0f}, "12.5 ms", f);
|
||||
const plot::Vec2 end = b.add_text("12.5 ms", {0.0f, 0.0f}, 1.0f, f);
|
||||
PLOT_CHECK(b.glyph_count() == 7);
|
||||
PLOT_CHECK(b.data().size() == 28); // x,y,u,v per glyph
|
||||
PLOT_CHECK(b.data().size() == 42); // x,y,u,v,ax,ay per glyph
|
||||
PLOT_CHECK_NEAR(end.x, f.measure("12.5 ms"), 1e-6);
|
||||
PLOT_CHECK_NEAR(end.y, 0.0, 1e-6);
|
||||
PLOT_CHECK_NEAR(b.data()[0], 0.0, 1e-6); // first quad at the pen
|
||||
PLOT_CHECK_NEAR(b.data()[0], 0.0, 1e-6); // first cell at the pen
|
||||
PLOT_CHECK_NEAR(b.data()[1], 0.0, 1e-6);
|
||||
PLOT_CHECK_NEAR(b.data()[4], f.advance('1'), 1e-6); // pen advanced per glyph
|
||||
// The UV origin of the staged quad is the glyph's atlas cell.
|
||||
const plot::Vec2 dot_uv =
|
||||
plot::render::BitmapFont::atlas_origin(static_cast<plot::u32>('.') - 32u);
|
||||
PLOT_CHECK_NEAR(b.data()[10], dot_uv.x, 1e-6);
|
||||
PLOT_CHECK_NEAR(b.data()[11], dot_uv.y, 1e-6);
|
||||
PLOT_CHECK_NEAR(b.data()[6], f.advance('1'), 1e-6); // pen advanced per glyph
|
||||
// The staged cell's UV is the glyph's own atlas rect (see `Glyph`), and the
|
||||
// basis of an unrotated run is the screen's +x. '.' is the string's third
|
||||
// glyph, so its instance is the third: floats 12..17.
|
||||
const plot::render::Glyph* dot = f.glyph(static_cast<plot::u32>('.'));
|
||||
PLOT_CHECK(dot != nullptr);
|
||||
PLOT_CHECK_NEAR(b.data()[14], dot->u0, 1e-6);
|
||||
PLOT_CHECK_NEAR(b.data()[15], dot->v0, 1e-6);
|
||||
PLOT_CHECK_NEAR(b.data()[16], 1.0, 1e-6); // ax
|
||||
PLOT_CHECK_NEAR(b.data()[17], 0.0, 1e-6); // ay
|
||||
// Every glyph a batch stages is read through one cell, and the batch publishes
|
||||
// it for the draw (`u_glyph_uv`).
|
||||
PLOT_CHECK_NEAR(b.glyph_cell_uv().x, dot->u1 - dot->u0, 1e-6);
|
||||
PLOT_CHECK_NEAR(b.glyph_cell_uv().y, dot->v1 - dot->v0, 1e-6);
|
||||
|
||||
// Non-ASCII runs stage one quad per codepoint, and preserve capacity on reuse.
|
||||
const std::size_t cap = b.data().capacity();
|
||||
b.begin();
|
||||
b.add_text({0.0f, 0.0f}, "1.5\xC2\xB5s", f); // 5 codepoints, 6 bytes
|
||||
PLOT_CHECK_NEAR(b.glyph_cell_uv().x, 0.0, 1e-6); // begin() clears the cell too
|
||||
b.add_text("1.5\xC2\xB5s", {0.0f, 0.0f}, 1.0f, f); // 5 codepoints, 6 bytes
|
||||
PLOT_CHECK(b.glyph_count() == 5);
|
||||
PLOT_CHECK(b.data().capacity() == cap);
|
||||
|
||||
// A run drawn at twice the glyph cell size advances twice as far, so the
|
||||
// staged label still ends where the scaled measurement says it does.
|
||||
// A run drawn at twice the backend's size advances twice as far, so the staged
|
||||
// label still ends where the scaled measurement says it does.
|
||||
b.begin();
|
||||
const plot::Vec2 scaled = b.add_text({10.0f, 4.0f}, "AW", f, 16.0f);
|
||||
const plot::Vec2 scaled = b.add_text("AW", {10.0f, 4.0f}, 2.0f, f);
|
||||
PLOT_CHECK(b.glyph_count() == 2);
|
||||
PLOT_CHECK_NEAR(scaled.x, 10.0f + 2.0f * f.measure("AW"), 1e-6);
|
||||
PLOT_CHECK_NEAR(scaled.y, 4.0, 1e-6);
|
||||
PLOT_CHECK_NEAR(b.data()[4], 10.0f + 2.0f * f.advance('A'), 1e-6); // second glyph's pen
|
||||
PLOT_CHECK_NEAR(b.data()[6], 10.0f + 2.0f * f.advance('A'), 1e-6); // second cell's pen
|
||||
}
|
||||
|
||||
// The pen follows the basis, not the screen axes. A run staged with `axis` has to
|
||||
// be exactly as long as the plot layer measured it *in that direction* -- that is
|
||||
// what reserves the room for a rotated y label -- and its cells have to march along
|
||||
// the same direction, or the ink lands beside the pen instead of up from it.
|
||||
PLOT_TEST(text_batch_advances_along_its_basis) {
|
||||
plot::render::BitmapFont f;
|
||||
plot::render::TextBatch b;
|
||||
const plot::Vec2 pen{100.0f, 40.0f};
|
||||
const f32 width = f.measure("12.5 ms");
|
||||
|
||||
// Unrotated, for the shape of the assertion: the run is `measure()` long and
|
||||
// does not move in y.
|
||||
b.begin();
|
||||
const plot::Vec2 flat = b.add_text("12.5 ms", pen, 1.0f, f);
|
||||
PLOT_CHECK_NEAR((flat - pen).length(), width, 1e-4);
|
||||
PLOT_CHECK_NEAR(flat.y, pen.y, 1e-6);
|
||||
|
||||
// Up the screen: the same length, measured upward, with a unit basis on every
|
||||
// staged cell.
|
||||
b.begin();
|
||||
const plot::Vec2 up = b.add_text("12.5 ms", pen, 1.0f, f, {0.0f, -1.0f});
|
||||
PLOT_CHECK_NEAR((up - pen).length(), width, 1e-4);
|
||||
PLOT_CHECK_NEAR(up.x, pen.x, 1e-6);
|
||||
PLOT_CHECK_NEAR(up.y, pen.y - width, 1e-4);
|
||||
for (plot::u32 i = 0; i < b.glyph_count(); ++i) {
|
||||
PLOT_CHECK_NEAR(b.data()[6u * i + 4u], 0.0, 1e-6);
|
||||
PLOT_CHECK_NEAR(b.data()[6u * i + 5u], -1.0, 1e-6);
|
||||
}
|
||||
// The second cell is one advance further up than the pen, which is the same rule
|
||||
// the unrotated case follows in x.
|
||||
PLOT_CHECK_NEAR(b.data()[6], pen.x, 1e-6);
|
||||
PLOT_CHECK_NEAR(b.data()[7], pen.y - f.advance('1'), 1e-6);
|
||||
|
||||
// A diagonal direction is normalised, so a caller can hand over any direction
|
||||
// and the basis the shader receives is still unit.
|
||||
b.begin();
|
||||
const plot::Vec2 diag = b.add_text("12.5 ms", pen, 1.0f, f, {3.0f, 4.0f});
|
||||
PLOT_CHECK_NEAR((diag - pen).length(), width, 1e-4);
|
||||
PLOT_CHECK_NEAR(b.data()[4], 0.6, 1e-5);
|
||||
PLOT_CHECK_NEAR(b.data()[5], 0.8, 1e-5);
|
||||
}
|
||||
Reference in new issue
Block a user