vireglass 2.2.0 → 2.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +39 -0
- package/dist/{chunk-2XJJO7SC.js → chunk-PM2SWCSF.js} +2 -2
- package/dist/{chunk-2XJJO7SC.js.map → chunk-PM2SWCSF.js.map} +1 -1
- package/dist/dom.cjs.map +1 -1
- package/dist/dom.js.map +1 -1
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +4 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.js +1 -1
- package/dist/web/index.cjs +261 -151
- package/dist/web/index.cjs.map +1 -1
- package/dist/web/index.d.cts +88 -2
- package/dist/web/index.d.ts +88 -2
- package/dist/web/index.js +254 -153
- package/dist/web/index.js.map +1 -1
- package/package.json +4 -2
- package/src/dom/index.ts +6 -5
- package/src/targets/glsl.ts +4 -0
- package/src/web/gl.ts +57 -4
- package/src/web/gpu-timer.ts +111 -0
- package/src/web/index.ts +1 -0
- package/src/web/renderer.ts +305 -218
package/dist/web/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { toGLSL, SURFACE_SHADER } from '../chunk-
|
|
1
|
+
import { toGLSL, SURFACE_SHADER } from '../chunk-PM2SWCSF.js';
|
|
2
2
|
import { LENS_SHADER, toLensProps, toSurfaceUniforms, toDeviceSurfaceUniforms, DYNAMIC_UNIFORMS, ICON_UNIFORMS, OVERLAY_UNIFORMS } from '../chunk-H5QTBF67.js';
|
|
3
3
|
import { lensPadDp, surfacePadDp, shadowOpacity } from '../chunk-SLRPHDV7.js';
|
|
4
4
|
import '../chunk-MMPK6DCK.js';
|
|
@@ -50,6 +50,7 @@ function createTexture(gl, options) {
|
|
|
50
50
|
const internalFormat = options.internalFormat ?? gl.RGBA8;
|
|
51
51
|
const format = options.format ?? gl.RGBA;
|
|
52
52
|
const type = options.type ?? gl.UNSIGNED_BYTE;
|
|
53
|
+
const wrap = options.wrap ?? gl.CLAMP_TO_EDGE;
|
|
53
54
|
gl.bindTexture(gl.TEXTURE_2D, texture);
|
|
54
55
|
gl.texImage2D(
|
|
55
56
|
gl.TEXTURE_2D,
|
|
@@ -64,11 +65,45 @@ function createTexture(gl, options) {
|
|
|
64
65
|
);
|
|
65
66
|
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
|
|
66
67
|
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
|
|
67
|
-
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S,
|
|
68
|
-
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T,
|
|
68
|
+
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, wrap);
|
|
69
|
+
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, wrap);
|
|
69
70
|
gl.bindTexture(gl.TEXTURE_2D, null);
|
|
70
71
|
return texture;
|
|
71
72
|
}
|
|
73
|
+
function locationCache(gl, program) {
|
|
74
|
+
const cache = /* @__PURE__ */ new Map();
|
|
75
|
+
return (name) => {
|
|
76
|
+
let loc = cache.get(name);
|
|
77
|
+
if (loc === void 0) {
|
|
78
|
+
loc = gl.getUniformLocation(program, name);
|
|
79
|
+
cache.set(name, loc);
|
|
80
|
+
}
|
|
81
|
+
return loc;
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
function setUniform(gl, loc, value) {
|
|
85
|
+
if (!loc) return;
|
|
86
|
+
if (typeof value === "number") {
|
|
87
|
+
gl.uniform1f(loc, value);
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
switch (value.length) {
|
|
91
|
+
case 1:
|
|
92
|
+
gl.uniform1f(loc, value[0]);
|
|
93
|
+
break;
|
|
94
|
+
case 2:
|
|
95
|
+
gl.uniform2f(loc, value[0], value[1]);
|
|
96
|
+
break;
|
|
97
|
+
case 3:
|
|
98
|
+
gl.uniform3f(loc, value[0], value[1], value[2]);
|
|
99
|
+
break;
|
|
100
|
+
case 4:
|
|
101
|
+
gl.uniform4f(loc, value[0], value[1], value[2], value[3]);
|
|
102
|
+
break;
|
|
103
|
+
default:
|
|
104
|
+
throw new Error(`vireglass/web: unsupported uniform size (${value.length})`);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
72
107
|
function createFramebuffer(gl, texture) {
|
|
73
108
|
const fbo = gl.createFramebuffer();
|
|
74
109
|
if (!fbo) throw new Error("vireglass/web: gl.createFramebuffer returned null");
|
|
@@ -88,6 +123,62 @@ function bindTextureAt(gl, unit, texture, program, uniformName) {
|
|
|
88
123
|
gl.uniform1i(loc, unit);
|
|
89
124
|
}
|
|
90
125
|
|
|
126
|
+
// src/web/gpu-timer.ts
|
|
127
|
+
var GPU_TIMER_RING_SIZE = 3;
|
|
128
|
+
function createGpuTimer(gl) {
|
|
129
|
+
const ext = gl.getExtension("EXT_disjoint_timer_query_webgl2");
|
|
130
|
+
const slots = ext ? Array.from({ length: GPU_TIMER_RING_SIZE }, () => ({
|
|
131
|
+
query: gl.createQuery(),
|
|
132
|
+
pending: false
|
|
133
|
+
})) : [];
|
|
134
|
+
let cursor = 0;
|
|
135
|
+
let active = null;
|
|
136
|
+
let lastMs = null;
|
|
137
|
+
function poll() {
|
|
138
|
+
if (!ext) return;
|
|
139
|
+
for (const slot of slots) {
|
|
140
|
+
if (!slot.pending) continue;
|
|
141
|
+
if (!gl.getQueryParameter(slot.query, gl.QUERY_RESULT_AVAILABLE)) continue;
|
|
142
|
+
slot.pending = false;
|
|
143
|
+
if (gl.getParameter(ext.GPU_DISJOINT_EXT)) continue;
|
|
144
|
+
const ns = gl.getQueryParameter(slot.query, gl.QUERY_RESULT);
|
|
145
|
+
lastMs = ns / 1e6;
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
function begin() {
|
|
149
|
+
active = null;
|
|
150
|
+
if (!ext || slots.length === 0) return;
|
|
151
|
+
poll();
|
|
152
|
+
const slot = slots[cursor];
|
|
153
|
+
if (slot.pending) return;
|
|
154
|
+
gl.beginQuery(ext.TIME_ELAPSED_EXT, slot.query);
|
|
155
|
+
active = slot;
|
|
156
|
+
}
|
|
157
|
+
function end() {
|
|
158
|
+
if (!ext || !active) return;
|
|
159
|
+
gl.endQuery(ext.TIME_ELAPSED_EXT);
|
|
160
|
+
active.pending = true;
|
|
161
|
+
cursor = (cursor + 1) % slots.length;
|
|
162
|
+
active = null;
|
|
163
|
+
}
|
|
164
|
+
function time(fn) {
|
|
165
|
+
begin();
|
|
166
|
+
try {
|
|
167
|
+
return fn();
|
|
168
|
+
} finally {
|
|
169
|
+
end();
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
function getLastMs() {
|
|
173
|
+
poll();
|
|
174
|
+
return lastMs;
|
|
175
|
+
}
|
|
176
|
+
function destroy() {
|
|
177
|
+
for (const slot of slots) gl.deleteQuery(slot.query);
|
|
178
|
+
}
|
|
179
|
+
return { time, getLastMs, destroy };
|
|
180
|
+
}
|
|
181
|
+
|
|
91
182
|
// src/web/probe.ts
|
|
92
183
|
var PROBE_GRID_WIDTH = 48;
|
|
93
184
|
var PROBE_GRID_HEIGHT = 96;
|
|
@@ -308,40 +399,6 @@ void main() {
|
|
|
308
399
|
fragColor = texture(u_content, uv);
|
|
309
400
|
}
|
|
310
401
|
`;
|
|
311
|
-
function locationCache(gl, program) {
|
|
312
|
-
const cache = /* @__PURE__ */ new Map();
|
|
313
|
-
return (name) => {
|
|
314
|
-
let loc = cache.get(name);
|
|
315
|
-
if (loc === void 0) {
|
|
316
|
-
loc = gl.getUniformLocation(program, name);
|
|
317
|
-
cache.set(name, loc);
|
|
318
|
-
}
|
|
319
|
-
return loc;
|
|
320
|
-
};
|
|
321
|
-
}
|
|
322
|
-
function setUniform(gl, loc, value) {
|
|
323
|
-
if (!loc) return;
|
|
324
|
-
if (typeof value === "number") {
|
|
325
|
-
gl.uniform1f(loc, value);
|
|
326
|
-
return;
|
|
327
|
-
}
|
|
328
|
-
switch (value.length) {
|
|
329
|
-
case 1:
|
|
330
|
-
gl.uniform1f(loc, value[0]);
|
|
331
|
-
break;
|
|
332
|
-
case 2:
|
|
333
|
-
gl.uniform2f(loc, value[0], value[1]);
|
|
334
|
-
break;
|
|
335
|
-
case 3:
|
|
336
|
-
gl.uniform3f(loc, value[0], value[1], value[2]);
|
|
337
|
-
break;
|
|
338
|
-
case 4:
|
|
339
|
-
gl.uniform4f(loc, value[0], value[1], value[2], value[3]);
|
|
340
|
-
break;
|
|
341
|
-
default:
|
|
342
|
-
throw new Error(`vireglass/web: unsupported uniform size (${value.length})`);
|
|
343
|
-
}
|
|
344
|
-
}
|
|
345
402
|
function applyChannel(gl, get, names, sizes, values) {
|
|
346
403
|
let cursor = 0;
|
|
347
404
|
for (let i = 0; i < names.length; i += 1) {
|
|
@@ -404,6 +461,12 @@ function createVireGlassRenderer(canvas) {
|
|
|
404
461
|
width: Math.max(width, 1),
|
|
405
462
|
height: Math.max(height, 1)
|
|
406
463
|
});
|
|
464
|
+
let backdropFbo = null;
|
|
465
|
+
function ensureBackdropFbo() {
|
|
466
|
+
if (!backdropFbo) backdropFbo = createFramebuffer(gl, contentTexture);
|
|
467
|
+
return backdropFbo;
|
|
468
|
+
}
|
|
469
|
+
const gpuTimer = createGpuTimer(gl);
|
|
407
470
|
const iconTexture = createTexture(gl, { width: 1, height: 1 });
|
|
408
471
|
const colorTexture = createTexture(gl, { width: 1, height: 1 });
|
|
409
472
|
gl.bindTexture(gl.TEXTURE_2D, iconTexture);
|
|
@@ -432,128 +495,164 @@ function createVireGlassRenderer(canvas) {
|
|
|
432
495
|
if (!sceneCtx) throw new Error("vireglass/web: scene 2D context unavailable");
|
|
433
496
|
gl.deleteTexture(contentTexture);
|
|
434
497
|
contentTexture = createTexture(gl, { width, height });
|
|
498
|
+
if (backdropFbo) {
|
|
499
|
+
gl.deleteFramebuffer(backdropFbo);
|
|
500
|
+
backdropFbo = null;
|
|
501
|
+
}
|
|
435
502
|
}
|
|
436
503
|
function render(options) {
|
|
437
504
|
if (!sceneCtx) throw new Error("vireglass/web: renderer not initialized (resize was never called)");
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
if (options.colorLayer) {
|
|
444
|
-
gl.bindTexture(gl.TEXTURE_2D, colorTexture);
|
|
445
|
-
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, options.colorLayer);
|
|
505
|
+
const ctx = sceneCtx;
|
|
506
|
+
if (options.backdrop && options.scene) {
|
|
507
|
+
throw new Error(
|
|
508
|
+
"vireglass/web: render() needs exactly one of options.scene or options.backdrop, not both"
|
|
509
|
+
);
|
|
446
510
|
}
|
|
447
|
-
if (options.
|
|
448
|
-
|
|
449
|
-
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, options.iconMask);
|
|
450
|
-
gl.bindTexture(gl.TEXTURE_2D, null);
|
|
511
|
+
if (!options.backdrop && !options.scene) {
|
|
512
|
+
throw new Error("vireglass/web: render() needs either options.scene or options.backdrop");
|
|
451
513
|
}
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
const stats = fresh ? settleStats(index, fresh) : null;
|
|
473
|
-
probes.push(stats);
|
|
474
|
-
gl.bindFramebuffer(gl.FRAMEBUFFER, null);
|
|
475
|
-
gl.viewport(0, 0, width, height);
|
|
476
|
-
gl.enable(gl.BLEND);
|
|
477
|
-
gl.blendFunc(gl.ONE, gl.ONE_MINUS_SRC_ALPHA);
|
|
478
|
-
const reachOf = (m) => m ? Math.hypot(m.offsetX, m.offsetY) + Math.max(m.width, m.height) / 2 + m.smoothing : 0;
|
|
479
|
-
const morphReach = Math.max(reachOf(piece.morph), reachOf(piece.morph2));
|
|
480
|
-
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;
|
|
481
|
-
const padPx = (lensPadDp(piece.geometry, piece.optics) + surfacePadDp(piece.geometry, 0) + morphReach + touchReach) * options.density;
|
|
482
|
-
const left = Math.max(0, Math.floor(piece.centerX - halfWidth - padPx));
|
|
483
|
-
const right = Math.min(width, Math.ceil(piece.centerX + halfWidth + padPx));
|
|
484
|
-
const top = Math.max(0, Math.floor(height - (piece.centerY + halfHeight + padPx)));
|
|
485
|
-
const bottom = Math.min(height, Math.ceil(height - (piece.centerY - halfHeight - padPx)));
|
|
486
|
-
gl.enable(gl.SCISSOR_TEST);
|
|
487
|
-
gl.scissor(left, top, Math.max(0, right - left), Math.max(0, bottom - top));
|
|
488
|
-
gl.useProgram(lensProgram);
|
|
489
|
-
bindTextureAt(gl, 0, contentTexture, lensProgram, "content");
|
|
490
|
-
setUniform(gl, lensLoc("u_contentSize"), [width, height]);
|
|
491
|
-
setUniform(gl, lensLoc("u_resolution"), [width, height]);
|
|
492
|
-
setUniform(gl, lensLoc("u_center"), [piece.centerX, piece.centerY]);
|
|
493
|
-
setUniform(gl, lensLoc("u_reach"), Math.max(width, height));
|
|
494
|
-
setUniform(gl, lensLoc("u_contentMin"), [1, 1]);
|
|
495
|
-
setUniform(gl, lensLoc("u_contentMax"), [width - 1, height - 1]);
|
|
496
|
-
if (stats) {
|
|
497
|
-
setUniform(gl, lensLoc("u_probeLuma"), stats.luma);
|
|
498
|
-
setUniform(gl, lensLoc("u_probeBusy"), stats.busy);
|
|
499
|
-
setUniform(gl, lensLoc("u_probeRange"), [stats.lo, stats.hi]);
|
|
500
|
-
setUniform(gl, lensLoc("u_probeSlope"), [stats.slopeX, stats.slopeY]);
|
|
501
|
-
setUniform(gl, lensLoc("u_probe"), [stats.r, stats.g, stats.b]);
|
|
514
|
+
return gpuTimer.time(() => {
|
|
515
|
+
if (options.backdrop) {
|
|
516
|
+
const fbo = ensureBackdropFbo();
|
|
517
|
+
gl.bindFramebuffer(gl.FRAMEBUFFER, fbo);
|
|
518
|
+
gl.viewport(0, 0, width, height);
|
|
519
|
+
options.backdrop(gl, { framebuffer: fbo, width, height });
|
|
520
|
+
gl.bindFramebuffer(gl.FRAMEBUFFER, null);
|
|
521
|
+
gl.viewport(0, 0, width, height);
|
|
522
|
+
gl.disable(gl.BLEND);
|
|
523
|
+
gl.disable(gl.SCISSOR_TEST);
|
|
524
|
+
gl.disable(gl.DEPTH_TEST);
|
|
525
|
+
gl.disable(gl.CULL_FACE);
|
|
526
|
+
gl.disable(gl.STENCIL_TEST);
|
|
527
|
+
gl.colorMask(true, true, true, true);
|
|
528
|
+
gl.depthMask(true);
|
|
529
|
+
gl.bindVertexArray(null);
|
|
530
|
+
gl.activeTexture(gl.TEXTURE0);
|
|
531
|
+
gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, false);
|
|
532
|
+
gl.pixelStorei(gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL, false);
|
|
533
|
+
gl.pixelStorei(gl.UNPACK_COLORSPACE_CONVERSION_WEBGL, gl.BROWSER_DEFAULT_WEBGL);
|
|
502
534
|
} else {
|
|
503
|
-
|
|
535
|
+
ctx.clearRect(0, 0, width, height);
|
|
536
|
+
options.scene?.(ctx, width, height, options.offsetX ?? 0, options.offsetY ?? 0, options.density);
|
|
537
|
+
gl.bindTexture(gl.TEXTURE_2D, contentTexture);
|
|
538
|
+
gl.texSubImage2D(gl.TEXTURE_2D, 0, 0, 0, gl.RGBA, gl.UNSIGNED_BYTE, sceneCanvas);
|
|
539
|
+
gl.bindTexture(gl.TEXTURE_2D, null);
|
|
504
540
|
}
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
gl.
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
morph2: piece.morph2,
|
|
522
|
-
bodyInLens: true,
|
|
523
|
-
touch: piece.touch,
|
|
524
|
-
progress: piece.progress,
|
|
525
|
-
// The shadow is denser over text, weaker over a flat backdrop (M 11:47). The rule lives
|
|
526
|
-
// in the core: while it lived here, Android didn't have it at all.
|
|
527
|
-
shadow: stats ? shadowOpacity(stats.busy) : 1,
|
|
528
|
-
// The probe has already computed the ambient color — the shadow gets it for free. Taken
|
|
529
|
-
// as-is: `settleStats` has already smoothed it, and rounding would bring back exactly the
|
|
530
|
-
// steps the smoothing was written to remove.
|
|
531
|
-
ambient: stats ? [stats.r, stats.g, stats.b] : void 0,
|
|
532
|
-
appear: piece.appear,
|
|
533
|
-
lift: piece.lift
|
|
534
|
-
});
|
|
535
|
-
applyObject(gl, surfaceLoc, {
|
|
536
|
-
...toDeviceSurfaceUniforms(rawSurface, options.density),
|
|
537
|
-
u_center: [piece.centerX, piece.centerY]
|
|
538
|
-
});
|
|
539
|
-
setUniform(gl, surfaceLoc(DYNAMIC_UNIFORMS[0]), piece.press ?? 0);
|
|
540
|
-
setUniform(gl, surfaceLoc(DYNAMIC_UNIFORMS[1]), piece.active ?? 0);
|
|
541
|
-
const hasIcon = Boolean(piece.icon && options.iconMask);
|
|
542
|
-
setUniform(gl, surfaceLoc(ICON_UNIFORMS[0]), hasIcon ? 1 : 0);
|
|
543
|
-
setUniform(gl, surfaceLoc(ICON_UNIFORMS[1]), 1);
|
|
544
|
-
setUniform(gl, surfaceLoc(ICON_UNIFORMS[2]), piece.inkIdle ?? [1, 1, 1, 1]);
|
|
545
|
-
setUniform(gl, surfaceLoc(ICON_UNIFORMS[3]), piece.inkActive ?? [1, 1, 1, 1]);
|
|
546
|
-
bindTextureAt(gl, 1, iconTexture, surfaceProgram, "u_icon");
|
|
547
|
-
setUniform(gl, surfaceLoc("u_iconSize"), hasIcon ? [width, height] : [1, 1]);
|
|
548
|
-
const hasColor = Boolean(piece.overlay && options.colorLayer);
|
|
549
|
-
setUniform(gl, surfaceLoc(OVERLAY_UNIFORMS[0]), hasColor ? 1 : 0);
|
|
550
|
-
bindTextureAt(gl, 2, colorTexture, surfaceProgram, "u_overlay");
|
|
551
|
-
setUniform(gl, surfaceLoc("u_overlaySize"), hasColor ? [width, height] : [1, 1]);
|
|
552
|
-
setUniform(gl, surfaceLoc("u_resolution"), [width, height]);
|
|
541
|
+
if (options.colorLayer) {
|
|
542
|
+
gl.bindTexture(gl.TEXTURE_2D, colorTexture);
|
|
543
|
+
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, options.colorLayer);
|
|
544
|
+
}
|
|
545
|
+
if (options.iconMask) {
|
|
546
|
+
gl.bindTexture(gl.TEXTURE_2D, iconTexture);
|
|
547
|
+
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, options.iconMask);
|
|
548
|
+
gl.bindTexture(gl.TEXTURE_2D, null);
|
|
549
|
+
}
|
|
550
|
+
gl.bindFramebuffer(gl.FRAMEBUFFER, null);
|
|
551
|
+
gl.viewport(0, 0, width, height);
|
|
552
|
+
gl.disable(gl.BLEND);
|
|
553
|
+
gl.disable(gl.SCISSOR_TEST);
|
|
554
|
+
gl.useProgram(blitProgram);
|
|
555
|
+
bindTextureAt(gl, 0, contentTexture, blitProgram, "u_content");
|
|
556
|
+
setUniform(gl, blitLoc("u_resolution"), [width, height]);
|
|
553
557
|
drawFullscreenTriangle(gl);
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
558
|
+
const probes = [];
|
|
559
|
+
for (let index = 0; index < options.pieces.length; index += 1) {
|
|
560
|
+
const piece = options.pieces[index];
|
|
561
|
+
const halfWidth = piece.geometry.width * options.density / 2;
|
|
562
|
+
const halfHeight = piece.geometry.height * options.density / 2;
|
|
563
|
+
const rect = {
|
|
564
|
+
centerX: piece.centerX,
|
|
565
|
+
centerY: piece.centerY,
|
|
566
|
+
halfWidth,
|
|
567
|
+
halfHeight
|
|
568
|
+
};
|
|
569
|
+
const fresh = index === 0 ? probe.sample(contentTexture, width, height, rect) : probe.statsFor(width, height, rect);
|
|
570
|
+
const stats = fresh ? settleStats(index, fresh) : null;
|
|
571
|
+
probes.push(stats);
|
|
572
|
+
gl.bindFramebuffer(gl.FRAMEBUFFER, null);
|
|
573
|
+
gl.viewport(0, 0, width, height);
|
|
574
|
+
gl.enable(gl.BLEND);
|
|
575
|
+
gl.blendFunc(gl.ONE, gl.ONE_MINUS_SRC_ALPHA);
|
|
576
|
+
const reachOf = (m) => m ? Math.hypot(m.offsetX, m.offsetY) + Math.max(m.width, m.height) / 2 + m.smoothing : 0;
|
|
577
|
+
const morphReach = Math.max(reachOf(piece.morph), reachOf(piece.morph2));
|
|
578
|
+
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;
|
|
579
|
+
const padPx = (lensPadDp(piece.geometry, piece.optics) + surfacePadDp(piece.geometry, 0) + morphReach + touchReach) * options.density;
|
|
580
|
+
const left = Math.max(0, Math.floor(piece.centerX - halfWidth - padPx));
|
|
581
|
+
const right = Math.min(width, Math.ceil(piece.centerX + halfWidth + padPx));
|
|
582
|
+
const top = Math.max(0, Math.floor(height - (piece.centerY + halfHeight + padPx)));
|
|
583
|
+
const bottom = Math.min(height, Math.ceil(height - (piece.centerY - halfHeight - padPx)));
|
|
584
|
+
gl.enable(gl.SCISSOR_TEST);
|
|
585
|
+
gl.scissor(left, top, Math.max(0, right - left), Math.max(0, bottom - top));
|
|
586
|
+
gl.useProgram(lensProgram);
|
|
587
|
+
bindTextureAt(gl, 0, contentTexture, lensProgram, "content");
|
|
588
|
+
setUniform(gl, lensLoc("u_contentSize"), [width, height]);
|
|
589
|
+
setUniform(gl, lensLoc("u_resolution"), [width, height]);
|
|
590
|
+
setUniform(gl, lensLoc("u_center"), [piece.centerX, piece.centerY]);
|
|
591
|
+
setUniform(gl, lensLoc("u_reach"), Math.max(width, height));
|
|
592
|
+
setUniform(gl, lensLoc("u_contentMin"), [1, 1]);
|
|
593
|
+
setUniform(gl, lensLoc("u_contentMax"), [width - 1, height - 1]);
|
|
594
|
+
if (stats) {
|
|
595
|
+
setUniform(gl, lensLoc("u_probeLuma"), stats.luma);
|
|
596
|
+
setUniform(gl, lensLoc("u_probeBusy"), stats.busy);
|
|
597
|
+
setUniform(gl, lensLoc("u_probeRange"), [stats.lo, stats.hi]);
|
|
598
|
+
setUniform(gl, lensLoc("u_probeSlope"), [stats.slopeX, stats.slopeY]);
|
|
599
|
+
setUniform(gl, lensLoc("u_probe"), [stats.r, stats.g, stats.b]);
|
|
600
|
+
} else {
|
|
601
|
+
setUniform(gl, lensLoc("u_probeLuma"), -1);
|
|
602
|
+
}
|
|
603
|
+
const lens = toLensProps(piece.optics, piece.geometry, options.density, {
|
|
604
|
+
debug: options.debug,
|
|
605
|
+
morph: piece.morph,
|
|
606
|
+
morph2: piece.morph2,
|
|
607
|
+
touch: piece.touch,
|
|
608
|
+
progress: piece.progress,
|
|
609
|
+
light: piece.light,
|
|
610
|
+
appear: piece.appear,
|
|
611
|
+
accent: piece.accent
|
|
612
|
+
});
|
|
613
|
+
applyChannel(gl, lensLoc, lens.uniformNames, lens.uniformSizes, lens.uniformValues);
|
|
614
|
+
drawFullscreenTriangle(gl);
|
|
615
|
+
gl.useProgram(surfaceProgram);
|
|
616
|
+
const rawSurface = toSurfaceUniforms(piece.optics, piece.geometry, {
|
|
617
|
+
debug: options.debug,
|
|
618
|
+
morph: piece.morph,
|
|
619
|
+
morph2: piece.morph2,
|
|
620
|
+
bodyInLens: true,
|
|
621
|
+
touch: piece.touch,
|
|
622
|
+
progress: piece.progress,
|
|
623
|
+
// The shadow is denser over text, weaker over a flat backdrop (M 11:47). The rule lives
|
|
624
|
+
// in the core: while it lived here, Android didn't have it at all.
|
|
625
|
+
shadow: stats ? shadowOpacity(stats.busy) : 1,
|
|
626
|
+
// The probe has already computed the ambient color — the shadow gets it for free. Taken
|
|
627
|
+
// as-is: `settleStats` has already smoothed it, and rounding would bring back exactly the
|
|
628
|
+
// steps the smoothing was written to remove.
|
|
629
|
+
ambient: stats ? [stats.r, stats.g, stats.b] : void 0,
|
|
630
|
+
appear: piece.appear,
|
|
631
|
+
lift: piece.lift
|
|
632
|
+
});
|
|
633
|
+
applyObject(gl, surfaceLoc, {
|
|
634
|
+
...toDeviceSurfaceUniforms(rawSurface, options.density),
|
|
635
|
+
u_center: [piece.centerX, piece.centerY]
|
|
636
|
+
});
|
|
637
|
+
setUniform(gl, surfaceLoc(DYNAMIC_UNIFORMS[0]), piece.press ?? 0);
|
|
638
|
+
setUniform(gl, surfaceLoc(DYNAMIC_UNIFORMS[1]), piece.active ?? 0);
|
|
639
|
+
const hasIcon = Boolean(piece.icon && options.iconMask);
|
|
640
|
+
setUniform(gl, surfaceLoc(ICON_UNIFORMS[0]), hasIcon ? 1 : 0);
|
|
641
|
+
setUniform(gl, surfaceLoc(ICON_UNIFORMS[1]), 1);
|
|
642
|
+
setUniform(gl, surfaceLoc(ICON_UNIFORMS[2]), piece.inkIdle ?? [1, 1, 1, 1]);
|
|
643
|
+
setUniform(gl, surfaceLoc(ICON_UNIFORMS[3]), piece.inkActive ?? [1, 1, 1, 1]);
|
|
644
|
+
bindTextureAt(gl, 1, iconTexture, surfaceProgram, "u_icon");
|
|
645
|
+
setUniform(gl, surfaceLoc("u_iconSize"), hasIcon ? [width, height] : [1, 1]);
|
|
646
|
+
const hasColor = Boolean(piece.overlay && options.colorLayer);
|
|
647
|
+
setUniform(gl, surfaceLoc(OVERLAY_UNIFORMS[0]), hasColor ? 1 : 0);
|
|
648
|
+
bindTextureAt(gl, 2, colorTexture, surfaceProgram, "u_overlay");
|
|
649
|
+
setUniform(gl, surfaceLoc("u_overlaySize"), hasColor ? [width, height] : [1, 1]);
|
|
650
|
+
setUniform(gl, surfaceLoc("u_resolution"), [width, height]);
|
|
651
|
+
drawFullscreenTriangle(gl);
|
|
652
|
+
}
|
|
653
|
+
gl.disable(gl.SCISSOR_TEST);
|
|
654
|
+
return { probes };
|
|
655
|
+
});
|
|
557
656
|
}
|
|
558
657
|
function destroy() {
|
|
559
658
|
probe.destroy();
|
|
@@ -563,10 +662,12 @@ function createVireGlassRenderer(canvas) {
|
|
|
563
662
|
gl.deleteTexture(contentTexture);
|
|
564
663
|
gl.deleteTexture(iconTexture);
|
|
565
664
|
gl.deleteTexture(colorTexture);
|
|
665
|
+
if (backdropFbo) gl.deleteFramebuffer(backdropFbo);
|
|
666
|
+
gpuTimer.destroy();
|
|
566
667
|
}
|
|
567
|
-
return { resize, render, destroy };
|
|
668
|
+
return { resize, render, getLastGpuMs: gpuTimer.getLastMs, destroy };
|
|
568
669
|
}
|
|
569
670
|
|
|
570
|
-
export { PROBE_GRID_HEIGHT, PROBE_GRID_WIDTH, createProbe, createVireGlassRenderer };
|
|
671
|
+
export { FULLSCREEN_TRIANGLE_VERTEX_SOURCE, PROBE_GRID_HEIGHT, PROBE_GRID_WIDTH, bindTextureAt, compileShader, createFramebuffer, createProbe, createProgram, createTexture, createVireGlassRenderer, drawFullscreenTriangle, locationCache, setUniform };
|
|
571
672
|
//# sourceMappingURL=index.js.map
|
|
572
673
|
//# sourceMappingURL=index.js.map
|