fix(render): drop x title rows the chrome clamp cut short
This commit is contained in:
1 parent
e211701d05
commit
9dc3b1e5e6
2 files changed
+168
-20
No files matched your search
+143
-1
@@ -377,13 +377,41 @@ RectInk scan_rect_ink(plottest::HeadlessContext& ctx, const plot::Rect& area, pl
|
||||
return ink;
|
||||
}
|
||||
|
||||
/// One band of inked columns: `[first, last]`, both inclusive, in framebuffer x.
|
||||
/// One band of inked lines: `[first, last]`, both inclusive, in the coordinate the
|
||||
/// band runs across -- framebuffer x for `ink_column_bands()`, framebuffer y for
|
||||
/// `ink_row_bands()`.
|
||||
struct InkBand {
|
||||
plot::u32 first = kNoColumn;
|
||||
plot::u32 last = 0;
|
||||
plot::u32 width() const noexcept { return first == kNoColumn ? 0u : last - first + 1u; }
|
||||
};
|
||||
|
||||
/// The bands of inked lines `text`-coloured ink forms in `area`, top to bottom: one
|
||||
/// band per drawn text line that a gap of blank lines separates from its neighbours,
|
||||
/// which is how a case counts the rows a strip holds (an x title's rows, a y strip's
|
||||
/// stacked labels). Two lines drawn over each other make one band.
|
||||
std::vector<InkBand> ink_row_bands(plottest::HeadlessContext& ctx, const plot::Rect& area,
|
||||
plot::Color text) {
|
||||
std::vector<InkBand> bands;
|
||||
for (plot::u32 y = static_cast<plot::u32>(area.y);
|
||||
y < static_cast<plot::u32>(area.bottom()); ++y) {
|
||||
bool has = false;
|
||||
for (plot::u32 x = static_cast<plot::u32>(area.x);
|
||||
x < static_cast<plot::u32>(area.right()) && !has; ++x) {
|
||||
plot::u8 px[4] = {};
|
||||
PLOT_CHECK(ctx.read_pixel(x, ctx.height - 1u - y, px));
|
||||
has = text_ink(px, text);
|
||||
}
|
||||
if (!has) continue;
|
||||
if (bands.empty() || bands.back().last + 1u != y) {
|
||||
bands.push_back(InkBand{y, y});
|
||||
} else {
|
||||
bands.back().last = y;
|
||||
}
|
||||
}
|
||||
return bands;
|
||||
}
|
||||
|
||||
/// The bands of inked columns `text`-coloured ink forms in `area`, left to right:
|
||||
/// one band per drawn run that a gap of blank columns separates from its
|
||||
/// neighbours. Two runs drawn over each other make one band; two separated by
|
||||
@@ -1547,6 +1575,120 @@ PLOT_TEST(narrow_viewport_keeps_only_the_y_title_columns_that_fit) {
|
||||
ctx.destroy();
|
||||
}
|
||||
|
||||
// The clamp reaches the x title from the vertical side: `x_title_height` is capped at
|
||||
// the viewport's allowance minus the tick label strip's height, so a viewport short
|
||||
// enough leaves the strip too short for a single row. A row staged anyway is not wholly
|
||||
// clipped -- its cell starts inside the strip, so the top of its glyphs lands in the
|
||||
// frame, a title cut off mid-stroke -- and neither the run's own clip (which cuts along
|
||||
// x) nor the layout's clamp can see that. So this is the one titled axis where nothing
|
||||
// fits, and nothing may be staged.
|
||||
PLOT_TEST(short_viewport_draws_no_x_title_it_cannot_fit) {
|
||||
// 80 px tall: the label strip takes 23.69 px of the 40 px allowance, leaving a
|
||||
// 16.31 px strip against the 19.69 px a row needs -- no room for a row, but 12.31 px
|
||||
// of a staged row's cell would still be inside the target.
|
||||
constexpr plot::u32 kShortW = 320;
|
||||
constexpr plot::u32 kShortH = 80;
|
||||
plottest::HeadlessContext ctx(kShortW, kShortH);
|
||||
plot::Status st;
|
||||
PLOT_CHECK(ctx.create(&st));
|
||||
PLOT_CHECK(plot::gl::load(nullptr));
|
||||
plot::Renderer r;
|
||||
PLOT_CHECK(r.init(&st));
|
||||
|
||||
plot::Plot p;
|
||||
p.set_viewport({0, 0, static_cast<plot::f32>(kShortW), static_cast<plot::f32>(kShortH)});
|
||||
p.add_x_axis({"seconds", {0.0, 1.0}, false, plot::AxisPlace::Bottom, false});
|
||||
p.add_y_axis({"v", {-1.0, 1.0}, false, plot::AxisPlace::Left, false});
|
||||
r.begin_frame(kShortW, kShortH,
|
||||
{0, 0, static_cast<plot::f32>(kShortW), static_cast<plot::f32>(kShortH)});
|
||||
r.draw(p);
|
||||
r.end_frame();
|
||||
PLOT_CHECK(!r.last_frame_had_gl_error());
|
||||
dump_if_requested(r, "x_title_clamped.ppm");
|
||||
|
||||
const plot::PlotLayout layout = p.layout();
|
||||
const plot::Color ink = r.style().axis_text;
|
||||
const plot::f32 line = r.axis_font_px();
|
||||
const plot::f32 pad = r.style().grid_label_pad;
|
||||
const plot::Rect& strip = layout.x_title_area;
|
||||
|
||||
// The clamp bit the way this case needs it to: not one row fits.
|
||||
PLOT_CHECK(p.titled_x_axis_count() == 1u);
|
||||
const plot::u32 fits = static_cast<plot::u32>(std::max(0.0f, (strip.h - pad) / line));
|
||||
PLOT_CHECK(fits == 0u);
|
||||
// ... and the plot kept its floor in a viewport this short.
|
||||
PLOT_CHECK(layout.axes.h >= 0.5f * static_cast<plot::f32>(kShortH));
|
||||
PLOT_CHECK(layout.axes.w > 0.0f);
|
||||
|
||||
// Nothing was staged for it: no title ink in the strip at all (a staged row's ink
|
||||
// would land here, cut off by the viewport's bottom edge), and none in the data
|
||||
// area either.
|
||||
PLOT_CHECK(!scan_rect_ink(ctx, strip, ink).any());
|
||||
PLOT_CHECK(!rect_has_exact(ctx, layout.axes, ink));
|
||||
ctx.destroy();
|
||||
}
|
||||
|
||||
// The same clamp one row short instead of absent: three titled x axes ask for three
|
||||
// rows and the strip holds one, so the rows it cut short are the trailing ones and must
|
||||
// be dropped rather than squeezed past its end. Each is partly visible otherwise -- its
|
||||
// cell starts inside the strip, and the rest of it is cut by the target's bottom edge,
|
||||
// which is exactly where an x strip ends -- so the strip's row bands are what says how
|
||||
// many were staged: one per row that fits, each inside its own row's cell.
|
||||
PLOT_TEST(short_viewport_keeps_only_the_x_title_rows_that_fit) {
|
||||
// 112 px tall: the label strip takes 23.69 px of the 56 px allowance, leaving a
|
||||
// 32.31 px strip against the 19.69 px a row needs and the 35.38 px two rows need, so
|
||||
// one row fits with 12.6 px of the strip left below it -- enough for the row that
|
||||
// must be dropped to show its glyph tops before the target's bottom edge cuts it.
|
||||
constexpr plot::u32 kShortW = 320;
|
||||
constexpr plot::u32 kShortH = 112;
|
||||
plottest::HeadlessContext ctx(kShortW, kShortH);
|
||||
plot::Status st;
|
||||
PLOT_CHECK(ctx.create(&st));
|
||||
PLOT_CHECK(plot::gl::load(nullptr));
|
||||
plot::Renderer r;
|
||||
PLOT_CHECK(r.init(&st));
|
||||
|
||||
plot::Plot p;
|
||||
p.set_viewport({0, 0, static_cast<plot::f32>(kShortW), static_cast<plot::f32>(kShortH)});
|
||||
p.add_x_axis({"seconds", {0.0, 1.0}, false, plot::AxisPlace::Bottom, false});
|
||||
p.add_x_axis({"samples", {0.0, 1.0}, false, plot::AxisPlace::Top, false});
|
||||
p.add_x_axis({"ticks", {0.0, 1.0}, false, plot::AxisPlace::Bottom, false});
|
||||
p.add_y_axis({"v", {-1.0, 1.0}, false, plot::AxisPlace::Left, false});
|
||||
r.begin_frame(kShortW, kShortH,
|
||||
{0, 0, static_cast<plot::f32>(kShortW), static_cast<plot::f32>(kShortH)});
|
||||
r.draw(p);
|
||||
r.end_frame();
|
||||
PLOT_CHECK(!r.last_frame_had_gl_error());
|
||||
dump_if_requested(r, "x_title_rows_clamped.ppm");
|
||||
|
||||
const plot::PlotLayout layout = p.layout();
|
||||
const plot::Color ink = r.style().axis_text;
|
||||
const plot::f32 line = r.axis_font_px();
|
||||
const plot::f32 pad = r.style().grid_label_pad;
|
||||
const plot::Rect& strip = layout.x_title_area;
|
||||
|
||||
// A row occupies the strip's top padding plus one line, so this is how many of the
|
||||
// three fit -- the rule the renderer applies, derived here rather than assumed.
|
||||
PLOT_CHECK(p.titled_x_axis_count() == 3u);
|
||||
const plot::u32 fits = static_cast<plot::u32>(std::max(0.0f, (strip.h - pad) / line));
|
||||
PLOT_CHECK(fits >= 1u);
|
||||
PLOT_CHECK(fits < 3u);
|
||||
PLOT_CHECK(layout.axes.h >= 0.5f * static_cast<plot::f32>(kShortH));
|
||||
PLOT_CHECK(layout.axes.w > 0.0f);
|
||||
|
||||
// One band of inked rows per row that fits, each inside its own row's cell (band i
|
||||
// starts at the padding plus i lines and is one line tall).
|
||||
const std::vector<InkBand> rows = ink_row_bands(ctx, strip, ink);
|
||||
PLOT_CHECK(rows.size() == fits);
|
||||
for (plot::u32 i = 0; i < static_cast<plot::u32>(rows.size()); ++i) {
|
||||
PLOT_CHECK(rows[i].first >=
|
||||
static_cast<plot::u32>(strip.y + pad + static_cast<plot::f32>(i) * line));
|
||||
PLOT_CHECK(rows[i].last <=
|
||||
static_cast<plot::u32>(strip.y + pad + static_cast<plot::f32>(i + 1u) * line));
|
||||
}
|
||||
ctx.destroy();
|
||||
}
|
||||
|
||||
// Finding 4 of the review: `mark_drawn()` dropped a failed fence's status, so the
|
||||
// slot stayed unsynchronised and the next upload memcpy'd into storage the GPU may
|
||||
// still have been reading -- exactly the tearing the ring exists to prevent. A
|
||||
|
||||
Reference in new issue
Block a user