@flare-engine/postfx 0.2.1 → 0.3.1

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