dasyatid1: “delta prime” (delta prime)

While experimenting with SDL 3 and poking its pre-init simple GUI message box support, I found out that version 3.4.8, rather than doing the simplistic X thing that I remember it doing before, invokes Zenity on my Arch Linux system. (A skim of the SDL source code suggests that if there's no Zenity, it still falls back to legacy X primitives on X, but fails completely on Wayland.) Seems basically reasonable, right?

Unfortunately, Zenity 4.2.2 on this (somewhat old) machine has a bit of an issue with startup time:

% time zenity --error --title="Oops" --text="Uh oh."
 #  zenity --error --title="Oops" --text="Uh oh.": user=1195ms sys=152ms wall=3040ms mem=115k

That's a full second of CPU time just to show a dialog box, and it does feel like about that long of a delay between hitting Return and seeing the message on the screen. Definitely an interactive flow killer. Let's also see what ldd says about what it's loading:

% ldd /usr/bin/zenity |cut -d' ' -f1
	linux-vdso.so.1
	libadwaita-1.so.0
	libgtk-4.so.1
	libpango-1.0.so.0
	libgio-2.0.so.0
	libgobject-2.0.so.0
	libglib-2.0.so.0
	libgcc_s.so.1
	libc.so.6
	libfribidi.so.0
	libgraphene-1.0.so.0
	libappstream.so.5
	libm.so.6
	libgmodule-2.0.so.0
	libpangocairo-1.0.so.0
	libcairo.so.2
	libharfbuzz.so.0
	libharfbuzz-subset.so.0
	libcairo-gobject.so.2
	libfontconfig.so.1
	libgdk_pixbuf-2.0.so.0
	libepoxy.so.0
	libgstplay-1.0.so.0
	libgstvideo-1.0.so.0
	libgstreamer-1.0.so.0
	libgstgl-1.0.so.0
	libgstallocators-1.0.so.0
	libXi.so.6
	libX11.so.6
	libpangoft2-1.0.so.0
	libcloudproviders.so.0
	libtinysparql-3.0.so.0
	libvulkan.so.1
	libpng16.so.16
	libtiff.so.6
	libjpeg.so.8
	libxkbcommon.so.0
	libwayland-client.so.0
	libwayland-egl.so.1
	libXext.so.6
	libXcursor.so.1
	libXdamage.so.1
	libXfixes.so.3
	libXrandr.so.2
	libXinerama.so.1
	libcairo-script-interpreter.so.2
	libcups.so.2
	libcolord.so.2
	libthai.so.0
	libz.so.1
	libmount.so.1
	libffi.so.8
	libpcre2-8.so.0
	/lib64/ld-linux-x86-64.so.2
	libcurl.so.4
	libxmlb.so.2
	libxml2.so.16
	libfyaml.so.0
	libsystemd.so.0
	libzstd.so.1
	libstemmer.so.0
	libfreetype.so.6
	libXrender.so.1
	libxcb.so.1
	libxcb-render.so.0
	libxcb-shm.so.0
	libpixman-1.so.0
	libgraphite2.so.3
	libexpat.so.1
	libglycin-2.so.0
	libgsttag-1.0.so.0
	libgstpbutils-1.0.so.0
	libgstbase-1.0.so.0
	liborc-0.4.so.0
	libunwind.so.8
	libdw.so.1
	libEGL.so.1
	libGLX.so.0
	libwayland-cursor.so.0
	libX11-xcb.so.1
	libgudev-1.0.so.0
	libdrm.so.2
	libgbm.so.1
	libjson-glib-1.0.so.0
	libsqlite3.so.0
	libdeflate.so.0
	libjbig.so.2.1
	liblzma.so.5
	libwebp.so.7
	liblzo2.so.2
	libavahi-common.so.3
	libavahi-client.so.3
	libgnutls.so.30
	liblcms2.so.2
	libudev.so.1
	libdatrie.so.1
	libblkid.so.1
	libnghttp3.so.9
	libngtcp2_crypto_ossl.so.0
	libngtcp2.so.16
	libnghttp2.so.14
	libidn2.so.0
	libssh2.so.1
	libpsl.so.5
	libssl.so.3
	libcrypto.so.3
	libgssapi_krb5.so.2
	libbrotlidec.so.1
	libicuuc.so.78
	libbz2.so.1.0
	libXau.so.6
	libXdmcp.so.6
	libseccomp.so.2
	libgstaudio-1.0.so.0
	libelf.so.1
	libGLdispatch.so.0
	libsharpyuv.so.0
	libdbus-1.so.3
	libleancrypto.so.1
	libp11-kit.so.0
	libunistring.so.5
	libtasn1.so.6
	libhogweed.so.7
	libnettle.so.9
	libgmp.so.10
	libbrotlienc.so.1
	libkrb5.so.3
	libk5crypto.so.3
	libcom_err.so.2
	libkrb5support.so.0
	libkeyutils.so.1
	libresolv.so.2
	libbrotlicommon.so.1
	libicudata.so.78
	libstdc++.so.6

wc -l says that's 135 mappings. This is easy to overinterpret—code being mapped into memory doesn't mean it's doing anything other than more-or-less cheaply sitting there just in case, and timing zenity --help confirms that the initial CPU suck isn't coming from the dynamic loader—but it's a decent enough proxy metric for exploding coordination and integration complexity that it still makes me cringe some.

Running it under strace -f shows 43 underlying clone calls and two separate execs of bwrap along with an exec of glycin-svg… I assume the sandboxing is for the SVG loader, so is that being used to display the error icon? It doesn't happen if I run zenity --text-info instead, which doesn't use an icon, though the overall CPU usage remains similar, so that's not the bottleneck.

I briefly tried running it under ltrace, but it either dumped core or didn't produce any trace output depending on what filters I set; I assume it doesn't have enough information to reconstruct the library calls here.

Out of curiosity, I also tried timing Yad, which describes itself as a fork of Zenity and which notably seems to use GTK+ 3 instead of GTK+ 4. With Yad, the user CPU time for a basic dialog looks closer to 200 ms, which still feels a little iffy but is overall much more reasonable. Funnily (though not unexpectedly), the ldd output is 146 lines and strace shows 66 clone calls—but something somewhere in that stack is much more efficient.

I guess it makes sense to expect Zenity to be more mainline in availability than one of its forks, in an XDG-ish environment… that said, looking at the source for SDL_Zenity_ShowMessageBox, I also see:

/* https://gitlab.gnome.org/GNOME/zenity/-/commit/c686bdb1b45e95acf010efd9ca0c75527fbb4dea
 * This commit removed --icon-name without adding a deprecation notice.
 * We need to handle it gracefully, otherwise no message box will be shown.
 */

(The commit in question is from 2022, and a skim of the diff does say it changed "icon-name" to "icon" in a few key places.)

Hmm. Bleh. Gee, I dunno. I don't think I'm going to go further down this rabbit hole today, but this is all making me kind of sad.

dasyatid1: “delta prime” (delta prime)

In the current function body of com.dasyatidae.sidereal.sdl2-cffi:make-basic-pixel-format: “There's a bit of a clash here. The way the values are defined in the header file use bitfields like this, but not all combinations that you'd expect to be valid work. In particular, there's the endianness-dependent SDL_PIXELFORMAT_RGBA32 and its companions. You'd think from the name that this maps to :array-u8 :rgba, but in fact they're defined within the fuller enum as packed types within a uint32, and functions like SDL_PixelFormatEnumToMasks don't recognize a synthesized array version even though they work on SDL_PIXELFORMAT_RGB24 which is defined as an array pixel format. But it'd be horrible to replicate the entire valid list, too. Bummer.”

Really it may yet just be more authentic to replicate the entire valid list, but…

dasyatid1: “delta prime” (delta prime)

As part of trying to regain some active affinity for my field (that having been in a difficult place lately for various reasons), I've been poking around at seeing if I can access SDL 2 from Common Lisp. The closest I've seen thus far is Gary Hollis's cl-sdl2 repository, but it's quite incomplete and has no license attached, and its design as far as putting Lispier interfaces on top is far enough from what I'd be looking for that I decided to explore building on CFFI myself instead.

One thing I've run into while translating declarations from the C header files is audio callbacks. Traditionally, SDL runs its audio processing on its own thread (or an unspecified thread, at least) and uses a callback-based approach similar to JACK or (from what I am told) Apple's Core Audio. The application is given an empty or full buffer for playback or capture respectively and is expected to provide or consume the audio data at the pleasure of the audio clock.

Now, like many current Lisp users, I'm running SBCL, and while CFFI-on-SBCL supports defcallback forms for making C-callable function pointers on many platforms, unfortunately, §13.9 of the SBCL manual states that running Lisp code is only supported on threads started from Lisp. So it's fine for synchronous callbacks, but we can't use a CFFI callback for the audio callback in that context. What alternatives come to mind?

  1. Use the newer SDL_QueueAudio and SDL_DequeueAudio functions that are intended to allow applications to do read/write style audio I/O from the main thread. This would be the most straightforward, but it feels instinctively iffy to me from an audio processing standpoint, and specifically I don't see a great way to get high-water/low-water notifications into the SDL event loop; the closest thing seems to be polling SDL_GetQueuedAudioSize.

  2. Write a callback in C and have it signal a Lisp-created thread with the buffer pointer, which does the audio processing and then signals back. There are two big extra complexity sources here. One is portable synchronization, though we could potentially use the SDL threading functions themselves for that. The other is getting the callback into the running process in the first place; bringing in C build system considerations is the opposite of what I was hoping for with CFFI bindings. A variation of this would be to make an audio queue similar to (1) but add richer main-loop synchronization using SDL_PushEvent.

  3. Switch to a CL implementation that allows calling into Lisp from foreign threads. This also has to be in a way that doesn't involve intense setup/teardown overhead or having to do something from the foreign thread prior, since I don't control the actual thread arrangement. It looks like Clozure CL might support this; the manual mentions “when native threads that aren't created by Clozure CL first call into lisp”, which implies that it's possible. ECL also seems like it might allow this, but its FFI and performance characteristics were somewhat weaker last I recall, and the manual didn't obviously state outright whether it would work. I vaguely recall having tried and failed to get Clozure CL to install on my machine before, so that could be its own rathole.

  4. Same as (2) except rather than writing in C, I do jiggery pokery to assemble a small machine-code shunt from Lisp and use that as the callback function. This avoids the C build system issue at the cost of being absolutely wacko and also distinctly nonportable and close to unmaintainable—maybe less so if there's a GNU Lightning equivalent as a CL library somewhere?—but then syscalls, and then it all flies apart. Let's not do this (unless I decide it would make a good joke).

  5. Give up on doing the audio processing in Lisp, leaving it with the role of the ‘scripting’ language directing a GStreamer effect graph or whatever. Awkward—it would be beneficial to pull in foreign audio library code in a more general sense (SDL_mixer is a common basic thing to get started with, and then a number of games use FMOD or the like), but losing most-to-all ability to deal with the sample data would be a pain for some use cases, and now we've potentially recreated the “how does the other code communicate with Lisp re the high-level disposition of playback/capture” on another level.

  6. Give up and switch to C++. Not that I haven't done synthesis code in C++ before, but that's not what I'm after here—I'd lose my REPL, and take on heavyweight template juggling and…

  7. Same as (6) but extend the implementation of the C++ language with all the Lisp stuff I want. Problem: then I fall into the dark void shockwave and never come out, and also I might as well design my dream language while I'm doing something like that, which idea leads to perhaps thrice as much falling into the dark void shockwave and never coming out. But flip that around, and does that idea just point in the direction of Clasp? I've never tried it before, but that might be another candidate to look at for (3) that would give me much easier C++ access along the way—which can be important in the gameish/interactive-media sort of space. One big disadvantage here would be that it doesn't currently support Windows.

I'm not sure what'll turn out to be best yet, but seeing if I can get Clozure CL to run seems like a reasonable next thing to try…

Powered by Dreamwidth Studios