@frapx/core 0.3.0 → 0.5.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
@@ -19,13 +19,150 @@ var UnsupportedError = class extends ShaderError {
19
19
  var TargetNotFoundError = class extends ShaderError {
20
20
  };
21
21
  var ShaderCompileError = class extends ShaderError {
22
+ constructor(message, details = {}) {
23
+ super(message);
24
+ this.stage = details.stage ?? null;
25
+ this.lines = details.lines ?? [];
26
+ }
22
27
  };
23
28
  var TextureLoadError = class extends ShaderError {
24
29
  };
25
30
  var DestroyedError = class extends ShaderError {
26
31
  };
27
32
 
33
+ // src/internal/shaderLog.ts
34
+ var ANGLE_STYLE = /^\s*ERROR:\s*\d+:(\d+):\s*(.*)$/;
35
+ var NVIDIA_STYLE = /^\s*\d+\((\d+)\)\s*:\s*error\s*\w*\s*:\s*(.*)$/i;
36
+ var NOISE = /compilation terminated|^\s*''\s*:\s*$/i;
37
+ var CONTEXT_LINES = 2;
38
+ var parseShaderLog = (log, source) => {
39
+ const sourceLines = source.split(/\r?\n/);
40
+ const lineAt = (line) => sourceLines[line - 1] ?? "";
41
+ const results = [];
42
+ const seen = /* @__PURE__ */ new Set();
43
+ for (const entry of log.split(/\r?\n/)) {
44
+ const match = ANGLE_STYLE.exec(entry) ?? NVIDIA_STYLE.exec(entry);
45
+ if (!match) continue;
46
+ const line = Number(match[1]);
47
+ const message = match[2].trim();
48
+ if (!message || NOISE.test(message)) continue;
49
+ const key = `${line}:${message}`;
50
+ if (seen.has(key)) continue;
51
+ seen.add(key);
52
+ const context = [];
53
+ const first = Math.max(1, line - CONTEXT_LINES);
54
+ const last = Math.min(sourceLines.length, line + CONTEXT_LINES);
55
+ for (let current = first; current <= last; current++) {
56
+ context.push({ line: current, source: lineAt(current) });
57
+ }
58
+ results.push({ line, message, source: lineAt(line), context });
59
+ }
60
+ return results;
61
+ };
62
+
63
+ // src/internal/webgl.ts
64
+ var defaultVertexShader = `
65
+ attribute vec2 a_position;
66
+ varying vec2 v_uv;
67
+
68
+ void main() {
69
+ v_uv = a_position * 0.5 + 0.5;
70
+ gl_Position = vec4(a_position, 0.0, 1.0);
71
+ }
72
+ `;
73
+ var defaultVertexShader300 = `#version 300 es
74
+ in vec2 a_position;
75
+ out vec2 v_uv;
76
+
77
+ void main() {
78
+ v_uv = a_position * 0.5 + 0.5;
79
+ gl_Position = vec4(a_position, 0.0, 1.0);
80
+ }
81
+ `;
82
+ var isGLSL300 = (src) => /^\s*#version\s+300\s+es\b/.test(src);
83
+ var isWebGL2Context = (gl) => typeof gl.createVertexArray === "function";
84
+ var createProgram = (gl, vertexSource, fragmentSource) => {
85
+ const vertex = compileShader(gl, gl.VERTEX_SHADER, vertexSource);
86
+ let fragment;
87
+ try {
88
+ fragment = compileShader(gl, gl.FRAGMENT_SHADER, fragmentSource);
89
+ } catch (error) {
90
+ gl.deleteShader(vertex);
91
+ throw error;
92
+ }
93
+ const program = gl.createProgram();
94
+ if (!program) {
95
+ throw new ShaderCompileError("Failed to create WebGL program.");
96
+ }
97
+ gl.attachShader(program, vertex);
98
+ gl.attachShader(program, fragment);
99
+ gl.linkProgram(program);
100
+ if (!gl.getProgramParameter(program, gl.LINK_STATUS)) {
101
+ const log = gl.getProgramInfoLog(program) ?? "Unknown program link error.";
102
+ gl.deleteProgram(program);
103
+ gl.deleteShader(vertex);
104
+ gl.deleteShader(fragment);
105
+ throw new ShaderCompileError(log, { stage: "link" });
106
+ }
107
+ gl.deleteShader(vertex);
108
+ gl.deleteShader(fragment);
109
+ return program;
110
+ };
111
+ var createFullscreenBuffer = (gl, program) => {
112
+ const buffer = gl.createBuffer();
113
+ if (!buffer) {
114
+ throw new ShaderCompileError("Failed to create fullscreen buffer.");
115
+ }
116
+ const location = gl.getAttribLocation(program, "a_position");
117
+ gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
118
+ gl.bufferData(
119
+ gl.ARRAY_BUFFER,
120
+ new Float32Array([-1, -1, 1, -1, -1, 1, -1, 1, 1, -1, 1, 1]),
121
+ gl.STATIC_DRAW
122
+ );
123
+ if (location >= 0) {
124
+ gl.enableVertexAttribArray(location);
125
+ gl.vertexAttribPointer(location, 2, gl.FLOAT, false, 0, 0);
126
+ }
127
+ return buffer;
128
+ };
129
+ var compileShader = (gl, type, source) => {
130
+ const shader = gl.createShader(type);
131
+ if (!shader) {
132
+ throw new ShaderCompileError("Failed to create WebGL shader.");
133
+ }
134
+ gl.shaderSource(shader, source);
135
+ gl.compileShader(shader);
136
+ if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
137
+ const log = gl.getShaderInfoLog(shader) ?? "Unknown shader compile error.";
138
+ gl.deleteShader(shader);
139
+ throw new ShaderCompileError(log, {
140
+ stage: type === gl.VERTEX_SHADER ? "vertex" : "fragment",
141
+ lines: parseShaderLog(log, source)
142
+ });
143
+ }
144
+ return shader;
145
+ };
146
+
28
147
  // src/internal/capture.ts
