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:
1 parent
3555ab63d7
commit
8f45108559
11 files changed
+492
-70
No files matched your search
+47
-32
@@ -91,10 +91,11 @@ bool Renderer::init(Status* st) noexcept {
|
||||
// best-effort (see api.hpp), so a context can advertise the extension without
|
||||
// the entry points being reachable.
|
||||
const gl::Caps& caps = gl::caps();
|
||||
persistent_ = caps.buffer_storage && caps.persistent_map && gl::glBufferStorage != nullptr &&
|
||||
gl::glMapBufferRange != nullptr && !force_gl33_upload();
|
||||
const bool persistent = caps.buffer_storage && caps.persistent_map &&
|
||||
gl::glBufferStorage != nullptr && gl::glMapBufferRange != nullptr &&
|
||||
!force_gl33_upload();
|
||||
const render::UploadMode mode =
|
||||
persistent_ ? render::UploadMode::PersistentMapped : render::UploadMode::Orphan;
|
||||
persistent ? render::UploadMode::PersistentMapped : render::UploadMode::Orphan;
|
||||
|
||||
font_backend_.reset(font_, style_.axis_font_px);
|
||||
|
||||
@@ -176,17 +177,20 @@ void Renderer::draw(Plot& plot) {
|
||||
// from the values the renderer draws with, so the label pad is never one number
|
||||
// for the layout and another for the text placed inside it.
|
||||
plot.set_style(style_);
|
||||
(void)plot.layout(); // autoscale, ticks and grid for the final axes size
|
||||
// One layout for the whole frame: autoscale, the strip fixed-point loop with
|
||||
// its tick rebuilds, and the grid build all run in here, and every helper
|
||||
// below reads the strips, the axes rect and the transforms it pins.
|
||||
const PlotLayout& layout = plot.layout();
|
||||
|
||||
// Draw order: chrome, then data, then the annotations over it, and the text
|
||||
// last (it is texture-bound, so it is one draw call after every line batch).
|
||||
draw_grid(plot);
|
||||
draw_axis_lines(plot);
|
||||
draw_tick_labels(plot);
|
||||
draw_titles(plot);
|
||||
draw_traces(plot);
|
||||
draw_cursors(plot);
|
||||
draw_measures(plot);
|
||||
draw_tick_labels(plot, layout);
|
||||
draw_titles(plot, layout);
|
||||
draw_traces(plot, layout);
|
||||
draw_cursors(plot, layout);
|
||||
draw_measures(plot, layout);
|
||||
draw_annotations(plot);
|
||||
flush_text();
|
||||
}
|
||||
@@ -241,8 +245,12 @@ void Renderer::flush_lines(const Mapping& map, Color color, f32 width) noexcept
|
||||
gl::glDrawArraysInstanced(gl::GL_TRIANGLE_STRIP, 0, 4,
|
||||
static_cast<gl::GLsizei>(line_vao_.instance_count()));
|
||||
// The draw is in the command stream now, so this is the point at which the
|
||||
// mapped path can let a fence follow it.
|
||||
line_vao_.mark_drawn();
|
||||
// mapped path can let a fence follow it. A driver that refused the fence has
|
||||
// already dropped this store to the baseline path; the frame reports it here
|
||||
// rather than rotating through a slot nothing guards.
|
||||
if (!line_vao_.mark_drawn()) {
|
||||
gl_error_ = true;
|
||||
}
|
||||
stats_.staged_segments += segments;
|
||||
++stats_.draw_calls;
|
||||
}
|
||||
@@ -265,7 +273,9 @@ void Renderer::flush_text() noexcept {
|
||||
text_vao_.bind();
|
||||
gl::glDrawArraysInstanced(gl::GL_TRIANGLE_STRIP, 0, 4,
|
||||
static_cast<gl::GLsizei>(text_vao_.instance_count()));
|
||||
text_vao_.mark_drawn();
|
||||
if (!text_vao_.mark_drawn()) {
|
||||
gl_error_ = true;
|
||||
}
|
||||
++stats_.draw_calls;
|
||||
}
|
||||
glyphs_.clear();
|
||||
@@ -363,8 +373,7 @@ void Renderer::draw_axis_lines(const Plot& plot) {
|
||||
}
|
||||
}
|
||||
|
||||
void Renderer::draw_tick_labels(const Plot& plot) {
|
||||
const PlotLayout& layout = plot.layout();
|
||||
void Renderer::draw_tick_labels(const Plot& plot, const PlotLayout& layout) {
|
||||
const f32 line_h = font_backend_.height();
|
||||
|
||||
// X labels: centred under their tick, on the strip's single text line.
|
||||
@@ -397,34 +406,41 @@ void Renderer::draw_tick_labels(const Plot& plot) {
|
||||
}
|
||||
}
|
||||
|
||||
void Renderer::draw_titles(const Plot& plot) {
|
||||
// The layout reserves one text line per strip for the tick labels, so an axis
|
||||
// title has no room outside the axes rect. It hugs the inside of its axis line
|
||||
// instead, at the far end of the axis, where it needs no extra layout space and
|
||||
// cannot collide with the labels of the axis it names.
|
||||
const Rect& axes = plot.layout().axes;
|
||||
void Renderer::draw_titles(const Plot& plot, const PlotLayout& layout) {
|
||||
// Every title goes in the strip its direction reserved for it, one text line
|
||||
// outside the tick labels: below the x labels for an x axis, beside the y labels
|
||||
// for a y axis. The strips are what makes a title over the data impossible --
|
||||
// `layout.axes` is inset by them -- rather than merely unlikely, so nothing here
|
||||
// may fall back to an inside-the-axes position.
|
||||
const f32 line_h = font_backend_.height();
|
||||
|
||||
for (u32 i = 0; i < plot.x_axis_count(); ++i) {
|
||||
const Axis& axis = plot.x_axis(i);
|
||||
if (axis.label.empty()) continue;
|
||||
// Centred on the strip: the tick labels have their own row above it, so the
|
||||
// title needs no alignment of its own.
|
||||
const f32 width = font_backend_.measure(axis.label.c_str());
|
||||
const f32 top = axis.place == AxisPlace::Top ? axes.y + 2.0f : axes.bottom() - line_h - 2.0f;
|
||||
stage_text({axes.right() - width - 2.0f, top}, axis.label.c_str());
|
||||
stage_text({layout.x_title_area.x + 0.5f * (layout.x_title_area.w - width),
|
||||
layout.x_title_area.y + 0.5f * (layout.x_title_area.h - line_h)},
|
||||
axis.label.c_str());
|
||||
}
|
||||
for (u32 i = 0; i < plot.y_axis_count(); ++i) {
|
||||
const Axis& axis = plot.y_axis(i);
|
||||
if (axis.label.empty()) continue;
|
||||
// A y title is a horizontal run in the vertical strip the layout sized for
|
||||
// the widest of them, centred on the axes' height: it reads like the x title,
|
||||
// only beside the plot instead of under it. A run longer than the axes is
|
||||
// clipped by the viewport, never folded back over the data.
|
||||
const f32 width = font_backend_.measure(axis.label.c_str());
|
||||
const f32 left = axis.place == AxisPlace::Right ? axes.right() - width - 2.0f : axes.x + 2.0f;
|
||||
stage_text({left, axes.y + 2.0f}, axis.label.c_str());
|
||||
stage_text({layout.y_title_area.x + 0.5f * (layout.y_title_area.w - width),
|
||||
layout.y_title_area.y + 0.5f * (layout.y_title_area.h - line_h)},
|
||||
axis.label.c_str());
|
||||
}
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------- data
|
||||
|
||||
void Renderer::draw_traces(const Plot& plot) {
|
||||
const PlotLayout& layout = plot.layout();
|
||||
void Renderer::draw_traces(const Plot& plot, const PlotLayout& layout) {
|
||||
if (!(layout.axes.w > 0.0f)) {
|
||||
return; // a collapsed plot area has no pixel to select a level for
|
||||
}
|
||||
@@ -472,8 +488,7 @@ void Renderer::draw_traces(const Plot& plot) {
|
||||
}
|
||||
}
|
||||
|
||||
void Renderer::draw_cursors(const Plot& plot) {
|
||||
const PlotLayout& layout = plot.layout();
|
||||
void Renderer::draw_cursors(const Plot& plot, const PlotLayout& layout) {
|
||||
const Range y_range = plot.y_range(0);
|
||||
const bool log_y = plot.y_axis(0).log;
|
||||
|
||||
@@ -495,8 +510,7 @@ void Renderer::draw_cursors(const Plot& plot) {
|
||||
}
|
||||
}
|
||||
|
||||
void Renderer::draw_measures(const Plot& plot) {
|
||||
const PlotLayout& layout = plot.layout();
|
||||
void Renderer::draw_measures(const Plot& plot, const PlotLayout& layout) {
|
||||
const Rect& axes = layout.axes;
|
||||
const Range y_range = plot.y_range(0);
|
||||
const bool log_y = plot.y_axis(0).log;
|
||||
@@ -544,8 +558,9 @@ void Renderer::draw_measures(const Plot& plot) {
|
||||
const f32 limit = std::max(axes.x + 2.0f, axes.right() - width - 2.0f);
|
||||
const f32 left = std::clamp(0.5f * (x0 + x1) - 0.5f * width, axes.x + 2.0f, limit);
|
||||
// The second line of the plot area, not the first: the first belongs to the
|
||||
// y axis title (and to cursor labels), and a wide readout clamped to the left
|
||||
// edge would overprint them.
|
||||
// cursor labels, and a wide readout clamped to the left edge would overprint
|
||||
// them. The y axis title is not a consideration here -- it lives in its own
|
||||
// strip outside the axes.
|
||||
stage_text({left, axes.y + line_h + 2.0f}, readout);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user