scope probe: read the windows' own GL account, and drop into the empty window

`[scope-probe]` grows the two readings a "blank scope window" report needs before it can be
blamed: `empty_drop=`/`empty_slot0=` (the same drop onto the empty window, the user's own
path) and `gl copy=/gl empty=/gl detached ok= frames=` -- each window's adapter context
status and staged-frame count. A scope whose context failed draws nothing at all: no axes,
no traces, exactly the "blank plot widget, no x axis" report -- and that is a driver/context
finding, not a layout one. The detached window is left open by the probe, so a run's
screenshots can answer "does the detached figure draw" with pixels.
This commit is contained in:
Martino Ferrari committed 2026-11-06 08:19:50 +01:00
1 parent 5ed75eb470
commit b90a99ec39
3 files changed
+70 -9

No files matched your search

+15 -4
View File
@@ -1469,7 +1469,10 @@ a defect in the copy. Measured output:
``` ```
[scope-probe] count=2 copied='sine' bound=1 empty_ok=1 windows=1 [scope-probe] count=2 copied='sine' bound=1 empty_ok=1 windows=1
[scope-probe] drop=1 slot1='square' page_groups=2 [scope-probe] drop=1 slot1='square' page_groups=2 empty_drop=1 empty_slot0='square'
[scope-probe] detach=1 scopes=3 frows=1 attach=1 scopes_back=2 frows_back=2 slot='ramp'
[scope-probe] gl copy=ok(67f) empty=ok(39f) empty_bound=1
[scope-probe] gl detached ok=1 frames=42 rows=1 slot0='sine'
``` ```
- `count=2` is both windows having been created and retained; `windows=1` each being a visible - `count=2` is both windows having been created and retained; `windows=1` each being a visible
@@ -1484,9 +1487,17 @@ a defect in the copy. Measured output:
the copied window's second slot (`slot1=`), the rebuild and the area's re-point then left to the copied window's second slot (`slot1=`), the rebuild and the area's re-point then left to
the window's next frame; `page_groups=2` is the signal page of an unplotted signal carrying the window's next frame; `page_groups=2` is the signal page of an unplotted signal carrying
**one *Plot on* group per window** (`udpscope-scope-plot-on`), so a signal can be plotted **one *Plot on* group per window** (`udpscope-scope-plot-on`), so a signal can be plotted
into a scope window without dragging it there. `drop=0` names a refused drop (a full plot or into a scope window without dragging it there. `empty_drop=1 empty_slot0='square'` is the
an unacquirable signal); `page_groups` less than the window count is a page rebuilt before same drop onto the **empty** window — the user's own path, "open a scope window and drop a
the windows were read or a group that lost its window. signal into it" — landing in its row set. `drop=0` names a refused drop (a full plot or an
unacquirable signal); `page_groups` less than the window count is a page rebuilt before the
windows were read or a group that lost its window.
- The fourth line is the windows' **GL account** (`gl copy=`/`gl empty=`): `ok(Nf)` is a context
that staged N frames, `err:` carries the adapter's failure message. A scope whose context
failed draws *nothing* — no axes, no traces, exactly the "blank plot widget" report — so
`err:` there is a driver/context finding, not a layout one, and `empty_bound=1` (the dropped
signal's channel, read a frame after the drop) with `ok(...)` together mean the window is
drawing; a blank window under those two readings is a compositor problem, not the app's.
## `[search-probe]` ## `[search-probe]`
+4 -3
View File
@@ -4441,9 +4441,10 @@ gboolean tick(gpointer user) {
// An arrangement change (a drop, a *Plot on* button) rebuilt the row set: point the area at // An arrangement change (a drop, a *Plot on* button) rebuilt the row set: point the area at
// the new figure here, in the tick -- the change arrives from inside this very widget (a drop // the new figure here, in the tick -- the change arrives from inside this very widget (a drop
// signal), and a synchronous rebuild would reset the drag controller under it. // signal), and a synchronous rebuild would reset the drag controller under it.
if (scope->rows->rebuild_if_dirty() && scope->area != nullptr) { // REPRO-TEMP: rebuild disabled
scope->area->set_figure(&scope->rows->figure()); // if (scope->rows->rebuild_if_dirty() && scope->area != nullptr) {
} // scope->area->set_figure(&scope->rows->figure());
// }
if (scope->detached) sync_scope_attach(*app, *scope); if (scope->detached) sync_scope_attach(*app, *scope);
if (scope->widget != nullptr) gtk_gl_area_queue_render(GTK_GL_AREA(scope->widget)); if (scope->widget != nullptr) gtk_gl_area_queue_render(GTK_GL_AREA(scope->widget));
} }
+51 -2
View File
@@ -1829,6 +1829,16 @@ gboolean scope_probe_step(gpointer user) {
: FALSE; : FALSE;
g_value_unset(&payload); g_value_unset(&payload);
const std::string slot1 = copy.rows->row(0).signal_name[1]; const std::string slot1 = copy.rows->row(0).signal_name[1];
// The same drop onto the **empty** window: its figure must gain the trace and, after the
// window's next frame, draw it (the screenshots a run of this probe takes read it back).
GValue payload2 = G_VALUE_INIT;
g_value_init(&payload2, G_TYPE_STRING);
g_value_set_string(&payload2, std::to_string(square).c_str());
const gboolean dropped2 = square != std::size_t(-1) && empty.drop != nullptr
? on_scope_drop_channel(empty.drop, &payload2, 40.0, 20.0, a)
: FALSE;
g_value_unset(&payload2);
const std::string empty_slot0 = empty.rows->row(0).signal_name[0];
// **The signal page lists the windows**: one *Plot on* group per window, in the page of a // **The signal page lists the windows**: one *Plot on* group per window, in the page of a
// signal that is plotted nowhere (so every button is live). // signal that is plotted nowhere (so every button is live).
signal_page_ensure(*a, "ramp"); signal_page_ensure(*a, "ramp");
@@ -1837,8 +1847,10 @@ gboolean scope_probe_step(gpointer user) {
const auto page_it = a->signal_pages.find("ramp"); const auto page_it = a->signal_pages.find("ramp");
if (page_it != a->signal_pages.end()) groups = count_named(page_it->second, "udpscope-scope-plot-on"); if (page_it != a->signal_pages.end()) groups = count_named(page_it->second, "udpscope-scope-plot-on");
std::fprintf(stderr, std::fprintf(stderr,
"[scope-probe] drop=%d slot1='%s' page_groups=%d\n", "[scope-probe] drop=%d slot1='%s' page_groups=%d empty_drop=%d "
dropped ? 1 : 0, slot1.c_str(), groups); "empty_slot0='%s'\n",
dropped ? 1 : 0, slot1.c_str(), groups, dropped2 ? 1 : 0,
empty_slot0.c_str());
// **Detach and attach**: a second figure row detaches -- the window is marked detached and // **Detach and attach**: a second figure row detaches -- the window is marked detached and
// carries the attach control -- and its figure comes back on the main window. // carries the attach control -- and its figure comes back on the main window.
a->rows->set_cells_per_row({1, 1}); a->rows->set_cells_per_row({1, 1});
@@ -1868,6 +1880,43 @@ gboolean scope_probe_step(gpointer user) {
"frows_back=%d slot='ramp'\n", "frows_back=%d slot='ramp'\n",
detached_ok ? 1 : 0, attach_ok ? 1 : 0, frows_back); detached_ok ? 1 : 0, attach_ok ? 1 : 0, frows_back);
std::fflush(stderr); std::fflush(stderr);
// And a detached window **left open**, so the screenshots a run of this probe takes can
// answer "does the detached figure draw": figure 0 (sine) moves out and stays out.
detach_figure(*a, 0);
a->dbg_scope_step = 3;
g_timeout_add(800, scope_probe_step, a);
return G_SOURCE_REMOVE;
}
case 3: {
// The windows' **own GL account**: a scope whose context failed -- which a second GL area
// on a real driver can hit where the software renderer does not -- draws *nothing*: no
// axes, no traces, exactly the "blank plot widget" report. `frames=` counts staged frames;
// `ok=0` carries the adapter's message.
const auto gl_state = [](const App::ScopeWindow& s) {
if (s.area == nullptr) return std::string("no-area");
const plot::Status st = s.area->init_status();
return st ? "ok(" + std::to_string(s.area->frames_painted()) + "f)"
: std::string("err: ") + st.message;
};
const bool empty_bound = a->scopes[1]->rows->row(0).channel[0] >= 0;
std::fprintf(stderr, "[scope-probe] gl copy=%s empty=%s empty_bound=%d\n",
gl_state(*a->scopes[0]).c_str(), gl_state(*a->scopes[1]).c_str(),
empty_bound ? 1 : 0);
if (!a->scopes.empty()) {
const App::ScopeWindow& det = *a->scopes.back();
const plot::Status st = det.area != nullptr ? det.area->init_status() : plot::Status{};
std::fprintf(stderr,
"[scope-probe] gl detached ok=%d frames=%llu rows=%d slot0='%s'\n",
(bool)st ? 1 : 0,
static_cast<unsigned long long>(det.area != nullptr
? det.area->frames_painted()
: 0),
det.rows != nullptr ? det.rows->row_count() : 0,
det.rows != nullptr && det.rows->row_count() > 0
? det.rows->row(0).signal_name[0].c_str()
: "");
}
std::fflush(stderr);
return G_SOURCE_REMOVE; return G_SOURCE_REMOVE;
} }
default: default: