Skip to main content

Shaders & effects

All GLSL lives in source/shaders/ (42 modules) and is gated by the SHADERS_ALLOWED feature flag — Flash and AIR build without it.

The *Shader / *Effect pair​

Most effects come in two parts:

  • XShader — a FlxShader subclass holding the fragment (and sometimes vertex) GLSL source and its uniforms.
  • XEffect — a small wrapper implementing shaders.Effect, exposing animatable parameters and an update(elapsed) the state calls each frame.
var effect = new GlitchEffect();
effect.waveAmplitude = 0.1;
camGame.setFilters([new ShaderFilter(effect.shader)]);

shaders.ShaderEffect and shaders.Effect are the shared bases; shaders.ErrorHandledShader wraps compilation so a broken shader logs and is skipped instead of taking the game down (important for mod-supplied shaders).

What ships​

EffectModule(s)
GlitchGlitchEffect + GlitchShader
Blocked glitchBlockedGlitchEffect + BlockedGlitchShader
VCR distortionVCRDistortionEffect + VCRDistortionShader
Chromatic aberrationChromaticAberrationEffect + ChromaticAberrationShader
BloomBloomEffect + BloomShader
GrainGrain, GrainEffect
ScanlinesScanline, ScanlineEffect
TiltshiftTiltshift, TiltshiftEffect
Greyscale / invertGreyscaleEffect + GreyscaleShader, InvertColorsEffect + InvertShader
PulsePulseEffect, PulseEffectAlt, PulseShader
3DThreeDEffect + ThreeDShader
WiggleWiggleEffect, WiggleEffectLua
Background distortionDistortBGEffect + DistortBGShader
Buildings (Philly)BuildingEffect + BuildingShader
RainRainShader (toggleable; optimized behind an option)
TriangleFuckingTriangle, FuckingTriangleEffect
VFD overlayVFDOverlay
Note colouringRGBPalette
TransitionsCustomFadeTransition, CrossFade
Runtime loaderRuntimeShaders

backend.FlxFixedShader is a corrected FlxShader base used where the stock one misbehaves.

Runtime shaders (mods)​

shaders.RuntimeShaders loads GLSL from disk at runtime, so a mod can ship mods/<mod>/shaders/<name>.frag (and .vert) and apply it from a script. Engine shaders that used to be loose files were embedded into RuntimeShaders to avoid the file read (for example pulseEffect.frag).

From Lua or Python:

initLuaShader('myShader')
setSpriteShader('boyfriend', 'myShader')
setShaderFloat('boyfriend', 'uTime', 0.0)

The full set — initLuaShader, setSpriteShader, setCameraShader, removeSpriteShader, removeCameraShader, clearShadersFromCamera, and the get/setShader{Float,Int,Bool}[Array] / setShaderSampler2D family — is listed in the Lua API reference.

Screen effects also have dedicated helpers (addGlitchEffect, addVCREffect, addBloomEffect, addGrainEffect, addScanlineEffect, addTiltshiftEffect, addChromaticAbberationEffect, addPulseEffect, add3DEffect, addWiggleEffect, addDistortionEffect, addInvertEffect, addGrayscaleEffect, clearEffects).

Writing a new engine effect​

  1. Add MyShader.hx with the GLSL in an @:glFragmentSource string.
  2. Add MyEffect.hx implementing Effect with the parameters and update(elapsed).
  3. Guard everything with #if SHADERS_ALLOWED.
  4. If scripts should reach it, register a callback in psychlua/callbacks/EffectCallbacks.hx (and psychlua/pystdlib/ for Python parity).
  5. Validate uniforms defensively — the Rainbow Eyesore shader is the cautionary tale (it needed validation and a waveSpeed fix after mods passed unexpected values).
Performance

Post-processing is per-pixel on the whole screen. On the low-end devices this engine targets, a single full-screen filter can cost more than the entire note rendering. Prefer sprite-level shaders, and keep effects behind options where possible (as the rain effect is).