vireglass 0.1.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.
Files changed (72) hide show
  1. package/LICENSE +202 -0
  2. package/NOTICE +10 -0
  3. package/README.md +216 -0
  4. package/android/build.gradle +15 -0
  5. package/android/src/main/AndroidManifest.xml +1 -0
  6. package/android/src/main/java/expo/modules/glasslens/GlassBackdropView.kt +335 -0
  7. package/android/src/main/java/expo/modules/glasslens/GlassLensModule.kt +49 -0
  8. package/android/src/main/java/expo/modules/glasslens/GlassLensView.kt +463 -0
  9. package/android/src/main/java/expo/modules/glasslens/GlassProbeView.kt +76 -0
  10. package/dist/adaptation-BSbbdExK.d.cts +78 -0
  11. package/dist/adaptation-BSbbdExK.d.ts +78 -0
  12. package/dist/adapters-BPB0D5nr.d.cts +357 -0
  13. package/dist/adapters-BgSc-smX.d.ts +357 -0
  14. package/dist/chunk-HF2AWZEZ.js +1741 -0
  15. package/dist/chunk-HF2AWZEZ.js.map +1 -0
  16. package/dist/chunk-JNRZNMJJ.js +20 -0
  17. package/dist/chunk-JNRZNMJJ.js.map +1 -0
  18. package/dist/index.cjs +2159 -0
  19. package/dist/index.cjs.map +1 -0
  20. package/dist/index.d.cts +294 -0
  21. package/dist/index.d.ts +294 -0
  22. package/dist/index.js +286 -0
  23. package/dist/index.js.map +1 -0
  24. package/dist/material-CkUHUBoy.d.cts +408 -0
  25. package/dist/material-CkUHUBoy.d.ts +408 -0
  26. package/dist/react.cjs +73 -0
  27. package/dist/react.cjs.map +1 -0
  28. package/dist/react.d.cts +25 -0
  29. package/dist/react.d.ts +25 -0
  30. package/dist/react.js +59 -0
  31. package/dist/react.js.map +1 -0
  32. package/dist/web/index.cjs +1990 -0
  33. package/dist/web/index.cjs.map +1 -0
  34. package/dist/web/index.d.cts +148 -0
  35. package/dist/web/index.d.ts +148 -0
  36. package/dist/web/index.js +657 -0
  37. package/dist/web/index.js.map +1 -0
  38. package/expo-module.config.json +6 -0
  39. package/package.json +127 -0
  40. package/src/accessibility.ts +51 -0
  41. package/src/adaptation.ts +85 -0
  42. package/src/adapters.ts +362 -0
  43. package/src/concentric.ts +25 -0
  44. package/src/geometry.ts +151 -0
  45. package/src/glass-scale.ts +37 -0
  46. package/src/group-model.ts +224 -0
  47. package/src/index.ts +17 -0
  48. package/src/lens-shader.ts +547 -0
  49. package/src/material.ts +553 -0
  50. package/src/native/environment.ts +70 -0
  51. package/src/native/glass-group.tsx +107 -0
  52. package/src/native/glass-ink.tsx +44 -0
  53. package/src/native/glass-surface.tsx +477 -0
  54. package/src/native/index.ts +18 -0
  55. package/src/native/lens.ts +106 -0
  56. package/src/native/provider.tsx +58 -0
  57. package/src/native/surface-registry.ts +51 -0
  58. package/src/native/system-accessibility.ts +90 -0
  59. package/src/optics.ts +177 -0
  60. package/src/react.ts +102 -0
  61. package/src/reference-scene.ts +222 -0
  62. package/src/scroll-edge.ts +30 -0
  63. package/src/sdf.ts +149 -0
  64. package/src/surface-shader.ts +287 -0
  65. package/src/targets/agsl.ts +8 -0
  66. package/src/targets/glsl.ts +140 -0
  67. package/src/touch-response.ts +195 -0
  68. package/src/web/gl.ts +119 -0
  69. package/src/web/index.ts +3 -0
  70. package/src/web/probe.ts +312 -0
  71. package/src/web/reference-draw.ts +142 -0
  72. package/src/web/renderer.ts +524 -0
@@ -0,0 +1,657 @@
1
+ import { toGLSL, LENS_SHADER, SURFACE_SHADER, REFERENCE_SURROUND, lensPadDp, surfacePadDp, toLensProps, toSurfaceUniforms, shadowOpacity, toDeviceSurfaceUniforms, DYNAMIC_UNIFORMS, ICON_UNIFORMS, OVERLAY_UNIFORMS, REFERENCE_SCENE_HEIGHT, refGradientSteps, refGray, refCheckerCells, REFERENCE_SCENE_WIDTH } from '../chunk-HF2AWZEZ.js';
2
+
3
+ // src/web/gl.ts
4
+ var FULLSCREEN_TRIANGLE_VERTEX_SOURCE = `#version 300 es
5
+ const vec2 VG_POS[3] = vec2[3](vec2(-1.0, -1.0), vec2(3.0, -1.0), vec2(-1.0, 3.0));
6
+ void main() {
7
+ gl_Position = vec4(VG_POS[gl_VertexID], 0.0, 1.0);
8
+ }
9
+ `;
10
+ function compileShader(gl, type, source) {
11
+ const shader = gl.createShader(type);
12
+ if (!shader) throw new Error("vireglass/web: gl.createShader returned null");
13
+ gl.shaderSource(shader, source);
14
+ gl.compileShader(shader);
15
+ if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
16
+ const log = gl.getShaderInfoLog(shader) ?? "(no log)";
17
+ gl.deleteShader(shader);
18
+ throw new Error(`vireglass/web: shader failed to compile:
19
+ ${log}`);
20
+ }
21
+ return shader;
22
+ }
23
+ function createProgram(gl, vertexSource, fragmentSource) {
24
+ const vertex = compileShader(gl, gl.VERTEX_SHADER, vertexSource);
25
+ const fragment = compileShader(gl, gl.FRAGMENT_SHADER, fragmentSource);
26
+ const program = gl.createProgram();
27
+ if (!program) throw new Error("vireglass/web: gl.createProgram returned null");
28
+ gl.attachShader(program, vertex);
29
+ gl.attachShader(program, fragment);
30
+ gl.linkProgram(program);
31
+ gl.deleteShader(vertex);
32
+ gl.deleteShader(fragment);
33
+ if (!gl.getProgramParameter(program, gl.LINK_STATUS)) {
34
+ const log = gl.getProgramInfoLog(program) ?? "(no log)";
35
+ gl.deleteProgram(program);
36
+ throw new Error(`vireglass/web: program failed to link:
37
+ ${log}`);
38
+ }
39
+ return program;
40
+ }
41
+ function drawFullscreenTriangle(gl) {
42
+ gl.drawArrays(gl.TRIANGLES, 0, 3);
43
+ }
44
+ function createTexture(gl, options) {
45
+ const texture = gl.createTexture();
46
+ if (!texture) throw new Error("vireglass/web: gl.createTexture returned null");
47
+ const internalFormat = options.internalFormat ?? gl.RGBA8;
48
+ const format = options.format ?? gl.RGBA;
49
+ const type = options.type ?? gl.UNSIGNED_BYTE;
50
+ gl.bindTexture(gl.TEXTURE_2D, texture);
51
+ gl.texImage2D(
52
+ gl.TEXTURE_2D,
53
+ 0,
54
+ internalFormat,
55
+ options.width,
56
+ options.height,
57
+ 0,
58
+ format,
59
+ type,
60
+ null
61
+ );
62
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
63
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
64
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
65
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
66
+ gl.bindTexture(gl.TEXTURE_2D, null);
67
+ return texture;
68
+ }
69
+ function createFramebuffer(gl, texture) {
70
+ const fbo = gl.createFramebuffer();
71
+ if (!fbo) throw new Error("vireglass/web: gl.createFramebuffer returned null");
72
+ gl.bindFramebuffer(gl.FRAMEBUFFER, fbo);
73
+ gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, texture, 0);
74
+ const status = gl.checkFramebufferStatus(gl.FRAMEBUFFER);
75
+ gl.bindFramebuffer(gl.FRAMEBUFFER, null);
76
+ if (status !== gl.FRAMEBUFFER_COMPLETE) {
77
+ throw new Error(`vireglass/web: FBO incomplete, status ${status}`);
78
+ }
79
+ return fbo;
80
+ }
81
+ function bindTextureAt(gl, unit, texture, program, uniformName) {
82
+ gl.activeTexture(gl.TEXTURE0 + unit);
83
+ gl.bindTexture(gl.TEXTURE_2D, texture);
84
+ const loc = gl.getUniformLocation(program, uniformName);
85
+ gl.uniform1i(loc, unit);
86
+ }
87
+
88
+ // src/web/probe.ts
89
+ var PROBE_GRID_WIDTH = 48;
90
+ var PROBE_GRID_HEIGHT = 96;
91
+ var DOWNSAMPLE_FRAGMENT_SOURCE = `#version 300 es
92
+ precision highp float;
93
+ out vec4 fragColor;
94
+ uniform sampler2D u_content;
95
+ uniform vec2 u_contentSize;
96
+ uniform vec2 u_gridSize;
97
+
98
+ // Cell \u2192 content UV mapping. Does NOT correspond to the canvas's visible top/bottom \u2014 this is an
99
+ // internal pass, its buffer is never displayed, only read back on the CPU with the same formula
100
+ // (see rectStats below). It doesn't need to match the blit pass's orientation.
101
+ void main() {
102
+ vec2 uv = gl_FragCoord.xy / u_gridSize;
103
+ vec3 sum = vec3(0.0);
104
+ const int N = 5;
105
+ vec2 block = u_contentSize / u_gridSize;
106
+ vec2 origin = uv * u_contentSize;
107
+ for (int y = 0; y < N; y++) {
108
+ for (int x = 0; x < N; x++) {
109
+ vec2 p = origin + (vec2(float(x), float(y)) + 0.5) * (block / float(N));
110
+ sum += texture(u_content, p / u_contentSize).rgb;
111
+ }
112
+ }
113
+ fragColor = vec4(sum / float(N * N), 1.0);
114
+ }
115
+ `;
116
+ function pickDownsampleFormat(gl) {
117
+ const hasFloat = gl.getExtension("EXT_color_buffer_float") !== null;
118
+ return hasFloat ? { internalFormat: gl.RGBA32F, type: gl.FLOAT, floatPrecision: true } : { internalFormat: gl.RGBA8, type: gl.UNSIGNED_BYTE, floatPrecision: false };
119
+ }
120
+ function luma(r, g, b) {
121
+ return 0.2126 * r + 0.7152 * g + 0.0722 * b;
122
+ }
123
+ function rectStats(buffer, floatPrecision, gridW, gridH, contentWidth, contentHeight, rect) {
124
+ const x0 = rect.centerX - rect.halfWidth;
125
+ const x1 = rect.centerX + rect.halfWidth;
126
+ const y0 = rect.centerY - rect.halfHeight;
127
+ const y1 = rect.centerY + rect.halfHeight;
128
+ const lumas = [];
129
+ const nx = [];
130
+ const ny = [];
131
+ let sr = 0;
132
+ let sg = 0;
133
+ let sb = 0;
134
+ const norm = floatPrecision ? 1 : 255;
135
+ for (let row = 0; row < gridH; row++) {
136
+ const cy = (row + 0.5) / gridH * contentHeight;
137
+ if (cy < y0 || cy > y1) continue;
138
+ for (let col = 0; col < gridW; col++) {
139
+ const cx = (col + 0.5) / gridW * contentWidth;
140
+ if (cx < x0 || cx > x1) continue;
141
+ const i = (row * gridW + col) * 4;
142
+ const r = buffer[i] / norm;
143
+ const g = buffer[i + 1] / norm;
144
+ const b = buffer[i + 2] / norm;
145
+ lumas.push(luma(r, g, b));
146
+ nx.push((cx - rect.centerX) / Math.max(rect.halfWidth, 1));
147
+ ny.push((cy - rect.centerY) / Math.max(rect.halfHeight, 1));
148
+ sr += r;
149
+ sg += g;
150
+ sb += b;
151
+ }
152
+ }
153
+ const n = lumas.length;
154
+ if (n === 0) return null;
155
+ let mean = 0;
156
+ for (const v of lumas) mean += v;
157
+ mean /= n;
158
+ let busy = 0;
159
+ for (const v of lumas) busy += Math.abs(v - mean);
160
+ busy = busy / n * 2;
161
+ const sorted = [...lumas].sort((a, b) => a - b);
162
+ const percentile = (p) => {
163
+ const idx = p * (n - 1);
164
+ const lo = Math.floor(idx);
165
+ const hi = Math.ceil(idx);
166
+ return sorted[lo] + (sorted[hi] - sorted[lo]) * (idx - lo);
167
+ };
168
+ const slope = (xs) => {
169
+ let mx = 0;
170
+ for (const v of xs) mx += v;
171
+ mx /= n;
172
+ let sxx = 0;
173
+ let sxl = 0;
174
+ for (let i = 0; i < n; i++) {
175
+ const dx = xs[i] - mx;
176
+ sxx += dx * dx;
177
+ sxl += dx * (lumas[i] - mean);
178
+ }
179
+ return sxx > 1e-6 ? sxl / sxx : 0;
180
+ };
181
+ return {
182
+ luma: mean,
183
+ busy: Math.min(busy, 1),
184
+ lo: percentile(0.1),
185
+ hi: percentile(0.9),
186
+ slopeX: slope(nx),
187
+ slopeY: slope(ny),
188
+ r: sr / n,
189
+ g: sg / n,
190
+ b: sb / n
191
+ };
192
+ }
193
+ function createProbe(gl, vertexSource) {
194
+ const program = createProgram(gl, vertexSource, DOWNSAMPLE_FRAGMENT_SOURCE);
195
+ const format = pickDownsampleFormat(gl);
196
+ const texture = gl.createTexture();
197
+ if (!texture) throw new Error("vireglass/web/probe: gl.createTexture returned null");
198
+ gl.bindTexture(gl.TEXTURE_2D, texture);
199
+ gl.texImage2D(
200
+ gl.TEXTURE_2D,
201
+ 0,
202
+ format.internalFormat,
203
+ PROBE_GRID_WIDTH,
204
+ PROBE_GRID_HEIGHT,
205
+ 0,
206
+ gl.RGBA,
207
+ format.type,
208
+ null
209
+ );
210
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
211
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
212
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
213
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
214
+ gl.bindTexture(gl.TEXTURE_2D, null);
215
+ const fbo = createFramebuffer(gl, texture);
216
+ const byteSize = PROBE_GRID_WIDTH * PROBE_GRID_HEIGHT * 4 * (format.floatPrecision ? 4 : 1);
217
+ const slots = [0, 1].map(() => {
218
+ const buffer = gl.createBuffer();
219
+ if (!buffer) throw new Error("vireglass/web/probe: gl.createBuffer returned null");
220
+ gl.bindBuffer(gl.PIXEL_PACK_BUFFER, buffer);
221
+ gl.bufferData(gl.PIXEL_PACK_BUFFER, byteSize, gl.STREAM_READ);
222
+ gl.bindBuffer(gl.PIXEL_PACK_BUFFER, null);
223
+ return { buffer, sync: null, pending: false };
224
+ });
225
+ let cursor = 0;
226
+ let latestRaw = null;
227
+ const contentLoc = gl.getUniformLocation(program, "u_content");
228
+ const contentSizeLoc = gl.getUniformLocation(program, "u_contentSize");
229
+ const gridSizeLoc = gl.getUniformLocation(program, "u_gridSize");
230
+ function pollReady(slot) {
231
+ if (!slot.pending || !slot.sync) return;
232
+ const status = gl.clientWaitSync(slot.sync, gl.SYNC_FLUSH_COMMANDS_BIT, 0);
233
+ if (status === gl.TIMEOUT_EXPIRED || status === gl.WAIT_FAILED) return;
234
+ gl.deleteSync(slot.sync);
235
+ slot.sync = null;
236
+ slot.pending = false;
237
+ gl.bindBuffer(gl.PIXEL_PACK_BUFFER, slot.buffer);
238
+ const out = format.floatPrecision ? new Float32Array(PROBE_GRID_WIDTH * PROBE_GRID_HEIGHT * 4) : new Uint8Array(PROBE_GRID_WIDTH * PROBE_GRID_HEIGHT * 4);
239
+ gl.getBufferSubData(gl.PIXEL_PACK_BUFFER, 0, out);
240
+ gl.bindBuffer(gl.PIXEL_PACK_BUFFER, null);
241
+ latestRaw = out;
242
+ }
243
+ function sample(contentTexture, contentWidth, contentHeight, rect) {
244
+ for (const slot2 of slots) pollReady(slot2);
245
+ gl.bindFramebuffer(gl.FRAMEBUFFER, fbo);
246
+ gl.viewport(0, 0, PROBE_GRID_WIDTH, PROBE_GRID_HEIGHT);
247
+ gl.useProgram(program);
248
+ bindTextureAt(gl, 0, contentTexture, program, "u_content");
249
+ gl.uniform1i(contentLoc, 0);
250
+ gl.uniform2f(contentSizeLoc, contentWidth, contentHeight);
251
+ gl.uniform2f(gridSizeLoc, PROBE_GRID_WIDTH, PROBE_GRID_HEIGHT);
252
+ gl.disable(gl.BLEND);
253
+ drawFullscreenTriangle(gl);
254
+ gl.bindFramebuffer(gl.FRAMEBUFFER, null);
255
+ const slot = slots[cursor];
256
+ cursor = (cursor + 1) % slots.length;
257
+ if (!slot.pending) {
258
+ gl.bindFramebuffer(gl.READ_FRAMEBUFFER, fbo);
259
+ gl.bindBuffer(gl.PIXEL_PACK_BUFFER, slot.buffer);
260
+ gl.readPixels(0, 0, PROBE_GRID_WIDTH, PROBE_GRID_HEIGHT, gl.RGBA, format.type, 0);
261
+ gl.bindBuffer(gl.PIXEL_PACK_BUFFER, null);
262
+ gl.bindFramebuffer(gl.READ_FRAMEBUFFER, null);
263
+ slot.sync = gl.fenceSync(gl.SYNC_GPU_COMMANDS_COMPLETE, 0);
264
+ slot.pending = true;
265
+ }
266
+ return statsFor(contentWidth, contentHeight, rect);
267
+ }
268
+ function statsFor(contentWidth, contentHeight, rect) {
269
+ if (!latestRaw) return null;
270
+ return rectStats(
271
+ latestRaw,
272
+ format.floatPrecision,
273
+ PROBE_GRID_WIDTH,
274
+ PROBE_GRID_HEIGHT,
275
+ contentWidth,
276
+ contentHeight,
277
+ rect
278
+ );
279
+ }
280
+ function destroy() {
281
+ for (const slot of slots) {
282
+ if (slot.sync) gl.deleteSync(slot.sync);
283
+ gl.deleteBuffer(slot.buffer);
284
+ }
285
+ gl.deleteFramebuffer(fbo);
286
+ gl.deleteTexture(texture);
287
+ gl.deleteProgram(program);
288
+ }
289
+ return { sample, statsFor, destroy };
290
+ }
291
+
292
+ // src/web/renderer.ts
293
+ var BLIT_FRAGMENT_SOURCE = `#version 300 es
294
+ precision highp float;
295
+ out vec4 fragColor;
296
+ uniform sampler2D u_content;
297
+ uniform vec2 u_resolution;
298
+ void main() {
299
+ // The same Y flip the transpiler does for the lens/surface (targets/glsl.ts): content is
300
+ // uploaded from the 2D canvas WITHOUT flipping, so its V=0 is the scene's top row. This keeps
301
+ // the same order here, otherwise the backdrop and what the lens refracts through it drift
302
+ // apart vertically.
303
+ vec2 uv = gl_FragCoord.xy / u_resolution;
304
+ uv.y = 1.0 - uv.y;
305
+ fragColor = texture(u_content, uv);
306
+ }
307
+ `;
308
+ function locationCache(gl, program) {
309
+ const cache = /* @__PURE__ */ new Map();
310
+ return (name) => {
311
+ let loc = cache.get(name);
312
+ if (loc === void 0) {
313
+ loc = gl.getUniformLocation(program, name);
314
+ cache.set(name, loc);
315
+ }
316
+ return loc;
317
+ };
318
+ }
319
+ function setUniform(gl, loc, value) {
320
+ if (!loc) return;
321
+ if (typeof value === "number") {
322
+ gl.uniform1f(loc, value);
323
+ return;
324
+ }
325
+ switch (value.length) {
326
+ case 1:
327
+ gl.uniform1f(loc, value[0]);
328
+ break;
329
+ case 2:
330
+ gl.uniform2f(loc, value[0], value[1]);
331
+ break;
332
+ case 3:
333
+ gl.uniform3f(loc, value[0], value[1], value[2]);
334
+ break;
335
+ case 4:
336
+ gl.uniform4f(loc, value[0], value[1], value[2], value[3]);
337
+ break;
338
+ default:
339
+ throw new Error(`vireglass/web: unsupported uniform size (${value.length})`);
340
+ }
341
+ }
342
+ function applyChannel(gl, get, names, sizes, values) {
343
+ let cursor = 0;
344
+ for (let i = 0; i < names.length; i += 1) {
345
+ const size = sizes[i];
346
+ setUniform(gl, get(names[i]), values.slice(cursor, cursor + size));
347
+ cursor += size;
348
+ }
349
+ }
350
+ function applyObject(gl, get, values) {
351
+ for (const name of Object.keys(values)) setUniform(gl, get(name), values[name]);
352
+ }
353
+ function createVireGlassRenderer(canvas) {
354
+ const context = canvas.getContext("webgl2", {
355
+ preserveDrawingBuffer: true,
356
+ alpha: false,
357
+ antialias: false
358
+ });
359
+ if (!context) throw new Error("vireglass/web: WebGL2 unavailable");
360
+ const gl = context;
361
+ const blitProgram = createProgram(gl, FULLSCREEN_TRIANGLE_VERTEX_SOURCE, BLIT_FRAGMENT_SOURCE);
362
+ const lensProgram = createProgram(gl, FULLSCREEN_TRIANGLE_VERTEX_SOURCE, toGLSL(LENS_SHADER));
363
+ const surfaceProgram = createProgram(
364
+ gl,
365
+ FULLSCREEN_TRIANGLE_VERTEX_SOURCE,
366
+ toGLSL(SURFACE_SHADER)
367
+ );
368
+ const probe = createProbe(gl, FULLSCREEN_TRIANGLE_VERTEX_SOURCE);
369
+ const blitLoc = locationCache(gl, blitProgram);
370
+ const lensLoc = locationCache(gl, lensProgram);
371
+ const surfaceLoc = locationCache(gl, surfaceProgram);
372
+ let width = canvas.width;
373
+ let height = canvas.height;
374
+ let sceneCanvas = document.createElement("canvas");
375
+ let sceneCtx = sceneCanvas.getContext("2d");
376
+ if (!sceneCtx) throw new Error("vireglass/web: scene 2D context unavailable");
377
+ const settled = /* @__PURE__ */ new Map();
378
+ const SETTLE = 0.12;
379
+ function settleStats(index, fresh) {
380
+ const prev = settled.get(index);
381
+ if (!prev) {
382
+ settled.set(index, fresh);
383
+ return fresh;
384
+ }
385
+ const mix = (a, b) => a + (b - a) * SETTLE;
386
+ const next = {
387
+ luma: mix(prev.luma, fresh.luma),
388
+ busy: mix(prev.busy, fresh.busy),
389
+ lo: mix(prev.lo, fresh.lo),
390
+ hi: mix(prev.hi, fresh.hi),
391
+ slopeX: mix(prev.slopeX, fresh.slopeX),
392
+ slopeY: mix(prev.slopeY, fresh.slopeY),
393
+ r: mix(prev.r, fresh.r),
394
+ g: mix(prev.g, fresh.g),
395
+ b: mix(prev.b, fresh.b)
396
+ };
397
+ settled.set(index, next);
398
+ return next;
399
+ }
400
+ let contentTexture = createTexture(gl, {
401
+ width: Math.max(width, 1),
402
+ height: Math.max(height, 1)
403
+ });
404
+ const iconTexture = createTexture(gl, { width: 1, height: 1 });
405
+ const colorTexture = createTexture(gl, { width: 1, height: 1 });
406
+ gl.bindTexture(gl.TEXTURE_2D, iconTexture);
407
+ gl.texSubImage2D(
408
+ gl.TEXTURE_2D,
409
+ 0,
410
+ 0,
411
+ 0,
412
+ 1,
413
+ 1,
414
+ gl.RGBA,
415
+ gl.UNSIGNED_BYTE,
416
+ new Uint8Array([0, 0, 0, 0])
417
+ );
418
+ gl.bindTexture(gl.TEXTURE_2D, null);
419
+ function resize(widthPx, heightPx) {
420
+ width = Math.max(1, Math.round(widthPx));
421
+ height = Math.max(1, Math.round(heightPx));
422
+ canvas.width = width;
423
+ canvas.height = height;
424
+ gl.viewport(0, 0, width, height);
425
+ sceneCanvas = document.createElement("canvas");
426
+ sceneCanvas.width = width;
427
+ sceneCanvas.height = height;
428
+ sceneCtx = sceneCanvas.getContext("2d");
429
+ if (!sceneCtx) throw new Error("vireglass/web: scene 2D context unavailable");
430
+ gl.deleteTexture(contentTexture);
431
+ contentTexture = createTexture(gl, { width, height });
432
+ }
433
+ function render(options) {
434
+ if (!sceneCtx) throw new Error("vireglass/web: renderer not initialized (resize was never called)");
435
+ sceneCtx.clearRect(0, 0, width, height);
436
+ options.scene(sceneCtx, width, height, options.offsetX ?? 0, options.offsetY ?? 0, options.density);
437
+ gl.bindTexture(gl.TEXTURE_2D, contentTexture);
438
+ gl.texSubImage2D(gl.TEXTURE_2D, 0, 0, 0, gl.RGBA, gl.UNSIGNED_BYTE, sceneCanvas);
439
+ gl.bindTexture(gl.TEXTURE_2D, null);
440
+ if (options.colorLayer) {
441
+ gl.bindTexture(gl.TEXTURE_2D, colorTexture);
442
+ gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, options.colorLayer);
443
+ }
444
+ if (options.iconMask) {
445
+ gl.bindTexture(gl.TEXTURE_2D, iconTexture);
446
+ gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, options.iconMask);
447
+ gl.bindTexture(gl.TEXTURE_2D, null);
448
+ }
449
+ gl.bindFramebuffer(gl.FRAMEBUFFER, null);
450
+ gl.viewport(0, 0, width, height);
451
+ gl.disable(gl.BLEND);
452
+ gl.disable(gl.SCISSOR_TEST);
453
+ gl.useProgram(blitProgram);
454
+ bindTextureAt(gl, 0, contentTexture, blitProgram, "u_content");
455
+ setUniform(gl, blitLoc("u_resolution"), [width, height]);
456
+ drawFullscreenTriangle(gl);
457
+ const probes = [];
458
+ for (let index = 0; index < options.pieces.length; index += 1) {
459
+ const piece = options.pieces[index];
460
+ const halfWidth = piece.geometry.width * options.density / 2;
461
+ const halfHeight = piece.geometry.height * options.density / 2;
462
+ const rect = {
463
+ centerX: piece.centerX,
464
+ centerY: piece.centerY,
465
+ halfWidth,
466
+ halfHeight
467
+ };
468
+ const fresh = index === 0 ? probe.sample(contentTexture, width, height, rect) : probe.statsFor(width, height, rect);
469
+ const stats = fresh ? settleStats(index, fresh) : null;
470
+ probes.push(stats);
471
+ gl.bindFramebuffer(gl.FRAMEBUFFER, null);
472
+ gl.viewport(0, 0, width, height);
473
+ gl.enable(gl.BLEND);
474
+ gl.blendFunc(gl.ONE, gl.ONE_MINUS_SRC_ALPHA);
475
+ const reachOf = (m) => m ? Math.hypot(m.offsetX, m.offsetY) + Math.max(m.width, m.height) / 2 + m.smoothing : 0;
476
+ const morphReach = Math.max(reachOf(piece.morph), reachOf(piece.morph2));
477
+ const touchReach = piece.touch ? Math.hypot(piece.touch.pullX, piece.touch.pullY) + piece.touch.waveAmp * 2 + Math.max(piece.geometry.width, piece.geometry.height) * 0.05 * piece.touch.press : 0;
478
+ const padPx = (lensPadDp(piece.geometry, piece.optics) + surfacePadDp(piece.geometry, 0) + morphReach + touchReach) * options.density;
479
+ const left = Math.max(0, Math.floor(piece.centerX - halfWidth - padPx));
480
+ const right = Math.min(width, Math.ceil(piece.centerX + halfWidth + padPx));
481
+ const top = Math.max(0, Math.floor(height - (piece.centerY + halfHeight + padPx)));
482
+ const bottom = Math.min(height, Math.ceil(height - (piece.centerY - halfHeight - padPx)));
483
+ gl.enable(gl.SCISSOR_TEST);
484
+ gl.scissor(left, top, Math.max(0, right - left), Math.max(0, bottom - top));
485
+ gl.useProgram(lensProgram);
486
+ bindTextureAt(gl, 0, contentTexture, lensProgram, "content");
487
+ setUniform(gl, lensLoc("u_contentSize"), [width, height]);
488
+ setUniform(gl, lensLoc("u_resolution"), [width, height]);
489
+ setUniform(gl, lensLoc("u_center"), [piece.centerX, piece.centerY]);
490
+ setUniform(gl, lensLoc("u_reach"), Math.max(width, height));
491
+ setUniform(gl, lensLoc("u_contentMin"), [1, 1]);
492
+ setUniform(gl, lensLoc("u_contentMax"), [width - 1, height - 1]);
493
+ if (stats) {
494
+ setUniform(gl, lensLoc("u_probeLuma"), stats.luma);
495
+ setUniform(gl, lensLoc("u_probeBusy"), stats.busy);
496
+ setUniform(gl, lensLoc("u_probeRange"), [stats.lo, stats.hi]);
497
+ setUniform(gl, lensLoc("u_probeSlope"), [stats.slopeX, stats.slopeY]);
498
+ setUniform(gl, lensLoc("u_probe"), [stats.r, stats.g, stats.b]);
499
+ } else {
500
+ setUniform(gl, lensLoc("u_probeLuma"), -1);
501
+ }
502
+ const lens = toLensProps(piece.optics, piece.geometry, options.density, {
503
+ debug: options.debug,
504
+ morph: piece.morph,
505
+ morph2: piece.morph2,
506
+ touch: piece.touch,
507
+ progress: piece.progress,
508
+ light: piece.light,
509
+ appear: piece.appear,
510
+ accent: piece.accent
511
+ });
512
+ applyChannel(gl, lensLoc, lens.uniformNames, lens.uniformSizes, lens.uniformValues);
513
+ drawFullscreenTriangle(gl);
514
+ gl.useProgram(surfaceProgram);
515
+ const rawSurface = toSurfaceUniforms(piece.optics, piece.geometry, {
516
+ debug: options.debug,
517
+ morph: piece.morph,
518
+ morph2: piece.morph2,
519
+ bodyInLens: true,
520
+ touch: piece.touch,
521
+ progress: piece.progress,
522
+ // The shadow is denser over text, weaker over a flat backdrop (M 11:47). The rule lives
523
+ // in the core: while it lived here, Android didn't have it at all.
524
+ shadow: stats ? shadowOpacity(stats.busy) : 1,
525
+ // The probe has already computed the ambient color — the shadow gets it for free. Taken
526
+ // as-is: `settleStats` has already smoothed it, and rounding would bring back exactly the
527
+ // steps the smoothing was written to remove.
528
+ ambient: stats ? [stats.r, stats.g, stats.b] : void 0,
529
+ appear: piece.appear,
530
+ lift: piece.lift
531
+ });
532
+ applyObject(gl, surfaceLoc, {
533
+ ...toDeviceSurfaceUniforms(rawSurface, options.density),
534
+ u_center: [piece.centerX, piece.centerY]
535
+ });
536
+ setUniform(gl, surfaceLoc(DYNAMIC_UNIFORMS[0]), piece.press ?? 0);
537
+ setUniform(gl, surfaceLoc(DYNAMIC_UNIFORMS[1]), piece.active ?? 0);
538
+ const hasIcon = Boolean(piece.icon && options.iconMask);
539
+ setUniform(gl, surfaceLoc(ICON_UNIFORMS[0]), hasIcon ? 1 : 0);
540
+ setUniform(gl, surfaceLoc(ICON_UNIFORMS[1]), 1);
541
+ setUniform(gl, surfaceLoc(ICON_UNIFORMS[2]), piece.inkIdle ?? [1, 1, 1, 1]);
542
+ setUniform(gl, surfaceLoc(ICON_UNIFORMS[3]), piece.inkActive ?? [1, 1, 1, 1]);
543
+ bindTextureAt(gl, 1, iconTexture, surfaceProgram, "u_icon");
544
+ setUniform(gl, surfaceLoc("u_iconSize"), hasIcon ? [width, height] : [1, 1]);
545
+ const hasColor = Boolean(piece.overlay && options.colorLayer);
546
+ setUniform(gl, surfaceLoc(OVERLAY_UNIFORMS[0]), hasColor ? 1 : 0);
547
+ bindTextureAt(gl, 2, colorTexture, surfaceProgram, "u_overlay");
548
+ setUniform(gl, surfaceLoc("u_overlaySize"), hasColor ? [width, height] : [1, 1]);
549
+ setUniform(gl, surfaceLoc("u_resolution"), [width, height]);
550
+ drawFullscreenTriangle(gl);
551
+ }
552
+ gl.disable(gl.SCISSOR_TEST);
553
+ return { probes };
554
+ }
555
+ function destroy() {
556
+ probe.destroy();
557
+ gl.deleteProgram(blitProgram);
558
+ gl.deleteProgram(lensProgram);
559
+ gl.deleteProgram(surfaceProgram);
560
+ gl.deleteTexture(contentTexture);
561
+ gl.deleteTexture(iconTexture);
562
+ gl.deleteTexture(colorTexture);
563
+ }
564
+ return { resize, render, destroy };
565
+ }
566
+
567
+ // src/web/reference-draw.ts
568
+ function drawLayer(ctx, layer, x, y, w, h, d) {
569
+ switch (layer.kind) {
570
+ case "flat": {
571
+ ctx.fillStyle = refGray(layer.level);
572
+ ctx.fillRect(x, y, w, h);
573
+ return;
574
+ }
575
+ case "stripes": {
576
+ ctx.fillStyle = refGray(layer.level);
577
+ ctx.fillRect(x, y, w, h);
578
+ ctx.fillStyle = refGray(layer.other);
579
+ for (let at = 0; at < w; at += layer.periodDp * d) {
580
+ ctx.fillRect(x + at, y, layer.widthDp * d, h);
581
+ }
582
+ return;
583
+ }
584
+ case "checker": {
585
+ for (const cell of refCheckerCells(layer, w / d, h / d)) {
586
+ ctx.fillStyle = refGray(cell.level);
587
+ ctx.fillRect(x + cell.xDp * d, y + cell.yDp * d, layer.cellDp * d + 1, layer.cellDp * d + 1);
588
+ }
589
+ return;
590
+ }
591
+ case "step": {
592
+ ctx.fillStyle = refGray(layer.left);
593
+ ctx.fillRect(x, y, w / 2, h);
594
+ ctx.fillStyle = refGray(layer.right);
595
+ ctx.fillRect(x + w / 2, y, w - w / 2, h);
596
+ return;
597
+ }
598
+ case "bar": {
599
+ ctx.fillStyle = refGray(layer.level);
600
+ ctx.fillRect(x, y, w, h);
601
+ const t = layer.thicknessDp * d;
602
+ ctx.fillStyle = refGray(layer.barLevel);
603
+ ctx.fillRect(x, y + Math.round((h - t) / 2), w, t);
604
+ return;
605
+ }
606
+ case "grid": {
607
+ ctx.fillStyle = refGray(layer.level);
608
+ ctx.fillRect(x, y, w, h);
609
+ const thin = 1;
610
+ const step = layer.stepDp * d;
611
+ ctx.fillStyle = refGray(layer.lineLevel);
612
+ for (let at = 0; at <= w; at += step) ctx.fillRect(x + Math.round(at), y, thin, h);
613
+ for (let at = 0; at <= h; at += step) ctx.fillRect(x, y + Math.round(at), w, thin);
614
+ return;
615
+ }
616
+ case "gradient": {
617
+ const steps = refGradientSteps(layer);
618
+ const band = h / steps.length;
619
+ for (let i = 0; i < steps.length; i += 1) {
620
+ ctx.fillStyle = refGray(steps[i]);
621
+ ctx.fillRect(x, y + Math.round(i * band), w, Math.ceil(band) + 1);
622
+ }
623
+ return;
624
+ }
625
+ }
626
+ }
627
+ function drawReferenceScene(ctx, scene, width, height, options) {
628
+ const d = options.density;
629
+ const bands = scene.bands(options.level ?? scene.level);
630
+ const fill = options.fit !== "band";
631
+ let x;
632
+ let y;
633
+ let w;
634
+ let scale;
635
+ if (fill) {
636
+ x = 0;
637
+ y = 0;
638
+ w = width;
639
+ scale = height / (REFERENCE_SCENE_HEIGHT * d);
640
+ } else {
641
+ ctx.fillStyle = REFERENCE_SURROUND;
642
+ ctx.fillRect(0, 0, width, height);
643
+ w = Math.round(REFERENCE_SCENE_WIDTH * d);
644
+ x = Math.round((width - w) / 2);
645
+ y = Math.round((height - REFERENCE_SCENE_HEIGHT * d) / 2);
646
+ scale = 1;
647
+ }
648
+ for (const band of bands) {
649
+ const h = Math.round(band.heightDp * d * scale);
650
+ drawLayer(ctx, band.layer, x, y, w, h, d);
651
+ y += h;
652
+ }
653
+ }
654
+
655
+ export { PROBE_GRID_HEIGHT, PROBE_GRID_WIDTH, createProbe, createVireGlassRenderer, drawReferenceScene };
656
+ //# sourceMappingURL=index.js.map
657
+ //# sourceMappingURL=index.js.map