diff --git a/README.md b/README.md index d1b3df7..6d8a953 100644 --- a/README.md +++ b/README.md @@ -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 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 -says so in its own title after two seconds (and on stderr): a blank plot is indistinguishable from -a context that failed, and the sentence names which. +says so on stderr and in the status bar after two seconds, naming its **GSK renderer**: a blank +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. **The window.** The plot is the window. Under it sits a fixed-height **badge bar** (36 px). Its left diff --git a/src/ui.cpp b/src/ui.cpp index d615d0b..b2d7419 100644 --- a/src/ui.cpp +++ b/src/ui.cpp @@ -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_signal_connect(scope->drop, "drop", G_CALLBACK(on_scope_drop_channel), &app); 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 // actions the main window's plot menu carries, aimed at **this window's** row set. GtkGesture* right = gtk_gesture_click_new(); @@ -4645,12 +4655,22 @@ gboolean tick(gpointer user) { const plot::Status st = scope->area->init_status(); const plot::u64 painted = scope->area->frames_painted(); if (!st || painted == 0) { - const std::string why = + std::string why = st ? std::string("the plot painted no frame in two seconds") : 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", scope->name.c_str(), why.c_str()); std::fflush(stderr); + set_notice(*app, "window '" + scope->name + "' is not drawing: " + why, 10); if (scope->window != nullptr) { const char* current = gtk_window_get_title(scope->window); gtk_window_set_title(scope->window,