Glint
Automated Minecraft shader screenshot catalog
Glint is a catalog of Minecraft shader screenshots, captured automatically and rendered under identical conditions so they can be compared honestly. It is three programs working together: a SvelteKit and TypeScript frontend for browsing and side-by-side comparison, a Rust backend on Axum and PostgreSQL that decides what to capture and stores the results, and a Kotlin and Java Minecraft mod — built for both Fabric and NeoForge — that does the rendering.
Shader screenshots posted online are nearly useless for comparison. Every one is a different scene, a different time of day, a different biome and camera angle, so you can never tell whether one shader's water genuinely looks better or whether it just caught a nicer sunset. A fair comparison needs both shaders rendered against pixel-identical conditions — same world, same spot, same light — and doing that by hand across dozens of shaders and scenes does not scale. Glint automates the game itself to produce it.
Rendering Minecraft headless
A shader is a GPU program, so rendering one needs a real graphics card — but a capture server has no monitor and no desktop session. The usual headless shortcut, software rendering through Mesa's llvmpipe, runs on the CPU and produces black or broken frames for anything this demanding.
The capture environment is instead a multi-stage Docker image built on nvidia/opengl. portablemc assembles a pinned Minecraft 1.21.4 and Fabric runtime at build time; Xvfb supplies a virtual X display; and VirtualGL intercepts the game's OpenGL calls and routes them to a real NVIDIA GPU passed into the container with --gpus all. Everything needed to launch the game is baked into the image, so a run is deterministic — only the mutable inputs, the shader packs and scene worlds and output, are mounted at runtime.
How the mod hooks into Minecraft
The mod is one codebase compiled for two loaders through Architectury, against the official Mojang mappings. It hooks into the game with mixins — bytecode injected at class-load time — to seize the framebuffer and drive high-resolution captures, and it loads and switches shaders by reflecting into Iris, the shader loader, rather than through any stable public API. Because a mixin can compile cleanly and still fail to apply at runtime, the build ships a smoke test that boots a headless client on each loader and watches for the injections to land.
Deciding what to capture next
The mod is a simple worker: it asks the backend one question — what needs capturing? — and renders whatever comes back. The backend answers by computing the gap rather than reading a queue. An earlier design had a real job queue, with atomic claiming, heartbeat monitoring, retries, and priority ordering; for a single short-lived mod instance working through captures in sequence, that was machinery with no payoff, so it was removed.
In its place, a request to GET /api/work runs one query over a capture_target_matrix view — the cross product of every shader version, scene, preset, and Iris profile — and subtracts everything that already has a fresh capture. Shaders that have never been captured come back first, then the most-downloaded ones; a shader version that has failed three times is set aside so it cannot stall the run.
The ordering is where it gets interesting, because captures do not cost the same. Loading a different shader takes two to five seconds; teleporting to a new scene, one to three; changing the time of day, about a tenth of a second. So the backend rewrites each batch to do the cheap transitions in bulk and the expensive ones as rarely as possible. Scenes within two blocks of each other are treated as one location photographed at different times and grouped together. Scenes within 512 blocks — roughly 32 chunks of overlap — share loaded terrain and are clustered so the game never reloads chunks it just had. The clusters are then visited by nearest-neighbor traversal. A run that might have reloaded a shader for every screenshot instead loads each shader once and sweeps every scene beneath it.
Keeping comparisons honest
A comparison only means something if both screenshots were shot against the same scene, so a scene is a versioned, first-class entity rather than a loose camera position. A Scene pins a location, camera, dimension, and an optional packaged world; a SceneVersion is one revision of that configuration; a ScenePreset is a time-and-weather variation within it — 'Sunset', 'Stormy Night' — so one scene yields many lighting conditions without being duplicated.
Every capture records the exact scene version it was taken against. When a scene's configuration changes, the older captures are not deleted — they are computed as stale the next time anything asks, which is the same signal the work generator uses to schedule a recapture. This freshness state, fresh or stale or superseded, is never written down as a flag; it falls out of comparing each capture against the current version at query time, so it cannot drift out of sync with reality.
The mod-to-backend boundary
The mod has no browser and nowhere to type a password, so it logs in with the OAuth 2.0 device authorization grant (RFC 8628) — the same flow a smart TV uses. It shows the player a short code and a URL; the player opens the URL in an ordinary browser, signs in, and clicks Authorize, while the mod polls in the background until the backend returns a session token. Sessions minted this way are tagged as device sessions, distinct from web logins, though they share the same table and middleware.
Image uploads skip the backend entirely. For each capture the mod claims an upload slot and receives a presigned R2 URL, PUTs the WebP straight to object storage, then asks the backend to confirm — at which point the backend reads back only the first 30 bytes, enough to verify the WebP magic number and the declared resolution, before marking the capture complete. The server signs and validates; it never moves the image bytes itself.
Tradeoffs
Glint is under active development, and parts of it lean on assumptions that keep the current scope simple. Work computation takes no locks because exactly one mod instance runs at a time; multi-agent capture would need either advisory locks or a small claim table. Capture only runs on a machine with an NVIDIA GPU and the container toolkit, which rules out most CI and cloud defaults. And history is append-only — every recapture is a new database row and a new object in storage — on the bet that object storage is cheap and a visual regression history is worth more than the bytes it costs.