perf: a frozen frame is not re-rendered sixty times a second
The tick queued a GL render unconditionally. In `Held` and `Paused` the rows are bound to an immutable snapshot on a pinned axis, so a frame in which none of the picture's own terms moved is the same frame again. The gate keys on a coarse signature of what a frozen view can be re-framed by, opens for `kSettleRenders` frames on a change because the figure lays out per render, and has a floor that renders anyway every few ticks. The layout editor is excluded: its buttons are placed from the last frame's rects, so a skipped frame there is a stale widget rather than a stale picture. `skips=` in the probe dump is the reading. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
1 parent
84c158ff21
commit
be71a1d4bb
5 files changed
+130
-4
No files matched your search
+45
-1
@@ -3706,7 +3706,51 @@ gboolean tick(gpointer user) {
|
||||
window_w < kSidebarRoom);
|
||||
}
|
||||
}
|
||||
if (app->area_widget != nullptr) gtk_gl_area_queue_render(GTK_GL_AREA(app->area_widget));
|
||||
// The render, queued once a frame -- but not unconditionally. In `Held` and `Paused` the rows
|
||||
// are bound to an immutable snapshot on a pinned axis and the producer is feeding the live ring,
|
||||
// not the series on screen, so a frame in which none of the picture's own terms moved is the
|
||||
// same frame again. Every other mode renders: `Live` and `Capturing` roll new samples under the
|
||||
// axis, and `Waiting` moves the live epoch, so in all three the picture differs every frame.
|
||||
//
|
||||
// The gate is two parts, and the second is what makes the first safe to keep coarse:
|
||||
// * a signature of what a frozen view *can* be re-framed by -- a new snapshot, a pan or zoom
|
||||
// (the group's range, which is where the gestures and the pin both write), a row added or
|
||||
// removed -- which renders the moment it changes;
|
||||
// * `kStaticRenderEvery`, a floor that renders anyway every few ticks, so anything not in the
|
||||
// signature is late rather than lost. A gate that can only be opened by a list of state is
|
||||
// a gate that goes wrong the first time the list is one item short.
|
||||
// Interaction needs neither: `PlotArea` queues its own render from the pointer, scroll and drag
|
||||
// handlers, so a crosshair and a cursor drag are immediate whatever this decides.
|
||||
//
|
||||
// The layout editor is excluded outright: its buttons are placed from the rects the last frame
|
||||
// was laid out with (`sync_layout_editor()` above), so it is the one state where a skipped frame
|
||||
// is a stale *widget* and not just a stale picture.
|
||||
if (app->area_widget != nullptr) {
|
||||
const bool frozen = (display == DisplayMode::Held || display == DisplayMode::Paused) &&
|
||||
!app->layout_editing;
|
||||
const plot::Range view = app->rows->view_range();
|
||||
const std::uint64_t seq = model.event_seq();
|
||||
const int rows = app->rows->row_count();
|
||||
const bool same = frozen && seq == app->render_event_seq && view.min == app->render_x_min &&
|
||||
view.max == app->render_x_max && rows == app->render_rows;
|
||||
// A change opens the gate for `kSettleRenders` frames, not one: the figure's left gutter is a
|
||||
// monotone floor that converges over two layout passes (see the `layout over N frames`
|
||||
// reading), and a layout pass happens per *render* -- so a single frame after a re-frame would
|
||||
// leave the gutter one pass short until the floor below fired, 64 ms later.
|
||||
if (!same) app->render_open = kSettleRenders;
|
||||
if (app->render_open > 0 || app->render_skipped + 1 >= kStaticRenderEvery) {
|
||||
if (app->render_open > 0) --app->render_open;
|
||||
app->render_skipped = 0;
|
||||
gtk_gl_area_queue_render(GTK_GL_AREA(app->area_widget));
|
||||
} else {
|
||||
++app->render_skipped;
|
||||
++app->dbg_render_skips;
|
||||
}
|
||||
app->render_event_seq = seq;
|
||||
app->render_x_min = view.min;
|
||||
app->render_x_max = view.max;
|
||||
app->render_rows = rows;
|
||||
}
|
||||
// What the side column *demands*, not what it got. A GTK box's minimum width is its
|
||||
// children's, a `GtkLabel`'s minimum is its text, and a minimum larger than the screen is
|
||||
// honoured by growing the toplevel -- which hands the plot an allocation wider than the window
|
||||
|
||||
Reference in new issue
Block a user