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

+31 -2
View File
@@ -16,16 +16,32 @@
namespace plot {
/// Pixel geometry of a plot's chrome, all of it in the same pixel space as
/// `Rect` (origin top-left). `axes` is the area data is drawn into; the two label
/// areas are the strips the tick labels live in.
/// `Rect` (origin top-left). `axes` is the area data is drawn into; the label
/// areas are the strips the tick labels live in and the title areas the strips
/// the axis titles live in, each outside `axes` and so never over the data.
///
/// `y_*` strips are left of `axes` and `x_*` ones below it, whatever an axis'
/// `place` says: an axis placed on the opposite edge still has its labels and
/// its title on the side the layout reserves, exactly as it always has.
struct PlotLayout {
Rect axes;
Rect y_label_area;
Rect x_label_area;
/// One text line each, just outside the matching label area. Zero-sized (and
/// unoccupied) when no axis of that direction has a title.
Rect y_title_area;
Rect x_title_area;
/// Left strip width. The default is used when no text backend has been set.
f32 y_axis_width = 56.0f;
/// Bottom strip height. The default is used when no text backend has been set.
f32 x_axis_height = 22.0f;
/// Width of `y_title_area` and height of `x_title_area`, both 0.0f when nothing
/// is reserved. An x title is one text line in a strip as wide as the axes; a y
/// title is a horizontal run in a vertical strip, so the strip is as wide as the
/// widest y title (plus the label padding) -- one text line would cut it off.
/// With no text backend there is no title to measure and both stay 0.0f.
f32 y_title_width = 0.0f;
f32 x_title_height = 0.0f;
};
/// The public façade: axes, traces, cursors, annotations and their layout.
@@ -155,6 +171,12 @@ class Plot {
/// layout. The label strips come from the text backend's metrics when one is
/// set (`measure()` of the widest y tick label, `height()` for the x strip) and
/// from PlotLayout's defaults otherwise.
///
/// `axes` is inset by the label strip of each occupied edge and, when that
/// direction has a titled axis, by the strip that title lives in (`y_title_width`
/// and `x_title_height` say how much that is): a title can then only be drawn
/// outside the data, whatever the title says. Call this once per frame -- it
/// rebuilds the ticks and the grid -- and pass the result down.
const PlotLayout& layout();
/// The layout as of the last refresh. The const overload never recomputes, so
/// a lookup on a const Plot cannot move the axes out from under the caller.
@@ -197,6 +219,13 @@ class Plot {
/// Widest y tick label plus the label padding on both sides: the left strip the
/// text backend asks for. Zero with no backend or no y axis.
f32 y_label_width() const;
/// True when any axis of that direction carries a title, i.e. when layout()
/// must reserve the strip `Renderer::draw_titles()` draws it in.
bool has_x_title() const noexcept;
bool has_y_title() const noexcept;
/// Widest y axis title plus the label padding on both sides: the vertical strip
/// a horizontal y title fits in. Zero with no backend or no titled y axis.
f32 y_title_strip_width() const;
Rect viewport_{};
render::Style style_{};