Skip to content

feat: ghostty-style custom shaders over the window - #1091

Draft
darkwebber wants to merge 1 commit into
herdrdev:masterfrom
darkwebber:feat/ghostty-shaders
Draft

darkwebber wants to merge 1 commit into
herdrdev:masterfrom
darkwebber:feat/ghostty-shaders

Conversation

@darkwebber

Copy link
Copy Markdown

Opening as a draft to discuss before anyone reviews it in detail: it's bigger than a usual change and touches GPUI. Ghostty users often have custom-shader files (cursor trails, glow, CRT); this lets a layout run the same files over the Herdr window, unchanged.

What it does

  • Herdr: a layout root takes "shaders": [paths] (relative to the app's folder, or ~/). src/shaders.rs supplies Ghostty's prelude and uniforms (iChannel0, iTime, iResolution, iCurrentCursor / iPreviousCursor / iTimeCursorChange, iCurrentCursorColor, iFocus / iTimeFocus, iPalette, the theme's colors). Shaders reload when their files change, compile errors show in a toast, and frames keep coming while the window has focus (like custom-shader-animation = true).
  • GPUI: a small API that knows nothing about Ghostty. Window::set_post_process_shaders(&[glsl]) takes GLSL 450 fragment shaders, and set_post_process_values fills their uniform block by member name. The Metal backend translates them with naga (glsl-in, msl-out), draws the scene to an offscreen frame and runs the passes into the drawable. Other backends report that post-processing isn't supported yet; wgpu could do the same with naga's WGSL output.
  • Uniforms are written at the offsets the generated Metal struct really uses: naga lays the block out as std140 but writes float[4] packed, so offsets after scalar arrays are corrected (with a test).
  • THIRD-PARTY-NOTICES is regenerated for naga's GLSL parser, and defaults/AGENTS.md documents shaders.

Before growing the core

  1. The app acts on it: it changes how every frame is drawn.
  2. Only the app (and GPUI) can see the frame.
  3. A mod can't draw over the whole window or read the frame.

Open questions

  • Whole-window pass (this) vs per terminal (Ghostty's are per surface).
  • Power: frames run continuously while focused. Should there be a setting to animate only after cursor or focus changes?
  • macOS only for now.

Testing

  • cargo test --locked passes; there are new tests in shaders.rs (the prelude with a typical cursor shader validates in naga, palette) and in post_process.rs (uniform offsets).
  • Running on macOS (Apple Silicon) with four real Ghostty shaders, cursor_blaze_no_trail, cursor_smear, a ripple on cursor jumps and an ambient bloom/vignette: trails follow the cursor, and the unfocused window dims.

🤖 Generated with Claude Code

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant