aboutsummaryrefslogtreecommitdiffstats
path: root/Makefile
diff options
context:
space:
mode:
authoruser <user@clank>2026-08-14 22:06:57 +0200
committeruser <user@clank>2026-08-14 22:06:57 +0200
commit2ae507a8a331799875e07c327730a1a4ca0363f4 (patch)
tree685b167c2a87f5e9f877cca31d31f43fd4e71734 /Makefile
parentsl0ppty: expand ~ in a path that a config file wrote (diff)
downloadslosh-2ae507a8a331799875e07c327730a1a4ca0363f4.tar.gz
slosh-2ae507a8a331799875e07c327730a1a4ca0363f4.tar.xz
slosh-2ae507a8a331799875e07c327730a1a4ca0363f4.zip
sl0ppty: shaders can be loaded from shared libraries
Adding an effect meant editing shader.c and rebuilding, which is fine for us and useless to anyone else. Now any `*.so` in the shader directory that exports a plugin table is loaded at startup, and what it declares is named in the config exactly like a built-in -- same syntax, same three parameters, same pass. Default location is a `shaders` directory beside the config file, so dropping the .so next to config.kdl is the whole installation. The interesting part is what a plugin can see, which is one header. shader_abi.h holds cell_t, color_t, shade_ctx_t, shader_t and nothing else: no libghostty-vt, no pty, no layout, no session. A shader is therefore unable to reach anything but the cell it was handed, not by rule but because nothing else is declared where it can find it. cell_t and color_t moved there from sl0ppty.h for the same reason -- that is the one place they are a contract with code compiled outside this tree rather than an internal type. Loading is deliberately narrow: - refused unless the library reports our ABI version *and* matching struct sizes. The version says which contract; the sizes catch someone editing their copy of the header, which a version number alone calls fine and a running session calls garbled colours; - refused if it redefines an existing name, because a config naming `dim` means the `dim` it was written against; - all-or-nothing per file, and per file: a bad library is a line on stderr and everything else still loads (D9 fail-open); - nothing is ever unloaded. A shade_fn may be sitting in a live config or on a pane, so dlclose() is a jump into an unmapped page. A reload picks up newly *added* plugins; replacing one needs a new session. This is the single exception to D9's "a plugin is a subprocess", and it is argued in D15 rather than drifted into: a shader is called per cell, per pass, per frame, which no subprocess can be in the path of, and the measured alternative -- interpreting bytecode per cell -- is 56ns against 8ns for the compiled function. On trust, `shell` and a layout's `command=` already let a file in the config directory run anything as you, so a .so beside them is not a new boundary. The docs say so plainly rather than implying a sandbox. contrib/shader-plugin is a working skeleton: Makefile, two commented effects (one positional, one animated), and the three rules a shader has to keep. It is built and loaded by the test suite, so a broken example is a failing test rather than someone else's afternoon.
Diffstat (limited to 'Makefile')
-rw-r--r--Makefile17
1 files changed, 17 insertions, 0 deletions
diff --git a/Makefile b/Makefile
index 45b6841..907d525 100644
--- a/Makefile
+++ b/Makefile
@@ -71,6 +71,23 @@ SHADER_TEST := build/shader_test
$(SHADER_TEST): tests/shader_test.c src/shader.c src/screen.c src/json.c $(VT_LIB) | build
$(CC) $(CFLAGS) tests/shader_test.c src/shader.c src/screen.c src/json.c $(VT_LIB) -o $@
+# Shader plugins, for test_shader_plugin.py: a good one, one that announces an
+# ABI we do not speak, and the example we ship in contrib -- which is built
+# here so that a broken example is a failing test rather than a bug report.
+PLUGIN_CFLAGS := -std=c23 -O1 -fPIC -shared -Isrc
+
+build/testshader.so: tests/shader_plugin_test.c src/shader_abi.h | build
+ $(CC) $(PLUGIN_CFLAGS) $< -o $@
+
+build/badshader.so: tests/shader_plugin_test.c src/shader_abi.h | build
+ $(CC) $(PLUGIN_CFLAGS) -DBAD_ABI $< -o $@
+
+build/exampleshader.so: contrib/shader-plugin/example.c src/shader_abi.h | build
+ $(CC) $(PLUGIN_CFLAGS) $< -o $@
+
+build/.pass-test_shader_plugin: build/testshader.so build/badshader.so \
+ build/exampleshader.so
+
build/.pass-%: tests/%.py tests/harness.py $(BIN) | build
@cd tests && timeout 300 python3 $(notdir $<) > ../build/.log-$* 2>&1 \
|| { echo "FAIL $(notdir $<)"; tail -25 ../build/.log-$*; exit 1; }