@flare-engine/postfx 0.2.0 → 0.3.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/dist/index.js CHANGED
@@ -1,311 +1,106 @@
1
- "use strict";
2
- var __defProp = Object.defineProperty;
3
- var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
- var __getOwnPropNames = Object.getOwnPropertyNames;
5
- var __hasOwnProp = Object.prototype.hasOwnProperty;
6
- var __export = (target, all) => {
7
- for (var name in all)
8
- __defProp(target, name, { get: all[name], enumerable: true });
9
- };
10
- var __copyProps = (to, from, except, desc) => {
11
- if (from && typeof from === "object" || typeof from === "function") {
12
- for (let key of __getOwnPropNames(from))
13
- if (!__hasOwnProp.call(to, key) && key !== except)
14
- __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
- }
16
- return to;
17
- };
18
- var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
-
20
- // src/index.ts
21
- var index_exports = {};
22
- __export(index_exports, {
23
- BLOOM_SKSL: () => BLOOM_SKSL,
24
- CHROMATIC_ABERRATION_SKSL: () => CHROMATIC_ABERRATION_SKSL,
25
- COLOR_GRADING_LUT_SKSL: () => COLOR_GRADING_LUT_SKSL,
26
- COMPOSITE_SKSL: () => COMPOSITE_SKSL,
27
- CRT_SKSL: () => CRT_SKSL,
28
- EffectChain: () => EffectChain,
29
- GOD_RAYS_SKSL: () => GOD_RAYS_SKSL,
30
- GRAYSCALE_FLASH_SKSL: () => GRAYSCALE_FLASH_SKSL,
31
- VIGNETTE_SKSL: () => VIGNETTE_SKSL,
32
- WEATHER_CLOUDS_SKSL: () => WEATHER_CLOUDS_SKSL,
33
- WEATHER_DARK_VIGNETTE_SKSL: () => WEATHER_DARK_VIGNETTE_SKSL,
34
- WEATHER_RAIN_SKSL: () => WEATHER_RAIN_SKSL,
35
- WEATHER_SUN_SHAFT_SKSL: () => WEATHER_SUN_SHAFT_SKSL,
36
- createBloom: () => createBloom,
37
- createChromaticAberration: () => createChromaticAberration,
38
- createColorGradingLut: () => createColorGradingLut,
39
- createCrt: () => createCrt,
40
- createGodRays: () => createGodRays,
41
- createGrayscaleFlash: () => createGrayscaleFlash,
42
- createNeutralLutBytes: () => createNeutralLutBytes,
43
- createPostFxComposite: () => createPostFxComposite,
44
- createPostFxController: () => createPostFxController,
45
- createPostFxRuntime: () => createPostFxRuntime,
46
- createVignette: () => createVignette,
47
- createWeatherClouds: () => createWeatherClouds,
48
- createWeatherDarkVignette: () => createWeatherDarkVignette,
49
- createWeatherRain: () => createWeatherRain,
50
- createWeatherSunShaft: () => createWeatherSunShaft
51
- });
52
- module.exports = __toCommonJS(index_exports);
53
-
54
- // src/effect.ts
55
- function createVignette(id = "vignette", uniforms = {}) {
56
- return {
57
- id,
58
- enabled: true,
59
- uniforms: {
60
- kind: "vignette",
61
- intensity: uniforms.intensity ?? 0.4,
62
- radius: uniforms.radius ?? 0.8,
63
- color: uniforms.color ?? [0, 0, 0]
64
- }
65
- };
66
- }
67
- function createGrayscaleFlash(id = "grayscale-flash", uniforms = {}) {
68
- return {
69
- id,
70
- enabled: true,
71
- uniforms: { kind: "grayscale-flash", intensity: uniforms.intensity ?? 0 }
72
- };
73
- }
74
- function createChromaticAberration(id = "chromatic-aberration", uniforms = {}) {
75
- return {
76
- id,
77
- enabled: true,
78
- uniforms: { kind: "chromatic-aberration", amount: uniforms.amount ?? 5e-3 }
79
- };
80
- }
81
- function createCrt(id = "crt", uniforms = {}) {
82
- return {
83
- id,
84
- enabled: true,
85
- uniforms: {
86
- kind: "crt",
87
- scanlineIntensity: uniforms.scanlineIntensity ?? 0.25,
88
- curvature: uniforms.curvature ?? 0.12
89
- }
90
- };
91
- }
92
- function createBloom(id = "bloom", uniforms = {}) {
93
- return {
94
- id,
95
- enabled: true,
96
- uniforms: {
97
- kind: "bloom",
98
- threshold: uniforms.threshold ?? 0.7,
99
- intensity: uniforms.intensity ?? 0.6
100
- }
101
- };
102
- }
103
- function createGodRays(id = "god-rays", uniforms = {}) {
104
- return {
105
- id,
106
- enabled: true,
107
- uniforms: {
108
- kind: "god-rays",
109
- lightX: uniforms.lightX ?? 0.5,
110
- lightY: uniforms.lightY ?? 0.1,
111
- color: uniforms.color ?? [1, 0.95, 0.8],
112
- intensity: uniforms.intensity ?? 0.6,
113
- decay: uniforms.decay ?? 0.96,
114
- density: uniforms.density ?? 0.5,
115
- weight: uniforms.weight ?? 0.4,
116
- samples: uniforms.samples ?? 48
117
- }
118
- };
119
- }
120
- function createColorGradingLut(uniforms, id = "color-grading-lut") {
121
- return {
122
- id,
123
- enabled: true,
124
- uniforms: {
125
- kind: "color-grading-lut",
126
- lut: uniforms.lut,
127
- intensity: uniforms.intensity ?? 1
128
- }
129
- };
130
- }
131
- function createWeatherRain(id = "weather-rain", uniforms = {}) {
132
- return {
133
- id,
134
- enabled: true,
135
- uniforms: {
136
- kind: "weather-rain",
137
- intensity: uniforms.intensity ?? 0.5,
138
- direction: uniforms.direction ?? 0,
139
- speed: uniforms.speed ?? 0,
140
- scrollOffsetY: uniforms.scrollOffsetY ?? 0,
141
- color: uniforms.color ?? [1, 1, 1],
142
- density: uniforms.density ?? 0.5,
143
- streakLen: uniforms.streakLen ?? 0.15
144
- }
145
- };
146
- }
147
- function createWeatherSunShaft(id = "weather-sun-shaft", uniforms = {}) {
148
- return {
149
- id,
150
- enabled: true,
151
- uniforms: {
152
- kind: "weather-sun-shaft",
153
- intensity: uniforms.intensity ?? 0.5,
154
- direction: uniforms.direction ?? 0
155
- }
156
- };
157
- }
158
- function createWeatherDarkVignette(id = "weather-dark-vignette", uniforms = {}) {
159
- return {
160
- id,
161
- enabled: true,
162
- uniforms: {
163
- kind: "weather-dark-vignette",
164
- intensity: uniforms.intensity ?? 0.5
165
- }
166
- };
167
- }
168
- function createWeatherClouds(id = "weather-clouds", uniforms = {}) {
169
- return {
170
- id,
171
- enabled: true,
172
- uniforms: {
173
- kind: "weather-clouds",
174
- intensity: uniforms.intensity ?? 0.5,
175
- direction: uniforms.direction ?? 0,
176
- speed: uniforms.speed ?? 0,
177
- scrollOffsetX: uniforms.scrollOffsetX ?? 0
178
- }
179
- };
180
- }
181
- function createPostFxComposite(id = "composite", uniforms = {}) {
182
- return {
183
- id,
184
- enabled: true,
185
- uniforms: {
186
- kind: "composite",
187
- vignetteIntensity: uniforms.vignetteIntensity ?? 0,
188
- vignetteRadius: uniforms.vignetteRadius ?? 0.8,
189
- vignetteColor: uniforms.vignetteColor ?? [0, 0, 0],
190
- bloomThreshold: uniforms.bloomThreshold ?? 0.7,
191
- bloomIntensity: uniforms.bloomIntensity ?? 0,
192
- flashIntensity: uniforms.flashIntensity ?? 0,
193
- chromaAmount: uniforms.chromaAmount ?? 0,
194
- crtScanlineIntensity: uniforms.crtScanlineIntensity ?? 0.25,
195
- crtCurvature: uniforms.crtCurvature ?? 0.12,
196
- crtEnabled: uniforms.crtEnabled ?? false,
197
- weatherKind: uniforms.weatherKind ?? 0,
198
- weatherIntensity: uniforms.weatherIntensity ?? 0,
199
- weatherDirection: uniforms.weatherDirection ?? 0,
200
- weatherScrollX: uniforms.weatherScrollX ?? 0,
201
- weatherScrollY: uniforms.weatherScrollY ?? 0,
202
- weatherColor: uniforms.weatherColor ?? [1, 1, 1],
203
- weatherDensity: uniforms.weatherDensity ?? 0.5,
204
- weatherStreakLen: uniforms.weatherStreakLen ?? 0.15
205
- }
206
- };
207
- }
208
-
209
- // src/chain.ts
1
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
+ //#region src/chain.ts
3
+ /**
4
+ * Ordered post-processing pipeline. Pure data structure — the runtime reads
5
+ * the enabled entries in order and feeds each through its compiled shader.
6
+ */
210
7
  var EffectChain = class {
211
- _effects = [];
212
- _globalIntensity = 1;
213
- get length() {
214
- return this._effects.length;
215
- }
216
- /**
217
- * Global intensity multiplier applied to every enabled effect's
218
- * "strength" uniform when the pass is built. Default 1 (no-op).
219
- *
220
- * Scales the following uniform fields at pass-build time:
221
- * - vignette.intensity
222
- * - grayscale-flash.intensity
223
- * - chromatic-aberration.amount
224
- * - crt.scanlineIntensity
225
- * - bloom.intensity
226
- *
227
- * Geometry-like uniforms (radius, curvature, threshold) are not scaled.
228
- * Intended for a11y reduced-motion (set to 0 to null all dampen-able
229
- * effects in one call) without touching per-effect state.
230
- *
231
- * Clamped to `>= 0`. Negative values are clamped to 0.
232
- */
233
- get globalIntensity() {
234
- return this._globalIntensity;
235
- }
236
- set globalIntensity(value) {
237
- this._globalIntensity = value < 0 ? 0 : value;
238
- }
239
- /** Convenience setter — mirrors the getter/setter. */
240
- setGlobalIntensity(value) {
241
- this.globalIntensity = value;
242
- }
243
- /** Snapshot view of the chain. Do not mutate. */
244
- all() {
245
- return this._effects;
246
- }
247
- /** Append an effect to the end of the chain. */
248
- add(effect) {
249
- if (this._effects.some((e) => e.id === effect.id)) {
250
- throw new Error(`EffectChain: duplicate id "${effect.id}"`);
251
- }
252
- this._effects.push(effect);
253
- }
254
- /** Insert an effect at a specific index (clamped to [0, length]). */
255
- insert(index, effect) {
256
- if (this._effects.some((e) => e.id === effect.id)) {
257
- throw new Error(`EffectChain: duplicate id "${effect.id}"`);
258
- }
259
- const i = Math.max(0, Math.min(this._effects.length, index));
260
- this._effects.splice(i, 0, effect);
261
- }
262
- remove(id) {
263
- const i = this._effects.findIndex((e) => e.id === id);
264
- if (i === -1) return false;
265
- this._effects.splice(i, 1);
266
- return true;
267
- }
268
- get(id) {
269
- return this._effects.find((e) => e.id === id) ?? null;
270
- }
271
- setEnabled(id, enabled) {
272
- const e = this.get(id);
273
- if (e) e.enabled = enabled;
274
- }
275
- /** Update uniforms in place. Kind must match the existing effect's kind. */
276
- setUniforms(id, uniforms) {
277
- const e = this.get(id);
278
- if (!e) return;
279
- if (e.uniforms.kind !== uniforms.kind) {
280
- throw new Error(
281
- `EffectChain: cannot change kind of "${id}" from "${e.uniforms.kind}" to "${uniforms.kind}"`
282
- );
283
- }
284
- e.uniforms = uniforms;
285
- }
286
- move(id, newIndex) {
287
- const i = this._effects.findIndex((e) => e.id === id);
288
- if (i === -1) return;
289
- const [removed] = this._effects.splice(i, 1);
290
- if (!removed) return;
291
- const clamped = Math.max(0, Math.min(this._effects.length, newIndex));
292
- this._effects.splice(clamped, 0, removed);
293
- }
294
- clear() {
295
- this._effects.length = 0;
296
- }
297
- /** Iterate only over enabled effects, in order. */
298
- *enabled() {
299
- for (const e of this._effects) {
300
- if (e.enabled) yield e;
301
- }
302
- }
8
+ _effects = [];
9
+ _globalIntensity = 1;
10
+ get length() {
11
+ return this._effects.length;
12
+ }
13
+ /**
14
+ * Global intensity multiplier applied to every enabled effect's
15
+ * "strength" uniform when the pass is built. Default 1 (no-op).
16
+ *
17
+ * Scales the following uniform fields at pass-build time:
18
+ * - vignette.intensity
19
+ * - grayscale-flash.intensity
20
+ * - chromatic-aberration.amount
21
+ * - crt.scanlineIntensity
22
+ * - bloom.intensity
23
+ *
24
+ * Geometry-like uniforms (radius, curvature, threshold) are not scaled.
25
+ * Intended for a11y reduced-motion (set to 0 to null all dampen-able
26
+ * effects in one call) without touching per-effect state.
27
+ *
28
+ * Clamped to `>= 0`. Negative values are clamped to 0.
29
+ */
30
+ get globalIntensity() {
31
+ return this._globalIntensity;
32
+ }
33
+ set globalIntensity(value) {
34
+ this._globalIntensity = value < 0 ? 0 : value;
35
+ }
36
+ /** Convenience setter — mirrors the getter/setter. */
37
+ setGlobalIntensity(value) {
38
+ this.globalIntensity = value;
39
+ }
40
+ /** Snapshot view of the chain. Do not mutate. */
41
+ all() {
42
+ return this._effects;
43
+ }
44
+ /** Append an effect to the end of the chain. */
45
+ add(effect) {
46
+ if (this._effects.some((e) => e.id === effect.id)) throw new Error(`EffectChain: duplicate id "${effect.id}"`);
47
+ this._effects.push(effect);
48
+ }
49
+ /** Insert an effect at a specific index (clamped to [0, length]). */
50
+ insert(index, effect) {
51
+ if (this._effects.some((e) => e.id === effect.id)) throw new Error(`EffectChain: duplicate id "${effect.id}"`);
52
+ const i = Math.max(0, Math.min(this._effects.length, index));
53
+ this._effects.splice(i, 0, effect);
54
+ }
55
+ remove(id) {
56
+ const i = this._effects.findIndex((e) => e.id === id);
57
+ if (i === -1) return false;
58
+ this._effects.splice(i, 1);
59
+ return true;
60
+ }
61
+ get(id) {
62
+ return this._effects.find((e) => e.id === id) ?? null;
63
+ }
64
+ setEnabled(id, enabled) {
65
+ const e = this.get(id);
66
+ if (e) e.enabled = enabled;
67
+ }
68
+ /** Update uniforms in place. Kind must match the existing effect's kind. */
69
+ setUniforms(id, uniforms) {
70
+ const e = this.get(id);
71
+ if (!e) return;
72
+ if (e.uniforms.kind !== uniforms.kind) throw new Error(`EffectChain: cannot change kind of "${id}" from "${e.uniforms.kind}" to "${uniforms.kind}"`);
73
+ e.uniforms = uniforms;
74
+ }
75
+ move(id, newIndex) {
76
+ const i = this._effects.findIndex((e) => e.id === id);
77
+ if (i === -1) return;
78
+ const [removed] = this._effects.splice(i, 1);
79
+ if (!removed) return;
80
+ const clamped = Math.max(0, Math.min(this._effects.length, newIndex));
81
+ this._effects.splice(clamped, 0, removed);
82
+ }
83
+ clear() {
84
+ this._effects.length = 0;
85
+ }
86
+ /** Iterate only over enabled effects, in order. */
87
+ *enabled() {
88
+ for (const e of this._effects) if (e.enabled) yield e;
89
+ }
303
90
  };
