Phosphor
Qt6 / Wayland library suite for window-management tools
 
Loading...
Searching...
No Matches
PhosphorAnimationShaders::AnimationShaderContract Namespace Reference

Cross-runtime named-uniform contract for animation/transition shaders. More...

Functions

QString slotKey (int vec, char comp)
 Format a customParams slot key — thin forwarder onto PhosphorShaders::CustomParams::slotKey, the cross-library canonical helper.
 
QString slotKey (int slot)
 Flat-slot overload: slot is 0..31 across the 8 vec4 slots.
 
QString colorKey (int slot)
 Format a customColor slot key — thin forwarder onto PhosphorShaders::CustomColors::colorKey.
 
bool isValidWrapToken (const QString &wrap)
 The accepted texture wrap vocabulary, shared by every animation validation site (metadata parse in AnimationShaderEffect::fromJson, and runtime-override translation in AnimationShaderRegistry).
 
bool isValidFilterToken (const QString &filter)
 The accepted buffer filter vocabulary: linear / nearest / mipmap.
 

Variables

constexpr const char * kITime = "iTime"
 float iTime — transition progress, normally in [0.0, 1.0].
 
constexpr const char * kIResolution = "iResolution"
 vec2 iResolution — surface size in logical pixels.
 
constexpr const char * kITimeDelta = "iTimeDelta"
 float iTimeDelta — wall-clock seconds between consecutive paint ticks that fed this transition.
 
constexpr const char * kIFrame = "iFrame"
 int iFrame — per-leg frame counter.
 
constexpr const char * kIDate = "iDate"
 vec4 iDate — local-time (year, month, day, seconds-since-midnight).
 
constexpr const char * kIIsReversed = "iIsReversed"
 int iIsReversed — direction signal for asymmetric leg rendering.
 
constexpr const char * kISurfaceScreenPos = "iSurfaceScreenPos"
 vec4 iSurfaceScreenPos — the shader surface's position plus the host screen dimensions, both in logical pixels.
 
constexpr const char * kIAnchorSize = "iAnchorSize"
 vec2 iAnchorSize — captured anchor (card) pixel size in logical pixels.
 
constexpr const char * kIAnchorPosInFbo = "iAnchorPosInFbo"
 vec2 iAnchorPosInFbo is the anchor's top-left position inside the FBO, in logical pixels.
 
constexpr const char * kIAnchorRectInTexture = "iAnchorRectInTexture"
 vec4 iAnchorRectInTexture — the card's UV sub-rect within uTexture0, as (x, y, width, height) in the texture's [0, 1] space.
 
constexpr const char * kIFromRect = "iFromRect"
 vec4 iFromRect / vec4 iToRect — geometry-morph endpoints in logical screen pixels (x, y, width, height).
 
constexpr const char * kIToRect = "iToRect"
 
constexpr const char * kIIconRect = "iIconRect"
 vec4 iIconRect — the window's task-manager icon rectangle in logical screen pixels (x, y, width, height), same coordinate space as iFromRect / iToRect and as iSurfaceScreenPos.xy + iAnchorSize (the window's frame rect).
 
constexpr const char * kUOldWindow = "uOldWindow"
 sampler2D uOldWindow — snapshot of the window's content captured at the old frame size just before the instant moveResize.
 
constexpr const char * kUSurfaceLayer = "uSurfaceLayer"
 sampler2D uSurfaceLayer — COMPOSITOR PATH ONLY.
 
constexpr const char * kIHasSurfaceLayer = "iHasSurfaceLayer"
 int iHasSurfaceLayer — COMPOSITOR PATH ONLY.
 
constexpr const char * kIHasOldWindow = "iHasOldWindow"
 int iHasOldWindow — COMPOSITOR PATH ONLY.
 
constexpr const char * kIOldWindowOpacity = "iOldWindowOpacity"
 float iOldWindowOpacity — COMPOSITOR PATH ONLY.
 
constexpr const char * kIMoveVelocity = "iMoveVelocity"
 vec2 iMoveVelocity — COMPOSITOR PATH ONLY.
 
constexpr const char * kIMoveOffset = "iMoveOffset"
 vec2 iMoveOffset — COMPOSITOR PATH ONLY.
 
constexpr const char * kIMoveVelocity2 = "iMoveVelocity2"
 vec2 iMoveVelocity2 — COMPOSITOR PATH ONLY.
 
constexpr const char * kIMoveTrail = "iMoveTrail"
 vec2 iMoveTrail[16] — COMPOSITOR PATH ONLY.
 
constexpr const char * kIMoveMesh = "iMoveMesh"
 vec2 iMoveMesh[16] — COMPOSITOR PATH ONLY.
 
constexpr const char * kILayerRectInTexture = "iLayerRectInTexture"
 Card/anchor-space [0,1] sub-rect of the animated surface WITHIN uSurfaceLayer's canvas (xy offset, zw scale) — the layer analogue of iAnchorRectInTexture.
 
constexpr const char * kIWindowOpacity = "iWindowOpacity"
 float iWindowOpacity — the window's effective rule-resolved opacity in [0.0, 1.0], COMPOSITOR PATH ONLY.
 
constexpr int kMaxUserTextureSlots = 3
 Maximum number of user-declared textures per animation effect.
 
constexpr int kMaxBufferPasses = 4
 Multipass buffer-pass budget.
 
constexpr const char * kIAudioSpectrumSize = "iAudioSpectrumSize"
 int iAudioSpectrumSize — CAVA bar count, 0 while the audio visualizer is off or cava is unavailable.
 
constexpr const char * kUAudioSpectrum = "uAudioSpectrum"
 sampler2D uAudioSpectrum — the CAVA spectrum texture (bars×1, R = bar value in 0..1).
 
constexpr const char * kIMouse = "iMouse"
 vec4 iMouse — cursor position in shader-local pixels.
 
