fix(render): bind identity into the epoch, titles into reserved strips, one layout per frame

Four review findings on the Task 7 renderer:

- InstanceStore::epoch() is derived from gl::Buffer::generation() -- advanced by
  every allocation, deletion and move -- plus the slot the instances were written
  to, so the orphan fallback of grow_persistent() can no longer leave the vertex
  array's attribute pointers bound to a deleted buffer: the epoch reports the new
  identity and upload() re-points them before the draw.
- PlotLayout reserves y_title_area/x_title_area outside axes (sized from the text
  metrics) and draw_titles() draws in them, so a title can no longer land on the
  data, whatever the envelope does.
- draw() computes plot.layout() once and hands it to the helpers, instead of six
  times per frame, each of which rebuilt the ticks and the grid.
- A refused fence is no longer dropped: mark_drawn() falls back to the orphan path
  and reports the failure rather than rotating through an unsynchronised slot.

New smoke cases force both refusals through the GL entry points (glMapBufferRange,
glFenceSync) and assert the pixels plus the live transfer mode; the title case
asserts the title colour is absent from the bands inside axes and present in both
strips. All three fail against the pre-fix sources.
This commit is contained in:
Martino Ferrari committed 2026-09-27 23:19:31 +02:00
1 parent 3555ab63d7
commit 8f45108559
11 files changed
+492 -70

No files matched your search

+40 -2
View File
@@ -63,6 +63,12 @@ PLOT_TEST(layout_without_text_backend_uses_default_label_areas) {
PLOT_CHECK_NEAR(l.x_label_area.y, 378.0f, 1e-6);
PLOT_CHECK_NEAR(l.x_label_area.w, 744.0f, 1e-6);
PLOT_CHECK_NEAR(l.x_label_area.h, 22.0f, 1e-6);
// "v" is a title, but no backend can measure it, so nothing is reserved: the
// defaults above are what a plot without a text backend keeps laying out with.
PLOT_CHECK_NEAR(l.y_title_width, 0.0f, 1e-6);
PLOT_CHECK_NEAR(l.x_title_height, 0.0f, 1e-6);
PLOT_CHECK_NEAR(l.y_title_area.w, 0.0f, 1e-6);
PLOT_CHECK_NEAR(l.x_title_area.h, 0.0f, 1e-6);
}
PLOT_TEST(layout_derives_label_areas_from_font_metrics) {
@@ -70,13 +76,45 @@ PLOT_TEST(layout_derives_label_areas_from_font_metrics) {
plot::Plot p;
p.set_text_backend(&font);
p.set_viewport({0.0f, 0.0f, 800.0f, 400.0f});
const u32 ay = p.add_y_axis({"v", {-1000.0, 1000.0}, false, plot::AxisPlace::Left, false});
const u32 ay = p.add_y_axis({"volts", {-1000.0, 1000.0}, false, plot::AxisPlace::Left, false});
const plot::PlotLayout& l = p.layout();
const f32 pad = p.style().grid_label_pad;
PLOT_CHECK_NEAR(l.y_axis_width, widest_major_label(p.y_axis(ay), font) + 2.0f * pad, 1e-4);
PLOT_CHECK_NEAR(l.x_axis_height, font.height() + 2.0f * pad, 1e-4);
// The y axis carries the title "volts": it is drawn horizontally, so the strip
// left of the label strip is as wide as the measured title. There is no x axis,
// so the x direction reserves nothing beyond its label strip.
PLOT_CHECK_NEAR(l.y_title_width, font.measure("volts") + 2.0f * pad, 1e-4);
PLOT_CHECK_NEAR(l.x_title_height, 0.0f, 1e-6);
PLOT_CHECK_NEAR(l.axes.x, l.y_title_width + l.y_axis_width, 1e-4);
PLOT_CHECK_NEAR(l.axes.w, 800.0f - (l.y_title_width + l.y_axis_width), 1e-4);
PLOT_CHECK_NEAR(l.axes.h, 400.0f - (l.x_axis_height + l.x_title_height), 1e-4);
// The strips tile the viewport outside `axes`: title, then labels, then data.
PLOT_CHECK_NEAR(l.y_title_area.x, 0.0f, 1e-6);
PLOT_CHECK_NEAR(l.y_title_area.right(), l.y_label_area.x, 1e-4);
PLOT_CHECK_NEAR(l.y_label_area.right(), l.axes.x, 1e-4);
PLOT_CHECK_NEAR(l.x_title_area.y, l.x_label_area.bottom(), 1e-4);
PLOT_CHECK_NEAR(l.x_title_area.bottom(), l.axes.bottom() + l.x_label_area.h + l.x_title_height,
1e-4);
PLOT_CHECK_NEAR(l.x_label_area.y, l.axes.bottom(), 1e-4);
}
/// An axis with no title reserves nothing: the strips exist so that a titled axis
/// has somewhere outside `axes` to draw in, not as a fixed cost per plot.
PLOT_TEST(layout_reserves_no_title_strip_without_a_title) {
plot::render::BitmapFont font;
plot::Plot p;
p.set_text_backend(&font);
p.set_viewport({0.0f, 0.0f, 800.0f, 400.0f});
p.add_x_axis({"", {0.0, 1.0}, false, plot::AxisPlace::Bottom, false});
p.add_y_axis({"", {-1.0, 1.0}, false, plot::AxisPlace::Left, false});
const plot::PlotLayout& l = p.layout();
PLOT_CHECK_NEAR(l.y_title_width, 0.0f, 1e-6);
PLOT_CHECK_NEAR(l.x_title_height, 0.0f, 1e-6);
PLOT_CHECK_NEAR(l.y_title_area.w, 0.0f, 1e-6);
PLOT_CHECK_NEAR(l.x_title_area.h, 0.0f, 1e-6);
PLOT_CHECK_NEAR(l.y_label_area.x, 0.0f, 1e-6);
PLOT_CHECK_NEAR(l.axes.x, l.y_axis_width, 1e-4);
PLOT_CHECK_NEAR(l.axes.w, 800.0f - l.y_axis_width, 1e-4);
PLOT_CHECK_NEAR(l.axes.h, 400.0f - l.x_axis_height, 1e-4);
}