scope windows: their own frame pump, and a diagnosis that names the GSK renderer

A second toplevel's frame clock is not the main window's: the tick binds, queues a render and
mirrors the span, but with nothing asking GTK for frames on *that* window the area can sit with
its queue full and present nothing -- a blank plot. Every scope window now carries a tick
callback on its own area (`gtk_gl_area_queue_render` per frame), which is the request that keeps
its frame clock running.

The "cannot paint" diagnosis now names the window's GSK renderer (`[GSK renderer: GskVulkanRenderer]`)
and says to restart with `GSK_RENDERER=gl` when it is the Vulkan one -- the interaction between
the Vulkan GSK renderer and an embedded `GtkGLArea` in a second toplevel is a known trouble spot,
and a tiling WM has no title bar to carry a title, so the sentence goes to stderr and the status
bar instead.
This commit is contained in:
Martino Ferrari committed 2026-11-06 10:09:28 +01:00
1 parent fa2d0f12fc
commit 2279e2140a
2 files changed
+27 -3

No files matched your search

+6 -2
View File
@@ -149,8 +149,12 @@ the signal page's *Plot on* buttons list **every window**: one group per window
menu** — y axis (auto or a fixed range), *Fit X axis*, *Fit all*, remove a trace — acting on that menu** — y axis (auto or a fixed range), *Fit X axis*, *Fit all*, remove a trace — acting on that
window's rows (the x fit resets the **shared** time span, so every view follows); a detached window's rows (the x fit resets the **shared** time span, so every view follows); a detached
window's menu carries **Attach back to the main window** beside them. A window that cannot paint window's menu carries **Attach back to the main window** beside them. A window that cannot paint
says so in its own title after two seconds (and on stderr): a blank plot is indistinguishable from says so on stderr and in the status bar after two seconds, naming its **GSK renderer**: a blank
a context that failed, and the sentence names which. plot is indistinguishable from a context that failed, and the GTK **Vulkan** renderer is known to
interact badly with an embedded `GtkGLArea` in a second toplevel — the sentence then says to
restart with `GSK_RENDERER=gl`. Each scope window also carries a **tick callback of its own**,
which keeps its toplevel's frame clock advancing: the main window's tick binds and queues the
render, but a second window whose clock never ticks presents nothing.
mode or the span. mode or the span.
**The window.** The plot is the window. Under it sits a fixed-height **badge bar** (36 px). Its left **The window.** The plot is the window. Under it sits a fixed-height **badge bar** (36 px). Its left
+21 -1
View File
@@ -621,6 +621,16 @@ void open_scope_window(App& app, int figure_row, bool detached) {
g_object_set_data(G_OBJECT(scope->drop), "udpscope-scope", scope.get()); g_object_set_data(G_OBJECT(scope->drop), "udpscope-scope", scope.get());
g_signal_connect(scope->drop, "drop", G_CALLBACK(on_scope_drop_channel), &app); g_signal_connect(scope->drop, "drop", G_CALLBACK(on_scope_drop_channel), &app);
gtk_widget_add_controller(scope->widget, GTK_EVENT_CONTROLLER(scope->drop)); gtk_widget_add_controller(scope->widget, GTK_EVENT_CONTROLLER(scope->drop));
// The window's own frame pump: a tick callback on its area asks GTK for one frame per tick,
// which is what keeps *this* toplevel's frame clock running. The main window's tick binds and
// queues the render, but a second toplevel whose clock is never advanced keeps its queue full
// and presents nothing -- a blank plot with axes nowhere to be seen.
gtk_widget_add_tick_callback(scope->widget,
+[](GtkWidget* widget, GdkFrameClock*, gpointer) -> gboolean {
gtk_gl_area_queue_render(GTK_GL_AREA(widget));
return G_SOURCE_CONTINUE;
},
nullptr, nullptr);
// The window's own right-click menu: its y scales, its traces, its removals -- the same // The window's own right-click menu: its y scales, its traces, its removals -- the same
// actions the main window's plot menu carries, aimed at **this window's** row set. // actions the main window's plot menu carries, aimed at **this window's** row set.
GtkGesture* right = gtk_gesture_click_new(); GtkGesture* right = gtk_gesture_click_new();
@@ -4645,12 +4655,22 @@ gboolean tick(gpointer user) {
const plot::Status st = scope->area->init_status(); const plot::Status st = scope->area->init_status();
const plot::u64 painted = scope->area->frames_painted(); const plot::u64 painted = scope->area->frames_painted();
if (!st || painted == 0) { if (!st || painted == 0) {
const std::string why = std::string why =
st ? std::string("the plot painted no frame in two seconds") st ? std::string("the plot painted no frame in two seconds")
: std::string("GL context failed: ") + st.message; : std::string("GL context failed: ") + st.message;
// Name the GSK renderer: the Vulkan one is known to interact badly with an embedded
// `GtkGLArea` in a second toplevel, and the workaround is one environment variable.
if (GskRenderer* r = gtk_native_get_renderer(GTK_NATIVE(scope->window))) {
const char* type = G_OBJECT_TYPE_NAME(r);
why += std::string(" [GSK renderer: ") + type + "]";
if (g_strstr_len(type, -1, "Vulkan") != nullptr) {
why += "; restart with GSK_RENDERER=gl to check";
}
}
std::fprintf(stderr, "[scope] window '%s' is not drawing: %s\n", std::fprintf(stderr, "[scope] window '%s' is not drawing: %s\n",
scope->name.c_str(), why.c_str()); scope->name.c_str(), why.c_str());
std::fflush(stderr); std::fflush(stderr);
set_notice(*app, "window '" + scope->name + "' is not drawing: " + why, 10);
if (scope->window != nullptr) { if (scope->window != nullptr) {
const char* current = gtk_window_get_title(scope->window); const char* current = gtk_window_get_title(scope->window);
gtk_window_set_title(scope->window, gtk_window_set_title(scope->window,