Add --renderer for displays with no usable GPU; gate the header reading
GTK4 defaults to Vulkan, and an X11-forwarded display has no device to make a swapchain from; the plot is a GtkGLArea, so it then asks for a GL context that is not there either. --renderer cairo is the software preset for that display: it exports GSK_RENDERER=cairo and LIBGL_ALWAYS_SOFTWARE=1 in main() before gtk_init()/adw_init(), because both are read when the renderer and the context are created. --renderer gl is the faster choice when indirect GL works, and --renderer vulkan makes the default explicit. Nothing is exported, and GTK's own choice stands, when the option is absent. The once-a-second header line is now behind UDPSCOPE_PROBE_HEADER, following the [notrace] rule: its widths are still sampled for the exit dump, but the line is detail behind that reading rather than output every run prints. UDPSCOPE_PROBE_RENDERER prints the exported pair before GTK starts and the GskRenderer GTK actually built. README, the manual's overview and docs/claude record the two symptoms and the cure.
This commit is contained in:
1 parent
ac054a30c9
commit
fcf268d249
9 files changed
+151
-7
No files matched your search
@@ -62,6 +62,12 @@ void print_usage(std::FILE* out, const char* argv0) {
|
||||
" time is then kept for export only\n"
|
||||
" --open FILE open an exported waveform (.npy) instead of a live source:\n"
|
||||
" signals from its columns, no socket, no acquisition\n"
|
||||
" --renderer NAME render with cairo (software), gl or vulkan. cairo also\n"
|
||||
" exports LIBGL_ALWAYS_SOFTWARE=1, because the plot is a\n"
|
||||
" GtkGLArea and still needs a GL context. Over SSH/X11\n"
|
||||
" forwarding use cairo: Vulkan cannot make a swapchain there\n"
|
||||
" and the GL area has no hardware context, so the window\n"
|
||||
" never paints or allocates\n"
|
||||
" -h, --help this text\n",
|
||||
argv0);
|
||||
}
|
||||
@@ -113,6 +119,15 @@ Config parse_args(int argc, char** argv) {
|
||||
c.capture_packets = static_cast<std::uint32_t>(n);
|
||||
} else if (a == "--open" || a.rfind("--open=", 0) == 0) {
|
||||
c.open_waveform = value_of(argc, argv, i, "--open");
|
||||
} else if (a == "--renderer" || a.rfind("--renderer=", 0) == 0) {
|
||||
const std::string v = value_of(argc, argv, i, "--renderer");
|
||||
// Only the three a source or a forwarded display actually chooses between. Anything
|
||||
// GTK does not know would fall back with a warning and leave the user reading a
|
||||
// renderer failure that is really a typo, so it is refused here instead.
|
||||
if (v != "cairo" && v != "gl" && v != "vulkan") {
|
||||
fail("--renderer must be cairo, gl or vulkan: " + v);
|
||||
}
|
||||
c.renderer = v;
|
||||
} else if (a == "--relative-time") {
|
||||
c.relative_time = true;
|
||||
c.relative_time_specified = true;
|
||||
|
||||
@@ -60,6 +60,15 @@ struct Config {
|
||||
/// and the chooser's Waveforms page sets this rather than the connection fields.
|
||||
std::string open_waveform;
|
||||
|
||||
/// `--renderer NAME`: the GSK renderer to force, by exporting `GSK_RENDERER` **before any GTK
|
||||
/// call**. Empty leaves GTK's own choice alone. `cairo` is the software preset over SSH/X11
|
||||
/// forwarding: it also exports `LIBGL_ALWAYS_SOFTWARE=1`, because the plot's `GtkGLArea` asks
|
||||
/// for a GL context whatever GSK draws with. `gl` is the faster alternative when indirect GL
|
||||
/// works. The variables are read when the renderer/context is created, so setting them anywhere
|
||||
/// after `gtk_init()`/`adw_init()` is a no-op -- which is why this is a `Config` field applied
|
||||
/// at the top of `main()` and not a preference.
|
||||
std::string renderer;
|
||||
|
||||
/// The source chooser's "Guided tour" entry. The tour is drawn on the application's own window,
|
||||
/// which does not exist while the chooser runs, so the request rides on the one value `main()`
|
||||
/// owns and hands to both the chooser and the front end (`open_waveform` and the preferences'
|
||||
|
||||
@@ -10,6 +10,8 @@
|
||||
#include "source_dialog.hpp"
|
||||
#include "ui.hpp"
|
||||
|
||||
#include <glib.h>
|
||||
|
||||
#include <cstdio>
|
||||
#include <exception>
|
||||
|
||||
@@ -26,6 +28,31 @@ int main(int argc, char** argv) {
|
||||
return 2;
|
||||
}
|
||||
|
||||
// `--renderer` is exported here and nowhere else, because GSK reads `GSK_RENDERER` when it
|
||||
// *creates* its renderer: inside the first `adw_init()`/`gtk_init()`, which `choose_source()`
|
||||
// below may call. Setting it after that point is a silent no-op, so the one place it can be
|
||||
// honoured is before anything touches GTK. Overwrite: a flag on the command line is a
|
||||
// statement about this run, and the environment is only a default it may override.
|
||||
//
|
||||
// `cairo` is the whole software preset and not just GSK's half: the plot is a `GtkGLArea`,
|
||||
// which asks GDK for a GL context of its own whatever GSK draws with. Over X11 forwarding
|
||||
// that request is what leaves the window unallocated (no `buffer allocated:` line, zero
|
||||
// width) once the Vulkan swapchain failure is out of the way, so `LIBGL_ALWAYS_SOFTWARE`
|
||||
// makes Mesa answer it with llvmpipe. This is the pair `tools/make-help-shots.sh` sets.
|
||||
if (!cfg.renderer.empty()) g_setenv("GSK_RENDERER", cfg.renderer.c_str(), TRUE);
|
||||
if (cfg.renderer == "cairo") g_setenv("LIBGL_ALWAYS_SOFTWARE", "1", TRUE);
|
||||
// The startup half of the renderer probe, printed before GTK exists: the request and the
|
||||
// variables this process actually exported. The window's `[renderer] realized=` line is the
|
||||
// other half and says which renderer GTK built.
|
||||
if (g_getenv("UDPSCOPE_PROBE_RENDERER") != nullptr) {
|
||||
const char* gsk = g_getenv("GSK_RENDERER");
|
||||
const char* gl = g_getenv("LIBGL_ALWAYS_SOFTWARE");
|
||||
std::fprintf(stderr, "[renderer] option=%s GSK_RENDERER=%s LIBGL_ALWAYS_SOFTWARE=%s\n",
|
||||
cfg.renderer.empty() ? "(unset)" : cfg.renderer.c_str(),
|
||||
gsk != nullptr ? gsk : "(unset)", gl != nullptr ? gl : "(unset)");
|
||||
std::fflush(stderr);
|
||||
}
|
||||
|
||||
// No `--server` means the user has not said which source to open, so ask. Before the model:
|
||||
// declining must cost no buffer and no socket, and nothing below has to cope with "no source".
|
||||
// `--open` is the other answer to that question, and the chooser knows it: its Waveforms page
|
||||
|
||||
+26
-3
@@ -3465,6 +3465,22 @@ gboolean tick(gpointer user) {
|
||||
ScopeModel& model = *app->model;
|
||||
const gint64 tick_start_us = g_get_monotonic_time();
|
||||
|
||||
// The renderer GTK actually built, printed once and only under the probe: `--renderer` exports
|
||||
// `GSK_RENDERER` before any GTK call, and this is the other half of that claim -- the class GTK
|
||||
// chose, not the name it was asked for. The renderer is null until the window is realized, so a
|
||||
// tick that finds none simply tries again.
|
||||
{
|
||||
static const bool renderer_probe = g_getenv("UDPSCOPE_PROBE_RENDERER") != nullptr;
|
||||
static bool renderer_printed = false;
|
||||
if (renderer_probe && !renderer_printed) {
|
||||
if (GskRenderer* r = gtk_native_get_renderer(GTK_NATIVE(app->window))) {
|
||||
std::fprintf(stderr, "[renderer] realized=%s\n", G_OBJECT_TYPE_NAME(r));
|
||||
std::fflush(stderr);
|
||||
renderer_printed = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// The paint probe's clock, claimed on the first tick that finds one: a widget has no
|
||||
// frame clock until it is realized, and the area is realized on its first draw.
|
||||
if (app->dbg_clock == nullptr && app->area_widget != nullptr) {
|
||||
@@ -3500,14 +3516,21 @@ gboolean tick(gpointer user) {
|
||||
// the title's centre box, i.e. window_button_w tracked the window width).
|
||||
if (app->window_button != nullptr) {
|
||||
static gint64 next_header_log = 0;
|
||||
// The widths stay sampled on this one-second cadence whatever the environment, because the
|
||||
// exit dump's `header:` line reads them; only the *line* is behind a variable. A reading
|
||||
// printed on every run is a log flood and not a reading -- the same rule the `[notrace]`
|
||||
// per-paint line follows.
|
||||
static const bool header_print = g_getenv("UDPSCOPE_PROBE_HEADER") != nullptr;
|
||||
const gint64 now = g_get_monotonic_time();
|
||||
if (now >= next_header_log) {
|
||||
next_header_log = now + G_TIME_SPAN_SECOND;
|
||||
app->dbg_win_btn_w = gtk_widget_get_width(app->window_button);
|
||||
app->dbg_bar_w = gtk_widget_get_width(app->window);
|
||||
std::fprintf(stderr, "[udpscope] header: window_button_w=%d bar_w=%d\n",
|
||||
app->dbg_win_btn_w, app->dbg_bar_w);
|
||||
std::fflush(stderr);
|
||||
if (header_print) {
|
||||
std::fprintf(stderr, "[udpscope] header: window_button_w=%d bar_w=%d\n",
|
||||
app->dbg_win_btn_w, app->dbg_bar_w);
|
||||
std::fflush(stderr);
|
||||
}
|
||||
}
|
||||
}
|
||||
// The help layer's own readings and the bounds the help's annotated figures crop, on the same
|
||||
|
||||
Reference in new issue
Block a user