This Org Babel backend renders GLSL fragment shaders from Org source blocks.
On Emacs 32, blocks without a :file header produce a live animated Canvas in
the Org buffer. Blocks with :file render one frame to a PNG.
- Emacs 32 built with dynamic module and Canvas support
- SDL2, used to create a portable hidden OpenGL context
- SDL2_image, used to save PNG files
- glbinding
- An OpenGL 3.3 capable driver
- CMake 3.11 or newer and a C++17 compiler
The initial implementation targets desktop OpenGL on Windows, macOS, and Linux. Context creation is isolated so an OpenGL ES/Android backend can be added later without changing the Elisp API.
Configure CMake with the directory containing the Emacs 32
emacs-module.h. For example:
cmake -S . -B build -G Ninja -DEMACS_INCLUDE_DIR=/path/to/emacs/include
cmake --build buildOn the Windows Emacs installation used during development, the first command is:
cmake -S . -B build -G Ninja -DEMACS_INCLUDE_DIR=C:/Emacs/includeThe build creates ob-glsl-module with the dynamic-module suffix used by the
platform.
- Put
ob-glsl.eland the built dynamic module onload-path. - Ensure the SDL2, SDL2_image, and glbinding shared libraries can be found by the operating system’s dynamic loader.
- Add
(glsl . t)toorg-babel-load-languages.
:file PATHRender one frame to PATH as a PNG. When omitted, insert an animated Emacs Canvas result.:width PIXELSPositive integer render width.:height PIXELSPositive integer render height.:time SECONDSFloating-point initial value ofiTime. It defaults to 0. For a file result, the one frame is rendered at exactly this time. For a Canvas, time continues advancing from this value.
If only one dimension is supplied, the other is calculated at a 4:3 aspect ratio. The default size is 400 by 300.
ob-glsl prepends these declarations:
#version 330 core
out vec4 fragColor;
uniform vec2 iResolution;
uniform float iTime;Source blocks provide the rest of the fragment shader, including main.
Executing this block inserts an animated 400 by 300 Canvas. Re-executing it
recompiles the shader and restarts iTime. Timer frames only update uniforms
and render; they never recompile the program.
void main() {
vec2 uv = gl_FragCoord.xy / iResolution;
float pulse = 0.5 + 0.5 * sin(iTime * 3.0);
fragColor = vec4(uv.x, uv.y, pulse, 1.0);
}Use :time 10.0 to begin the animation at ten seconds.
vec3 mandel(vec2 z0) {
float k = 0.0;
vec2 z = vec2(0.0);
for (int i = 0; i < 420; ++i) {
z = vec2(z.x*z.x-z.y*z.y, z.x*z.y*2.0) + z0;
if (length(z) > 20.0) break;
k += 1.0;
}
float mu = k + 1.0 - log2(log(length(z)));
return sin(mu*0.1 + vec3(0.0, 0.5, 1.0));
}
void main() {
float ar = iResolution.x / iResolution.y;
vec2 uv = gl_FragCoord.xy / iResolution.yy - vec2(0.66 * ar, 0.5);
float scale = 0.5;
vec2 offset = vec2(-0.3, 0.0);
uv += offset * scale;
uv /= scale;
fragColor = vec4(mandel(uv), 1.0);
}Each Canvas result owns its shader program, framebuffer resources, two asynchronous pixel-pack buffers, timer, and native renderer handle. The timer targets 60 FPS and publishes a completed PBO without waiting on an unfinished GPU fence. A single memory copy transfers the completed packed ARGB32 frame into Emacs-owned Canvas memory.
Replacing or removing the result, killing its buffer, or encountering a rendering error cancels the timer and destroys the native GL resources. A native user-pointer finalizer is retained as a last-resort cleanup path.
Canvas results are intended for interactive use. Use :file when a durable
or exported image is required.
With the build directory and source directory on load-path:
emacs -Q --batch -L build -L . \
--load tests/ob-glsl-tests.el \
-f ert-run-tests-batch-and-exit