# HyprTile runtime compositor effects

These effects are executed by `hyprtile-screenfx-plugin` inside Hyprland.
Unlike the regular `shader/screenshader/` modules, compositor effects may consist of
multiple render passes and can therefore implement real bloom, blur and future
temporal/multi-buffer effects.

No plugin rebuild is needed when adding or changing files here. After
installation, edit system files under `/usr/local/share/hyprtile/shader/fx`
or preferably create user overrides under `~/.hyprtile/shader/fx`.

F11 -> Shaders discovers the installed compositor effect and preset directories
dynamically. A newly installed effect therefore appears in the UI without a
HyprTile or plugin rebuild.

## Directory structure

    effects/<effect>/effect.conf
    effects/<effect>/*.frag
    presets/<effect>/<preset>.preset

`effect.conf` contains one or more ordered `pass=` entries:

    id=bloom
    label=Bloom
    pass=bright|bright.frag|0.25
    pass=blur-h|blur-h.frag|0.25
    pass=blur-v|blur-v.frag|0.25
    pass=spread-h|spread-h.frag|0.25
    pass=spread-v|spread-v.frag|0.25
    pass=composite|composite.frag|1.0

The scale is relative to the monitor framebuffer. A pass at 0.25 renders at a
quarter of the width and height; this is useful for inexpensive blur stages.

Each pass sees `u_source`, the input image to the current effect, and
`u_previous`, the output of the previous pass. The final pass becomes the input
to the next effect in the compositor stack.

Preset values are generic uniforms. For example:

    threshold=0.62
    radius=1.55
    spread=2.15
    strength=0.46

maps to `u_threshold`, `u_radius`, `u_spread` and `u_strength` in GLSL. Values
containing 2-4 comma-separated floats map to vec2/vec3/vec4 uniforms.

## Bundled effects

- `bloom` - bright-pass + tight blur + wide spread + additive composite
- `blur` - full-screen multi-pass blur, useful as a diagnostic/reference effect
- `chromatic-aberration` - RGB channel separation with edge-sensitive presets
- `saturation` - saturation plus adaptive vibrance (`muted` through `extreme`)
- `grain` - temporal film grain with discrete frame-to-frame grain changes; uses `continuous=1` and `u_time`
- `crt-curvature` - barrel warp and rounded CRT-style screen corners
- `vhs` - analog line jitter, tracking/tear bands, chroma bleed and tape noise
- `dither` - ordered Bayer 2x2/4x4/8x8 dithering plus monochrome mode
- `posterize` - configurable color-level reduction for retro/palette looks
- `duotone` - two-color luminance palette mapping (amber, green, cyan/purple, blue/red)
- `temperature-tint` - warm/cool white balance plus magenta/green tint control
- `cyberpunk-glitch` - event-driven signal breach with slice/block displacement, private-history holds, localized RGB desync and digital overlays
- `hyprtile-crt-royale` - four-pass high-end CRT synthesis with dual-radius optical glow, luminance-dependent beam width, aperture/slot/shadow masks, convergence, geometry and gamma-aware output
- `hyprtile-film-lab` - multi-pass film-stock rendering with halation, optical diffusion, tone curves, moving grain and gate weave
- `hyprtile-lcd-grid` - LCD/handheld panel simulation with subpixels, pixel gaps, panel response/ghosting and reflective/backlit display presets
- `brightness` - gamma-aware exposure/brightness control with photographic highlight protection
- `gamma` - dedicated display-gamma curve control for mid-tone shaping
- `halation` - standalone warm film/emulsion halation plus broad optical diffusion
- `hyprtile-guest-advanced` - original HyprTile high-end CRT pipeline inspired by Guest-Advanced / Guest-Dr-Venom style feature sets: sharp reconstruction, adaptive beams, masks, deconvergence, bloom and halation

## Hot reload

The plugin checks the active config and all files belonging to the active stack
several times per second. Saving a manifest, preset or fragment shader triggers
a runtime rebuild of the stack. A compile error is rejected and the previous
working stack stays active.

## Safe temporary tests

A single compositor effect can be tried for 1-60 seconds without replacing the
saved stack permanently:

    hyprtile-screenfx compositor test bloom:neon 10
    hyprtile-screenfx compositor test chromatic-aberration:subtle 10
    hyprtile-screenfx compositor test saturation:vivid 10
    hyprtile-screenfx compositor test grain:film 10
    hyprtile-screenfx compositor test crt-curvature:classic 10
    hyprtile-screenfx compositor test vhs:tape 10
    hyprtile-screenfx compositor test dither:bayer4 10
    hyprtile-screenfx compositor test posterize:8 10
    hyprtile-screenfx compositor test duotone:amber-black 10
    hyprtile-screenfx compositor test temperature-tint:warm 10
    hyprtile-screenfx compositor test cyberpunk-glitch:default 10
    hyprtile-screenfx compositor test hyprtile-crt-royale:default 15
    hyprtile-screenfx compositor test brightness:bright 10
    hyprtile-screenfx compositor test gamma:bright 10
    hyprtile-screenfx compositor test halation:film 10
    hyprtile-screenfx compositor test hyprtile-guest-advanced:dr-venom 15

If the duration is omitted, 10 seconds are used. The exact previous compositor
configuration is restored automatically when the timer expires. The timer
carries a unique test token; if another test is started or a permanent
`compositor chain`/`off` command is issued while the test is active, the older
timer will not overwrite the newer choice.

F11 provides a `Test 5s` button for every effect already placed in the editable
compositor stack.

## Temporal effects and history (host 0.9+)

An effect can opt into a private per-monitor previous-frame texture and/or a
continuous compositor frame loop in `effect.conf`:

    history=1
    continuous=1

History-enabled shaders receive `u_history`, `u_history_size` and
`u_history_valid`. The host stores the completed effect output into that private
history texture after every frame. The texture is entirely plugin-owned; it is
never one of Hyprland's work framebuffers.

Temporal shaders also receive `u_delta_time` (seconds since this monitor's last
FX frame) and `u_frame`. This allows decay to be refresh-rate independent.

`continuous=1` is intentionally opt-in because the v0.8/v0.9 correctness-first
pipeline renders a full monitor frame while compositor FX are active. Use it for
truly temporal effects, not ordinary static filters.

Additional bundled effects:

- `persistence` - refresh-rate-aware phosphor afterglow / motion trails using history
- `interlace` - alternating fields plus an optional animated rolling scan band
- `sharpen` - bounded unsharp-mask style detail enhancement
- `pixelate` - 2x/4x/8x/16x screen-space pixel blocks
- `edge-neon` - Sobel edge extraction with outline, neon and vector-monitor presets

## Continuous FX pacing (host 0.12+)

Temporal effects may specify a self-scheduling rate:

    continuous=1
    target_fps=30

`target_fps` accepts 1-240 and defaults to 60 when omitted. The host uses a
Hyprland event-loop timer to request new compositor frames at that rate instead
of immediately requesting another frame after every present. This prevents an
idle 144/165/240 Hz monitor from running a temporal full-screen stack at native
refresh just because an effect is marked continuous.

Normal client/input damage remains responsive: if Hyprland already renders a
frame between timer ticks, the current FX stack is applied to that frame. The
limit controls the frames generated by the effect itself, not ordinary desktop
updates.

## Fail-safe and emergency bypass (host 1.0+)

The plugin never replaces Hyprland's `currentFB` until all private FX passes and
the final private present copy have completed without GL errors. A failed frame
therefore presents the untouched Hyprland source image. After three consecutive
runtime render failures the plugin latches a fail-safe, stops continuous FX
scheduling and bypasses compositor FX until the config changes/reloads.

For an immediate user-side escape hatch:

    hyprtile-screenfx panic

This disables the saved compositor stack, clears the normal Hyprland
`screen_shader`, asks freeze FX to stop and attempts to unload the ScreenFX
plugin. The compositor config stays disabled even if unloading the plugin path
is not possible.