constexpr const char * kCustomParamsArray = "customParams"
 vec4 customParams[N] — per-effect declared parameter slots.
 
constexpr int kMaxCustomParams = PhosphorShaders::CustomParams::kVecCount
 Number of vec4 slots in the customParams array (8).
 
constexpr int kMaxParameterSlots = PhosphorShaders::CustomParams::kFlatSlotCount
 Number of float sub-slots (4 per vec4 × 8 vec4s = 32).
 
constexpr const char * kCustomColorsArray = "customColors"
 vec4 customColors[N] — per-effect declared color parameter slots.
 
constexpr int kMaxCustomColors = PhosphorShaders::CustomColors::kColorCount
 

Detailed Description

Cross-runtime named-uniform contract for animation/transition shaders.

Phosphor has two distinct shader registries:

  1. Animation/transition shadersAnimationShaderRegistry, sourced from data/animations/*‍/. Short-lived transitions driven by a 0..1 timeline (open, close, snap, drag, etc.).
  2. Overlay/zone-background shadersPhosphorShaders::ShaderRegistry, sourced from data/overlays/*‍/. Long-lived ambient effects with access to the rich BaseUniforms UBO (iMouse, iDate, customColors[16], audio-spectrum / wallpaper / multipass textures, etc.). Daemon-only (RHI/multipass infrastructure has no compositor-side equivalent).

This header documents the contract for the first category. It applies identically across both runtime execution sites:

Compositor (window-content) executionkwin-effect running inside the KWin compositor process. Uses classic OpenGL via KWin::GLShader. Animates window contents during lifecycle events (window.appearance.open, window.movement.move, window.movement.snapIn, …).

Daemon (overlay-surface) executionSurfaceAnimator::runLeg in the Phosphor daemon. Uses Qt RHI via PhosphorRendering::ShaderEffectShaderNodeRhi. Animates daemon-owned overlay surfaces (snap-assist popup, OSD notification, layout-picker, zone-selector show/hide).

Both runtimes drive the same animation shaders with the same uniform values. Authors write one effect.frag; it runs identically wherever it's invoked.

UBO source convention
Animation shaders declare their uniforms inside the canonical AnimationUniforms UBO via:
#version 450
#include "animation_uniforms.glsl"
// ...read from iTime, iResolution, customParams[N].xyz...

The canonical header (data/animations/shared/animation_uniforms.glsl) has two #ifdef-selected branches:

• Default branch (daemon path): layout(std140, binding = 0) uniform AnimationUniforms { ... }; — std140-aligned with PhosphorShaders::BaseUniforms covering the base footprint (672 bytes; pinned by BaseUniforms.h's static_asserts) plus the 48-byte anchor extension (AnimationUniformExtension, total 720), populated by Qt-RHI's binding=0 upload.

#ifdef PLASMAZONES_KWIN branch (compositor path): plain default-block uniform float iTime;-style declarations. The kwin-effect prepends #define PLASMAZONES_KWIN after the shader's #version line before passing the source to KWin::ShaderManager::generateCustomShader, which selects this branch. KWin's KWin::GLShader::setUniform(loc, val) API addresses default-block uniforms only and never binds UBOs.

Shader authors do NOT need their own #ifdef PLASMAZONES_KWIN blocks — both branches expose the same identifiers (iTime, iResolution, customParams[N], uTexture0, etc.). The macro switch describes the uniform-binding ABI, not the runtime feature set.

Per-effect declared parameters
Every parameter declared in metadata.json lands in either a customParams[N].xyz slot (float / int / bool) or a customColors[N] slot (color), in declaration order. The two allocators advance independently — a color parameter does NOT consume a customParams sub-slot, so a [color, float] declaration produces customColors[0] + customParams[0].x, not customColors[0] + customParams[0].y. Float / int / bool parameters fill customParams[0].xcustomParams[7].w (32 slots); color parameters fill customColors[0]customColors[15] (16 slots).

AnimationShaderRegistry::translateAnimationParams(effectId, friendlyMap) converts a friendly parameter map (e.g. {"direction": 1, "parallax": 0.2, "tint": "#ff8800"}) into the slot-keyed map (e.g. {"customParams1_x": 1, "customParams1_y": 0.2, "customColor1": QColor(0xff, 0x88, 0x00)}) both runtimes consume. Color values are coerced to QColor at that boundary — strings parseable by the QColor constructor are accepted alongside QColor instances; everything else falls back to the declared default, then transparent.

Core animation contract
iTime, iResolution, customParams[8], and customColors[16] are the active contract fields populated by both runtimes.
Cross-runtime field coverage
Both runtimes populate the same core animation contract:

iTime — per-leg progress in [0,1] • iResolution — surface size • iTimeDelta — wall-clock seconds since the previous paint tick for this leg (0 on the first tick). Daemon: SurfaceAnimator's driver delta. Kwin: paintWindow's steady-clock delta. • iFrame — per-leg frame counter starting at 0 on each fresh attach. Daemon: SurfaceAnimator's per-leg counter. Kwin: ShaderTransition::frameCount. • iDate — local-time (year, month, day, seconds-since-midnight). Daemon: ShaderNodeRhi scene-data sync (throttled to 1 Hz). Kwin: QDateTime::currentDateTime() per paint. • iMouse — cursor position in shader-local logical pixels; (-1, -1) when off-surface. Daemon: QQuickHoverHandler on the attached shader item. Kwin: effects->cursorPos() minus the window's frame-geometry origin, gated on frameGeometry() containment — except on a held-move leg, which takes no sentinel and clamps into the frame instead (see kIMouse below). • customParams[N] / customColors[N] — per-effect declared parameters resolved at transition begin time. • uTexture0 — redirected surface texture. Daemon: live FBO from the layer-enabled shader anchor. Kwin: KWin's OffscreenEffect-managed window snapshot. • iTextureResolution[] — pixel sizes of the bound user textures. Daemon: node-resolved live. Kwin: pushed by paintWindow's per-effect texture loop.

Audio spectrum (opt-in module, both runtimes)
iAudioSpectrumSize + the uAudioSpectrum sampler are an opt-in module: a pack includes data/animations/shared/audio.glsl (after the canonical header) and declares "audio": true in its metadata. The daemon feeds them via SurfaceAnimator::setAudioSpectrum (the OverlayService wires CAVA's IAudioSpectrumProvider::spectrumUpdated here); the kwin-effect reuses its surface-decoration CAVA provider and pushes both per frame from paintWindow. The metadata flag is what keys the kwin CAVA run-gate — an assigned audio pack keeps the provider warm so a transition's first frame already has a spectrum. The helpers read 0 (render static) while the visualizer is off.
Daemon-only extensions
Shaders that need these must run on the daemon overlay path until the compositor wires the matching producers — but the two fail in DIFFERENT ways on the compositor:

iChannelResolution[] — auto-populated when multipass buffer shaders are bound. NOT declared by the canonical header's #ifdef PLASMAZONES_KWIN branch (the compositor is single-pass, no buffer FBOs), so reaching for it there is a COMPILE error, not a zero read. • iTimeHi — auto-computed wrap counterpart of iTime. Declared on both branches for source parity; reads zero on the compositor.

Spatial uniforms — per-runtime capture geometry
These four fields describe where the captured card sits inside the texture / FBO the shader renders into. Both runtimes populate all four — KWin via classic-GL setUniform, the daemon via AnimationUniformExtension — so none are guarded by #ifdef PLASMAZONES_KWIN. They matter because neither runtime captures the bare card: KWin redirects the EXPANDED window geometry (frame + decoration + shadow); the daemon captures the shader anchor, which a PopupFrame enlarges past the card by a glow margin so the glow animates with the card.

iAnchorSize — the visible card's pixel size, logical pixels. • iAnchorPosInFbo — the card's top-left within the FBO the shader rasterises, logical pixels. anchorRemap consumes it to fold surface-extent vTexCoord into card space; (0, 0) for anchor-extent, where the vertex stage already delivers card- space vTexCoord. • iAnchorRectInTexture — the card's UV sub-rect within uTexture0. surfaceColor() folds it in so a card-space [0, 1] sample addresses the card's region of the padded texture. A bare card-sized anchor carries the (0, 0, 1, 1) identity. • iSurfaceScreenPos — the surface's origin (.xy — global workspace coordinates on the kwin path, position within the host wl_surface on the daemon) plus the host extent (.zw — screen size on the kwin path, the host surface's size on the daemon). The minimize-to-icon packs reconstruct the window rect from it, and position-keyed noise seeds hash it.

iFlipBufferY, qt_Matrix, qt_Opacity, _appField0 / _appField1 are daemon-only and absent from the canonical header's #ifdef PLASMAZONES_KWIN branch entirely.

Function Documentation

◆ colorKey()

QString PhosphorAnimationShaders::AnimationShaderContract::colorKey ( int  slot)
inline

Format a customColor slot key — thin forwarder onto PhosphorShaders::CustomColors::colorKey.

Sibling of slotKey(int) for the customParams region. Kept here for the same reason: animation call sites stay inside this contract namespace instead of leaking the underlying phosphor-shaders header. See <PhosphorShaders/CustomParamsKey.h> for the format and the out-of-range graceful-degradation contract.

◆ isValidFilterToken()

bool PhosphorAnimationShaders::AnimationShaderContract::isValidFilterToken ( const QString &  filter)
inline

The accepted buffer filter vocabulary: linear / nearest / mipmap.

An empty string is NOT a member — callers treat empty as "use the runtime default" and handle it before consulting this predicate.

Hoisted for the same reason as isValidWrapToken: it was hand-inlined at a single site in the surface tree and entirely ABSENT from the animation tree, so an animation pack's "bufferFilter": "linaer" was accepted, silently coerced by the runtime, and re-persisted to disk on the next save.

◆ isValidWrapToken()

bool PhosphorAnimationShaders::AnimationShaderContract::isValidWrapToken ( const QString &  wrap)
inline

The accepted texture wrap vocabulary, shared by every animation validation site (metadata parse in AnimationShaderEffect::fromJson, and runtime-override translation in AnimationShaderRegistry).

Multipass limitation (compositor path)
Animation shaders may declare multipass buffer shaders, wallpaper, and depth in their metadata. The daemon's SurfaceAnimator wires these through to PhosphorRendering::ShaderEffect which has full multipass support. However, the kwin-effect compositor path uses KWin::GLShader via OffscreenEffect, which is single-pass with no auxiliary FBOs. Multipass animation shaders degrade to single-pass on the compositor with a diagnostic log.
Std140 offset contract
The canonical data/animations/shared/animation_uniforms.glsl UBO declares its fields at the same byte offsets as PhosphorShaders::BaseUniforms (the daemon's binding=0 upload struct). That alignment is what lets a single effect.frag source run on both runtimes without per-runtime overrides.

The C++ side of the contract is pinned by static_assert(offsetof(...)) statements in <PhosphorShaders/BaseUniforms.h> for every BASE field declared in the GLSL UBO (through iIsReversed at byte 660); the anchor tail (iSurfaceScreenPos .. iAnchorRectInTexture, bytes 672-719, 720 total) is supplied by AnimationUniformExtension and pinned by the size static_asserts in <PhosphorAnimation/AnimationUniformExtension.h>. If anyone reorders BaseUniforms, those asserts fail at compile time and the canonical GLSL header has to be updated to match. The GLSL side is exercised at build time by tests/unit/ui/shaders/test_animation_shader_bake.cpp, which runs every built-in animation shader through qsb (which in turn computes std140 offsets) — a layout drift would surface there as a bake failure. Returns true only for the three canonical tokens clamp / repeat / mirror. An empty string is NOT a member — callers treat empty as "use the runtime default" and handle it explicitly before consulting this predicate.

Centralised here for the same reason as its surface twin (SurfaceShaderContract::isValidWrapToken): the vocabulary was hand-inlined at the metadata site and simply MISSING at the runtime-override site, so a typo in a shader profile was accepted, handed to wrapStringToEnum, and silently coerced to clamp with no diagnostic — while the same typo in metadata.json warned. Vocabulary matches the runtime normaliser.

◆ slotKey() [1/2]

QString PhosphorAnimationShaders::AnimationShaderContract::slotKey ( int  slot)
inline

Flat-slot overload: slot is 0..31 across the 8 vec4 slots.

◆ slotKey() [2/2]

QString PhosphorAnimationShaders::AnimationShaderContract::slotKey ( int  vec,
char  comp 
)
inline

Format a customParams slot key — thin forwarder onto PhosphorShaders::CustomParams::slotKey, the cross-library canonical helper.

Kept here so animation-shader call sites can refer to a name inside this contract namespace and consumers don't need to import the phosphor-shaders header directly. See <PhosphorShaders/CustomParamsKey.h> for the format, the rationale, and the full list of consumers.

Variable Documentation

◆ kCustomColorsArray

constexpr const char* PhosphorAnimationShaders::AnimationShaderContract::kCustomColorsArray = "customColors"
inlineconstexpr

vec4 customColors[N] — per-effect declared color parameter slots.

Cross-runtime element-name lookup constant, symmetric with kCustomParamsArray above: mirrored by (not consumed from) the kwin-effect's glGetUniformLocation("customColors[N]") calls (the canonical header's #ifdef PLASMAZONES_KWIN branch declares them as default-block uniforms customColors[0]..customColors[15]) and as a documentation anchor for shader authors.

Carries straight (non-premultiplied) RGBA: the encoder writes QColor::redF/greenF/blueF/alphaF verbatim, so a 50%-alpha red arrives at the shader as (1.0, 0.0, 0.0, 0.5) not (0.5, 0.0, 0.0, 0.5). Authors should premultiply manually if their composite math expects it.

Naming asymmetry: the GLSL array is plural (customColors[N]) but the slot-key the encoder/decoder pass through QVariantMap is singular and 1-based (customColor1customColor16). The encoder produces the singular form directly via PhosphorShaders::CustomColors::colorKey(slot) (a "customColor"

  • (slot+1) join — no string-manipulation step). This matches the customParams[N]customParamsN_<x|y|z|w> pattern — both choose 1-based singular keys for QML/JSON friendliness while keeping the GLSL declaration in 0-based plural form per std140 convention.

◆ kCustomParamsArray

constexpr const char* PhosphorAnimationShaders::AnimationShaderContract::kCustomParamsArray = "customParams"
inlineconstexpr

vec4 customParams[N] — per-effect declared parameter slots.

Cross-runtime element-name lookup constant: mirrored by (not consumed from) the kwin-effect's glGetUniformLocation("customParams[N]") calls (the canonical header's #ifdef PLASMAZONES_KWIN branch declares them as default-block uniforms customParams[0]..customParams[7]) and as a documentation anchor for shader authors. Symmetric with kCustomColorsArray below.

◆ kIAnchorPosInFbo

constexpr const char* PhosphorAnimationShaders::AnimationShaderContract::kIAnchorPosInFbo = "iAnchorPosInFbo"
inlineconstexpr

vec2 iAnchorPosInFbo is the anchor's top-left position inside the FBO, in logical pixels.

Combined with iAnchorSize and iResolution, shaders compute the anchor's UV region for vTexCoord → anchor-space remap (used by morph + broken-glass; previously did this via customParams[7].x which is no longer reserved): vec2 anchorTopLeftUv = iAnchorPosInFbo / iResolution; vec2 anchorSizeUv = iAnchorSize / iResolution; vec2 anchorUv = (vTexCoord - anchorTopLeftUv) / anchorSizeUv; Daemon-side written via AnimationUniformExtension; kwin-effect uses classic-GL setUniform. On kwin the value is the anchor's top-left offset within whatever rect the FBO spans: the shadow / decoration inset within the EXPANDED window geometry for an anchor-extent transition (KWin's OffscreenEffect redirects the whole window item — see kIAnchorRectInTexture below), or the window's position within the output for a surface-extent transition. It collapses to (0, 0) only when the expanded geometry equals the frame (an undecorated, shadowless window). Shaders MUST apply the anchor-space remap (anchorRemap in anchor_remap.glsl) on both runtimes; tests/unit/ui/effect/test_anchor_uniforms.cpp pins the kwin inset values.

◆ kIAnchorRectInTexture

constexpr const char* PhosphorAnimationShaders::AnimationShaderContract::kIAnchorRectInTexture = "iAnchorRectInTexture"
inlineconstexpr

vec4 iAnchorRectInTexture — the card's UV sub-rect within uTexture0, as (x, y, width, height) in the texture's [0, 1] space.

Populated on BOTH runtimes: each captures more than the bare card into uTexture0. KWin's OffscreenEffect redirects the whole window item (decoration + shadow included), so uTexture0 covers the EXPANDED geometry. The daemon renders the shader anchor into uTexture0, and a PopupFrame enlarges that anchor past the card by a glow margin so the glow animates with the card. surfaceColor() folds this rect in so a card-space [0, 1] sample addresses the card's sub-region instead of stretching the whole texture across the card (the "card animates smaller than it lands" artefact); on the daemon animation.vert also divides texCoord by it so anchor- extent shaders receive card-space vTexCoord in the first place. A bare card-sized anchor with no capture margin carries the (0, 0, 1, 1) identity. Daemon: written through AnimationUniformExtension (UBO offset 704). kwin-effect: pushed via classic-GL setUniform.

◆ kIAnchorSize

constexpr const char* PhosphorAnimationShaders::AnimationShaderContract::kIAnchorSize = "iAnchorSize"
inlineconstexpr

vec2 iAnchorSize — captured anchor (card) pixel size in logical pixels.

Decoupled from iResolution so vertex shaders can rely on it under any fboExtentKind; iResolution is auto-reset by Qt to the shader item's bounds on every geometry event and would otherwise clobber any anchor-size override on a fboExtentKind: Surface item. Daemon-side written via AnimationUniformExtension; kwin-effect uses classic-GL setUniform.

◆ kIAudioSpectrumSize

constexpr const char* PhosphorAnimationShaders::AnimationShaderContract::kIAudioSpectrumSize = "iAudioSpectrumSize"
inlineconstexpr

int iAudioSpectrumSize — CAVA bar count, 0 while the audio visualizer is off or cava is unavailable.

Daemon: BaseUniforms UBO member fed by SurfaceAnimator::setAudioSpectrum. Kwin: default-block uniform declared by the opt-in data/animations/shared/audio.glsl module, pushed per frame via bindSurfaceAudio (shared with the surface decoration path).

◆ kIDate

constexpr const char* PhosphorAnimationShaders::AnimationShaderContract::kIDate = "iDate"
inlineconstexpr

vec4 iDate — local-time (year, month, day, seconds-since-midnight).

Year and month are integers stored as floats (Year=2026.0, January=1.0); day-of-month likewise; the .w component carries fractional seconds for shaders that key on time-of-day. Read once per paint on the kwin path; daemon throttles its sync to 1 Hz.

◆ kIFrame

constexpr const char* PhosphorAnimationShaders::AnimationShaderContract::kIFrame = "iFrame"
inlineconstexpr

int iFrame — per-leg frame counter.

Reset to 0 on every fresh beginShaderTransition install (or supersession), increments by 1 on every paintWindow tick that feeds the transition. Daemon equivalent: SurfaceAnimator's per-leg frame counter — same 0-based reset-on-attach semantic so shaders that key staggered effects off iFrame see identical sequences on both runtimes.

◆ kIFromRect

constexpr const char* PhosphorAnimationShaders::AnimationShaderContract::kIFromRect = "iFromRect"
inlineconstexpr

vec4 iFromRect / vec4 iToRect — geometry-morph endpoints in logical screen pixels (x, y, width, height).

Used by the window geometry morph (snap / layout switch / maximize): the window jumps to its destination instantly via moveResize, and the shader animates the visual transition by interpolating the drawn quad from iFromRect (old frame) to iToRect (new frame) by iTime, cross-fading the captured old content (uOldWindow) into the live new content. Both default to (0,0,0,0) for non-morph transitions (window.open/close/etc.), which a shader can treat as "no morph". COMPOSITOR PATH ONLY, and deliberately NOT declared by the canonical shared header: geometry-morph packs are compositor-only (appliesTo: ["geometry"]), so each one (flow, fold, phosphor-stream, ripple-snap, stretch, window-morph) declares the pair as plain default-block uniforms with no guard — the daemon never bakes or attaches such packs (shaderEffectIsCompositorOnly gates the warm-bake, SurfaceAnimator, the daemon-target bake tests, and shadervalidate), so its strict SPIR-V path never sees the loose declarations.

◆ kIHasOldWindow

constexpr const char* PhosphorAnimationShaders::AnimationShaderContract::kIHasOldWindow = "iHasOldWindow"
inlineconstexpr

int iHasOldWindow — COMPOSITOR PATH ONLY.

1 when uOldWindow holds a genuinely captured old-content snapshot for this transition, 0 when no capture ran (a held lifecycle event like window.movement.move begins with no geometry change to snapshot). Old-content samplers MUST gate on this and fall back to surfaceColor() when 0: the compositor's no-snapshot fallback aliases uOldWindow onto unit 0 (the RAW undecorated window), so an ungated cross-fade from "old" blanks every decoration pack until the fade completes.

◆ kIHasSurfaceLayer

constexpr const char* PhosphorAnimationShaders::AnimationShaderContract::kIHasSurfaceLayer = "iHasSurfaceLayer"
inlineconstexpr

int iHasSurfaceLayer — COMPOSITOR PATH ONLY.

1 when uSurfaceLayer holds a valid layered surface for this frame (the window has ≥1 active surface layer), 0 otherwise. surfaceColor() branches on it so a window with no surface layers animates the bare uTexture0 exactly as before. Pushed every frame by the kwin-effect; defaults to 0 (GL zero-init), so a shader that never receives it falls back to the unlayered path.

◆ kIIconRect

constexpr const char* PhosphorAnimationShaders::AnimationShaderContract::kIIconRect = "iIconRect"
inlineconstexpr

vec4 iIconRect — the window's task-manager icon rectangle in logical screen pixels (x, y, width, height), same coordinate space as iFromRect / iToRect and as iSurfaceScreenPos.xy + iAnchorSize (the window's frame rect).

Captured from EffectWindow::iconGeometry() when the transition installs and pushed every frame, so a minimize-to-icon pack (genie, phosphor-siphon) can deform the window toward its taskbar icon. The rect comes from the task manager via PlasmaWindowManagement; a window that sits in no task manager carries (0, 0, 0, 0), which a pack MUST treat as "no icon target" and degrade to an in-place animation. The rect may sit on a DIFFERENT output than the window (a taskbar on another monitor); the window is painted only during its own output's pass, so a deformation toward a foreign-output icon clips at that output's edge. COMPOSITOR PATH ONLY and deliberately NOT declared by the canonical shared header. Unlike iFromRect / iToRect (whose geometry-pack consumers are compositor-only and declare them unguarded), the packs that read this one (genie, phosphor-siphon) are appearance-class and DAEMON-CAPABLE, so each declares it inside its own #ifdef PLASMAZONES_KWIN block — that guard is what keeps the daemon's strict SPIR-V bake of these dual-runtime packs away from the loose declaration.

◆ kIIsReversed

constexpr const char* PhosphorAnimationShaders::AnimationShaderContract::kIIsReversed = "iIsReversed"
inlineconstexpr

int iIsReversed — direction signal for asymmetric leg rendering.

Value is exactly 1 on reverse legs (window.close / going-to- minimized / unmaximize on the kwin path; hide leg on the daemon path) and exactly 0 on forward legs. The runtime also flips iTime for reverse legs so symmetric shaders auto-mirror — asymmetric shaders branch on this when the iTime flip alone can't express the open-vs-close difference.

Authoring rule: branch with iIsReversed == 1 (NOT iIsReversed != 0 or implicit-truthy). This pins the behaviour against any future runtime that elects to extend the encoding (e.g. negative for "unknown direction"). Bundled packs read direction through the p_reversed / legProgress() helpers in the canonical header, which encode this rule once.

◆ kILayerRectInTexture

constexpr const char* PhosphorAnimationShaders::AnimationShaderContract::kILayerRectInTexture = "iLayerRectInTexture"
inlineconstexpr

Card/anchor-space [0,1] sub-rect of the animated surface WITHIN uSurfaceLayer's canvas (xy offset, zw scale) — the layer analogue of iAnchorRectInTexture.

The compositor pads the layer canvas by the decoration chain's outer margin (glow reach), so the layer needs its own remap; an unpadded layer carries the same value as iAnchorRectInTexture. Compositor-only, like uSurfaceLayer itself.

◆ kIMouse

constexpr const char* PhosphorAnimationShaders::AnimationShaderContract::kIMouse = "iMouse"
inlineconstexpr

vec4 iMouse — cursor position in shader-local pixels.

.xy = (cursorX, cursorY) relative to the shader surface's origin (window frame origin on the kwin path; overlay surface origin on the daemon path); (-1, -1) when the cursor is outside the shader's surface. .zw on the kwin path carry the same cursor position normalised to the frame size ([0, 1] inside the window, negative when the off-surface sentinel applies) — phosphor-vortex reads them; the daemon overlay contract reserves .zw for click state.

ONE EXCEPTION, the held-move leg (move class, kwin path): no sentinel is ever applied, and the position is clamped into the frame instead. The sentinel answers a HOVER question, and a drag is not a hover — the cursor is the grab anchor for the whole leg. The pointer leaves the frame routinely mid-drag (KWin clamps the window at the screen edge while the pointer travels on), and a move pack that centres on .zw would jump to its fallback and stay there. So a move pack may read .xy / .zw unconditionally: they stay inside [0, size) / [0, 1) for the whole hold.

◆ kIMoveMesh

constexpr const char* PhosphorAnimationShaders::AnimationShaderContract::kIMoveMesh = "iMoveMesh"
inlineconstexpr

vec2 iMoveMesh[16] — COMPOSITOR PATH ONLY.

The solved displacements of a 4x4 neighbour-coupled soft-body control lattice for HELD move transitions: slot i+4*j is node (i,j)'s deflection from its ideal grid position on the current frame, logical px, row 0 at the top. The host runs the spring simulation (grip-constrained, wave-propagating); a pack interpolates the lattice across its render mesh (bilinear or bicubic Bezier) to deform WITH real physics. All zeros at rest / non-held / daemon. Generic: wobble is the first consumer, but any geometry pack can read the same lattice.

◆ kIMoveOffset

constexpr const char* PhosphorAnimationShaders::AnimationShaderContract::kIMoveOffset = "iMoveOffset"
inlineconstexpr

vec2 iMoveOffset — COMPOSITOR PATH ONLY.

Raw displacement of the window's frame origin since the interactive grab, logical px. Zero for non-held transitions and on the daemon path.

◆ kIMoveTrail

constexpr const char* PhosphorAnimationShaders::AnimationShaderContract::kIMoveTrail = "iMoveTrail"
inlineconstexpr

vec2 iMoveTrail[16] — COMPOSITOR PATH ONLY.

Short motion history for HELD move transitions: slot k holds the window origin as it was k*15 ms in the past, RELATIVE to the current origin (so a static window publishes all zeros). 16 slots = 240 ms of path. The host merely records positions — all deformation logic stays in the shader, which samples the trail at a per-vertex delay to follow the drag PATH (delayed-follow is what a spring mesh visually does), typically weighted by distance from the grip point (iMouse).

◆ kIMoveVelocity

constexpr const char* PhosphorAnimationShaders::AnimationShaderContract::kIMoveVelocity = "iMoveVelocity"
inlineconstexpr

vec2 iMoveVelocity — COMPOSITOR PATH ONLY.

Spring-smoothed window velocity in logical px/s during a HELD interactive move transition (holdUntilRelease; an interactive resize starts no shader transition). The smoothing filter is underdamped on purpose: after the pointer stops or releases, the value decays through zero with a slight overshoot, so a velocity-driven deformation (wobble, tilt) relaxes with a natural spring settle instead of freezing. Zero for time-driven transitions and on the daemon path.

◆ kIMoveVelocity2

constexpr const char* PhosphorAnimationShaders::AnimationShaderContract::kIMoveVelocity2 = "iMoveVelocity2"
inlineconstexpr

vec2 iMoveVelocity2 — COMPOSITOR PATH ONLY.

A second, LOOSER spring over the same frame velocity (lower stiffness, lighter damping), so it lags iMoveVelocity and rings longer after release. Blending between the two by distance from the grip point gives a jelly deformation whose far regions move out of phase with the grip — the KDE-wobbly billow — without any per-vertex state in the shader.

◆ kIOldWindowOpacity

constexpr const char* PhosphorAnimationShaders::AnimationShaderContract::kIOldWindowOpacity = "iOldWindowOpacity"
inlineconstexpr

float iOldWindowOpacity — COMPOSITOR PATH ONLY.

The OLD side's own resolved opacity, applied by oldColor() in old_content.glsl. Equal to iWindowOpacity for every self-cross-fade (the old content is the same window's past, so the same SetOpacity verdict covers both sides). It exists for the tab class, where uOldWindow holds a DIFFERENT window — the outgoing tab — and dimming its capture by the ARRIVING window's opacity dimmed the wrong side whenever a SetOpacity rule distinguished the two tabs of one column. 1.0 when the snapshot already carries its opacity baked in (the decorated-composite seed).

◆ kIResolution

constexpr const char* PhosphorAnimationShaders::AnimationShaderContract::kIResolution = "iResolution"
inlineconstexpr

vec2 iResolution — surface size in logical pixels.

Window frame size on the compositor execution site; overlay-surface size on the daemon execution site.

◆ kISurfaceScreenPos

constexpr const char* PhosphorAnimationShaders::AnimationShaderContract::kISurfaceScreenPos = "iSurfaceScreenPos"
inlineconstexpr

vec4 iSurfaceScreenPos — the shader surface's position plus the host screen dimensions, both in logical pixels.

.xy = (surfaceX, surfaceY) — top-left of the shader surface. On the kwin path this is the window's GLOBAL (workspace) origin — the raw frameGeometry() top-left, the same space as iFromRect / iToRect / iIconRect — which coincides with screen-relative only on a single output at the workspace origin. The daemon pushes the anchor's position within its host wl_surface (the playing field), whose size fills .zw there. .zw = (screenWidth, screenHeight) of the host screen on the kwin path; the host surface's size on the daemon path

Daemon: written to the appended AnimationUniformExtension (UBO offset 672 = sizeof(BaseUniforms)). The extension is installed by SurfaceAnimator::attachShaderToAnchor so this field is animation- only — it never lands in zone-shader UBOs (those use ZoneUniformExtension instead).

kwin-effect: pushed via classic-GL setUniform keyed on this exact name. Independent of the UBO mechanism — the UBO contract isolation only matters on the daemon path.

Shaders that need the surface's position read this: the minimize-to-icon packs (genie, phosphor-siphon) reconstruct the window rect as vec4(iSurfaceScreenPos.xy, iAnchorSize), valid because they pair it with the same-space iIconRect, and surfaceSeed() in noise.glsl hashes it for position-keyed noise. It is NOT suitable for per-output edge-distance math — fly-in's nearest-edge pick deliberately avoids it (a global coordinate does not share an origin with a per-output extent on multi-monitor setups) and uses iAnchorPosInFbo / iResolution instead. Population cadence: the daemon writes it on leg attach and on every anchor or window geometry signal; the kwin path pushes it every paint frame.

◆ kITime

constexpr const char* PhosphorAnimationShaders::AnimationShaderContract::kITime = "iTime"
inlineconstexpr

float iTime — transition progress, normally in [0.0, 1.0].

**iTime CAN LEAVE [0, 1].** A curve that overshoots (an underdamped spring, a back / elastic ease) delivers its overshoot to the shader rather than being flattened: iTime may exceed 1.0 and dip below 0.0 for those curves, because the overshoot IS the curve, and the geometry animator bounces past its target on the same pick. Authors MUST clamp(iTime, 0.0, 1.0) defensively wherever an out-of-range value would misbehave — a texture() fetch, or a mix() whose endpoints must not be extrapolated past. Where the overshoot is meaningful (a rect lerp, a scale), let it through and the pack bounces for free. Non-overshooting curves are still clamped by the host.

Curve shape is NOT guaranteed to be linear. Both runtimes feed iTime through the resolved Profile's easing curve:

• Compositor (kwin-effect): BOTH branches are curve-shaped. The time-driven branch (window.appearance.* — open, close, minimize, focus — and the desktop switch) eases its linear (now - startTimeMs) / durationMs through the curve resolved by the motion cascade (global → the category's "All" node → the per-event node → a Rule override). The animator-driven branch (window.movement.* via applyWindowGeometry) reads the already curve-shaped geometry animation's state value. A stateful spring integrates per frame toward 1 and rings out over its settle time. There is no longer a linear branch: a null curve is reachable only before settings load, so in practice every event is curved. • Daemon (SurfaceAnimator): the shaderTime AnimatedValue runs under the resolved showShaderProfile / hideShaderProfile curve, falling back to the opacity profile's curve when the shader profile is empty. So an OutCubic opacity profile makes iTime arrive on an OutCubic ramp, not a linear ramp.

Authors writing mix(a, b, iTime) should be aware: if the resolved curve is non-linear, the visual blend follows that curve. If a shader requires linear progress, register a Linear-curve profile and bind it via showShaderProfile. Most transitions look better with a curved iTime (the curve smooths the visual progression), so this is the documented default rather than a forced linear override.

◆ kITimeDelta

constexpr const char* PhosphorAnimationShaders::AnimationShaderContract::kITimeDelta = "iTimeDelta"
inlineconstexpr

float iTimeDelta — wall-clock seconds between consecutive paint ticks that fed this transition.

First tick of a fresh transition reports 0.0; subsequent ticks report (now - lastPaint) / 1000. Daemon equivalent: SurfaceAnimator's driver-tick delta, fed by the same monotonic clock semantics.

◆ kIToRect

constexpr const char* PhosphorAnimationShaders::AnimationShaderContract::kIToRect = "iToRect"
inlineconstexpr

◆ kIWindowOpacity

constexpr const char* PhosphorAnimationShaders::AnimationShaderContract::kIWindowOpacity = "iWindowOpacity"
inlineconstexpr

float iWindowOpacity — the window's effective rule-resolved opacity in [0.0, 1.0], COMPOSITOR PATH ONLY.

A SetOpacity rule must dim the window for the whole duration of a transition, but the custom transition shader is compiled MapTexture-only (no Modulate trait), so KWin never applies data.opacity() to it — surfaceColor() multiplies the premultiplied surface sample by this uniform instead. The kwin-effect pushes it every frame via classic-GL setUniform, defaulting to 1.0 for windows with no matching rule (a no-op dim).

Absent on the daemon path: overlay-surface animations have no window-rule opacity (SetOpacity is a compositor window-rule feature), so the canonical header declares this uniform — and folds it into surfaceColor() — only inside the #ifdef PLASMAZONES_KWIN branch.

◆ kMaxBufferPasses

constexpr int PhosphorAnimationShaders::AnimationShaderContract::kMaxBufferPasses = 4
inlineconstexpr

Multipass buffer-pass budget.

Pinned to the runtime's binding budget (PhosphorRendering::kMaxBufferPasses, ShaderNodeRhi) and the GLSL contract's vec4 iChannelResolution[4]: the downstream setters read at most this many entries, so a pack declaring more would silently lose the tail — the parser caps (with a warning) at this bound instead.

◆ kMaxCustomColors

constexpr int PhosphorAnimationShaders::AnimationShaderContract::kMaxCustomColors = PhosphorShaders::CustomColors::kColorCount
inlineconstexpr

◆ kMaxCustomParams

constexpr int PhosphorAnimationShaders::AnimationShaderContract::kMaxCustomParams = PhosphorShaders::CustomParams::kVecCount
inlineconstexpr

Number of vec4 slots in the customParams array (8).

Forwards to the canonical constant in <PhosphorShaders/CustomParamsKey.h> so a single source of truth governs both libraries.

Note: this is the vec4 slot count, NOT the per-effect parameter budget. The per-parameter budget is kMaxParameterSlots (32 sub-slots across the 8 vec4s); a shader can declare up to 32 float/int/bool parameters before translateAnimationParams starts dropping overflow. The kMaxCustomColors constant below IS the per-color-param budget (16) — this naming asymmetry exists because customColors slots are whole-vec4 (one colour per slot) while customParams slots are sub-vec4 (one float per .xyzw component).

◆ kMaxParameterSlots

constexpr int PhosphorAnimationShaders::AnimationShaderContract::kMaxParameterSlots = PhosphorShaders::CustomParams::kFlatSlotCount
inlineconstexpr

Number of float sub-slots (4 per vec4 × 8 vec4s = 32).

Caps the count of declared parameters an animation shader can carry without spilling into a region the daemon's overlay extension owns. Forwards to the canonical constant in <PhosphorShaders/CustomParamsKey.h>.

◆ kMaxUserTextureSlots

constexpr int PhosphorAnimationShaders::AnimationShaderContract::kMaxUserTextureSlots = 3
inlineconstexpr

Maximum number of user-declared textures per animation effect.

Each declared texture binds to one of the canonical samplers uTexture1 / uTexture2 / uTexture3. The redirected surface itself (uTexture0, binding 7 on the daemon, TEXTURE0 on KWin) is not counted here — that's a separate runtime-managed slot. The daemon's PhosphorRendering::kMaxUserTextures = 4 includes slot 0, hence the off-by-one in the budget. Pinned to the daemon constant by the static_assert in libs/phosphor-animation/src/contract_pins.cpp.

The compile-time pin against PhosphorRendering::kMaxUserTextures lives in src/contract_pins.cpp — see the comment there explaining why the assert can't sit in this header (epoxy/Qt typedef collision in the kwin-effect TU).

◆ kUAudioSpectrum

constexpr const char* PhosphorAnimationShaders::AnimationShaderContract::kUAudioSpectrum = "uAudioSpectrum"
inlineconstexpr

sampler2D uAudioSpectrum — the CAVA spectrum texture (bars×1, R = bar value in 0..1).

Declared by the opt-in audio.glsl module: binding 6 on the daemon (the overlay convention), a named sampler bound to a dedicated unit at draw time on the kwin path. Never sampled while iAudioSpectrumSize is 0.

◆ kUOldWindow

constexpr const char* PhosphorAnimationShaders::AnimationShaderContract::kUOldWindow = "uOldWindow"
inlineconstexpr

sampler2D uOldWindow — snapshot of the window's content captured at the old frame size just before the instant moveResize.

The morph shader cross-fades this (alpha 1 - iTime) against the live new content in uTexture0 (alpha iTime), each mapped at native aspect, so an aspect-ratio-changing resize doesn't stretch the content. Bound to a dedicated texture unit on the kwin path; a transparent 1×1 fallback is bound when no snapshot was captured. The sampler is NOT declared by the canonical header — only the iHasOldWindow gate int is; packs that sample old content opt in via data/animations/shared/old_content.glsl, which declares the sampler unguarded: every including pack is compositor-only, excluded from the daemon's SPIR-V bake entirely via shaderEffectIsCompositorOnly.

◆ kUSurfaceLayer

constexpr const char* PhosphorAnimationShaders::AnimationShaderContract::kUSurfaceLayer = "uSurfaceLayer"
inlineconstexpr

sampler2D uSurfaceLayer — COMPOSITOR PATH ONLY.

The window's surface after the surface-layer stack (border / rounded corners, and any future layers such as a colour tint) has been composited into an FBO. When iHasSurfaceLayer != 0, surfaceColor() samples THIS in place of the bare live uTexture0, so an animation composites OVER the layered surface instead of the raw window — the border (and any other surface layer) stays visible for the whole transition instead of vanishing the instant the animation shader takes the draw slot. Bound to a dedicated texture unit on the kwin path; produced by PlasmaZonesEffect::renderSurfaceChain. Absent on the daemon path (the daemon composes its own multipass surface).