304
-
305
- // src/shaders.ts
306
- var VIGNETTE_SKSL = (
307
- /* sksl */
308
- `
91
+ //#endregion
92
+ //#region src/shaders.ts
93
+ /**
94
+ * SkSL runtime-effect shaders used by the postfx stack.
95
+ *
96
+ * Each source declares a `uniform shader src;` plus per-effect uniforms. They
97
+ * are sampled as `src.eval(fragCoord)` against the scene's offscreen surface.
98
+ *
99
+ * Shader strings are emitted from the package as-is. Fallback: when
100
+ * `Skia.RuntimeEffect.Make` is unavailable, `runtime.ts` short-circuits the
101
+ * chain to a passthrough.
102
+ */
103
+ const VIGNETTE_SKSL = `
309
104
  uniform shader src;
310
105
  uniform float2 resolution;
311
106
  uniform float intensity;
@@ -320,11 +115,8 @@ half4 main(float2 coord) {
320
115
  float v = smoothstep(radius, radius * 0.5, d);
321
116
  return half4(mix(half3(color), c.rgb, mix(1.0 - intensity, 1.0, v)), c.a);
322
117
  }
323
- `
324
- );
325
- var GRAYSCALE_FLASH_SKSL = (
326
- /* sksl */
327
- `
118
+ `;
119
+ const GRAYSCALE_FLASH_SKSL = `
328
120
  uniform shader src;
329
121
  uniform float intensity;
330
122
 
@@ -335,11 +127,8 @@ half4 main(float2 coord) {
335
127
  half3 mixed = mix(c.rgb, gray, half(intensity));
336
128
  return half4(mixed, c.a);
337
129
  }
338
- `
339
- );
340
- var CHROMATIC_ABERRATION_SKSL = (
341
- /* sksl */
342
- `
130
+ `;
131
+ const CHROMATIC_ABERRATION_SKSL = `
343
132
  uniform shader src;
344
133
  uniform float2 resolution;
345
134
  uniform float amount;
@@ -353,11 +142,8 @@ half4 main(float2 coord) {
353
142
  half a = src.eval(coord).a;
354
143
  return half4(r, g, b, a);
355
144
  }
356
- `
357
- );
358
- var CRT_SKSL = (
359
- /* sksl */
360
- `
145
+ `;
146
+ const CRT_SKSL = `
361
147
  uniform shader src;
362
148
  uniform float2 resolution;
363
149
  uniform float scanlineIntensity;
@@ -376,11 +162,8 @@ half4 main(float2 coord) {
376
162
  float dim = mix(1.0, scan, scanlineIntensity);
377
163
  return half4(c.rgb * dim, c.a);
378
164
  }
379
- `
380
- );
381
- var BLOOM_SKSL = (
382
- /* sksl */
383
- `
165
+ `;
166
+ const BLOOM_SKSL = `
384
167
  uniform shader src;
385
168
  uniform float2 resolution;
386
169
  uniform float threshold;
@@ -393,11 +176,8 @@ half4 main(float2 coord) {
393
176
  half3 boost = c.rgb * mask * intensity;
394
177
  return half4(c.rgb + boost, c.a);
395
178
  }
396
- `
397
- );
398
- var COLOR_GRADING_LUT_SKSL = (
399
- /* sksl */
400
- `
179
+ `;
180
+ const COLOR_GRADING_LUT_SKSL = `
401
181
  uniform shader src;
402
182
  uniform shader lut;
403
183
  uniform float intensity;
@@ -426,11 +206,8 @@ half4 main(float2 coord) {
426
206
 
427
207
  return half4(result, scene.a);
428
208
  }
429
- `
430
- );
431
- var WEATHER_RAIN_SKSL = (
432
- /* sksl */
433
- `
209
+ `;
210
+ const WEATHER_RAIN_SKSL = `
434
211
  uniform shader src;
435
212
  uniform float2 resolution;
436
213
  uniform float intensity;
@@ -458,7 +235,7 @@ half4 main(float2 coord) {
458
235
 
459
236
  float coverage = 0.0;
460
237
 
461
- // near layer \u2014 COLS=45, tiling=6, brightness=1.0
238
+ // near layer — COLS=45, tiling=6, brightness=1.0
462
239
  {
463
240
  float COLS = 45.0;
464
241
  float tiling = 6.0;
@@ -477,7 +254,7 @@ half4 main(float2 coord) {
477
254
  }
478
255
  }
479
256
 
480
- // far layer \u2014 COLS=85, tiling=11, brightness=0.5
257
+ // far layer — COLS=85, tiling=11, brightness=0.5
481
258
  {
482
259
  float COLS = 85.0;
483
260
  float tiling = 11.0;
@@ -499,11 +276,8 @@ half4 main(float2 coord) {
499
276
  half3 mixed = mix(scene.rgb, half3(color), half(coverage * intensity));
500
277
  return half4(mixed, scene.a);
501
278
  }
502
- `
503
- );
504
- var WEATHER_SUN_SHAFT_SKSL = (
505
- /* sksl */
506
- `
279
+ `;
280
+ const WEATHER_SUN_SHAFT_SKSL = `
507
281
  uniform shader src;
508
282
  uniform float2 resolution;
509
283
  uniform float intensity;
@@ -522,11 +296,8 @@ half4 main(float2 coord) {
522
296
  half shaft = half(shafts * falloff * intensity * 0.35);
523
297
  return half4(scene.rgb + half3(shaft * 0.95, shaft * 0.9, shaft * 0.6), scene.a);
524
298
  }
525
- `
526
- );
527
- var WEATHER_DARK_VIGNETTE_SKSL = (
528
- /* sksl */
529
- `
299
+ `;
300
+ const WEATHER_DARK_VIGNETTE_SKSL = `
530
301
  uniform shader src;
531
302
  uniform float2 resolution;
532
303
  uniform float intensity;
@@ -539,15 +310,13 @@ half4 main(float2 coord) {
539
310
  half dark = half(v * intensity);
540
311
  return half4(scene.rgb * (1.0 - dark), scene.a);
541
312
  }
542
- `
543
- );
544
- var WEATHER_CLOUDS_SKSL = (
545
- /* sksl */
546
- `
313
+ `;
314
+ const WEATHER_CLOUDS_SKSL = `
547
315
  uniform shader src;
548
316
  uniform float2 resolution;
549
317
  uniform float intensity;
550
318
  uniform float scrollOffsetX;
319
+ uniform float direction;
551
320
 
552
321
  float hash(float2 p) { return fract(sin(dot(p, float2(127.1, 311.7))) * 43758.5453); }
553
322
  float vnoise(float2 p) {
@@ -561,18 +330,21 @@ float fbm(float2 p) { return vnoise(p) * 0.6 + vnoise(p * 2.3) * 0.4; }
561
330
  half4 main(float2 coord) {
562
331
  half4 scene = src.eval(coord);
563
332
  float2 uv = coord / resolution;
564
- float2 cloudUv = float2(uv.x + scrollOffsetX / resolution.x, uv.y * 0.5);
333
+ // direction=0 → pure horizontal scroll (cos=1, sin=0), matching the pre-direction default.
334
+ float rad = direction * 3.14159 / 180.0;
335
+ float2 scrollDir = float2(cos(rad), sin(rad));
336
+ float2 cloudUv = float2(
337
+ uv.x + scrollOffsetX * scrollDir.x / resolution.x,
338
+ uv.y * 0.5 + scrollOffsetX * scrollDir.y / resolution.y
339
+ );
565
340
  float cloud = fbm(cloudUv * 3.0);
566
341
  float heightFade = smoothstep(0.6, 0.2, uv.y);
567
342
  float alpha = cloud * heightFade * intensity * 0.5;
568
343
  half3 cloudColor = half3(0.9, 0.9, 0.95);
569
344
  return half4(mix(scene.rgb, cloudColor, half(alpha)), scene.a);
570
345
  }
571
- `
572
- );
573
- var COMPOSITE_SKSL = (
574
- /* sksl */
575
- `
346
+ `;
347
+ const COMPOSITE_SKSL = `
576
348
  uniform shader src;
577
349
 
578
350
  uniform float2 resolution;
@@ -639,7 +411,7 @@ half4 main(float2 coord) {
639
411
  {
640
412
  float wk = weatherKind;
641
413
  if (wk >= 0.5 && wk < 1.5) {
642
- // rain \u2014 falling sawtooth streaks, 2 parallax layers, alpha-over blend
414
+ // rain — falling sawtooth streaks, 2 parallax layers, alpha-over blend
643
415
  // weatherScrollY is normalized [0,1) fall phase (increment speed*dt/screenH each tick)
644
416
  float rad = weatherDirection * 3.14159 / 180.0;
645
417
  float sinA = sin(rad);
@@ -650,7 +422,7 @@ half4 main(float2 coord) {
650
422
 
651
423
  float coverage = 0.0;
652
424
 
653
- // near layer \u2014 COLS=45, tiling=6, brightness=1.0
425
+ // near layer — COLS=45, tiling=6, brightness=1.0
654
426
  {
655
427
  float COLS = 45.0;
656
428
  float tiling = 6.0;
@@ -669,7 +441,7 @@ half4 main(float2 coord) {
669
441
  }
670
442
  }
671
443
 
672
- // far layer \u2014 COLS=85, tiling=11, brightness=0.5
444
+ // far layer — COLS=85, tiling=11, brightness=0.5
673
445
  {
674
446
  float COLS = 85.0;
675
447
  float tiling = 11.0;
@@ -731,11 +503,8 @@ half4 main(float2 coord) {
731
503
 
732
504
  return half4(c, 1.0);
733
505
  }
734
- `
735
- );
736
- var GOD_RAYS_SKSL = (
737
- /* sksl */
738
- `
506
+ `;
507
+ const GOD_RAYS_SKSL = `
739
508
  uniform shader src;
740
509
  uniform float2 resolution;
741
510
  uniform float2 u_lightPos;
@@ -767,325 +536,546 @@ half4 main(float2 coord) {
767
536
  half3 rays = accumulated * half3(u_color) * half(u_weight) * half(u_intensity);
768
537
  return half4(orig.rgb + rays, orig.a);
769
538
  }
770
- `
771
- );
772
-
773
- // src/runtime.ts
774
- var EMPTY_CHILDREN = Object.freeze([]);
775
- var SKSL_SOURCE = {
776
- vignette: VIGNETTE_SKSL,
777
- "grayscale-flash": GRAYSCALE_FLASH_SKSL,
778
- "chromatic-aberration": CHROMATIC_ABERRATION_SKSL,
779
- crt: CRT_SKSL,
780
- bloom: BLOOM_SKSL,
781
- "god-rays": GOD_RAYS_SKSL,
782
- "color-grading-lut": COLOR_GRADING_LUT_SKSL,
783
- "weather-rain": WEATHER_RAIN_SKSL,
784
- "weather-sun-shaft": WEATHER_SUN_SHAFT_SKSL,
785
- "weather-dark-vignette": WEATHER_DARK_VIGNETTE_SKSL,
786
- "weather-clouds": WEATHER_CLOUDS_SKSL,
787
- composite: COMPOSITE_SKSL
539
+ `;
540
+ //#endregion
541
+ //#region src/runtime.ts
542
+ /** Shared frozen sentinel — every pass with no extra children points here. */
543
+ const EMPTY_CHILDREN = Object.freeze([]);
544
+ const SKSL_SOURCE = {
545
+ vignette: VIGNETTE_SKSL,
546
+ "grayscale-flash": GRAYSCALE_FLASH_SKSL,
547
+ "chromatic-aberration": CHROMATIC_ABERRATION_SKSL,
548
+ crt: CRT_SKSL,
549
+ bloom: BLOOM_SKSL,
550
+ "god-rays": GOD_RAYS_SKSL,
551
+ "color-grading-lut": COLOR_GRADING_LUT_SKSL,
552
+ "weather-rain": WEATHER_RAIN_SKSL,
553
+ "weather-sun-shaft": WEATHER_SUN_SHAFT_SKSL,
554
+ "weather-dark-vignette": WEATHER_DARK_VIGNETTE_SKSL,
555
+ "weather-clouds": WEATHER_CLOUDS_SKSL,
556
+ composite: COMPOSITE_SKSL
788
557
  };
789
- function createPostFxRuntime(skia) {
790
- const cache = /* @__PURE__ */ new Map();
791
- const _loggedCompileFailures = /* @__PURE__ */ new Set();
792
- const _loggedCompileSuccesses = /* @__PURE__ */ new Set();
793
- const supported = skia !== null && typeof skia.RuntimeEffect?.Make === "function";
794
- return {
795
- supported,
796
- compile(kind) {
797
- if (!supported || !skia) return null;
798
- const cached = cache.get(kind);
799
- if (cached) return cached;
800
- const effect = skia.RuntimeEffect.Make(SKSL_SOURCE[kind]);
801
- if (!effect) {
802
- if (!_loggedCompileFailures.has(kind)) {
803
- _loggedCompileFailures.add(kind);
804
- console.warn(
805
- `[postfx] RuntimeEffect.Make returned null for "${kind}" \u2014 shader failed to compile. Pass will be silently skipped.`
806
- );
807
- }
808
- return null;
809
- }
810
- if (!_loggedCompileSuccesses.has(kind)) {
811
- _loggedCompileSuccesses.add(kind);
812
- if (typeof effect.getUniformFloatCount === "function") {
813
- console.info(
814
- `[postfx] compiled "${kind}" \u2014 getUniformFloatCount=${effect.getUniformFloatCount()}`
815
- );
816
- }
817
- }
818
- const compiled = { kind, effect };
819
- cache.set(kind, compiled);
820
- return compiled;
821
- },
822
- packUniforms(effect, width, height) {
823
- return packUniforms(effect.uniforms, width, height);
824
- },
825
- buildPass(chain, width, height) {
826
- const out = [];
827
- if (!supported) return out;
828
- const scale = chain.globalIntensity;
829
- for (const effect of chain.enabled()) {
830
- const compiled = this.compile(effect.uniforms.kind);
831
- if (!compiled) continue;
832
- const uniforms = packUniforms(effect.uniforms, width, height);
833
- if (scale !== 1) scaleIntensity(effect.uniforms.kind, uniforms, scale);
834
- const children = effect.uniforms.kind === "color-grading-lut" ? [effect.uniforms.lut] : EMPTY_CHILDREN;
835
- out.push({
836
- id: effect.id,
837
- kind: effect.uniforms.kind,
838
- shader: compiled.effect,
839
- uniforms,
840
- children
841
- });
842
- }
843
- return out;
844
- },
845
- reset() {
846
- cache.clear();
847
- }
848
- };
558
+ /**
559
+ * Create a runtime against the given Skia-like module. If the module is
560
+ * absent or lacks `RuntimeEffect.Make`, returns a runtime with
561
+ * `supported: false` — callers should fall through to a passthrough pipeline.
562
+ */
563
+ function createPostFxRuntime(skia, opts) {
564
+ const cache = /* @__PURE__ */ new Map();
565
+ const _loggedCompileFailures = /* @__PURE__ */ new Set();
566
+ const _loggedCompileSuccesses = /* @__PURE__ */ new Set();
567
+ const supported = skia !== null && typeof skia.RuntimeEffect?.Make === "function";
568
+ return {
569
+ supported,
570
+ compile(kind) {
571
+ if (!supported || !skia) return null;
572
+ const cached = cache.get(kind);
573
+ if (cached) return cached;
574
+ const effect = skia.RuntimeEffect.Make(SKSL_SOURCE[kind]);
575
+ if (!effect) {
576
+ if (!_loggedCompileFailures.has(kind)) {
577
+ _loggedCompileFailures.add(kind);
578
+ console.warn(`[postfx] RuntimeEffect.Make returned null for "${kind}" — shader failed to compile. Pass will be silently skipped.`);
579
+ }
580
+ return null;
581
+ }
582
+ if (!_loggedCompileSuccesses.has(kind)) {
583
+ _loggedCompileSuccesses.add(kind);
584
+ if (opts?.verbose === true && typeof effect.getUniformFloatCount === "function") console.info(`[postfx] compiled "${kind}" — getUniformFloatCount=${effect.getUniformFloatCount()}`);
585
+ }
586
+ const compiled = {
587
+ kind,
588
+ effect
589
+ };
590
+ cache.set(kind, compiled);
591
+ return compiled;
592
+ },
593
+ packUniforms(effect, width, height) {
594
+ return packUniforms(effect.uniforms, width, height);
595
+ },
596
+ buildPass(chain, width, height) {
597
+ const out = [];
598
+ if (!supported) return out;
599
+ const scale = chain.globalIntensity;
600
+ for (const effect of chain.enabled()) {
601
+ const compiled = this.compile(effect.uniforms.kind);
602
+ if (!compiled) continue;
603
+ const uniforms = packUniforms(effect.uniforms, width, height);
604
+ if (scale !== 1) scaleIntensity(effect.uniforms.kind, uniforms, scale);
605
+ const children = effect.uniforms.kind === "color-grading-lut" ? [effect.uniforms.lut] : EMPTY_CHILDREN;
606
+ out.push({
607
+ id: effect.id,
608
+ kind: effect.uniforms.kind,
609
+ shader: compiled.effect,
610
+ uniforms,
611
+ children
612
+ });
613
+ }
614
+ return out;
615
+ },
616
+ reset() {
617
+ cache.clear();
618
+ }
619
+ };
849
620
  }
850
621
  function packUniforms(u, width, height) {
851
- switch (u.kind) {
852
- case "vignette": {
853
- const col = u.color ?? [0, 0, 0];
854
- return new Float32Array([width, height, u.intensity, u.radius, col[0], col[1], col[2]]);
855
- }
856
- case "grayscale-flash":
857
- return new Float32Array([u.intensity]);
858
- case "chromatic-aberration":
859
- return new Float32Array([width, height, u.amount]);
860
- case "crt":
861
- return new Float32Array([width, height, u.scanlineIntensity, u.curvature]);
862
- case "bloom":
863
- return new Float32Array([width, height, u.threshold, u.intensity]);
864
- case "god-rays": {
865
- const samples = Math.max(1, Math.min(64, Math.round(u.samples)));
866
- return new Float32Array([
867
- width,
868
- height,
869
- u.lightX,
870
- u.lightY,
871
- u.color[0],
872
- u.color[1],
873
- u.color[2],
874
- u.intensity,
875
- u.decay,
876
- u.density,
877
- u.weight,
878
- samples
879
- ]);
880
- }
881
- case "color-grading-lut":
882
- return new Float32Array([u.intensity]);
883
- // Weather kinds: TS interfaces are wider than the packed SkSL inputs (speed/direction
884
- // for clouds, speed for rain are B5-passthrough fields; the shader doesn't consume them).
885
- case "weather-rain": {
886
- const col = u.color ?? [1, 1, 1];
887
- return new Float32Array([
888
- width,
889
- height,
890
- u.intensity,
891
- u.direction,
892
- u.scrollOffsetY,
893
- col[0],
894
- col[1],
895
- col[2],
896
- u.density ?? 0.5,
897
- u.streakLen ?? 0.15
898
- ]);
899
- }
900
- case "weather-sun-shaft":
901
- return new Float32Array([width, height, u.intensity, u.direction]);
902
- case "weather-dark-vignette":
903
- return new Float32Array([width, height, u.intensity]);
904
- case "weather-clouds":
905
- return new Float32Array([width, height, u.intensity, u.scrollOffsetX]);
906
- case "composite":
907
- return new Float32Array([
908
- width,
909
- height,
910
- u.vignetteIntensity,
911
- u.vignetteRadius,
912
- u.vignetteColor[0],
913
- u.vignetteColor[1],
914
- u.vignetteColor[2],
915
- u.bloomThreshold,
916
- u.bloomIntensity,
917
- u.flashIntensity,
918
- u.chromaAmount,
919
- u.crtScanlineIntensity,
920
- u.crtCurvature,
921
- u.crtEnabled ? 1 : 0,
922
- u.weatherKind,
923
- u.weatherIntensity,
924
- u.weatherDirection,
925
- u.weatherScrollX,
926
- u.weatherScrollY,
927
- u.weatherColor[0],
928
- u.weatherColor[1],
929
- u.weatherColor[2],
930
- u.weatherDensity,
931
- u.weatherStreakLen
932
- ]);
933
- }
622
+ switch (u.kind) {
623
+ case "vignette": {
624
+ const col = u.color ?? [
625
+ 0,
626
+ 0,
627
+ 0
628
+ ];
629
+ return new Float32Array([
630
+ width,
631
+ height,
632
+ u.intensity,
633
+ u.radius,
634
+ col[0],
635
+ col[1],
636
+ col[2]
637
+ ]);
638
+ }
639
+ case "grayscale-flash": return new Float32Array([u.intensity]);
640
+ case "chromatic-aberration": return new Float32Array([
641
+ width,
642
+ height,
643
+ u.amount
644
+ ]);
645
+ case "crt": return new Float32Array([
646
+ width,
647
+ height,
648
+ u.scanlineIntensity,
649
+ u.curvature
650
+ ]);
651
+ case "bloom": return new Float32Array([
652
+ width,
653
+ height,
654
+ u.threshold,
655
+ u.intensity
656
+ ]);
657
+ case "god-rays": {
658
+ const samples = Math.max(1, Math.min(64, Math.round(u.samples)));
659
+ return new Float32Array([
660
+ width,
661
+ height,
662
+ u.lightX,
663
+ u.lightY,
664
+ u.color[0],
665
+ u.color[1],
666
+ u.color[2],
667
+ u.intensity,
668
+ u.decay,
669
+ u.density,
670
+ u.weight,
671
+ samples
672
+ ]);
673
+ }
674
+ case "color-grading-lut": return new Float32Array([u.intensity]);
675
+ case "weather-rain": {
676
+ const col = u.color ?? [
677
+ 1,
678
+ 1,
679
+ 1
680
+ ];
681
+ return new Float32Array([
682
+ width,
683
+ height,
684
+ u.intensity,
685
+ u.direction,
686
+ u.scrollOffsetY,
687
+ col[0],
688
+ col[1],
689
+ col[2],
690
+ u.density ?? .5,
691
+ u.streakLen ?? .15
692
+ ]);
693
+ }
694
+ case "weather-sun-shaft": return new Float32Array([
695
+ width,
696
+ height,
697
+ u.intensity,
698
+ u.direction
699
+ ]);
700
+ case "weather-dark-vignette": return new Float32Array([
701
+ width,
702
+ height,
703
+ u.intensity
704
+ ]);
705
+ case "weather-clouds": return new Float32Array([
706
+ width,
707
+ height,
708
+ u.intensity,
709
+ u.scrollOffsetX,
710
+ u.direction
711
+ ]);
712
+ case "composite": return new Float32Array([
713
+ width,
714
+ height,
715
+ u.vignetteIntensity,
716
+ u.vignetteRadius,
717
+ u.vignetteColor[0],
718
+ u.vignetteColor[1],
719
+ u.vignetteColor[2],
720
+ u.bloomThreshold,
721
+ u.bloomIntensity,
722
+ u.flashIntensity,
723
+ u.chromaAmount,
724
+ u.crtScanlineIntensity,
725
+ u.crtCurvature,
726
+ u.crtEnabled ? 1 : 0,
727
+ u.weatherKind,
728
+ u.weatherIntensity,
729
+ u.weatherDirection,
730
+ u.weatherScrollX,
731
+ u.weatherScrollY,
732
+ u.weatherColor[0],
733
+ u.weatherColor[1],
734
+ u.weatherColor[2],
735
+ u.weatherDensity,
736
+ u.weatherStreakLen
737
+ ]);
738
+ }
934
739
  }
740
+ /**
741
+ * Scale the "strength" slot of a packed uniform buffer in place. Geometry
742
+ * fields (radius, curvature, threshold) are intentionally left alone.
743
+ */
935
744
  function scaleIntensity(kind, packed, scale) {
936
- switch (kind) {
937
- case "vignette":
938
- packed[2] = (packed[2] ?? 0) * scale;
939
- return;
940
- case "grayscale-flash":
941
- packed[0] = (packed[0] ?? 0) * scale;
942
- return;
943
- case "chromatic-aberration":
944
- packed[2] = (packed[2] ?? 0) * scale;
945
- return;
946
- case "crt":
947
- packed[2] = (packed[2] ?? 0) * scale;
948
- return;
949
- case "bloom":
950
- packed[3] = (packed[3] ?? 0) * scale;
951
- return;
952
- case "god-rays":
953
- packed[7] = (packed[7] ?? 0) * scale;
954
- return;
955
- case "color-grading-lut":
956
- packed[0] = (packed[0] ?? 0) * scale;
957
- return;
958
- case "weather-rain":
959
- case "weather-sun-shaft":
960
- case "weather-dark-vignette":
961
- case "weather-clouds":
962
- packed[2] = (packed[2] ?? 0) * scale;
963
- return;
964
- case "composite":
965
- packed[2] = (packed[2] ?? 0) * scale;
966
- packed[8] = (packed[8] ?? 0) * scale;
967
- packed[9] = (packed[9] ?? 0) * scale;
968
- packed[10] = (packed[10] ?? 0) * scale;
969
- packed[15] = (packed[15] ?? 0) * scale;
970
- return;
971
- }
745
+ switch (kind) {
746
+ case "vignette":
747
+ packed[2] = (packed[2] ?? 0) * scale;
748
+ return;
749
+ case "grayscale-flash":
750
+ packed[0] = (packed[0] ?? 0) * scale;
751
+ return;
752
+ case "chromatic-aberration":
753
+ packed[2] = (packed[2] ?? 0) * scale;
754
+ return;
755
+ case "crt":
756
+ packed[2] = (packed[2] ?? 0) * scale;
757
+ return;
758
+ case "bloom":
759
+ packed[3] = (packed[3] ?? 0) * scale;
760
+ return;
761
+ case "god-rays":
762
+ packed[7] = (packed[7] ?? 0) * scale;
763
+ return;
764
+ case "color-grading-lut":
765
+ packed[0] = (packed[0] ?? 0) * scale;
766
+ return;
767
+ case "weather-rain":
768
+ case "weather-sun-shaft":
769
+ case "weather-dark-vignette":
770
+ case "weather-clouds":
771
+ packed[2] = (packed[2] ?? 0) * scale;
772
+ return;
773
+ case "composite":
774
+ packed[2] = (packed[2] ?? 0) * scale;
775
+ packed[8] = (packed[8] ?? 0) * scale;
776
+ packed[9] = (packed[9] ?? 0) * scale;
777
+ packed[10] = (packed[10] ?? 0) * scale;
778
+ packed[15] = (packed[15] ?? 0) * scale;
779
+ return;
780
+ }
972
781
  }
973
-
974
- // src/lut.ts
782
+ //#endregion
783
+ //#region src/controller.ts
784
+ /**
785
+ * PostFxController — a reusable bridge that owns an EffectChain + PostFxRuntime
786
+ * and adds two game-common conveniences:
787
+ *
788
+ * - hit-flash: spike a grayscale-flash effect to 1 and auto-decay it.
789
+ * - motion scale: forward a single scalar to `EffectChain.setGlobalIntensity`
790
+ * so a11y reduced-motion can null the whole stack at once.
791
+ *
792
+ * Games can instantiate multiple controllers and swap implementations freely.
793
+ */
794
+ const DEFAULT_FLASH_DECAY = 4.2;
795
+ function createPostFxController(options) {
796
+ const chain = new EffectChain();
797
+ for (let i = 0; i < options.effects.length; i++) {
798
+ const e = options.effects[i];
799
+ if (e) chain.add(e);
800
+ }
801
+ const runtime = createPostFxRuntime(options.skia ?? null);
802
+ const flashCfg = options.flash;
803
+ const flashDecay = flashCfg?.decay ?? DEFAULT_FLASH_DECAY;
804
+ let flashIntensity = 0;
805
+ const timedRemaining = /* @__PURE__ */ new Map();
806
+ const timedRestore = /* @__PURE__ */ new Map();
807
+ return {
808
+ chain,
809
+ runtime,
810
+ triggerHitFlash() {
811
+ if (!flashCfg) return;
812
+ flashIntensity = 1;
813
+ chain.setUniforms(flashCfg.id, {
814
+ kind: "grayscale-flash",
815
+ intensity: 1
816
+ });
817
+ },
818
+ triggerTimed(effectId, decayMs) {
819
+ const effect = chain.get(effectId);
820
+ if (!effect) return;
821
+ timedRestore.set(effectId, { ...effect.uniforms });
822
+ chain.setEnabled(effectId, true);
823
+ timedRemaining.set(effectId, decayMs);
824
+ },
825
+ update(dt) {
826
+ if (flashCfg && flashIntensity > 0) {
827
+ flashIntensity = Math.max(0, flashIntensity - dt * flashDecay);
828
+ chain.setUniforms(flashCfg.id, {
829
+ kind: "grayscale-flash",
830
+ intensity: flashIntensity
831
+ });
832
+ }
833
+ if (timedRemaining.size > 0) {
834
+ const dtMs = dt * 1e3;
835
+ const expired = [];
836
+ for (const [id, rem] of timedRemaining) {
837
+ const next = rem - dtMs;
838
+ if (next <= 0) expired.push(id);
839
+ else timedRemaining.set(id, next);
840
+ }
841
+ if (expired.length > 0) for (const id of expired) {
842
+ const snapshot = timedRestore.get(id);
843
+ if (snapshot !== void 0) chain.setUniforms(id, snapshot);
844
+ chain.setEnabled(id, false);
845
+ timedRemaining.delete(id);
846
+ timedRestore.delete(id);
847
+ }
848
+ }
849
+ },
850
+ buildPass(width, height) {
851
+ return runtime.buildPass(chain, width, height);
852
+ },
853
+ setMotionScale(k) {
854
+ chain.setGlobalIntensity(k);
855
+ }
856
+ };
857
+ }
858
+ //#endregion
859
+ //#region src/effect.ts
860
+ function createVignette(id = "vignette", uniforms = {}) {
861
+ return {
862
+ id,
863
+ enabled: true,
864
+ uniforms: {
865
+ kind: "vignette",
866
+ intensity: uniforms.intensity ?? .4,
867
+ radius: uniforms.radius ?? .8,
868
+ color: uniforms.color ?? [
869
+ 0,
870
+ 0,
871
+ 0
872
+ ]
873
+ }
874
+ };
875
+ }
876
+ function createGrayscaleFlash(id = "grayscale-flash", uniforms = {}) {
877
+ return {
878
+ id,
879
+ enabled: true,
880
+ uniforms: {
881
+ kind: "grayscale-flash",
882
+ intensity: uniforms.intensity ?? 0
883
+ }
884
+ };
885
+ }
886
+ function createChromaticAberration(id = "chromatic-aberration", uniforms = {}) {
887
+ return {
888
+ id,
889
+ enabled: true,
890
+ uniforms: {
891
+ kind: "chromatic-aberration",
892
+ amount: uniforms.amount ?? .005
893
+ }
894
+ };
895
+ }
896
+ function createCrt(id = "crt", uniforms = {}) {
897
+ return {
898
+ id,
899
+ enabled: true,
900
+ uniforms: {
901
+ kind: "crt",
902
+ scanlineIntensity: uniforms.scanlineIntensity ?? .25,
903
+ curvature: uniforms.curvature ?? .12
904
+ }
905
+ };
906
+ }
907
+ function createBloom(id = "bloom", uniforms = {}) {
908
+ return {
909
+ id,
910
+ enabled: true,
911
+ uniforms: {
912
+ kind: "bloom",
913
+ threshold: uniforms.threshold ?? .7,
914
+ intensity: uniforms.intensity ?? .6
915
+ }
916
+ };
917
+ }
918
+ function createGodRays(id = "god-rays", uniforms = {}) {
919
+ return {
920
+ id,
921
+ enabled: true,
922
+ uniforms: {
923
+ kind: "god-rays",
924
+ lightX: uniforms.lightX ?? .5,
925
+ lightY: uniforms.lightY ?? .1,
926
+ color: uniforms.color ?? [
927
+ 1,
928
+ .95,
929
+ .8
930
+ ],
931
+ intensity: uniforms.intensity ?? .6,
932
+ decay: uniforms.decay ?? .96,
933
+ density: uniforms.density ?? .5,
934
+ weight: uniforms.weight ?? .4,
935
+ samples: uniforms.samples ?? 48
936
+ }
937
+ };
938
+ }
939
+ function createColorGradingLut(id, uniforms) {
940
+ return {
941
+ id,
942
+ enabled: true,
943
+ uniforms: {
944
+ kind: "color-grading-lut",
945
+ lut: uniforms.lut,
946
+ intensity: uniforms.intensity ?? 1
947
+ }
948
+ };
949
+ }
950
+ function createWeatherRain(id = "weather-rain", uniforms = {}) {
951
+ return {
952
+ id,
953
+ enabled: true,
954
+ uniforms: {
955
+ kind: "weather-rain",
956
+ intensity: uniforms.intensity ?? .5,
957
+ direction: uniforms.direction ?? 0,
958
+ scrollOffsetY: uniforms.scrollOffsetY ?? 0,
959
+ color: uniforms.color ?? [
960
+ 1,
961
+ 1,
962
+ 1
963
+ ],
964
+ density: uniforms.density ?? .5,
965
+ streakLen: uniforms.streakLen ?? .15
966
+ }
967
+ };
968
+ }
969
+ function createWeatherSunShaft(id = "weather-sun-shaft", uniforms = {}) {
970
+ return {
971
+ id,
972
+ enabled: true,
973
+ uniforms: {
974
+ kind: "weather-sun-shaft",
975
+ intensity: uniforms.intensity ?? .5,
976
+ direction: uniforms.direction ?? 0
977
+ }
978
+ };
979
+ }
980
+ function createWeatherDarkVignette(id = "weather-dark-vignette", uniforms = {}) {
981
+ return {
982
+ id,
983
+ enabled: true,
984
+ uniforms: {
985
+ kind: "weather-dark-vignette",
986
+ intensity: uniforms.intensity ?? .5
987
+ }
988
+ };
989
+ }
990
+ function createWeatherClouds(id = "weather-clouds", uniforms = {}) {
991
+ return {
992
+ id,
993
+ enabled: true,
994
+ uniforms: {
995
+ kind: "weather-clouds",
996
+ intensity: uniforms.intensity ?? .5,
997
+ direction: uniforms.direction ?? 0,
998
+ scrollOffsetX: uniforms.scrollOffsetX ?? 0
999
+ }
1000
+ };
1001
+ }
1002
+ function createPostFxComposite(id = "composite", uniforms = {}) {
1003
+ return {
1004
+ id,
1005
+ enabled: true,
1006
+ uniforms: {
1007
+ kind: "composite",
1008
+ vignetteIntensity: uniforms.vignetteIntensity ?? 0,
1009
+ vignetteRadius: uniforms.vignetteRadius ?? .8,
1010
+ vignetteColor: uniforms.vignetteColor ?? [
1011
+ 0,
1012
+ 0,
1013
+ 0
1014
+ ],
1015
+ bloomThreshold: uniforms.bloomThreshold ?? .7,
1016
+ bloomIntensity: uniforms.bloomIntensity ?? 0,
1017
+ flashIntensity: uniforms.flashIntensity ?? 0,
1018
+ chromaAmount: uniforms.chromaAmount ?? 0,
1019
+ crtScanlineIntensity: uniforms.crtScanlineIntensity ?? .25,
1020
+ crtCurvature: uniforms.crtCurvature ?? .12,
1021
+ crtEnabled: uniforms.crtEnabled ?? false,
1022
+ weatherKind: uniforms.weatherKind ?? 0,
1023
+ weatherIntensity: uniforms.weatherIntensity ?? 0,
1024
+ weatherDirection: uniforms.weatherDirection ?? 0,
1025
+ weatherScrollX: uniforms.weatherScrollX ?? 0,
1026
+ weatherScrollY: uniforms.weatherScrollY ?? 0,
1027
+ weatherColor: uniforms.weatherColor ?? [
1028
+ 1,
1029
+ 1,
1030
+ 1
1031
+ ],
1032
+ weatherDensity: uniforms.weatherDensity ?? .5,
1033
+ weatherStreakLen: uniforms.weatherStreakLen ?? .15
1034
+ }
1035
+ };
1036
+ }
1037
+ //#endregion
1038
+ //#region src/lut.ts
975
1039
  function createNeutralLutBytes() {
976
- const buf = new Uint8Array(4096 * 64 * 4);
977
- for (let b = 0; b < 64; b++) {
978
- for (let g = 0; g < 64; g++) {
979
- for (let r = 0; r < 64; r++) {
980
- const x = b * 64 + r;
981
- const y = g;
982
- const off = (y * 4096 + x) * 4;
983
- buf[off] = Math.round(r / 63 * 255);
984
- buf[off + 1] = Math.round(g / 63 * 255);
985
- buf[off + 2] = Math.round(b / 63 * 255);
986
- buf[off + 3] = 255;
987
- }
988
- }
989
- }
990
- return buf;
1040
+ const buf = new Uint8Array(4096 * 64 * 4);
1041
+ for (let b = 0; b < 64; b++) for (let g = 0; g < 64; g++) for (let r = 0; r < 64; r++) {
1042
+ const x = b * 64 + r;
1043
+ const off = (g * 4096 + x) * 4;
1044
+ buf[off] = Math.round(r / 63 * 255);
1045
+ buf[off + 1] = Math.round(g / 63 * 255);
1046
+ buf[off + 2] = Math.round(b / 63 * 255);
1047
+ buf[off + 3] = 255;
1048
+ }
1049
+ return buf;
991
1050
  }
1051
+ //#endregion
1052
+ exports.BLOOM_SKSL = BLOOM_SKSL;
1053
+ exports.CHROMATIC_ABERRATION_SKSL = CHROMATIC_ABERRATION_SKSL;
1054
+ exports.COLOR_GRADING_LUT_SKSL = COLOR_GRADING_LUT_SKSL;
1055
+ exports.COMPOSITE_SKSL = COMPOSITE_SKSL;
1056
+ exports.CRT_SKSL = CRT_SKSL;
1057
+ exports.EffectChain = EffectChain;
1058
+ exports.GOD_RAYS_SKSL = GOD_RAYS_SKSL;
1059
+ exports.GRAYSCALE_FLASH_SKSL = GRAYSCALE_FLASH_SKSL;
1060
+ exports.VIGNETTE_SKSL = VIGNETTE_SKSL;
1061
+ exports.WEATHER_CLOUDS_SKSL = WEATHER_CLOUDS_SKSL;
1062
+ exports.WEATHER_DARK_VIGNETTE_SKSL = WEATHER_DARK_VIGNETTE_SKSL;
1063
+ exports.WEATHER_RAIN_SKSL = WEATHER_RAIN_SKSL;
1064
+ exports.WEATHER_SUN_SHAFT_SKSL = WEATHER_SUN_SHAFT_SKSL;
1065
+ exports.createBloom = createBloom;
1066
+ exports.createChromaticAberration = createChromaticAberration;
1067
+ exports.createColorGradingLut = createColorGradingLut;
1068
+ exports.createCrt = createCrt;
1069
+ exports.createGodRays = createGodRays;
1070
+ exports.createGrayscaleFlash = createGrayscaleFlash;
1071
+ exports.createNeutralLutBytes = createNeutralLutBytes;
1072
+ exports.createPostFxComposite = createPostFxComposite;
1073
+ exports.createPostFxController = createPostFxController;
1074
+ exports.createPostFxRuntime = createPostFxRuntime;
1075
+ exports.createVignette = createVignette;
1076
+ exports.createWeatherClouds = createWeatherClouds;
1077
+ exports.createWeatherDarkVignette = createWeatherDarkVignette;
1078
+ exports.createWeatherRain = createWeatherRain;
1079
+ exports.createWeatherSunShaft = createWeatherSunShaft;
992
1080
 
993
- // src/controller.ts
994
- var DEFAULT_FLASH_DECAY = 4.2;
995
- function createPostFxController(options) {
996
- const chain = new EffectChain();
997
- for (let i = 0; i < options.effects.length; i++) {
998
- const e = options.effects[i];
999
- if (e) chain.add(e);
1000
- }
1001
- const runtime = createPostFxRuntime(options.skia ?? null);
1002
- const flashCfg = options.flash;
1003
- const flashDecay = flashCfg?.decay ?? DEFAULT_FLASH_DECAY;
1004
- let flashIntensity = 0;
1005
- const timedRemaining = /* @__PURE__ */ new Map();
1006
- const timedRestore = /* @__PURE__ */ new Map();
1007
- return {
1008
- chain,
1009
- runtime,
1010
- triggerHitFlash() {
1011
- if (!flashCfg) return;
1012
- flashIntensity = 1;
1013
- chain.setUniforms(flashCfg.id, { kind: "grayscale-flash", intensity: 1 });
1014
- },
1015
- triggerTimed(effectId, decayMs) {
1016
- const effect = chain.get(effectId);
1017
- if (!effect) return;
1018
- timedRestore.set(effectId, { ...effect.uniforms });
1019
- chain.setEnabled(effectId, true);
1020
- timedRemaining.set(effectId, decayMs);
1021
- },
1022
- update(dt) {
1023
- if (flashCfg && flashIntensity > 0) {
1024
- flashIntensity = Math.max(0, flashIntensity - dt * flashDecay);
1025
- chain.setUniforms(flashCfg.id, {
1026
- kind: "grayscale-flash",
1027
- intensity: flashIntensity
1028
- });
1029
- }
1030
- if (timedRemaining.size > 0) {
1031
- const dtMs = dt * 1e3;
1032
- const expired = [];
1033
- for (const [id, rem] of timedRemaining) {
1034
- const next = rem - dtMs;
1035
- if (next <= 0) {
1036
- expired.push(id);
1037
- } else {
1038
- timedRemaining.set(id, next);
1039
- }
1040
- }
1041
- if (expired.length > 0) {
1042
- for (const id of expired) {
1043
- const snapshot = timedRestore.get(id);
1044
- if (snapshot !== void 0) chain.setUniforms(id, snapshot);
1045
- chain.setEnabled(id, false);
1046
- timedRemaining.delete(id);
1047
- timedRestore.delete(id);
1048
- }
1049
- }
1050
- }
1051
- },
1052
- buildPass(width, height) {
1053
- return runtime.buildPass(chain, width, height);
1054
- },
1055
- setMotionScale(k) {
1056
- chain.setGlobalIntensity(k);
1057
- }
1058
- };
1059
- }
1060
- // Annotate the CommonJS export names for ESM import in node:
1061
- 0 && (module.exports = {
1062
- BLOOM_SKSL,
1063
- CHROMATIC_ABERRATION_SKSL,
1064
- COLOR_GRADING_LUT_SKSL,
1065
- COMPOSITE_SKSL,
1066
- CRT_SKSL,
1067
- EffectChain,
1068
- GOD_RAYS_SKSL,
1069
- GRAYSCALE_FLASH_SKSL,
1070
- VIGNETTE_SKSL,
1071
- WEATHER_CLOUDS_SKSL,
1072
- WEATHER_DARK_VIGNETTE_SKSL,
1073
- WEATHER_RAIN_SKSL,
1074
- WEATHER_SUN_SHAFT_SKSL,
1075
- createBloom,
1076
- createChromaticAberration,
1077
- createColorGradingLut,
1078
- createCrt,
1079
- createGodRays,
1080
- createGrayscaleFlash,
1081
- createNeutralLutBytes,
1082
- createPostFxComposite,
1083
- createPostFxController,
1084
- createPostFxRuntime,
1085
- createVignette,
1086
- createWeatherClouds,
1087
- createWeatherDarkVignette,
1088
- createWeatherRain,
1089
- createWeatherSunShaft
1090
- });
1091
1081
  //# sourceMappingURL=index.js.map