148
+ var waitForGpu = (gl) => {
149
+ if (!isWebGL2Context(gl)) return Promise.resolve();
150
+ const sync = gl.fenceSync(gl.SYNC_GPU_COMMANDS_COMPLETE, 0);
151
+ if (!sync) return Promise.resolve();
152
+ gl.flush();
153
+ return new Promise((resolve) => {
154
+ const poll = () => {
155
+ const status = gl.clientWaitSync(sync, 0, 0);
156
+ if (status === gl.TIMEOUT_EXPIRED) {
157
+ setTimeout(poll, 4);
158
+ return;
159
+ }
160
+ gl.deleteSync(sync);
161
+ resolve();
162
+ };
163
+ poll();
164
+ });
165
+ };
29
166
  var readPixels = (gl, framebuffer, width, height, format) => {
30
167
  const length = width * height * 4;
31
168
  const data = format === "rgba16f" ? new Float32Array(length) : new Uint8Array(length);
@@ -166,6 +303,8 @@ var RuntimeHost = class {
166
303
  this.rafId = 0;
167
304
  this.running = false;
168
305
  this.inRender = false;
306
+ this.captureDrawing = false;
307
+ this.resizePending = false;
169
308
  this.destroyed = false;
170
309
  this.gates = /* @__PURE__ */ new Set();
171
310
  this.reducedMotionActive = false;
@@ -181,6 +320,7 @@ var RuntimeHost = class {
181
320
  this.removeListeners = [];
182
321
  this.currentStatus = "idle";
183
322
  this.contextRestoredShouldRun = false;
323
+ this.statusBeforeLoss = "idle";
184
324
  this.manualDelta = 0;
185
325
  this.lastManualTime = null;
186
326
  this.objectFitRestore = null;
@@ -285,30 +425,35 @@ var RuntimeHost = class {
285
425
  if (this.inRender) {
286
426
  throw new Error("captureFrame() cannot be called while a frame is drawing.");
287
427
  }
288
- if (format === "rgba16f" && this.options.captureSource?.()?.format !== "rgba16f") {
428
+ const sourceFormat = this.options.captureSource?.()?.format;
429
+ if (format === "rgba16f" && sourceFormat !== "rgba16f" && sourceFormat !== "rgba32f") {
289
430
  throw new UnsupportedError(
290
431
  "rgba16f capture needs a float render target, such as feedback with format rgba16f."
291
432
  );
292
433
  }
293
- this.renderInternal(this.elapsed, this.isManual ? this.manualDelta : 0);
294
- const gl = this.context;
295
- if (format === "png") {
296
- return canvasToPng(this.dom.canvas);
434
+ options.signal?.throwIfAborted();
435
+ const delta = this.isManual ? this.manualDelta : 0;
436
+ const drawContext = this.context;
437
+ if (!this.options.onCaptureDraw) {
438
+ this.renderInternal(this.elapsed, delta);
439
+ return this.readCapture(format, drawContext);
297
440
  }
298
- if (format === "rgba8") {
299
- return readPixels(gl, null, this.width, this.height, "rgba8");
441
+ try {
442
+ await this.renderForCapture(this.options.onCaptureDraw, delta, options.signal);
443
+ return this.readCapture(format, drawContext);
444
+ } finally {
445
+ if (this.resizePending) {
446
+ this.resizePending = false;
447
+ this.resize();
448
+ }
300
449
  }
301
- const source = this.options.captureSource();
302
- return readPixels(
303
- gl,
304
- source.framebuffer,
305
- source.width,
306
- source.height,
307
- "rgba16f"
308
- );
309
450
  }
310
451
  resize() {
311
452
  if (this.destroyed || !this.dom) return;
453
+ if (this.captureDrawing) {
454
+ this.resizePending = true;
455
+ return;
456
+ }
312
457
  const prevWidth = this.width;
313
458
  const prevHeight = this.height;
314
459
  const prevViewportWidth = this.viewportWidth;
@@ -401,7 +546,7 @@ var RuntimeHost = class {
401
546
  this.renderInternal(this.elapsed, 0);
402
547
  }
403
548
  renderInternal(time, delta) {
404
- if (!this.dom || !this.context) return;
549
+ if (!this.dom || !this.context || this.inRender) return;
405
550
  const gl = this.context;
406
551
  this.inRender = true;
407
552
  try {
@@ -412,6 +557,47 @@ var RuntimeHost = class {
412
557
  this.inRender = false;
413
558
  }
414
559
  }
560
+ // Holds the render lock across an asynchronous capture draw so no other
561
+ // frame interleaves with it.
562
+ async renderForCapture(draw, delta, signal) {
563
+ if (!this.dom || !this.context) return;
564
+ this.inRender = true;
565
+ this.captureDrawing = true;
566
+ try {
567
+ this.measureSize();
568
+ await draw(this.createFrameState(this.elapsed, delta, this.context), signal);
569
+ this.frame++;
570
+ } finally {
571
+ this.inRender = false;
572
+ this.captureDrawing = false;
573
+ }
574
+ }
575
+ // Reads the frame just drawn, in the task that drew it: the drawing buffer
576
+ // is not preserved. Fails if the context drawn into is gone, including when
577
+ // it was lost and restored while an asynchronous draw was pending.
578
+ readCapture(format, drawContext) {
579
+ if (this.destroyed) {
580
+ throw new DestroyedError("Runtime host was destroyed.");
581
+ }
582
+ const gl = this.context;
583
+ if (!gl || gl !== drawContext || !this.dom) {
584
+ throw new UnsupportedError("The WebGL context was lost while capturing.");
585
+ }
586
+ if (format === "png") {
587
+ return canvasToPng(this.dom.canvas);
588
+ }
589
+ if (format === "rgba8") {
590
+ return readPixels(gl, null, this.width, this.height, "rgba8");
591
+ }
592
+ const source = this.options.captureSource();
593
+ return readPixels(
594
+ gl,
595
+ source.framebuffer,
596
+ source.width,
597
+ source.height,
598
+ "rgba16f"
599
+ );
600
+ }
415
601
  createFrameState(time, delta, gl = this.context) {
416
602
  return {
417
603
  gl,
@@ -647,6 +833,7 @@ var RuntimeHost = class {
647
833
  const onLost = (event) => {
648
834
  event.preventDefault();
649
835
  this.contextRestoredShouldRun = this.running;
836
+ this.statusBeforeLoss = this.currentStatus;
650
837
  this.cancelFrame();
651
838
  this.context = null;
652
839
  this.currentStatus = "context-lost";
@@ -661,6 +848,7 @@ var RuntimeHost = class {
661
848
  const afterRestore = async () => {
662
849
  try {
663
850
  await this.options.onContextRestored?.(gl, this.width, this.height);
851
+ if (this.currentStatus === "context-lost") this.currentStatus = this.statusBeforeLoss;
664
852
  if (this.contextRestoredShouldRun) this.start();
665
853
  else if (!this.isManual) this.render();
666
854
  } catch (error) {
@@ -720,6 +908,211 @@ var assertWithinLimits = (gl, resolution) => {
720
908
  var toUniformName = (name) => name.startsWith("u_") ? name : `u_${name}`;
721
909
  var fromUniformName = (name) => name.startsWith("u_") ? name.slice(2) : name;
722
910
 
911
+ // src/internal/uniforms.ts
912
+ var normalizeUniform = (input) => {
913
+ if (typeof input === "number") {
914
+ return { type: "float", value: [input] };
915
+ }
916
+ if (input instanceof Float32Array) {
917
+ if (input.length === 9) return { type: "mat3", value: input };
918
+ if (input.length === 16) return { type: "mat4", value: input };
919
+ if (input.length === 2) return { type: "vec2", value: input };
920
+ if (input.length === 3) return { type: "vec3", value: input };
921
+ if (input.length === 4) return { type: "vec4", value: input };
922
+ }
923
+ if (isExplicitUniform(input)) {
924
+ return { type: input.type, value: input.value };
925
+ }
926
+ if (Array.isArray(input)) {
927
+ if (input.length === 2) return { type: "vec2", value: input };
928
+ if (input.length === 3) return { type: "vec3", value: input };
929
+ if (input.length === 4) return { type: "vec4", value: input };
930
+ }
931
+ return { type: "float", value: [0] };
932
+ };
933
+ var applyUniform = (gl, location, uniform) => {
934
+ const value = uniform.value;
935
+ switch (uniform.type) {
936
+ case "float":
937
+ gl.uniform1f(location, value[0] ?? 0);
938
+ break;
939
+ case "vec2":
940
+ gl.uniform2f(location, value[0] ?? 0, value[1] ?? 0);
941
+ break;
942
+ case "vec3":
943
+ gl.uniform3f(location, value[0] ?? 0, value[1] ?? 0, value[2] ?? 0);
944
+ break;
945
+ case "vec4":
946
+ gl.uniform4f(
947
+ location,
948
+ value[0] ?? 0,
949
+ value[1] ?? 0,
950
+ value[2] ?? 0,
951
+ value[3] ?? 0
952
+ );
953
+ break;
954
+ case "mat3":
955
+ gl.uniformMatrix3fv(location, false, value);
956
+ break;
957
+ case "mat4":
958
+ gl.uniformMatrix4fv(location, false, value);
959
+ break;
960
+ }
961
+ };
962
+ var applyProgramUniform = (gl, program, location, uniform) => {
963
+ gl.useProgram(program);
964
+ applyUniform(gl, location, uniform);
965
+ };
966
+ var applyUniforms = (gl, locations, cache, onMissing) => {
967
+ for (const [name, uniform] of cache) {
968
+ const location = locations.get(name);
969
+ if (!location) {
970
+ onMissing?.(name);
971
+ continue;
972
+ }
973
+ applyUniform(gl, location, uniform);
974
+ }
975
+ };
976
+ var collectUniformLocations = (gl, program) => {
977
+ const locations = /* @__PURE__ */ new Map();
978
+ const count = gl.getProgramParameter(program, gl.ACTIVE_UNIFORMS);
979
+ for (let index = 0; index < count; index += 1) {
980
+ const active = gl.getActiveUniform(program, index);
981
+ if (!active) continue;
982
+ const name = active.name.replace(/\[0\]$/, "");
983
+ const location = gl.getUniformLocation(program, name);
984
+ if (location) {
985
+ locations.set(name, location);
986
+ }
987
+ }
988
+ return locations;
989
+ };
990
+ var createUniformCache = (uniforms) => {
991
+ const cache = /* @__PURE__ */ new Map();
992
+ for (const [name, value] of Object.entries(uniforms ?? {})) {
993
+ cache.set(toUniformName(name), normalizeUniform(value));
994
+ }
995
+ return cache;
996
+ };
997
+ var isExplicitUniform = (input) => {
998
+ if (!input || typeof input !== "object" || Array.isArray(input)) return false;
999
+ return "type" in input && "value" in input;
1000
+ };
1001
+
1002
+ // src/internal/bindings.ts
1003
+ var createUniformBindings = () => {
1004
+ const sources = [];
1005
+ const evaluate = (time) => {
1006
+ const merged = /* @__PURE__ */ new Map();
1007
+ for (const source of sources) {
1008
+ for (const [name, value] of Object.entries(source.get(time))) {
1009
+ merged.set(toUniformName(name), normalizeUniform(value));
1010
+ }
1011
+ }
1012
+ return merged;
1013
+ };
1014
+ return {
1015
+ add(source) {
1016
+ sources.push(source);
1017
+ let removed = false;
1018
+ return () => {
1019
+ if (removed) return;
1020
+ removed = true;
1021
+ const index = sources.indexOf(source);
1022
+ if (index >= 0) sources.splice(index, 1);
1023
+ };
1024
+ },
1025
+ get size() {
1026
+ return sources.length;
1027
+ },
1028
+ evaluate,
1029
+ signature(time) {
1030
+ if (sources.length === 0) return "";
1031
+ let text = "";
1032
+ for (const [name, uniform] of evaluate(time)) {
1033
+ text += `${name}:${uniform.type}:${Array.from(uniform.value).join(",")};`;
1034
+ }
1035
+ return text;
1036
+ }
1037
+ };
1038
+ };
1039
+ var EASES = {
1040
+ linear: (t) => t,
1041
+ smooth: (t) => t * t * (3 - 2 * t),
1042
+ step: () => 0
1043
+ };
1044
+ var toEaseFunction = (ease) => {
1045
+ if (typeof ease === "function") return ease;
1046
+ const fn = EASES[ease];
1047
+ if (!fn) throw new RangeError(`Unknown ease: ${String(ease)}`);
1048
+ return fn;
1049
+ };
1050
+ var componentsOf = (value) => typeof value === "number" ? [value] : value;
1051
+ var isKeyList = (track) => Array.isArray(track);
1052
+ var compileTrack = (name, track, fallback) => {
1053
+ const list = isKeyList(track);
1054
+ const keys = list ? track : track.keys;
1055
+ const ease = toEaseFunction((list ? void 0 : track.ease) ?? fallback);
1056
+ if (keys.length === 0) throw new TypeError(`Keyframe track ${name} has no keys.`);
1057
+ const sorted = [...keys].sort((a, b) => a[0] - b[0]);
1058
+ const scalar = typeof sorted[0][1] === "number";
1059
+ const size = componentsOf(sorted[0][1]).length;
1060
+ for (const [time, value] of sorted) {
1061
+ if (!Number.isFinite(time)) throw new TypeError(`Keyframe track ${name} has a non-finite time.`);
1062
+ const components = componentsOf(value);
1063
+ if (typeof value === "number" !== scalar || components.length !== size) {
1064
+ throw new TypeError(`Keyframe track ${name} mixes values of different sizes.`);
1065
+ }
1066
+ if (!components.every(Number.isFinite)) {
1067
+ throw new TypeError(`Keyframe track ${name} has a non-finite value.`);
1068
+ }
1069
+ }
1070
+ return {
1071
+ times: sorted.map(([time]) => time),
1072
+ values: sorted.map(([, value]) => componentsOf(value)),
1073
+ scalar,
1074
+ ease
1075
+ };
1076
+ };
1077
+ var sampleTrack = (track, time) => {
1078
+ const { times, values } = track;
1079
+ const last = times.length - 1;
1080
+ let components;
1081
+ if (time <= times[0]) {
1082
+ components = values[0];
1083
+ } else if (time >= times[last]) {
1084
+ components = values[last];
1085
+ } else {
1086
+ let low = 0;
1087
+ let high = last;
1088
+ while (high - low > 1) {
1089
+ const middle = low + high >> 1;
1090
+ if (times[middle] <= time) low = middle;
1091
+ else high = middle;
1092
+ }
1093
+ const span = times[high] - times[low];
1094
+ const t = span > 0 ? track.ease((time - times[low]) / span) : 0;
1095
+ const from = values[low];
1096
+ const to = values[high];
1097
+ components = from.map((value, index) => value + (to[index] - value) * t);
1098
+ }
1099
+ return track.scalar ? components[0] : Array.from(components);
1100
+ };
1101
+ var keyframes = (tracks, options = {}) => {
1102
+ const fallback = options.ease ?? "linear";
1103
+ toEaseFunction(fallback);
1104
+ const compiled = Object.entries(tracks).map(
1105
+ ([name, track]) => [name, compileTrack(name, track, fallback)]
1106
+ );
1107
+ return {
1108
+ get(time) {
1109
+ const values = {};
1110
+ for (const [name, track] of compiled) values[name] = sampleTrack(track, time);
1111
+ return values;
1112
+ }
1113
+ };
1114
+ };
1115
+
723
1116
  // src/internal/textures.ts
724
1117
  var loadTexture = async (gl, name, input, unit) => {
725
1118
  const options = normalizeTextureInput(input);
@@ -777,79 +1170,118 @@ var isTextureSource = (input) => typeof HTMLImageElement !== "undefined" && (inp
777
1170
  var getSourceWidth = (source) => "naturalWidth" in source ? source.naturalWidth : source.width;
778
1171
  var getSourceHeight = (source) => "naturalHeight" in source ? source.naturalHeight : source.height;
779
1172
 
780
- // src/internal/webgl.ts
781
- var defaultVertexShader = `
782
- attribute vec2 a_position;
783
- varying vec2 v_uv;
784
-
785
- void main() {
786
- v_uv = a_position * 0.5 + 0.5;
787
- gl_Position = vec4(a_position, 0.0, 1.0);
788
- }
789
- `;
790
- var defaultVertexShader300 = `#version 300 es
791
- in vec2 a_position;
792
- out vec2 v_uv;
793
-
794
- void main() {
795
- v_uv = a_position * 0.5 + 0.5;
796
- gl_Position = vec4(a_position, 0.0, 1.0);
797
- }
798
- `;
799
- var isGLSL300 = (src) => /^\s*#version\s+300\s+es\b/.test(src);
800
- var isWebGL2Context = (gl) => typeof gl.createVertexArray === "function";
801
- var createProgram = (gl, vertexSource, fragmentSource) => {
802
- const vertex = compileShader(gl, gl.VERTEX_SHADER, vertexSource);
803
- const fragment = compileShader(gl, gl.FRAGMENT_SHADER, fragmentSource);
804
- const program = gl.createProgram();
805
- if (!program) {
806
- throw new ShaderCompileError("Failed to create WebGL program.");
807
- }
808
- gl.attachShader(program, vertex);
809
- gl.attachShader(program, fragment);
810
- gl.linkProgram(program);
811
- if (!gl.getProgramParameter(program, gl.LINK_STATUS)) {
812
- const log = gl.getProgramInfoLog(program) ?? "Unknown program link error.";
813
- gl.deleteProgram(program);
814
- gl.deleteShader(vertex);
815
- gl.deleteShader(fragment);
816
- throw new ShaderCompileError(log);
817
- }
818
- gl.deleteShader(vertex);
819
- gl.deleteShader(fragment);
820
- return program;
821
- };
822
- var createFullscreenBuffer = (gl, program) => {
823
- const buffer = gl.createBuffer();
824
- if (!buffer) {
825
- throw new ShaderCompileError("Failed to create fullscreen buffer.");
826
- }
827
- const location = gl.getAttribLocation(program, "a_position");
828
- gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
829
- gl.bufferData(
830
- gl.ARRAY_BUFFER,
831
- new Float32Array([-1, -1, 1, -1, -1, 1, -1, 1, 1, -1, 1, 1]),
832
- gl.STATIC_DRAW
833
- );
834
- if (location >= 0) {
835
- gl.enableVertexAttribArray(location);
836
- gl.vertexAttribPointer(location, 2, gl.FLOAT, false, 0, 0);
837
- }
838
- return buffer;
1173
+ // src/internal/textureSet.ts
1174
+ var createTextureSet = (inputs = {}) => {
1175
+ const textureInputs = new Map(Object.entries(inputs));
1176
+ let textures = /* @__PURE__ */ new Map();
1177
+ const units = /* @__PURE__ */ new Map();
1178
+ const versions = /* @__PURE__ */ new Map();
1179
+ const reserveUnit = (gl, name) => {
1180
+ const existing = units.get(name);
1181
+ if (existing !== void 0) return existing;
1182
+ const maxUnits = gl.getParameter(gl.MAX_TEXTURE_IMAGE_UNITS);
1183
+ const used = new Set(units.values());
1184
+ for (let unit = 0; unit < maxUnits; unit += 1) {
1185
+ if (!used.has(unit)) {
1186
+ units.set(name, unit);
1187
+ return unit;
1188
+ }
1189
+ }
1190
+ throw new TextureLoadError(
1191
+ `Texture unit limit exceeded while loading texture: ${name}`
1192
+ );
1193
+ };
1194
+ return {
1195
+ get textures() {
1196
+ return textures;
1197
+ },
1198
+ hasInput(name) {
1199
+ return textureInputs.has(name);
1200
+ },
1201
+ reserveUnit,
1202
+ async loadAll(gl, isCancelled) {
1203
+ const loaded = /* @__PURE__ */ new Map();
1204
+ for (const [name, input] of textureInputs) {
1205
+ if (isCancelled()) break;
1206
+ const unit = reserveUnit(gl, name);
1207
+ loaded.set(name, await loadTexture(gl, name, input, unit));
1208
+ }
1209
+ for (const texture of textures.values()) {
1210
+ gl.deleteTexture(texture.texture);
1211
+ }
1212
+ textures = loaded;
1213
+ },
1214
+ async set(gl, name, input, isCancelled) {
1215
+ const version = (versions.get(name) ?? 0) + 1;
1216
+ versions.set(name, version);
1217
+ try {
1218
+ const unit = reserveUnit(gl, name);
1219
+ const loaded = await loadTexture(gl, name, input, unit);
1220
+ if (isCancelled() || versions.get(name) !== version) {
1221
+ gl.deleteTexture(loaded.texture);
1222
+ return null;
1223
+ }
1224
+ const previous = textures.get(name);
1225
+ textureInputs.set(name, input);
1226
+ textures.set(name, loaded);
1227
+ if (previous) gl.deleteTexture(previous.texture);
1228
+ return loaded;
1229
+ } catch (error) {
1230
+ if (!textures.has(name) && !textureInputs.has(name)) {
1231
+ units.delete(name);
1232
+ }
1233
+ throw error;
1234
+ }
1235
+ },
1236
+ bind(gl, locations, onMissing) {
1237
+ for (const texture of textures.values()) {
1238
+ const unit = units.get(texture.name);
1239
+ if (unit === void 0) continue;
1240
+ const location = locations.get(texture.uniformName);
1241
+ if (!location) {
1242
+ onMissing?.(texture.uniformName);
1243
+ continue;
1244
+ }
1245
+ gl.activeTexture(gl.TEXTURE0 + unit);
1246
+ gl.bindTexture(gl.TEXTURE_2D, texture.texture);
1247
+ gl.uniform1i(location, unit);
1248
+ }
1249
+ },
1250
+ dispose(gl) {
1251
+ for (const texture of textures.values()) {
1252
+ gl.deleteTexture(texture.texture);
1253
+ }
1254
+ }
1255
+ };
839
1256
  };
840
- var compileShader = (gl, type, source) => {
841
- const shader = gl.createShader(type);
842
- if (!shader) {
843
- throw new ShaderCompileError("Failed to create WebGL shader.");
844
- }
845
- gl.shaderSource(shader, source);
846
- gl.compileShader(shader);
847
- if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
848
- const log = gl.getShaderInfoLog(shader) ?? "Unknown shader compile error.";
849
- gl.deleteShader(shader);
850
- throw new ShaderCompileError(log);
851
- }
852
- return shader;
1257
+
1258
+ // src/internal/fullscreenProgram.ts
1259
+ var createFullscreenProgram = (gl, fragment, vertex) => {
1260
+ const defaultVertex = isGLSL300(fragment) ? defaultVertexShader300 : defaultVertexShader;
1261
+ const program = createProgram(gl, vertex ?? defaultVertex, fragment);
1262
+ gl.useProgram(program);
1263
+ const buffer = createFullscreenBuffer(gl, program);
1264
+ const attribLocation = gl.getAttribLocation(program, "a_position");
1265
+ const locations = collectUniformLocations(gl, program);
1266
+ return {
1267
+ program,
1268
+ locations,
1269
+ use() {
1270
+ gl.useProgram(program);
1271
+ gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
1272
+ if (attribLocation >= 0) {
1273
+ gl.enableVertexAttribArray(attribLocation);
1274
+ gl.vertexAttribPointer(attribLocation, 2, gl.FLOAT, false, 0, 0);
1275
+ }
1276
+ },
1277
+ draw() {
1278
+ gl.drawArrays(gl.TRIANGLES, 0, 6);
1279
+ },
1280
+ dispose() {
1281
+ gl.deleteBuffer(buffer);
1282
+ gl.deleteProgram(program);
1283
+ }
1284
+ };
853
1285
  };
854
1286
 
855
1287
  // src/internal/blit.ts
@@ -1007,16 +1439,17 @@ var Target = class {
1007
1439
  throw new ShaderCompileError("Failed to create render target framebuffer.");
1008
1440
  }
1009
1441
  gl.bindTexture(gl.TEXTURE_2D, texture);
1010
- if (this.format === "rgba16f" && isWebGL2Context(gl)) {
1442
+ if (this.format !== "rgba8" && isWebGL2Context(gl)) {
1443
+ const half = this.format === "rgba16f";
1011
1444
  gl.texImage2D(
1012
1445
  gl.TEXTURE_2D,
1013
1446
  0,
1014
- gl.RGBA16F,
1447
+ half ? gl.RGBA16F : gl.RGBA32F,
1015
1448
  this.width,
1016
1449
  this.height,
1017
1450
  0,
1018
1451
  gl.RGBA,
1019
- gl.HALF_FLOAT,
1452
+ half ? gl.HALF_FLOAT : gl.FLOAT,
1020
1453
  null
1021
1454
  );
1022
1455
  } else {
@@ -1033,7 +1466,8 @@ var Target = class {
1033
1466
  );
1034
1467
  }
1035
1468
  const wrap = getWrap2(gl, this.options.wrap ?? "clamp");
1036
- const filter = this.options.filter === "nearest" ? gl.NEAREST : gl.LINEAR;
1469
+ const linear = this.options.filter !== "nearest" && (this.format !== "rgba32f" || gl.getExtension("OES_texture_float_linear") !== null);
1470
+ const filter = linear ? gl.LINEAR : gl.NEAREST;
1037
1471
  gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, wrap);
1038
1472
  gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, wrap);
1039
1473
  gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, filter);
@@ -1076,12 +1510,20 @@ export {
1076
1510
  TargetNotFoundError,
1077
1511
  TextureLoadError,
1078
1512
  UnsupportedError,
1513
+ applyProgramUniform,
1514
+ applyUniform,
1515
+ applyUniforms,
1516
+ collectUniformLocations,
1079
1517
  createBlitter,
1080
1518
  createFullscreenBuffer,
1519
+ createFullscreenProgram,
1081
1520
  createPingPong,
1082
1521
  createProgram,
1083
1522
  createRenderTarget,
1084
1523
  createRuntimeHost,
1524
+ createTextureSet,
1525
+ createUniformBindings,
1526
+ createUniformCache,
1085
1527
  defaultVertexShader,
1086
1528
  defaultVertexShader300,
1087
1529
  fromUniformName,
@@ -1091,13 +1533,17 @@ export {
1091
1533
  hexToRgba,
1092
1534
  isGLSL300,
1093
1535
  isWebGL2Context,
1536
+ keyframes,
1094
1537
  loadTexture,
1538
+ normalizeUniform,
1539
+ parseShaderLog,
1095
1540
  resizeCanvasToTarget,
1096
1541
  resolveDpr,
1097
1542
  resolveElement,
1098
1543
  resolveRenderTargetFormat,
1099
1544
  setupCanvas,
1100
1545
  supportsRenderTargetFormat,
1101
- toUniformName
1546
+ toUniformName,
1547
+ waitForGpu
1102
1548
  };
1103
1549
  //# sourceMappingURL=index.js.map