kuinetic 0.1.4 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +93 -10
- package/dist/esm/advanced/index.mjs +4727 -0
- package/dist/esm/chunk-AEQGC2KM.mjs +189 -0
- package/dist/esm/chunk-MQSNMYRP.mjs +9332 -0
- package/dist/esm/chunk-RVTYN6NZ.mjs +3446 -0
- package/dist/esm/chunk-U43BX5O2.mjs +2984 -0
- package/dist/esm/core/index.mjs +16 -9
- package/dist/esm/effects/index.mjs +19 -4
- package/dist/esm/index.mjs +17 -10
- package/dist/kuinetic.advanced.js +5070 -0
- package/dist/kuinetic.advanced.min.js +749 -0
- package/dist/kuinetic.all.js +15147 -6039
- package/dist/kuinetic.all.min.js +2 -0
- package/dist/kuinetic.css +2801 -318
- package/dist/kuinetic.js +15147 -6038
- package/dist/kuinetic.min.css +1 -0
- package/dist/kuinetic.min.js +7 -0
- package/dist/types/3d/angles.d.ts +30 -0
- package/dist/types/3d/flattening.d.ts +49 -0
- package/dist/types/3d/index.d.ts +36 -0
- package/dist/types/3d/model-3d.d.ts +115 -0
- package/dist/types/3d/register.d.ts +26 -0
- package/dist/types/3d/webgl.d.ts +24 -0
- package/dist/types/advanced/audio.d.ts +322 -0
- package/dist/types/advanced/base.d.ts +233 -0
- package/dist/types/advanced/camera-3d.d.ts +236 -0
- package/dist/types/advanced/fluid-cursor.d.ts +149 -0
- package/dist/types/advanced/gl-utils.d.ts +52 -0
- package/dist/types/advanced/glsl.d.ts +60 -0
- package/dist/types/advanced/index.d.ts +23 -0
- package/dist/types/advanced/particles.d.ts +108 -0
- package/dist/types/advanced/scenes.d.ts +207 -0
- package/dist/types/advanced/shaders.d.ts +972 -0
- package/dist/types/browser/boot.d.ts +72 -0
- package/dist/types/core/activation.d.ts +227 -2
- package/dist/types/core/animator.d.ts +446 -11
- package/dist/types/core/attrs.d.ts +9 -0
- package/dist/types/core/breakpoints.d.ts +260 -0
- package/dist/types/core/bundles.d.ts +71 -0
- package/dist/types/core/callback.d.ts +64 -0
- package/dist/types/core/capabilities.d.ts +47 -0
- package/dist/types/core/channels.d.ts +120 -2
- package/dist/types/core/cloak-selectors.d.ts +47 -0
- package/dist/types/core/compile.d.ts +207 -2
- package/dist/types/core/control.d.ts +218 -0
- package/dist/types/core/declarations.d.ts +128 -0
- package/dist/types/core/derived/aggregate.d.ts +43 -0
- package/dist/types/core/derived/book.d.ts +30 -0
- package/dist/types/core/derived/diagnostics.d.ts +51 -0
- package/dist/types/core/derived/group-gate.d.ts +62 -0
- package/dist/types/core/derived/install.d.ts +110 -0
- package/dist/types/core/derived/late-matches.d.ts +19 -0
- package/dist/types/core/derived/types.d.ts +108 -0
- package/dist/types/core/easing.d.ts +57 -0
- package/dist/types/core/element-config.d.ts +47 -6
- package/dist/types/core/element-size.d.ts +23 -0
- package/dist/types/core/event-sources.d.ts +39 -0
- package/dist/types/core/flip.d.ts +34 -1
- package/dist/types/core/gesture.d.ts +12 -0
- package/dist/types/core/host-facts.d.ts +155 -0
- package/dist/types/core/index.d.ts +7 -3
- package/dist/types/core/owned-styles.d.ts +129 -2
- package/dist/types/core/params.d.ts +48 -0
- package/dist/types/core/parse.d.ts +28 -1
- package/dist/types/core/register-into.d.ts +29 -0
- package/dist/types/core/registry.d.ts +10 -3
- package/dist/types/core/repeat.d.ts +91 -0
- package/dist/types/core/sequence.d.ts +126 -0
- package/dist/types/core/stagger-config.d.ts +199 -0
- package/dist/types/core/stagger-keys.d.ts +63 -0
- package/dist/types/core/stagger.d.ts +187 -6
- package/dist/types/core/style-plan.d.ts +21 -16
- package/dist/types/core/target.d.ts +128 -0
- package/dist/types/core/threshold-reachability.d.ts +45 -0
- package/dist/types/core/time-scale.d.ts +9 -0
- package/dist/types/core/toggle-actions.d.ts +141 -0
- package/dist/types/core/travel.d.ts +59 -0
- package/dist/types/core/types.d.ts +699 -13
- package/dist/types/core/unquoted-selectors.d.ts +53 -0
- package/dist/types/effects/carousel/drag.d.ts +80 -0
- package/dist/types/effects/carousel/index.d.ts +149 -0
- package/dist/types/effects/catalog/discrete.d.ts +13 -0
- package/dist/types/effects/catalog/interaction-proximity.d.ts +24 -0
- package/dist/types/effects/catalog/interaction-reveal.d.ts +248 -0
- package/dist/types/effects/catalog/interaction-shared.d.ts +42 -0
- package/dist/types/effects/catalog/interaction-states.d.ts +68 -0
- package/dist/types/effects/catalog/materials.d.ts +67 -0
- package/dist/types/effects/catalog/media-shared.d.ts +3 -2
- package/dist/types/effects/catalog/numbers-shared.d.ts +8 -5
- package/dist/types/effects/catalog/shared.d.ts +1 -1
- package/dist/types/effects/catalog/text-shared.d.ts +86 -10
- package/dist/types/effects/catalog/transforms.d.ts +13 -0
- package/dist/types/effects/catalog/view-transitions.d.ts +13 -0
- package/dist/types/effects/forms/primitives.d.ts +15 -4
- package/dist/types/effects/gestures/index.d.ts +53 -0
- package/dist/types/effects/index.d.ts +5 -0
- package/dist/types/effects/layout/presets.d.ts +35 -0
- package/dist/types/effects/motion-path/index.d.ts +35 -0
- package/dist/types/effects/scroll-mechanics/params.d.ts +23 -0
- package/dist/types/effects/scroll-mechanics/presets.d.ts +51 -0
- package/dist/types/effects/scroll-mechanics/section-index.d.ts +47 -0
- package/dist/types/effects/shared.d.ts +124 -4
- package/dist/types/effects/step-index.d.ts +121 -0
- package/dist/types/effects/step-marking.d.ts +41 -33
- package/dist/types/effects/svg/icon-parts.d.ts +23 -0
- package/dist/types/effects/three-d/flip-parts.d.ts +46 -0
- package/dist/types/effects/tween/index.d.ts +57 -0
- package/dist/types/effects/tween/properties.d.ts +107 -0
- package/dist/types/effects/tween/waypoints.d.ts +106 -0
- package/dist/types/showcase/compare.d.ts +4 -0
- package/dist/types/showcase/device-frame.d.ts +3 -0
- package/dist/types/showcase/hotspots.d.ts +3 -0
- package/dist/types/showcase/index.d.ts +30 -0
- package/dist/types/showcase/lightbox.d.ts +3 -0
- package/dist/types/showcase/media-source.d.ts +13 -0
- package/dist/types/showcase/modal-shell.d.ts +22 -0
- package/dist/types/showcase/scroll-story.d.ts +4 -0
- package/dist/types/showcase/shared.d.ts +47 -0
- package/dist/types/showcase/slideshow.d.ts +3 -0
- package/dist/types/showcase/slow-mo.d.ts +5 -0
- package/package.json +14 -7
- package/dist/esm/chunk-7WMNPIOZ.mjs +0 -4883
- package/dist/esm/chunk-JT4PZL3A.mjs +0 -773
- package/dist/esm/chunk-TJDDIQRG.mjs +0 -1290
|
@@ -0,0 +1,972 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* kUInetic Shaders - Shared WebGL2 Shader System
|
|
3
|
+
*/
|
|
4
|
+
import type { EffectInstance, EffectParams, PrepareContext, Preset, Primitive } from '../core/types.js';
|
|
5
|
+
import type { Registry } from '../core/registry.js';
|
|
6
|
+
import type { Animator } from '../core/animator.js';
|
|
7
|
+
import { type AdvancedEnv, type AnyWindow, type AnyDocument, type RafFunction, type CafFunction } from './base.js';
|
|
8
|
+
import { type ProgramLocations } from './gl-utils.js';
|
|
9
|
+
import { type AudioBand } from './audio.js';
|
|
10
|
+
/**
|
|
11
|
+
* `gl-utils.ts`'s location set plus the one uniform only this module's programs declare.
|
|
12
|
+
*
|
|
13
|
+
* Kept here rather than in `ProgramLocations` because `u_audio` is a `glsl.ts` uniform and this
|
|
14
|
+
* file is the only thing that uploads it; every field is optional there, so a plain
|
|
15
|
+
* `ProgramLocations` (an older cached program, or a caller that built one itself) still satisfies
|
|
16
|
+
* this type and simply has no audio location to upload to.
|
|
17
|
+
*/
|
|
18
|
+
export type ShaderProgramLocations = ProgramLocations & {
|
|
19
|
+
u_audio?: WebGLUniformLocation | null;
|
|
20
|
+
u_noise?: WebGLUniformLocation | null;
|
|
21
|
+
};
|
|
22
|
+
export interface ShaderUniformOptions {
|
|
23
|
+
time?: number;
|
|
24
|
+
speed?: number;
|
|
25
|
+
strength?: number;
|
|
26
|
+
frequency?: number;
|
|
27
|
+
chromatic?: number;
|
|
28
|
+
iridescence?: number;
|
|
29
|
+
progress?: number;
|
|
30
|
+
/** One audio band's current value (0..1), or 0/absent for "no audio". */
|
|
31
|
+
audio?: number;
|
|
32
|
+
localMouse?: {
|
|
33
|
+
x: number;
|
|
34
|
+
y: number;
|
|
35
|
+
};
|
|
36
|
+
tintRgba?: [number, number, number, number];
|
|
37
|
+
blendMode?: number;
|
|
38
|
+
isDuotone?: boolean;
|
|
39
|
+
c1Rgba?: [number, number, number, number];
|
|
40
|
+
c2Rgba?: [number, number, number, number];
|
|
41
|
+
uvOrigin?: [number, number];
|
|
42
|
+
uvScale?: [number, number];
|
|
43
|
+
/** Border-box centre and half-extents in device px, y up from the canvas bottom. */
|
|
44
|
+
maskBox?: [number, number, number, number];
|
|
45
|
+
/** Corner radii in device px, TL, TR, BR, BL. */
|
|
46
|
+
maskRx?: [number, number, number, number];
|
|
47
|
+
maskRy?: [number, number, number, number];
|
|
48
|
+
/** What the ancestors' own opacity leaves this element painted at. */
|
|
49
|
+
maskAlpha?: number;
|
|
50
|
+
seed?: number;
|
|
51
|
+
scale?: number;
|
|
52
|
+
warp?: number;
|
|
53
|
+
/**
|
|
54
|
+
* How much of `fluid`/`liquid`/`particles`/`morph`'s trig stand-in is replaced by real noise.
|
|
55
|
+
*
|
|
56
|
+
* Zero in the six programs that do not declare it, and zero by default in the four that do, which
|
|
57
|
+
* is what keeps every page already running these modes rendering exactly what it rendered before.
|
|
58
|
+
*/
|
|
59
|
+
noise?: number;
|
|
60
|
+
grain?: number;
|
|
61
|
+
/** Hue rotation in **radians**. The parameter is authored as an angle; `readAngleRadians` converts. */
|
|
62
|
+
hue?: number;
|
|
63
|
+
detail?: number;
|
|
64
|
+
bands?: number;
|
|
65
|
+
/** How many of `color1`..`color5` the author actually set. Below 2 the palette falls back. */
|
|
66
|
+
colorCount?: number;
|
|
67
|
+
/** The five palette stops, flattened to 20 floats for one `uniform4fv`. */
|
|
68
|
+
palette?: Float32Array;
|
|
69
|
+
/**
|
|
70
|
+
* Which stencil `mode: logo` cuts the field with — 0 none, 1 alpha, 2 luma, 3 inverted luma.
|
|
71
|
+
*
|
|
72
|
+
* See {@link MASK_MODES} and `glyphMask()` in `glsl.ts`. `0` in every mode but `logo`.
|
|
73
|
+
*/
|
|
74
|
+
maskMode?: number;
|
|
75
|
+
/**
|
|
76
|
+
* Which pointer gestures the generative field responds to, as bits — see {@link HOVER_MODES}.
|
|
77
|
+
*
|
|
78
|
+
* The pointer position itself already arrives as {@link localMouse}, which every program that
|
|
79
|
+
* declares `u_mouse` has been receiving all along; this is only the switch that decides whether
|
|
80
|
+
* `GRADIENT_FS` reads it. `0` — the default — is the one value under which the pointer cannot
|
|
81
|
+
* reach a pixel at all.
|
|
82
|
+
*/
|
|
83
|
+
hover?: number;
|
|
84
|
+
/** The colour the generative field is composited over, straight RGBA. Alpha `0` is "unset". */
|
|
85
|
+
backdropRgba?: [number, number, number, number];
|
|
86
|
+
/** The field's orientation in **radians**. Authored as an angle; `parseAngleRadians` converts. */
|
|
87
|
+
angle?: number;
|
|
88
|
+
/** How the generative field moves — see {@link MOTION_MODES}. `0` (`evolve`) is the default. */
|
|
89
|
+
motion?: number;
|
|
90
|
+
}
|
|
91
|
+
export interface ShaderDrawOptions extends ShaderUniformOptions {
|
|
92
|
+
mode: string;
|
|
93
|
+
strength: number;
|
|
94
|
+
speed: number;
|
|
95
|
+
frequency: number;
|
|
96
|
+
chromatic: number;
|
|
97
|
+
iridescence: number;
|
|
98
|
+
tintRgba: [number, number, number, number];
|
|
99
|
+
isDuotone: boolean;
|
|
100
|
+
c1Rgba: [number, number, number, number];
|
|
101
|
+
c2Rgba: [number, number, number, number];
|
|
102
|
+
blendMode: number;
|
|
103
|
+
texture?: WebGLTexture | null;
|
|
104
|
+
to?: string;
|
|
105
|
+
toTexture?: WebGLTexture | null;
|
|
106
|
+
/**
|
|
107
|
+
* Which `audio-source` band this instance follows, or `null` for none.
|
|
108
|
+
*
|
|
109
|
+
* The authored choice, not a value: the value is read per frame off the element (see
|
|
110
|
+
* `readAudioBand`) and arrives at the draw as {@link ShaderUniformOptions.audio}.
|
|
111
|
+
*/
|
|
112
|
+
audioBand?: AudioBand | null;
|
|
113
|
+
/**
|
|
114
|
+
* Whether this instance follows `--kui-progress`, the property the scroll primitives publish.
|
|
115
|
+
*
|
|
116
|
+
* The authored choice, not a value — the same shape as {@link audioBand} and for the same
|
|
117
|
+
* reason: the value is read per frame off the element (see `readElementProgress`) and arrives
|
|
118
|
+
* at the draw as {@link ShaderUniformOptions.progress}.
|
|
119
|
+
*/
|
|
120
|
+
scrubsFromScroll?: boolean;
|
|
121
|
+
/**
|
|
122
|
+
* This element's already-measured box, or absent to measure it here.
|
|
123
|
+
*
|
|
124
|
+
* The render loop measures every registered instance in its read pass and passes the answer down
|
|
125
|
+
* (see `renderFrame`); measuring inside the draw would interleave `getComputedStyle` and
|
|
126
|
+
* `getBoundingClientRect` with the previous instance's `opacity` write and force a style
|
|
127
|
+
* recalculation per element per frame.
|
|
128
|
+
*/
|
|
129
|
+
geometry?: ElementGeometry | null;
|
|
130
|
+
}
|
|
131
|
+
export type ShaderProgramEntry = ProgramLocations | WebGLProgram | null | undefined;
|
|
132
|
+
export interface ShaderRendererLike {
|
|
133
|
+
gl: WebGLRenderingContext | WebGL2RenderingContext | null;
|
|
134
|
+
canvas: HTMLCanvasElement | null;
|
|
135
|
+
window?: AnyWindow;
|
|
136
|
+
mouse: {
|
|
137
|
+
x: number;
|
|
138
|
+
y: number;
|
|
139
|
+
};
|
|
140
|
+
/**
|
|
141
|
+
* Whether `mouse` has ever been written by a real pointer event.
|
|
142
|
+
*
|
|
143
|
+
* `false` means it has not, and {@link computeMousePos} answers the element's centre instead of
|
|
144
|
+
* believing the initial `{0, 0}`. `undefined` — a caller that does not model this at all — is
|
|
145
|
+
* taken as "trust `mouse`", so nothing outside this module changes.
|
|
146
|
+
*/
|
|
147
|
+
hasPointer?: boolean;
|
|
148
|
+
quadBuffer: WebGLBuffer | null;
|
|
149
|
+
programs: Record<string, ShaderProgramEntry>;
|
|
150
|
+
}
|
|
151
|
+
/**
|
|
152
|
+
* Where a colour parse gets its canvas from — the injected adapter, not a global.
|
|
153
|
+
*
|
|
154
|
+
* Structurally `ResolvedEnv['createCanvas']`, named here because that is the *only* part of the
|
|
155
|
+
* environment the parser wants: everything above this line (hex, `rgb()`, {@link NAMED_COLORS}) is
|
|
156
|
+
* pure string work that must stay callable with no browser at all.
|
|
157
|
+
*/
|
|
158
|
+
export type ColorCanvasFactory = () => HTMLCanvasElement | null;
|
|
159
|
+
/**
|
|
160
|
+
* An authored colour as premultiplied-free RGBA in 0..1.
|
|
161
|
+
*
|
|
162
|
+
* Hex, `rgb()`/`rgba()` and the small {@link NAMED_COLORS} table are parsed here. Anything else —
|
|
163
|
+
* `hsl()`, `oklch()`, `rebeccapurple`, a `color-mix()` — is handed to a 1x1 canvas, which is the
|
|
164
|
+
* browser's own parser and the only complete one.
|
|
165
|
+
*
|
|
166
|
+
* @param str - The authored value. Empty or unset answers opaque white, the harmless fallback for
|
|
167
|
+
* an unset `color1`; `backdrop` reads the raw string first precisely because white is *not*
|
|
168
|
+
* harmless there.
|
|
169
|
+
* @param createCanvas - Where the fallback canvas comes from. Defaults to the ambient document's,
|
|
170
|
+
* resolved through `resolveEnv` rather than off a bare global, so a host with no document
|
|
171
|
+
* answers `null` instead of throwing a `ReferenceError` into the `catch` above — which is what
|
|
172
|
+
* reading `document` directly did, and it made the result silently environment-dependent.
|
|
173
|
+
* `prepareShaders` passes its own resolved env, so an injected canvas always wins.
|
|
174
|
+
* @complexity O(n) in string length, plus one cached context construction per canvas source.
|
|
175
|
+
*/
|
|
176
|
+
export declare function parseColor(str?: string, createCanvas?: ColorCanvasFactory): [number, number, number, number];
|
|
177
|
+
/**
|
|
178
|
+
* An authored `angle` parameter as radians, for a consumer that is GLSL rather than a stylesheet.
|
|
179
|
+
*
|
|
180
|
+
* `core/params.ts` normalises a bare `hue:30` to `30deg` but leaves an explicitly-united value
|
|
181
|
+
* alone, so all four CSS angle units can arrive here. Parsed rather than fed through `params.num`,
|
|
182
|
+
* which is documented as "bare number, or a percentage" and would answer the fallback for every
|
|
183
|
+
* spelling an angle parameter actually accepts.
|
|
184
|
+
*
|
|
185
|
+
* @param raw - The validated parameter string, e.g. `"30deg"`, `"0.5turn"`.
|
|
186
|
+
* @returns The angle in radians, or `0` for anything unparseable.
|
|
187
|
+
* @complexity O(n) in string length.
|
|
188
|
+
*/
|
|
189
|
+
export declare function parseAngleRadians(raw: string | undefined): number;
|
|
190
|
+
/** How many palette stops the generative program's `u_colors` array holds. */
|
|
191
|
+
export declare const PALETTE_SIZE = 5;
|
|
192
|
+
/**
|
|
193
|
+
* The five palette stops as one flat `Float32Array`, plus how many are real.
|
|
194
|
+
*
|
|
195
|
+
* `colorCount` drives the ramp's segment count in GLSL, so the unset tail of the array is never
|
|
196
|
+
* read — it is filled with the last real stop anyway rather than left at zero, because a driver
|
|
197
|
+
* that clamps an index differently than expected should land on a colour that exists rather than
|
|
198
|
+
* on black.
|
|
199
|
+
*
|
|
200
|
+
* @param authored - The five parameter strings in order, empty for unset.
|
|
201
|
+
* @param createCanvas - Passed straight through to {@link parseColor}; see the note there.
|
|
202
|
+
* @returns The flattened palette and the number of stops the ramp should span.
|
|
203
|
+
* @complexity O(1) — the array is a fixed five entries.
|
|
204
|
+
*/
|
|
205
|
+
export declare function buildPalette(authored: string[], createCanvas?: ColorCanvasFactory): {
|
|
206
|
+
palette: Float32Array;
|
|
207
|
+
colorCount: number;
|
|
208
|
+
};
|
|
209
|
+
export declare const BLEND_MODES: {
|
|
210
|
+
normal: number;
|
|
211
|
+
screen: number;
|
|
212
|
+
multiply: number;
|
|
213
|
+
add: number;
|
|
214
|
+
};
|
|
215
|
+
export type ProgramOrLocations = ShaderProgramLocations | WebGLProgram | null;
|
|
216
|
+
export declare function uploadUniforms(gl: WebGLRenderingContext | WebGL2RenderingContext, progInfo: ProgramOrLocations, opt: ShaderUniformOptions): void;
|
|
217
|
+
export declare function uploadColorUniforms(gl: WebGLRenderingContext | WebGL2RenderingContext, progInfo: ProgramOrLocations, opt: ShaderUniformOptions): void;
|
|
218
|
+
/** An axis-aligned box in viewport-relative CSS pixels. */
|
|
219
|
+
export interface Inset {
|
|
220
|
+
left: number;
|
|
221
|
+
top: number;
|
|
222
|
+
right: number;
|
|
223
|
+
bottom: number;
|
|
224
|
+
}
|
|
225
|
+
export interface ElementGeometry {
|
|
226
|
+
/** Border box. What the corner mask is measured against. */
|
|
227
|
+
border: Inset;
|
|
228
|
+
/** Where the replaced content would be painted if nothing clipped it. */
|
|
229
|
+
paint: Inset;
|
|
230
|
+
/** Content box narrowed by every clipping ancestor. */
|
|
231
|
+
clip: Inset;
|
|
232
|
+
/** Border-box corner radii, `[x, y]` per corner in TL, TR, BR, BL order. */
|
|
233
|
+
radii: [number, number, number, number, number, number, number, number];
|
|
234
|
+
/**
|
|
235
|
+
* What the replica has to be painted at: the ancestors' opacities multiplied together, and the
|
|
236
|
+
* element's own on top of them.
|
|
237
|
+
*
|
|
238
|
+
* The host's own used to be left out, on the reasoning that `opacity` is this module's to write —
|
|
239
|
+
* but that confuses the property with the author's value for it. A `.hero { opacity: .25 }`
|
|
240
|
+
* carrying a shader had its original hidden by `hideBehindRenderer` and its replica drawn at 1,
|
|
241
|
+
* so the one element on the page that was meant to be a quarter visible was the only one at full
|
|
242
|
+
* strength. See {@link hostAlpha} for how the author's value survives being overwritten.
|
|
243
|
+
*/
|
|
244
|
+
alpha: number;
|
|
245
|
+
/**
|
|
246
|
+
* The host's own opacity alone, already folded into {@link alpha}.
|
|
247
|
+
*
|
|
248
|
+
* Handed back so a caller can cache it. `hideBehindRenderer` writes `opacity: 0` on the very
|
|
249
|
+
* element this is measured from, so from the second frame onwards a live read answers 0 — fold
|
|
250
|
+
* that in and the replica vanishes, which leaves the host hidden behind nothing at all. The
|
|
251
|
+
* instance therefore keeps the last value read while the element was still visible and passes it
|
|
252
|
+
* back in; see `measureElementGeometry`'s `hostAlpha` parameter.
|
|
253
|
+
*/
|
|
254
|
+
hostAlpha?: number;
|
|
255
|
+
}
|
|
256
|
+
/**
|
|
257
|
+
* Everything about one element's box the draw has to reproduce, in CSS pixels.
|
|
258
|
+
*
|
|
259
|
+
* Pure reads, no GL and no canvas: the device-pixel conversion needs the canvas dimensions and
|
|
260
|
+
* happens later, in {@link resolveDrawBox}, so that a caller can measure once per frame in the
|
|
261
|
+
* read pass and draw from the cached answer.
|
|
262
|
+
*
|
|
263
|
+
* @param el - The element the shader stands in for.
|
|
264
|
+
* @param win - The window whose `innerHeight`/`devicePixelRatio`/`getComputedStyle` to use.
|
|
265
|
+
* @param fullBox - Paint to the border box instead of the content box. What a *generated* image
|
|
266
|
+
* wants: it is a background, and backgrounds paint to the border box, so measuring the content
|
|
267
|
+
* box would inset the field by the element's border and padding while `radiiOf` keeps measuring
|
|
268
|
+
* the corner mask against the border box — visibly mismatched corners on any padded host.
|
|
269
|
+
* The replaced-content path (every image filter) must keep the content box, because that is the
|
|
270
|
+
* box `object-fit` resolves against.
|
|
271
|
+
* @param hostAlpha - The element's own opacity, for a caller that already hid it. Omitted means
|
|
272
|
+
* "read it live", which is right up to the first `hideBehindRenderer` and right forever for a
|
|
273
|
+
* generative mode, which never hides its host. After that the live value is this module's own
|
|
274
|
+
* `0` and the author's has to be supplied from the caller's cache.
|
|
275
|
+
* @returns Its geometry, or `null` when it is not on screen at all.
|
|
276
|
+
* @complexity O(d) in ancestor depth, capped at {@link ANCESTOR_WALK_LIMIT}.
|
|
277
|
+
*/
|
|
278
|
+
export declare function measureElementGeometry(el: HTMLElement, win?: AnyWindow, fullBox?: boolean, hostAlpha?: number): ElementGeometry | null;
|
|
279
|
+
export interface ScissorResult {
|
|
280
|
+
rect: DOMRect;
|
|
281
|
+
uvOrigin: [number, number];
|
|
282
|
+
uvScale: [number, number];
|
|
283
|
+
/** Border-box centre and half-extents in device px, y measured up from the canvas bottom. */
|
|
284
|
+
maskBox: [number, number, number, number];
|
|
285
|
+
/** Corner x-radii in device px, TL, TR, BR, BL — matching `maskRy`. */
|
|
286
|
+
maskRx: [number, number, number, number];
|
|
287
|
+
maskRy: [number, number, number, number];
|
|
288
|
+
alpha: number;
|
|
289
|
+
}
|
|
290
|
+
/**
|
|
291
|
+
* Turn one element's CSS-pixel geometry into a scissor rect and the UV window over its texture.
|
|
292
|
+
*
|
|
293
|
+
* The UV window is what makes `object-fit` work: the quad only ever covers the part of the painted
|
|
294
|
+
* box that survives clipping, and `uvOrigin`/`uvScale` say which part of the *image* that is. A
|
|
295
|
+
* `cover` hero therefore samples the same crop CSS shows, and a half-off-screen element samples
|
|
296
|
+
* its visible half, through one mechanism rather than two.
|
|
297
|
+
*
|
|
298
|
+
* @returns `null` when nothing of the element is left to draw.
|
|
299
|
+
* @complexity O(1).
|
|
300
|
+
*/
|
|
301
|
+
export declare function resolveDrawBox(geom: ElementGeometry, canvas: HTMLCanvasElement, dpr: number): (ScissorResult & {
|
|
302
|
+
sx: number;
|
|
303
|
+
sy: number;
|
|
304
|
+
sw: number;
|
|
305
|
+
sh: number;
|
|
306
|
+
}) | null;
|
|
307
|
+
/**
|
|
308
|
+
* Measure `el`, point the viewport and scissor box at it, and hand back its UV window.
|
|
309
|
+
*
|
|
310
|
+
* Kept as one call for the unit suites and for any caller that has no cached geometry; the render
|
|
311
|
+
* loop measures in its read pass instead and passes the result in as
|
|
312
|
+
* {@link ShaderDrawOptions.geometry}.
|
|
313
|
+
*/
|
|
314
|
+
export declare function setupScissor(gl: WebGLRenderingContext | WebGL2RenderingContext, el: HTMLElement, canvas: HTMLCanvasElement, win?: AnyWindow): ScissorResult | null;
|
|
315
|
+
export declare function bindShaderTexture(gl: WebGLRenderingContext | WebGL2RenderingContext, progInfo: ProgramOrLocations, texture: WebGLTexture | null | undefined, toTexture?: WebGLTexture | null | undefined): void;
|
|
316
|
+
export declare function drawElementQuad(renderer: ShaderRendererLike, el: HTMLElement, opt?: Partial<ShaderDrawOptions>): boolean;
|
|
317
|
+
export type ShaderParamAccessor = EffectParams | {
|
|
318
|
+
keyword?: (name: string) => string;
|
|
319
|
+
text: (name: string, fallback?: string) => string;
|
|
320
|
+
num: (name: string, fallback?: number) => number;
|
|
321
|
+
};
|
|
322
|
+
/**
|
|
323
|
+
* Modes that generate their image from mathematics rather than sampling a source.
|
|
324
|
+
*
|
|
325
|
+
* Three behaviours key off this set, and each one is a place the filter assumption is baked in:
|
|
326
|
+
*
|
|
327
|
+
* 1. **The texture requirement.** `drawCall`'s `drew = !!texture && …` is the single line that made
|
|
328
|
+
* every mode a filter — no loaded `<img>`, no draw. A generator has no source to wait for.
|
|
329
|
+
* 2. **Hiding the host.** A filter stands in for the element it replaces, so hiding it is right. A
|
|
330
|
+
* generator is drawn over an element the author may have put content in, so hiding it would
|
|
331
|
+
* destroy that content to no purpose.
|
|
332
|
+
* 3. **The quad's box.** A generator is a background, and backgrounds paint to the border box; the
|
|
333
|
+
* replaced-content path measures the content box, which insets by border and padding while the
|
|
334
|
+
* corner mask still measures the border box.
|
|
335
|
+
*/
|
|
336
|
+
export declare const GENERATIVE_MODES: Set<string>;
|
|
337
|
+
/**
|
|
338
|
+
* Every mode the `mode:` keyword accepts, in one list.
|
|
339
|
+
*
|
|
340
|
+
* The single source for `SHADER_PARAMETERS.mode.keywords` *and* for `prepareShaders`' gate; they
|
|
341
|
+
* were two literals before, which is one edit away from a keyword the gate rejects.
|
|
342
|
+
*
|
|
343
|
+
* `logo` is deliberately **not** in {@link GENERATIVE_MODES}. It generates its colour, but it
|
|
344
|
+
* behaves in every other respect exactly like the five filters: its texture is required (the
|
|
345
|
+
* image is the stencil), its host is hidden (the field replaces it), and it measures the content
|
|
346
|
+
* box so `object-fit` resolves the way it does for `displace`. That is what made it the mode that
|
|
347
|
+
* needed no structural change to ship.
|
|
348
|
+
*/
|
|
349
|
+
export declare const SHADER_MODES: readonly ["displace", "fluid", "liquid", "particles", "morph", "gradient", "logo"];
|
|
350
|
+
/**
|
|
351
|
+
* `mask:` as the `u_maskMode` the generative program branches on. See `glyphMask()` in `glsl.ts`.
|
|
352
|
+
*
|
|
353
|
+
* `gradient` is pinned to `0` whatever the author wrote: there is no texture bound in that mode,
|
|
354
|
+
* so a stencil is not a thing it can have, and silently honouring `mask:` there would produce a
|
|
355
|
+
* black rectangle rather than a warning.
|
|
356
|
+
*/
|
|
357
|
+
export declare const MASK_MODES: Record<string, number>;
|
|
358
|
+
/**
|
|
359
|
+
* `hover:` as the bitfield `u_hover` tests — `both` is `stir | light` and not a third code path.
|
|
360
|
+
*
|
|
361
|
+
* Bits rather than four ordinals because the shader's two gestures act at different stages (one on
|
|
362
|
+
* the sample coordinate, one on the colour that coordinate produced), so `both` is genuinely the
|
|
363
|
+
* two of them and there is nothing for a third branch to say.
|
|
364
|
+
*/
|
|
365
|
+
export declare const HOVER_MODES: Record<string, number>;
|
|
366
|
+
/**
|
|
367
|
+
* `motion:` as the int `u_motion` branches on.
|
|
368
|
+
*
|
|
369
|
+
* `evolve` is `0` and is the default, because it is what this program has always done: time as the
|
|
370
|
+
* noise field's third axis, the pattern changing in place. `drift` and `swirl` are additions on
|
|
371
|
+
* top of it. See {@link SHADER_PARAMETERS.motion} for why the inert keyword had to exist.
|
|
372
|
+
*/
|
|
373
|
+
export declare const MOTION_MODES: Record<string, number>;
|
|
374
|
+
/**
|
|
375
|
+
* @param params - The authored parameters.
|
|
376
|
+
* @param createCanvas - The env's canvas source, threaded to every colour parse below. Optional
|
|
377
|
+
* because a caller holding nothing but an accessor is a supported shape, and the colours it
|
|
378
|
+
* authored are overwhelmingly hex; `prepareShaders` resolves its env first and passes it, so the
|
|
379
|
+
* one path that reaches a real page never falls back.
|
|
380
|
+
*/
|
|
381
|
+
export declare function extractShaderOptions(params: ShaderParamAccessor, createCanvas?: ColorCanvasFactory): ShaderDrawOptions;
|
|
382
|
+
/**
|
|
383
|
+
* This element's scroll/scrub progress, or -1 when nothing has declared one.
|
|
384
|
+
*
|
|
385
|
+
* **Only called for an instance that authored `scrub: scroll`** — see `readInputs`. Reading it
|
|
386
|
+
* unasked is what the parameter exists to stop: the fallback below means an *ancestor's*
|
|
387
|
+
* `scroll-progress` reaches this element, and at the top of a scroll range that value is `0`,
|
|
388
|
+
* which every program treats as "no effect". A shader anywhere inside a scrollytelling section
|
|
389
|
+
* therefore rendered a pixel-faithful copy of its own source and looked dead, with no way to say
|
|
390
|
+
* no. The `audio:` parameter twelve lines below the schema entry had already been written against
|
|
391
|
+
* exactly this failure; the scroll channel simply never got the same gate.
|
|
392
|
+
*
|
|
393
|
+
* Checks the element's own inline style first — the fast path, and the only one that mattered
|
|
394
|
+
* while a shader element could only ever read a scroll-mechanics primitive on itself. Falling back
|
|
395
|
+
* to `getComputedStyle` is what makes an *ancestor's* primitive reach it too: `--kui-progress` is
|
|
396
|
+
* an ordinary, unregistered custom property (nothing in this codebase declares it with
|
|
397
|
+
* `@property`), so it inherits down the DOM tree by default — a `scroll-progress` element three
|
|
398
|
+
* levels up is already visible to `getComputedStyle` on this element without this module doing
|
|
399
|
+
* anything special, once something here actually asks.
|
|
400
|
+
*
|
|
401
|
+
* Callers must not call this once per element as each one draws; see `SharedShaderRenderer`'s
|
|
402
|
+
* `progressReaders` pass, which reads every registered instance's progress before any instance's
|
|
403
|
+
* draw call runs and can write a style. Reading and writing style in the same pass across several
|
|
404
|
+
* elements would force a style recalculation per element instead of at most once per frame.
|
|
405
|
+
*
|
|
406
|
+
* @complexity O(1) time (one inline read, at most one computed-style read); O(1) space.
|
|
407
|
+
*/
|
|
408
|
+
export declare function readElementProgress(el: HTMLElement): number;
|
|
409
|
+
/**
|
|
410
|
+
* Where the shared canvas sits in the page's stacking order when the author says nothing.
|
|
411
|
+
*
|
|
412
|
+
* **Deliberately low.** This was `9999` and the defence written for it — that anything lower hides
|
|
413
|
+
* the replica behind a positioned card — does not survive examination: a positioned card that
|
|
414
|
+
* overlaps a shader element probably *should* paint on top, which is stacking working rather than
|
|
415
|
+
* stacking broken. What `9999` actually bought was the shared canvas painting over the page's own
|
|
416
|
+
* modals, sticky headers and toasts, which is unambiguously wrong and is the bug that was filed.
|
|
417
|
+
*
|
|
418
|
+
* `1` rather than `0` or `auto` so the canvas still sits above the page's ordinary static content,
|
|
419
|
+
* which is the one thing it does have to do.
|
|
420
|
+
*/
|
|
421
|
+
export declare const DEFAULT_SHADER_Z_INDEX = 1;
|
|
422
|
+
/** The custom property an author sets to move the shared canvas. See {@link readShaderZIndex}. */
|
|
423
|
+
export declare const SHADER_Z_PROPERTY = "--kui-shader-z";
|
|
424
|
+
/**
|
|
425
|
+
* The author's stacking order for the shared canvas, or {@link DEFAULT_SHADER_Z_INDEX}.
|
|
426
|
+
*
|
|
427
|
+
* ```css
|
|
428
|
+
* :root { --kui-shader-z: 500; }
|
|
429
|
+
* ```
|
|
430
|
+
*
|
|
431
|
+
* One line in the page's own stylesheet, no JS and no build step — custom properties inherit, so a
|
|
432
|
+
* value on `:root` reaches a canvas this module created even though no stylesheet can name it.
|
|
433
|
+
*
|
|
434
|
+
* **Why not a `data-kui` parameter.** `SharedShaderRenderer` keeps *one* canvas for the whole page.
|
|
435
|
+
* A per-element `zIndex:` would look per-element and silently not be — two shader elements asking
|
|
436
|
+
* for different values, last writer wins, and the loser has no way to tell. That is the "one key,
|
|
437
|
+
* two meanings" failure `core/types.ts` already documents. A page-level object gets a page-level
|
|
438
|
+
* knob.
|
|
439
|
+
*
|
|
440
|
+
* **This does not close the architectural ceiling.** Moving the number lets *one page cope*; it
|
|
441
|
+
* still does not let a shader sit *behind* content in any general way. There is one canvas, so the
|
|
442
|
+
* value moves every replica on the page at once, and whatever is positioned above the new number
|
|
443
|
+
* now covers the shaders instead. A shader that genuinely composes with the content over it needs
|
|
444
|
+
* per-element canvases, which is a decision that has not been made. See `docs/advanced-modules.md`.
|
|
445
|
+
*
|
|
446
|
+
* Read the same shape as `readElementProgress`: inline first, computed as the fallback, so a value
|
|
447
|
+
* set in a stylesheet reaches it. Anything that is not a finite number — `auto`, a `var()` that
|
|
448
|
+
* resolved to nothing, junk — falls back to the default rather than writing a broken `z-index`.
|
|
449
|
+
*
|
|
450
|
+
* @complexity O(1): one inline read and at most one computed-style read, once per canvas.
|
|
451
|
+
*/
|
|
452
|
+
export declare function readShaderZIndex(doc: AnyDocument, win: AnyWindow): number;
|
|
453
|
+
export type ShaderDrawFn = (gl: WebGLRenderingContext | WebGL2RenderingContext, timeSeconds: number) => void;
|
|
454
|
+
/**
|
|
455
|
+
* Singleton WebGL2 Renderer.
|
|
456
|
+
*/
|
|
457
|
+
export declare class SharedShaderRenderer {
|
|
458
|
+
env: AdvancedEnv;
|
|
459
|
+
window: AnyWindow;
|
|
460
|
+
document: AnyDocument;
|
|
461
|
+
createCanvas: () => HTMLCanvasElement | null;
|
|
462
|
+
raf: RafFunction;
|
|
463
|
+
caf: CafFunction;
|
|
464
|
+
canvas: HTMLCanvasElement | null;
|
|
465
|
+
gl: WebGLRenderingContext | WebGL2RenderingContext | null;
|
|
466
|
+
quadBuffer: WebGLBuffer | null;
|
|
467
|
+
rafId: number | null;
|
|
468
|
+
drawCalls: Map<string, ShaderDrawFn>;
|
|
469
|
+
contextCallbacks: Map<string, {
|
|
470
|
+
onLost: () => void;
|
|
471
|
+
onRestored: () => void;
|
|
472
|
+
}>;
|
|
473
|
+
/**
|
|
474
|
+
* One style-reading pass per registered instance — scroll progress and, when the author asked
|
|
475
|
+
* for one, an audio band — run in full before any instance's draw call this frame. See
|
|
476
|
+
* `renderFrame` for why the ordering, not just the reads themselves, is the point.
|
|
477
|
+
*/
|
|
478
|
+
inputReaders: Map<string, () => void>;
|
|
479
|
+
programs: Record<string, ShaderProgramLocations | null>;
|
|
480
|
+
refCount: number;
|
|
481
|
+
mouse: {
|
|
482
|
+
x: number;
|
|
483
|
+
y: number;
|
|
484
|
+
};
|
|
485
|
+
/** Cleared until a real `pointermove` lands. See {@link computeMousePos}. */
|
|
486
|
+
hasPointer: boolean;
|
|
487
|
+
isContextLost: boolean;
|
|
488
|
+
/**
|
|
489
|
+
* Bumped every time this renderer's GL objects stop being valid, i.e. on each context loss.
|
|
490
|
+
*
|
|
491
|
+
* It exists because "is this handle still real?" has no other answer. A `WebGLTexture` from a lost
|
|
492
|
+
* context is an ordinary live JavaScript object — binding it raises `INVALID_OPERATION` and
|
|
493
|
+
* samples nothing, and neither the draw nor `drawElementQuad`'s return value can tell.
|
|
494
|
+
*
|
|
495
|
+
* The hole it closes: `cancel()`/`finish()` unregister an instance, which takes its entry out of
|
|
496
|
+
* `contextCallbacks` with it, so an *inactive* instance is never told the context went away. Its
|
|
497
|
+
* texture holders kept answering with pre-loss handles, and `acquireRenderer` short-circuits on
|
|
498
|
+
* re-activation because the renderer itself was never destroyed — so a hover, a hover-out, a
|
|
499
|
+
* context loss and a second hover drew a blank replica over a hidden image, permanently.
|
|
500
|
+
*
|
|
501
|
+
* Stamping the holders rather than keeping every acquired instance registered for context events:
|
|
502
|
+
* the stamp is checked where the stale handle is actually read, so it holds however the instance
|
|
503
|
+
* came to miss the event, and it costs two compares per holder per frame. See
|
|
504
|
+
* {@link initTextureHolder}.
|
|
505
|
+
*/
|
|
506
|
+
contextGeneration: number;
|
|
507
|
+
mapKey: object;
|
|
508
|
+
onPointer?: (e: PointerEvent | {
|
|
509
|
+
clientX: number;
|
|
510
|
+
clientY: number;
|
|
511
|
+
}) => void;
|
|
512
|
+
onContextLost?: (e: Event) => void;
|
|
513
|
+
onContextRestored?: () => void;
|
|
514
|
+
constructor(env?: AdvancedEnv);
|
|
515
|
+
/**
|
|
516
|
+
* Whether `destroy()` has run. A destroyed renderer never comes back to life.
|
|
517
|
+
*
|
|
518
|
+
* `destroy()` un-maps itself, so the object an already-prepared instance is still holding is no
|
|
519
|
+
* longer the one `getSharedShaderRenderer` hands out. Letting `acquire()` re-`init()` that object
|
|
520
|
+
* therefore built a *second* full-viewport canvas and WebGL2 context in one document — and then
|
|
521
|
+
* a third, because the next element to be prepared found nothing in the map and made its own,
|
|
522
|
+
* and whichever of them tore down next deleted the shared document key from under the live one.
|
|
523
|
+
* Cycling shader elements on an SPA route walked straight into the browser's 8-16 context cap.
|
|
524
|
+
*
|
|
525
|
+
* So a teardown is final, and `RendererRef` is what gets an orphaned instance back onto the live
|
|
526
|
+
* renderer.
|
|
527
|
+
*/
|
|
528
|
+
isDestroyed: boolean;
|
|
529
|
+
acquire(): boolean;
|
|
530
|
+
release(): void;
|
|
531
|
+
init(): boolean;
|
|
532
|
+
/**
|
|
533
|
+
* Undo a half-built `init()` and report it as a failure.
|
|
534
|
+
*
|
|
535
|
+
* Not `destroy()`: nothing has acquired this renderer yet, so there is no teardown to broadcast,
|
|
536
|
+
* and marking it `isDestroyed` would retire an object that is free to try again the next time
|
|
537
|
+
* something acquires it. Under transient memory pressure that retry is the whole point — the
|
|
538
|
+
* alternative is a permanently dead renderer for a `createBuffer` that would succeed a second
|
|
539
|
+
* later. The canvas goes because it is already in the document by this point; leaving it would
|
|
540
|
+
* stack one invisible full-viewport canvas per attempt.
|
|
541
|
+
*/
|
|
542
|
+
private abandonInit;
|
|
543
|
+
/**
|
|
544
|
+
* Allocate and fill the one static buffer every draw in this module reads its vertices from.
|
|
545
|
+
*
|
|
546
|
+
* Returns whether it worked, and the caller has to care. `gl.createBuffer()` returns `null` under
|
|
547
|
+
* memory pressure rather than throwing, and so does nothing else downstream: `vertexAttribPointer`
|
|
548
|
+
* and `drawArrays` against a null binding only *set* a GL error, and `drawElementQuad` used to
|
|
549
|
+
* return `true` regardless. The visible result was the worst possible one — every affected
|
|
550
|
+
* element's `opacity` driven to 0 by `hideBehindRenderer` with nothing drawn over it, so the page
|
|
551
|
+
* lost its images rather than losing an effect. A renderer that cannot allocate this is not a
|
|
552
|
+
* renderer, so it fails here instead.
|
|
553
|
+
*
|
|
554
|
+
* `bufferData` is guarded too, and only for a host that throws rather than raising `OUT_OF_MEMORY`
|
|
555
|
+
* through the error queue. Reading the queue is deliberately not done: `getError` is a
|
|
556
|
+
* synchronous round-trip to the GPU process, and the allocation failure it would catch is already
|
|
557
|
+
* caught by the null above.
|
|
558
|
+
*/
|
|
559
|
+
initQuad(): boolean;
|
|
560
|
+
initPrograms(): void;
|
|
561
|
+
bindEvents(): void;
|
|
562
|
+
createTexture(source: TexImageSource): WebGLTexture | null;
|
|
563
|
+
register(id: string, fn: ShaderDrawFn, callbacks?: {
|
|
564
|
+
onLost: () => void;
|
|
565
|
+
onRestored: () => void;
|
|
566
|
+
}, readInputs?: () => void): void;
|
|
567
|
+
unregister(id: string): void;
|
|
568
|
+
startLoop(): void;
|
|
569
|
+
stopLoop(): void;
|
|
570
|
+
renderFrame(timeSeconds: number): void;
|
|
571
|
+
/**
|
|
572
|
+
* Every registered instance's draw, in registration order, each one isolated from its neighbours.
|
|
573
|
+
*
|
|
574
|
+
* The snapshot is here because a draw call may unregister itself — the `catch` below does — and
|
|
575
|
+
* mutating `drawCalls` under a live iterator would skip its neighbour. But a snapshot of the
|
|
576
|
+
* *entries* must not become a snapshot of the *context*, which is why the `gl` is re-read per
|
|
577
|
+
* iteration rather than taken from `renderFrame`'s. `destroy()` nulls `gl` and deletes the
|
|
578
|
+
* programs, so a draw call that dropped the last reference mid-frame left every remaining entry
|
|
579
|
+
* in the snapshot drawing through a disposed context, on a renderer already `isDestroyed` with
|
|
580
|
+
* its `drawCalls` cleared. The same re-read covers a context lost mid-frame, where `gl` survives
|
|
581
|
+
* but the programs and buffers do not.
|
|
582
|
+
*
|
|
583
|
+
* @param timeSeconds - This frame's timestamp, passed to every draw call.
|
|
584
|
+
* @complexity O(n) in registered instances.
|
|
585
|
+
*/
|
|
586
|
+
private runDrawCalls;
|
|
587
|
+
destroy(): void;
|
|
588
|
+
}
|
|
589
|
+
export declare function getSharedShaderRenderer(env?: AdvancedEnv): SharedShaderRenderer;
|
|
590
|
+
/**
|
|
591
|
+
* The live shared renderer for one document, across a teardown.
|
|
592
|
+
*
|
|
593
|
+
* `prepareShaders` runs during the animator's scan while `activate()` can be much later — a
|
|
594
|
+
* below-the-fold `on:enter` element is prepared on load and activated on scroll — so the renderer
|
|
595
|
+
* an instance was handed at prepare time can be destroyed before it is ever used. This re-resolves
|
|
596
|
+
* instead of reviving: `getSharedShaderRenderer` builds a fresh renderer, the map keeps pointing at
|
|
597
|
+
* the live one, and the document still has exactly one canvas and one context.
|
|
598
|
+
*/
|
|
599
|
+
export interface RendererRef {
|
|
600
|
+
get(): SharedShaderRenderer;
|
|
601
|
+
}
|
|
602
|
+
export declare function createRendererRef(env?: AdvancedEnv): RendererRef;
|
|
603
|
+
/**
|
|
604
|
+
* Install (or clear) the renderer for one document.
|
|
605
|
+
*
|
|
606
|
+
* A renderer's `destroy()` removes exactly one map entry: the key it was built for. Storing an
|
|
607
|
+
* instance under some *other* document's key therefore made the two halves asymmetric — destroy
|
|
608
|
+
* cleaned up document A, and document B was left holding a renderer whose canvas had been removed
|
|
609
|
+
* and whose GL resources were gone, handed out to every later `getSharedShaderRenderer` for B.
|
|
610
|
+
*
|
|
611
|
+
* So the instance's own key wins, and a mismatched caller key is cleared rather than pointed at
|
|
612
|
+
* another document's renderer: whatever is reachable from the map is always reachable from the
|
|
613
|
+
* key its own teardown will delete.
|
|
614
|
+
*/
|
|
615
|
+
export declare function setSharedShaderRenderer(instance: SharedShaderRenderer | null, env?: AdvancedEnv): void;
|
|
616
|
+
export declare function prepareShaders(el: Element, params: EffectParams, ctx?: PrepareContext | null): EffectInstance;
|
|
617
|
+
export declare const SHADER_PARAMETERS: {
|
|
618
|
+
mode: {
|
|
619
|
+
type: "keyword";
|
|
620
|
+
default: string;
|
|
621
|
+
keywords: ("morph" | "displace" | "gradient" | "fluid" | "liquid" | "particles" | "logo")[];
|
|
622
|
+
cssProperty: string;
|
|
623
|
+
};
|
|
624
|
+
strength: {
|
|
625
|
+
type: "number";
|
|
626
|
+
default: string;
|
|
627
|
+
minimum: number;
|
|
628
|
+
maximum: number;
|
|
629
|
+
cssProperty: string;
|
|
630
|
+
};
|
|
631
|
+
speed: {
|
|
632
|
+
type: "number";
|
|
633
|
+
default: string;
|
|
634
|
+
minimum: number;
|
|
635
|
+
maximum: number;
|
|
636
|
+
cssProperty: string;
|
|
637
|
+
};
|
|
638
|
+
frequency: {
|
|
639
|
+
type: "number";
|
|
640
|
+
default: string;
|
|
641
|
+
minimum: number;
|
|
642
|
+
maximum: number;
|
|
643
|
+
cssProperty: string;
|
|
644
|
+
};
|
|
645
|
+
chromatic: {
|
|
646
|
+
type: "number";
|
|
647
|
+
default: string;
|
|
648
|
+
minimum: number;
|
|
649
|
+
maximum: number;
|
|
650
|
+
cssProperty: string;
|
|
651
|
+
};
|
|
652
|
+
/**
|
|
653
|
+
* How far the colour ramp is wrapped back on itself — the shimmer count.
|
|
654
|
+
*
|
|
655
|
+
* Ranged 0..8 rather than the 0..1 it carried before `mode: gradient` existed. The old bound was
|
|
656
|
+
* right for a mix amount and wrong for a wrap count, and it cost nothing to police because **no
|
|
657
|
+
* fragment program declared `u_iridescence` at all** — the value was extracted and uploaded to a
|
|
658
|
+
* location that was `null` in all five programs. It reaches a shader for the first time here.
|
|
659
|
+
*/
|
|
660
|
+
iridescence: {
|
|
661
|
+
type: "number";
|
|
662
|
+
default: string;
|
|
663
|
+
minimum: number;
|
|
664
|
+
maximum: number;
|
|
665
|
+
cssProperty: string;
|
|
666
|
+
};
|
|
667
|
+
tint: {
|
|
668
|
+
type: "color";
|
|
669
|
+
default: string;
|
|
670
|
+
cssProperty: string;
|
|
671
|
+
};
|
|
672
|
+
color1: {
|
|
673
|
+
type: "color";
|
|
674
|
+
default: string;
|
|
675
|
+
cssProperty: string;
|
|
676
|
+
};
|
|
677
|
+
color2: {
|
|
678
|
+
type: "color";
|
|
679
|
+
default: string;
|
|
680
|
+
cssProperty: string;
|
|
681
|
+
};
|
|
682
|
+
color3: {
|
|
683
|
+
type: "color";
|
|
684
|
+
default: string;
|
|
685
|
+
cssProperty: string;
|
|
686
|
+
};
|
|
687
|
+
color4: {
|
|
688
|
+
type: "color";
|
|
689
|
+
default: string;
|
|
690
|
+
cssProperty: string;
|
|
691
|
+
};
|
|
692
|
+
color5: {
|
|
693
|
+
type: "color";
|
|
694
|
+
default: string;
|
|
695
|
+
cssProperty: string;
|
|
696
|
+
};
|
|
697
|
+
/** Which pattern. The same seed gives the same image on every load, which is the point of it. */
|
|
698
|
+
seed: {
|
|
699
|
+
type: "number";
|
|
700
|
+
default: string;
|
|
701
|
+
minimum: number;
|
|
702
|
+
maximum: number;
|
|
703
|
+
integer: boolean;
|
|
704
|
+
cssProperty: string;
|
|
705
|
+
};
|
|
706
|
+
/** How zoomed the field is. Larger means smaller, more numerous shapes. */
|
|
707
|
+
scale: {
|
|
708
|
+
type: "number";
|
|
709
|
+
default: string;
|
|
710
|
+
minimum: number;
|
|
711
|
+
maximum: number;
|
|
712
|
+
cssProperty: string;
|
|
713
|
+
};
|
|
714
|
+
/**
|
|
715
|
+
* How many octaves of detail ride on the base shapes.
|
|
716
|
+
*
|
|
717
|
+
* Named `detail` rather than the reference tools' "definition" — the plainer word for the same
|
|
718
|
+
* idea. Capped at 6, which is `KUI_MAX_OCTAVES` in the shader; a seventh would be accepted here
|
|
719
|
+
* and silently ignored there.
|
|
720
|
+
*/
|
|
721
|
+
detail: {
|
|
722
|
+
type: "number";
|
|
723
|
+
default: string;
|
|
724
|
+
minimum: number;
|
|
725
|
+
maximum: number;
|
|
726
|
+
integer: boolean;
|
|
727
|
+
cssProperty: string;
|
|
728
|
+
};
|
|
729
|
+
/** How far a second field distorts the first. The difference between contour rings and liquid. */
|
|
730
|
+
warp: {
|
|
731
|
+
type: "number";
|
|
732
|
+
default: string;
|
|
733
|
+
minimum: number;
|
|
734
|
+
maximum: number;
|
|
735
|
+
cssProperty: string;
|
|
736
|
+
};
|
|
737
|
+
/**
|
|
738
|
+
* How much of `fluid`, `liquid`, `particles` and `morph`'s motion comes from real noise.
|
|
739
|
+
*
|
|
740
|
+
* Those four shipped with a single trigonometric term standing in for noise, which bands and
|
|
741
|
+
* repeats visibly. This crossfades that term into the same `kuiFbm` field `mode: gradient`
|
|
742
|
+
* generates from — `0` is the shipped picture exactly, `1` is the field on its own, and the
|
|
743
|
+
* values between are a genuine dial rather than a rounded switch.
|
|
744
|
+
*
|
|
745
|
+
* **It is off by default and must stay that way.** Four modes are already in use; turning this
|
|
746
|
+
* on for them without being asked would restyle live pages. See `FILTER_NOISE_GLSL` in
|
|
747
|
+
* `glsl.ts` for why it is a parameter of its own rather than `warp` doing double duty.
|
|
748
|
+
*
|
|
749
|
+
* Above `0` it is also what gives `seed`, `scale`, `warp` and `detail` a meaning on these four
|
|
750
|
+
* modes; below it they are read and uploaded but nothing samples them. `mode: displace`'s ripple
|
|
751
|
+
* is a deliberate radial wave rather than fake noise, and `gradient`/`logo` are already nothing
|
|
752
|
+
* but the noise core, so all three ignore this.
|
|
753
|
+
*/
|
|
754
|
+
noise: {
|
|
755
|
+
type: "number";
|
|
756
|
+
default: string;
|
|
757
|
+
minimum: number;
|
|
758
|
+
maximum: number;
|
|
759
|
+
cssProperty: string;
|
|
760
|
+
};
|
|
761
|
+
/** Posterise the ramp into this many steps. `0` is off, as with `chromatic` and `iridescence`. */
|
|
762
|
+
bands: {
|
|
763
|
+
type: "number";
|
|
764
|
+
default: string;
|
|
765
|
+
minimum: number;
|
|
766
|
+
maximum: number;
|
|
767
|
+
integer: boolean;
|
|
768
|
+
cssProperty: string;
|
|
769
|
+
};
|
|
770
|
+
/** Film grain amount. */
|
|
771
|
+
grain: {
|
|
772
|
+
type: "number";
|
|
773
|
+
default: string;
|
|
774
|
+
minimum: number;
|
|
775
|
+
maximum: number;
|
|
776
|
+
cssProperty: string;
|
|
777
|
+
};
|
|
778
|
+
/**
|
|
779
|
+
* Hue rotation, as a real angle (`hue: 30deg`, `hue: 0.25turn`).
|
|
780
|
+
*
|
|
781
|
+
* An `angle` rather than a normalised scalar, matching `carousel`'s `tilt:` and this repository's
|
|
782
|
+
* standing rejection of normalised ranges where a CSS unit exists. A bare `hue: 30` is accepted
|
|
783
|
+
* too — `core/params.ts` normalises it to `30deg`.
|
|
784
|
+
*/
|
|
785
|
+
hue: {
|
|
786
|
+
type: "angle";
|
|
787
|
+
default: string;
|
|
788
|
+
cssProperty: string;
|
|
789
|
+
};
|
|
790
|
+
/**
|
|
791
|
+
* Which part of `mode: logo`'s host image is the mark. Ignored by every other mode.
|
|
792
|
+
*
|
|
793
|
+
* `alpha` for the usual transparent-ground SVG or PNG. `luma` and `luma-invert` are for a format
|
|
794
|
+
* that has no alpha to read — a JPEG under `alpha` gives an all-opaque stencil and fills the
|
|
795
|
+
* whole rectangle. `luma` takes the bright pixels as the mark, `luma-invert` the dark ones; both
|
|
796
|
+
* directions are spelled out because guessing produces a perfect negative of the mark, which
|
|
797
|
+
* looks deliberate and is exactly wrong.
|
|
798
|
+
*/
|
|
799
|
+
mask: {
|
|
800
|
+
type: "keyword";
|
|
801
|
+
default: string;
|
|
802
|
+
keywords: string[];
|
|
803
|
+
cssProperty: string;
|
|
804
|
+
};
|
|
805
|
+
/**
|
|
806
|
+
* How `mode: gradient` and `mode: logo` answer the pointer. Ignored by the five filter modes.
|
|
807
|
+
*
|
|
808
|
+
* Being *responsive to the pointer* is the one thing a shader background does that a video of a
|
|
809
|
+
* shader cannot, and these two modes — the two built to stand in for a video background — were
|
|
810
|
+
* the only ones in the tier with no pointer response at all. `displace` and `fluid` have read
|
|
811
|
+
* `u_mouse` since they were written.
|
|
812
|
+
*
|
|
813
|
+
* - `stir` — the cursor drags the colour flow around it, like stirring thick paint. It moves the
|
|
814
|
+
* field's *sample point*, so on `mode: logo` the mark's edges do not move: `glyphMask()` reads
|
|
815
|
+
* the host image at the undistorted coordinate and multiplies the finished colour, which means
|
|
816
|
+
* the fill swirls inside a stencil that stays exactly where it was.
|
|
817
|
+
* - `light` — the cursor is a torch held over the surface. Nothing moves; the colour under the
|
|
818
|
+
* pointer is multiplied up, which raises its saturation rather than washing it toward white.
|
|
819
|
+
* - `both` — the two together. They compose cleanly because they act at different stages, one on
|
|
820
|
+
* the coordinate and one on the colour that coordinate produced.
|
|
821
|
+
*
|
|
822
|
+
* `none` by default, and `none` means the shader never reads `u_mouse` at all — not that it
|
|
823
|
+
* reads it and multiplies by zero. Every page already running these modes renders what it always
|
|
824
|
+
* rendered, byte for byte.
|
|
825
|
+
*
|
|
826
|
+
* With no pointer yet — the state of every page on load, every phone, and every screenshot —
|
|
827
|
+
* `computeMousePos` answers the element's centre, so `light` rests as a centred glow and `stir`
|
|
828
|
+
* as a centred swirl. Both are compositions someone might have authored on purpose, which is the
|
|
829
|
+
* bar a resting state has to clear.
|
|
830
|
+
*/
|
|
831
|
+
hover: {
|
|
832
|
+
type: "keyword";
|
|
833
|
+
default: string;
|
|
834
|
+
keywords: string[];
|
|
835
|
+
cssProperty: string;
|
|
836
|
+
};
|
|
837
|
+
/**
|
|
838
|
+
* A colour painted behind the generative field, inside the element's own shape.
|
|
839
|
+
*
|
|
840
|
+
* What it is for is `mode: logo`: outside the mark the field has no coverage, so this is the
|
|
841
|
+
* plate the mark sits on instead of the page showing through.
|
|
842
|
+
*
|
|
843
|
+
* On a bare `mode: gradient` it is **invisible**, and that is arithmetic rather than an
|
|
844
|
+
* oversight — the field is opaque, so there is nothing behind it to see. It becomes visible
|
|
845
|
+
* there through a `tint` whose alpha is below 1, which is how a field is made to sit *on* a page
|
|
846
|
+
* colour rather than replace it. Unset by default, and read through the raw parameter string
|
|
847
|
+
* rather than `parseColor`, whose empty-input fallback is opaque white.
|
|
848
|
+
*/
|
|
849
|
+
backdrop: {
|
|
850
|
+
type: "color";
|
|
851
|
+
default: string;
|
|
852
|
+
cssProperty: string;
|
|
853
|
+
};
|
|
854
|
+
/**
|
|
855
|
+
* The generative field's orientation: the axis it runs along.
|
|
856
|
+
*
|
|
857
|
+
* `transform: rotate()`'s convention — clockwise-positive, `0deg` pointing right — because that
|
|
858
|
+
* is the one an author already has. It turns the field's whole domain, so it is visible at the
|
|
859
|
+
* library defaults rather than only in company with another parameter, and it is the axis
|
|
860
|
+
* `motion: drift` travels along. One idea with two consequences, not two behaviours: the field
|
|
861
|
+
* has an axis, this names it, and `drift` slides along the thing it named.
|
|
862
|
+
*
|
|
863
|
+
* An `angle` rather than a normalised scalar, matching `hue` above and this repository's
|
|
864
|
+
* standing rejection of 0..1 ranges where a CSS unit exists. A bare `angle: 30` is accepted —
|
|
865
|
+
* `core/params.ts` normalises it to `30deg`.
|
|
866
|
+
*/
|
|
867
|
+
angle: {
|
|
868
|
+
type: "angle";
|
|
869
|
+
default: string;
|
|
870
|
+
cssProperty: string;
|
|
871
|
+
};
|
|
872
|
+
/**
|
|
873
|
+
* How the generative field moves. Ignored by the five filter modes.
|
|
874
|
+
*
|
|
875
|
+
* - `evolve` — the field changes *in place*. Time is the noise function's third axis rather than
|
|
876
|
+
* a translation of a 2D field, which is why it reads as the pattern genuinely evolving instead
|
|
877
|
+
* of sliding past. See `NOISE_GLSL` in `glsl.ts`.
|
|
878
|
+
* - `drift` — it also travels along `angle`, at a rate proportional to `scale` so the visual
|
|
879
|
+
* speed is the same at any zoom.
|
|
880
|
+
* - `swirl` — it also turns about the element's centre.
|
|
881
|
+
*
|
|
882
|
+
* **`evolve` exists so that the default can be inert.** `drift` would have been the natural
|
|
883
|
+
* default name, and it is what this parameter's brief asked for — but `drift` is a new look, and
|
|
884
|
+
* defaulting to it would restyle every page already running `gradient` or `logo` on the next
|
|
885
|
+
* release. That is the same trap `noise:` above is written around, and the reason `hover:`
|
|
886
|
+
* defaults to `none`. So the shipped behaviour keeps the default and gets an accurate name, and
|
|
887
|
+
* the two new ones are opt-in. `drift` and `swirl` add to `evolve` rather than replacing it, so
|
|
888
|
+
* a travelling field is still changing as it goes rather than one frozen picture towed across
|
|
889
|
+
* the box.
|
|
890
|
+
*/
|
|
891
|
+
motion: {
|
|
892
|
+
type: "keyword";
|
|
893
|
+
default: string;
|
|
894
|
+
keywords: string[];
|
|
895
|
+
cssProperty: string;
|
|
896
|
+
};
|
|
897
|
+
blend: {
|
|
898
|
+
type: "keyword";
|
|
899
|
+
default: string;
|
|
900
|
+
keywords: string[];
|
|
901
|
+
cssProperty: string;
|
|
902
|
+
};
|
|
903
|
+
to: {
|
|
904
|
+
type: "text";
|
|
905
|
+
default: string;
|
|
906
|
+
cssProperty: string;
|
|
907
|
+
};
|
|
908
|
+
/**
|
|
909
|
+
* A fixed scrub position, 0..1. `-1` is the unauthored sentinel and means "no scrub".
|
|
910
|
+
*
|
|
911
|
+
* `--kui-shader-progress`, **not** `--kui-progress`. Every authored parameter reaches
|
|
912
|
+
* `element.style` as its custom property — `core/compile.ts`'s `buildPlan` runs `resolveParams`
|
|
913
|
+
* over JS-rendered entries too — so while this pointed at `--kui-progress` an authored
|
|
914
|
+
* `progress: 0.4` wrote the scroll primitives' own channel onto the element: it raced
|
|
915
|
+
* `scroll-progress` when the two were composed, and it inherited down to every descendant
|
|
916
|
+
* shader and pinned timeline underneath. `test/carousel-3d.test.ts` already carries a
|
|
917
|
+
* regression test that a primitive must not publish `--kui-progress` meaning something else.
|
|
918
|
+
* Nothing is lost by renaming — this module reads the authored value from the parameter, never
|
|
919
|
+
* back off the property.
|
|
920
|
+
*/
|
|
921
|
+
progress: {
|
|
922
|
+
type: "number";
|
|
923
|
+
default: string;
|
|
924
|
+
minimum: number;
|
|
925
|
+
maximum: number;
|
|
926
|
+
cssProperty: string;
|
|
927
|
+
};
|
|
928
|
+
/**
|
|
929
|
+
* Whether this shader is scrubbed by `--kui-progress` — the property `scroll-progress`,
|
|
930
|
+
* `timeline: pin` and the rest of the scroll family publish.
|
|
931
|
+
*
|
|
932
|
+
* Opt-in, and `off` by default, for the same reason `audio:` below is: the value arrives as a
|
|
933
|
+
* custom property written by some *other* primitive, and `--kui-progress` is an ordinary
|
|
934
|
+
* inheriting custom property, so a shader that read one unasked changed its own output the
|
|
935
|
+
* moment an unrelated `scroll-progress` appeared anywhere above it in the tree. That was not a
|
|
936
|
+
* subtle drift — at the top of a scroll range the published value is `0`, every program
|
|
937
|
+
* multiplies its effect by it, and the shader rendered an untouched copy of the source image.
|
|
938
|
+
* Dead, silently, with no way for the author to decline.
|
|
939
|
+
*
|
|
940
|
+
* `keyword`, so the list is closed and a typo warns instead of quietly doing nothing.
|
|
941
|
+
* `--kui-shader-scrub` is a name read by this module's JavaScript and by no stylesheet, so
|
|
942
|
+
* setting the custom property directly does nothing; the value itself is read from
|
|
943
|
+
* `--kui-progress`, which is a different contract entirely.
|
|
944
|
+
*/
|
|
945
|
+
scrub: {
|
|
946
|
+
type: "keyword";
|
|
947
|
+
default: string;
|
|
948
|
+
keywords: string[];
|
|
949
|
+
cssProperty: string;
|
|
950
|
+
};
|
|
951
|
+
/**
|
|
952
|
+
* Which `audio-source` band drives this shader, if any.
|
|
953
|
+
*
|
|
954
|
+
* Opt-in, and `off` by default: the value arrives as a custom property written by some *other*
|
|
955
|
+
* primitive, so a shader that read one unasked would change its own output the moment an
|
|
956
|
+
* unrelated `audio-source` appeared anywhere above it in the tree.
|
|
957
|
+
*
|
|
958
|
+
* `keyword`, so the list is closed — and `--kui-shader-audio` is a name, read by this module's
|
|
959
|
+
* JavaScript and by no stylesheet, so setting the custom property in CSS does nothing. The
|
|
960
|
+
* band values themselves are read from `--kui-audio-*`, which is a different contract entirely
|
|
961
|
+
* (`audio.ts`'s `AUDIO_BAND_PROPERTIES`).
|
|
962
|
+
*/
|
|
963
|
+
audio: {
|
|
964
|
+
type: "keyword";
|
|
965
|
+
default: string;
|
|
966
|
+
keywords: string[];
|
|
967
|
+
cssProperty: string;
|
|
968
|
+
};
|
|
969
|
+
};
|
|
970
|
+
export declare const SHADERS_PRIMITIVE: Primitive;
|
|
971
|
+
export declare const SHADERS_PRESETS: Preset[];
|
|
972
|
+
export declare function registerShaders(target: unknown): Registry | Animator;
|