@displayxr/inline3d 1.47.5 → 1.48.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/CHANGELOG.md +143 -1
- package/README.md +7 -1
- package/call.d.ts +98 -8
- package/camera.d.ts +34 -5
- package/dist/call.js +8 -6
- package/docs/content-lifecycle-rules.md +270 -0
- package/docs/woven-canvas-rules.md +25 -13
- package/js/call/element.js +7 -3
- package/js/call/options.js +20 -3
- package/js/call/transport.js +11 -1
- package/js/call/ui.js +8 -1
- package/js/call/wire.js +163 -6
- package/js/camera/capture.js +107 -20
- package/js/camera/conv-meter.js +152 -0
- package/js/inline3d-call.js +392 -71
- package/js/inline3d-camera.js +29 -6
- package/js/inline3d-model-entry.js +3 -0
- package/js/inline3d-model-playcanvas.js +36 -2
- package/js/inline3d-pc-auto-release.js +191 -0
- package/js/inline3d-pc-release.js +107 -0
- package/js/inline3d-pc-shader-cache.js +172 -0
- package/js/inline3d-playcanvas-engine.js +2 -1
- package/js/inline3d-player.js +8 -4
- package/js/inline3d-splat-deferred.js +2 -0
- package/js/inline3d-splat-diag.js +125 -4
- package/js/inline3d-splat-playcanvas.js +40 -3
- package/js/inline3d-splat.js +2 -0
- package/llms.txt +1 -0
- package/model.d.ts +12 -0
- package/package.json +12 -1
- package/signaling/README.md +289 -0
- package/signaling/dev-server.mjs +320 -0
- package/signaling/gate.mjs +112 -0
- package/signaling/keys.mjs +123 -0
- package/signaling/limits.mjs +221 -0
- package/signaling/room.mjs +322 -0
- package/signaling/turn-budget.mjs +254 -0
- package/splat.d.ts +12 -0
package/js/inline3d-player.js
CHANGED
|
@@ -658,10 +658,14 @@ function attachFlatPaint(canvas, video, { mode, getPoster, dissolve, fit = null,
|
|
|
658
658
|
// pair: two squished halves, no 3D. That is why the "scrim" here is a bottom band with a
|
|
659
659
|
// capped height, and NOT the full-height gradient a 2D player would reach for. Same reason
|
|
660
660
|
// the centre badge is a small circle rather than a full-tile click-catcher.
|
|
661
|
-
// 2. NO `backdrop-filter
|
|
662
|
-
// resource
|
|
663
|
-
//
|
|
664
|
-
//
|
|
661
|
+
// 2. NO `backdrop-filter` in this chrome. On a pre-draw-order browser exclusion needs the
|
|
662
|
+
// element as an isolated composited resource, and `backdrop-filter` is a function of what is
|
|
663
|
+
// behind it, so the element either weaves anyway or drops out; the near-solid background
|
|
664
|
+
// below is the substitute, and the player keeps it so one build works on every generation.
|
|
665
|
+
// On DisplayXR Browser 0.1.16+ a frosted element OVER a tile works (browser#120: the
|
|
666
|
+
// compositor draws the tile mono inside each frost rect; samples/composition is the
|
|
667
|
+
// validated case) — never a frost over the WHOLE visible tile, and never an effect on the
|
|
668
|
+
// canvas or an ancestor (woven-canvas rule 7).
|
|
665
669
|
// 3. Promotion is `will-change: transform`, never a CSS `filter` — a filter's render surface is
|
|
666
670
|
// flattened away in the weave path.
|
|
667
671
|
//
|
|
@@ -126,6 +126,8 @@ export function addSplatDeferred(wall, canvas, src, opts) {
|
|
|
126
126
|
},
|
|
127
127
|
// Mesh-program warm before the adapter has loaded: runs once it has (it needs the engine).
|
|
128
128
|
warmEntity: (entity, o) => out.ready.then(() => out.warmEntity(entity, o)),
|
|
129
|
+
// Nothing a page added can exist before the engine has: nothing to release yet.
|
|
130
|
+
releaseEntity: () => ({ programs: 0, container: null }),
|
|
129
131
|
setEffect: (name, params) => {
|
|
130
132
|
validateEffectCall(name, params, 'set');
|
|
131
133
|
pending.push(['setEffect', [name, params]]);
|
|
@@ -34,6 +34,19 @@
|
|
|
34
34
|
// located them, and a live outgoing photo goes back on the pre-1.24 node chain (which
|
|
35
35
|
// scales its disparity by the two rigs' window ratio at the swap). A/B for "the
|
|
36
36
|
// outgoing photo jumps when the incoming rig is declared".
|
|
37
|
+
// keepshaders — leave the engine's compiled-shader cache as it ships (no per-program release,
|
|
38
|
+
// ./inline3d-pc-shader-cache.js). Page-wide: the engine's shader class is shared.
|
|
39
|
+
// keepprograms — a material the page destroys keeps the programs it linked, as the engine ships
|
|
40
|
+
// (./inline3d-pc-auto-release.js). Per tile.
|
|
41
|
+
//
|
|
42
|
+
// LIVE GL OBJECTS (docs/content-lifecycle-rules.md): programs and shaders created / deleted on the
|
|
43
|
+
// tile's context since diag attached, and the live difference, on the overlay and in the dump.
|
|
44
|
+
// Sampled at every content swap the SDK can see: each setSource, each glTF container the tile's
|
|
45
|
+
// asset registry loads (a page swapping meshes under handle.engine.root), each
|
|
46
|
+
// handle.releaseEntity. When the live program count has not dropped across the last LEAK_SWAPS
|
|
47
|
+
// swaps of one kind and rose at more than 60% of them, one console warning says so. A page that
|
|
48
|
+
// releases what a swap replaces goes up and down, or plateaus once its own transitions are
|
|
49
|
+
// compiled (a few rises, then flat); one that leaks climbs.
|
|
37
50
|
//
|
|
38
51
|
// Per frame, `rigAt` / `rigIn` / `rigOut` say which declared rig the views were located for
|
|
39
52
|
// (the tracker's id; null = none matched), which rig the current photo was drawn through
|
|
@@ -52,8 +65,28 @@
|
|
|
52
65
|
// - the settle itself: the SDK's settle work in ms, then how long the SAME task kept running
|
|
53
66
|
// after it (the page's continuation of `await setSource`).
|
|
54
67
|
|
|
68
|
+
import { autoReleaseStats, autoReleaseState } from './inline3d-pc-auto-release.js';
|
|
69
|
+
|
|
55
70
|
/** The switches `diag` / `?dxrdiag` understand, besides the plain on values. */
|
|
56
|
-
export const DIAG_SWITCHES = Object.freeze(['norig', 'frozen', 'nowarm', 'cold', 'nooverlay', 'oldpick', 'nolayerrig', 'oldrig']);
|
|
71
|
+
export const DIAG_SWITCHES = Object.freeze(['norig', 'frozen', 'nowarm', 'cold', 'nooverlay', 'oldpick', 'nolayerrig', 'oldrig', 'keepshaders', 'keepprograms']);
|
|
72
|
+
/**
|
|
73
|
+
* The leak watch's window: content swaps of one kind with no drop in live programs, more than 60%
|
|
74
|
+
* of them a rise. Wide enough that a first pass through the transitions (each compiles its own
|
|
75
|
+
* programs once: four rises for cut / crossfade / wavefront / flip) does not trip it.
|
|
76
|
+
*/
|
|
77
|
+
export const LEAK_SWAPS = 10;
|
|
78
|
+
|
|
79
|
+
/** The leak watch's rule on one kind's samples (oldest first): the warning's condition. */
|
|
80
|
+
export function looksLikeLeak(samples, n = LEAK_SWAPS) {
|
|
81
|
+
if (samples.length < n + 1) return false;
|
|
82
|
+
const w = samples.slice(-(n + 1));
|
|
83
|
+
let rises = 0;
|
|
84
|
+
for (let i = 1; i < w.length; i++) {
|
|
85
|
+
if (w[i] < w[i - 1]) return false;
|
|
86
|
+
if (w[i] > w[i - 1]) rises++;
|
|
87
|
+
}
|
|
88
|
+
return rises > n * 0.6;
|
|
89
|
+
}
|
|
57
90
|
const ON_TOKENS = new Set(['1', 'on', 'true', 'yes']);
|
|
58
91
|
|
|
59
92
|
/**
|
|
@@ -207,6 +240,12 @@ export class DiagRecorder {
|
|
|
207
240
|
this._listeners = new Set();
|
|
208
241
|
this._lafo = null;
|
|
209
242
|
this._gl = null; // the wrapped context + its originals, for dispose()
|
|
243
|
+
/** GL object counts on the tile's context (instrumentGl): programs / shaders created + deleted. */
|
|
244
|
+
this.glObjects = { programs: { created: 0, deleted: 0 }, shaders: { created: 0, deleted: 0 } };
|
|
245
|
+
this.shaderCacheState = null; // () => the engine shader-cache patch state (./inline3d-pc-shader-cache.js)
|
|
246
|
+
this.programReleaseState = null; // () => { state, released, … } (./inline3d-pc-auto-release.js)
|
|
247
|
+
this._swapLive = new Map(); // kind → live programs at each content swap of that kind (the leak watch)
|
|
248
|
+
this._leakWarned = false;
|
|
210
249
|
this._burst = null; // the pick burst of the current task
|
|
211
250
|
}
|
|
212
251
|
|
|
@@ -266,6 +305,25 @@ export class DiagRecorder {
|
|
|
266
305
|
*/
|
|
267
306
|
instrumentGl(gl) {
|
|
268
307
|
if (!gl || this._gl) return;
|
|
308
|
+
// Object counts first (no timing): what a content swap leaves behind.
|
|
309
|
+
const objs = this.glObjects;
|
|
310
|
+
const counters = {
|
|
311
|
+
createProgram: (r) => r && objs.programs.created++,
|
|
312
|
+
deleteProgram: (r, a) => a[0] && objs.programs.deleted++,
|
|
313
|
+
createShader: (r) => r && objs.shaders.created++,
|
|
314
|
+
deleteShader: (r, a) => a[0] && objs.shaders.deleted++,
|
|
315
|
+
};
|
|
316
|
+
const counted = [];
|
|
317
|
+
for (const [name, count] of Object.entries(counters)) {
|
|
318
|
+
const orig = gl[name];
|
|
319
|
+
if (typeof orig !== 'function') continue;
|
|
320
|
+
gl[name] = function (...a) {
|
|
321
|
+
const r = orig.apply(this, a);
|
|
322
|
+
count(r, a);
|
|
323
|
+
return r;
|
|
324
|
+
};
|
|
325
|
+
counted.push(name);
|
|
326
|
+
}
|
|
269
327
|
const LINK = 0x8b82;
|
|
270
328
|
const COMPLETION = 0x91b1;
|
|
271
329
|
const kinds = {
|
|
@@ -294,7 +352,37 @@ export class DiagRecorder {
|
|
|
294
352
|
};
|
|
295
353
|
saved.push(name);
|
|
296
354
|
}
|
|
297
|
-
this._gl = { gl, saved };
|
|
355
|
+
this._gl = { gl, saved: [...counted, ...saved] };
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
/** Live programs / shaders on the tile's context since instrumentGl (created − deleted). */
|
|
359
|
+
liveGl() {
|
|
360
|
+
const { programs: p, shaders: s } = this.glObjects;
|
|
361
|
+
return { programs: p.created - p.deleted, shaders: s.created - s.deleted };
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
/**
|
|
365
|
+
* A content swap happened (`kind`: 'setSource' at its start, 'container' at a glTF load,
|
|
366
|
+
* 'release' after a releaseEntity): sample the live program count, and warn once when the
|
|
367
|
+
* samples of that kind look like a leak (looksLikeLeak).
|
|
368
|
+
*/
|
|
369
|
+
contentSwap(kind) {
|
|
370
|
+
if (!this._gl) return null;
|
|
371
|
+
const live = this.liveGl();
|
|
372
|
+
let w = this._swapLive.get(kind);
|
|
373
|
+
if (!w) this._swapLive.set(kind, (w = []));
|
|
374
|
+
w.push(live.programs);
|
|
375
|
+
if (w.length > LEAK_SWAPS + 1) w.shift();
|
|
376
|
+
this._event('swap', { kind, ...live }, this.now());
|
|
377
|
+
if (!this._leakWarned && looksLikeLeak(w)) {
|
|
378
|
+
this._leakWarned = true;
|
|
379
|
+
console.warn(
|
|
380
|
+
`[inline3d/${this.label.split('#')[0]}] diag: live WebGL programs never dropped across the last ${LEAK_SWAPS} content swaps ` +
|
|
381
|
+
`and rose at most of them (${kind}: ${w.join(' → ')}). Something each swap replaces is not released — see docs/content-lifecycle-rules.md ` +
|
|
382
|
+
'(handle.releaseEntity for content under handle.engine.root). Warned once.',
|
|
383
|
+
);
|
|
384
|
+
}
|
|
385
|
+
return live;
|
|
298
386
|
}
|
|
299
387
|
|
|
300
388
|
/** One GL call of `kind` that took `ms`, at `t` (instrumentGl's wrappers; tests call it directly). */
|
|
@@ -578,6 +666,7 @@ export class DiagRecorder {
|
|
|
578
666
|
label: this.label,
|
|
579
667
|
switches: [...this.switches],
|
|
580
668
|
rigLocked: this.rigLocked,
|
|
669
|
+
glObjects: { ...this.glObjects, live: this.liveGl(), shaderCache: safe(() => this.shaderCacheState?.()) ?? null, programRelease: safe(() => this.programReleaseState?.()) ?? null },
|
|
581
670
|
transitions: this.transitions,
|
|
582
671
|
events: this.events,
|
|
583
672
|
frames: this.frames,
|
|
@@ -794,7 +883,8 @@ export function startDiagLoop(rec, { overlay = true, doc = typeof document !== '
|
|
|
794
883
|
`dxr-diag ${rec.label} phase ${rec.phase}` +
|
|
795
884
|
(Number.isFinite(rec.callAt) ? ` +${Math.round(now - rec.callAt)} ms since setSource` : '') +
|
|
796
885
|
(rec.switches.size ? ` [${[...rec.switches].join(',')}]` : '') +
|
|
797
|
-
(last ? `\nframe dt ${last.dt} ms held ${last.held ? 'YES' : 'no'} img ${last.img}${last.img !== 'none' ? ' ' + last.imgW : ''}` : '')
|
|
886
|
+
(last ? `\nframe dt ${last.dt} ms held ${last.held ? 'YES' : 'no'} img ${last.img}${last.img !== 'none' ? ' ' + last.imgW : ''}` : '') +
|
|
887
|
+
(rec._gl ? `\n${glObjectsLine(rec)}` : '');
|
|
798
888
|
strip(rec.frames, 0, now - WINDOW_MS, now, rec.callAt);
|
|
799
889
|
if (frozen && g) {
|
|
800
890
|
strip(frozen.window.map((f) => ({ ...f })), STRIP_H + 18, -PRE_MS, WINDOW_MS - PRE_MS, 0);
|
|
@@ -818,6 +908,37 @@ export function startDiagLoop(rec, { overlay = true, doc = typeof document !== '
|
|
|
818
908
|
};
|
|
819
909
|
}
|
|
820
910
|
|
|
911
|
+
/**
|
|
912
|
+
* The live-GL-object watch on a PlayCanvas tile: count the context's programs and shaders, show
|
|
913
|
+
* the engine shader-cache patch state, and sample at the content swaps the SDK can see (a
|
|
914
|
+
* container load in the tile's registry, a releaseEntity). setSource samples itself.
|
|
915
|
+
*/
|
|
916
|
+
export function watchContent(rec, app, viewer) {
|
|
917
|
+
rec.instrumentGl(app?.graphicsDevice?.gl);
|
|
918
|
+
rec.shaderCacheState = () => app?.graphicsDevice?.__dxrShaderCache ?? null;
|
|
919
|
+
rec.programReleaseState = () => {
|
|
920
|
+
const state = app?.graphicsDevice?.__dxrAutoRelease ?? null;
|
|
921
|
+
const st = autoReleaseStats(app);
|
|
922
|
+
return state ? { state, ...(st || {}) } : null;
|
|
923
|
+
};
|
|
924
|
+
const ar = autoReleaseState(app);
|
|
925
|
+
if (ar) ar.onSweep = (n) => n && rec.contentSwap('auto-release');
|
|
926
|
+
if (viewer) viewer.onContentReleased = () => rec.contentSwap('release');
|
|
927
|
+
app?.assets?.on?.('load', (a) => a?.type === 'container' && rec.contentSwap('container'));
|
|
928
|
+
}
|
|
929
|
+
|
|
930
|
+
/** The overlay's GL objects line: live programs and shaders (created / deleted), the cache patch. */
|
|
931
|
+
export function glObjectsLine(rec) {
|
|
932
|
+
const { programs: p, shaders: s } = rec.glObjects;
|
|
933
|
+
const cache = safe(() => rec.shaderCacheState?.());
|
|
934
|
+
const rel = safe(() => rec.programReleaseState?.());
|
|
935
|
+
return (
|
|
936
|
+
`gl programs ${p.created - p.deleted} live (+${p.created} −${p.deleted}) shaders ${s.created - s.deleted} live (+${s.created} −${s.deleted})` +
|
|
937
|
+
(cache ? ` cache ${cache}` : '') +
|
|
938
|
+
(rel ? ` release ${rel.state}${rel.released !== undefined ? ` (−${rel.released})` : ''}` : '')
|
|
939
|
+
);
|
|
940
|
+
}
|
|
941
|
+
|
|
821
942
|
/**
|
|
822
943
|
* `window.__dxrDiag`: every diag tile on the page. `dump()` is the JSON to paste back;
|
|
823
944
|
* `last` the most recent tile's recorder; `transitions` every tile's transition summaries.
|
|
@@ -839,7 +960,7 @@ export function registerDiag(rec) {
|
|
|
839
960
|
},
|
|
840
961
|
};
|
|
841
962
|
}
|
|
842
|
-
rec.label =
|
|
963
|
+
rec.label = `${rec.label || 'splat'}#${g.tiles.length + 1}`;
|
|
843
964
|
g.tiles.push(rec);
|
|
844
965
|
const dispose = rec.dispose.bind(rec);
|
|
845
966
|
rec.dispose = () => {
|
|
@@ -73,7 +73,7 @@ import { viewerEaseFor, frameTrackingState } from './inline3d-viewer-ease.js';
|
|
|
73
73
|
import { VideoPlane, validateSetVideo, PAGE_VIDEO_ERROR, eyeSplit, EYE_SPLIT_UNIFORM, makeSbsMaterial } from './inline3d-splat-video.js';
|
|
74
74
|
import { LayerRigCameras, validateLayerRig, validateLayerRigOptions, DEFAULT_VIEWER_DISTANCE_M } from './inline3d-splat-layer-rig.js';
|
|
75
75
|
import { RigTracker, remapViews, nodePose, sameRig } from './inline3d-splat-rig-map.js';
|
|
76
|
-
import { resolveDiag, DiagRecorder, startDiagLoop, registerDiag, DIAG_SWITCHES } from './inline3d-splat-diag.js';
|
|
76
|
+
import { resolveDiag, DiagRecorder, startDiagLoop, registerDiag, watchContent, DIAG_SWITCHES } from './inline3d-splat-diag.js';
|
|
77
77
|
import {
|
|
78
78
|
glBufferLimits,
|
|
79
79
|
probeBufferLimits,
|
|
@@ -127,6 +127,9 @@ import {
|
|
|
127
127
|
framedSubjectBounds,
|
|
128
128
|
} from './inline3d-splat-shared.js';
|
|
129
129
|
import { resolveCursorOption } from './inline3d-cursor-option.js';
|
|
130
|
+
import { installShaderCacheRelease } from './inline3d-pc-shader-cache.js';
|
|
131
|
+
import { installAutoProgramRelease } from './inline3d-pc-auto-release.js';
|
|
132
|
+
import { releaseEntity as releaseEntityIn } from './inline3d-pc-release.js';
|
|
130
133
|
|
|
131
134
|
/**
|
|
132
135
|
* The component systems the tile's `AppBase` registers. Camera + GSplat draw the splat; Render,
|
|
@@ -1284,6 +1287,18 @@ export class PlayCanvasSplatViewer {
|
|
|
1284
1287
|
return shaders.size;
|
|
1285
1288
|
}
|
|
1286
1289
|
|
|
1290
|
+
/**
|
|
1291
|
+
* Destroy `entity` (content a page added under `engine.root`) and release the programs its
|
|
1292
|
+
* materials linked, plus its glTF container (`asset`) when nothing else draws from it —
|
|
1293
|
+
* ./inline3d-pc-release.js, docs/content-lifecycle-rules.md. Returns { programs, container }.
|
|
1294
|
+
*/
|
|
1295
|
+
releaseEntity(entity, { asset = null } = {}) {
|
|
1296
|
+
if (!this.app || this._disposed) return { programs: 0, container: null };
|
|
1297
|
+
const r = releaseEntityIn(this.app, entity, { asset });
|
|
1298
|
+
this.onContentReleased?.(r);
|
|
1299
|
+
return r;
|
|
1300
|
+
}
|
|
1301
|
+
|
|
1287
1302
|
useSceneColor(on = true) {
|
|
1288
1303
|
this.sceneColor = !!on;
|
|
1289
1304
|
const cams = [this.eye, ...(this._viewPath === 'cameras' ? this._views : [])];
|
|
@@ -1415,7 +1430,7 @@ export class PlayCanvasSplatViewer {
|
|
|
1415
1430
|
* @param {object} o.perf the resolved playcanvasPerfSettings().
|
|
1416
1431
|
* @param {string} [o.viewPath] force 'cameras' | 'renderview' (diagnostics).
|
|
1417
1432
|
*/
|
|
1418
|
-
async attachEngine(pc, { preserveDrawingBuffer = false, perf, viewPath, antialias = false, patchSplats = true } = {}) {
|
|
1433
|
+
async attachEngine(pc, { preserveDrawingBuffer = false, perf, viewPath, antialias = false, patchSplats = true, shaderCacheRelease = true, programRelease = true } = {}) {
|
|
1419
1434
|
this.pc = pc;
|
|
1420
1435
|
const device = await pc.createGraphicsDevice(this.canvas, {
|
|
1421
1436
|
deviceTypes: [pc.DEVICETYPE_WEBGL2],
|
|
@@ -1431,6 +1446,11 @@ export class PlayCanvasSplatViewer {
|
|
|
1431
1446
|
device.destroy?.();
|
|
1432
1447
|
return null;
|
|
1433
1448
|
}
|
|
1449
|
+
// The engine keeps every compiled shader until the device dies, even once its program is
|
|
1450
|
+
// destroyed: count them per program and free the last one out (./inline3d-pc-shader-cache.js).
|
|
1451
|
+
// `?dxrdiag=keepshaders` leaves the engine's cache as it ships (A/B; page-wide, first tile wins).
|
|
1452
|
+
if (shaderCacheRelease) installShaderCacheRelease(device);
|
|
1453
|
+
else device.__dxrShaderCache ??= 'off (keepshaders)';
|
|
1434
1454
|
const opts = new pc.AppOptions();
|
|
1435
1455
|
opts.graphicsDevice = device;
|
|
1436
1456
|
// No xr (AppBase constructs XrManager only when asked, and XrManager is what probes and
|
|
@@ -1442,6 +1462,11 @@ export class PlayCanvasSplatViewer {
|
|
|
1442
1462
|
const app = new pc.AppBase(this.canvas);
|
|
1443
1463
|
app.init(opts);
|
|
1444
1464
|
guardDuplicateSystems(app);
|
|
1465
|
+
// A material the page destroys (entity.destroy, a container's unload) keeps every program it
|
|
1466
|
+
// linked for the device's life: release them a frame later, when nothing else uses them
|
|
1467
|
+
// (./inline3d-pc-auto-release.js). `?dxrdiag=keepprograms` leaves the engine as it ships (A/B).
|
|
1468
|
+
if (programRelease) installAutoProgramRelease(app, pc.Material);
|
|
1469
|
+
else device.__dxrAutoRelease ??= 'off (keepprograms)';
|
|
1445
1470
|
// The SDK sizes the buffer (double-width in 3D, 1:1 in mono), so the engine must never
|
|
1446
1471
|
// resize it. RESOLUTION_FIXED is AppBase's DEFAULT, and with it `updateCanvasSize()` is a
|
|
1447
1472
|
// no-op. Deliberately NOT calling setCanvasResolution/setCanvasFillMode: without explicit
|
|
@@ -3583,6 +3608,14 @@ export function attachPlayCanvasSplat(out, wall, canvas, src, opts, pending = []
|
|
|
3583
3608
|
await first.catch(() => null);
|
|
3584
3609
|
return removed ? 0 : viewer.warmEntity(entity, o);
|
|
3585
3610
|
},
|
|
3611
|
+
/**
|
|
3612
|
+
* Destroy `entity` (content under handle.engine.root) and release the programs its materials
|
|
3613
|
+
* linked, and its container `asset` when nothing else draws from it — viewer.releaseEntity.
|
|
3614
|
+
* Returns { programs, container }.
|
|
3615
|
+
*/
|
|
3616
|
+
releaseEntity(entity, o) {
|
|
3617
|
+
return removed ? { programs: 0, container: null } : viewer.releaseEntity(entity, o);
|
|
3618
|
+
},
|
|
3586
3619
|
setStereo,
|
|
3587
3620
|
setDepthEnvelope,
|
|
3588
3621
|
/**
|
|
@@ -4745,12 +4778,15 @@ export function attachPlayCanvasSplat(out, wall, canvas, src, opts, pending = []
|
|
|
4745
4778
|
perf: perfResolved,
|
|
4746
4779
|
viewPath: opts.playcanvasViewPath,
|
|
4747
4780
|
antialias: antialias === true,
|
|
4781
|
+
shaderCacheRelease: !diag?.has('keepshaders'),
|
|
4782
|
+
programRelease: !diag?.has('keepprograms'),
|
|
4748
4783
|
});
|
|
4749
4784
|
if (!app || removed) return null;
|
|
4750
4785
|
pcModule = pc;
|
|
4751
4786
|
// diag: count the GL calls that can block (compile, link, status queries, readbacks, syncs),
|
|
4752
4787
|
// from before the first asset's compile on
|
|
4753
|
-
|
|
4788
|
+
// + the live program / shader counts and the content-swap leak watch
|
|
4789
|
+
if (diag) watchContent(diag, app, viewer);
|
|
4754
4790
|
/**
|
|
4755
4791
|
* ADVANCED, not covered by the semver promise: the engine objects behind this window.
|
|
4756
4792
|
* `app` is the tile's `pc.AppBase`; `root` the content root, in the splat's content space
|
|
@@ -5335,6 +5371,7 @@ export function attachPlayCanvasSplat(out, wall, canvas, src, opts, pending = []
|
|
|
5335
5371
|
const gen = ++sourceGen;
|
|
5336
5372
|
if (diag) {
|
|
5337
5373
|
rigLockArmed = true;
|
|
5374
|
+
diag.contentSwap('setSource'); // the previous swap's outgoing asset is released by now
|
|
5338
5375
|
diag.setPhase('swap', {
|
|
5339
5376
|
transition: plan.sequence ? `${plan.sequence.name}(${plan.sequence.out.effect}>${plan.sequence.in.effect})` : plan.transition,
|
|
5340
5377
|
outgoing: plan.sequence ? 'none (one splat)' : plan.outgoing || 'default',
|
package/js/inline3d-splat.js
CHANGED
|
@@ -447,6 +447,8 @@ function addSplatUnguarded(wall, canvas, src, opts = {}) {
|
|
|
447
447
|
},
|
|
448
448
|
/** PlayCanvas-backend feature (its renderer links synchronously); nothing to warm here. */
|
|
449
449
|
warmEntity: () => Promise.resolve(0),
|
|
450
|
+
/** PlayCanvas-backend feature: three.js content is the page's to dispose (docs/content-lifecycle-rules.md). */
|
|
451
|
+
releaseEntity: () => ({ programs: 0, container: null }),
|
|
450
452
|
stopEffect() {
|
|
451
453
|
throw effectsNotOnSpark('stopEffect()');
|
|
452
454
|
},
|
package/llms.txt
CHANGED
|
@@ -15,6 +15,7 @@ detail, a cover that always comes off). The SDK's defaults do the rest.
|
|
|
15
15
|
## Reference
|
|
16
16
|
- docs/authoring-inline-3d.md — every API, both view rigs, comfort maths
|
|
17
17
|
- docs/woven-canvas-rules.md — what the browser does to a woven canvas; CSS, remount and cover rules
|
|
18
|
+
- docs/content-lifecycle-rules.md — swapping content in a window: release programs, textures, containers (handle.releaseEntity)
|
|
18
19
|
- docs/porting-three-js-apps.md — moving an existing three.js app over
|
|
19
20
|
- docs/authoring-motion-and-effects.md, docs/splat-effects.md — motion, reveals, depth envelopes
|
|
20
21
|
- index.d.ts, three.d.ts, viewer.d.ts, model.d.ts, splat.d.ts — the public API with JSDoc
|
package/model.d.ts
CHANGED
|
@@ -283,6 +283,18 @@ export interface ModelHandle {
|
|
|
283
283
|
* docs/playcanvas-adapter.md §warmEntity.
|
|
284
284
|
*/
|
|
285
285
|
warmEntity(entity?: unknown, opts?: { timeoutMs?: number }): Promise<number>;
|
|
286
|
+
/**
|
|
287
|
+
* `engine: 'playcanvas'` only (a no-op returning `{ programs: 0, container: null }` on three).
|
|
288
|
+
* Destroy `entity` — content you added under `handle.engine.root` — and release what it held on
|
|
289
|
+
* the GPU: the shader programs its materials linked (PlayCanvas never frees a program by itself:
|
|
290
|
+
* its ProgramLibrary caches every variant for the device's life, and `Material.destroy()` does
|
|
291
|
+
* not destroy them), and, with `asset`, the glTF container it was instantiated from (unloaded
|
|
292
|
+
* and taken out of the registry unless something else in the tile still draws from it). A
|
|
293
|
+
* program another material still uses is kept. Call it for whatever a content swap replaces.
|
|
294
|
+
* Returns the number of programs destroyed and what happened to the container.
|
|
295
|
+
* docs/content-lifecycle-rules.md.
|
|
296
|
+
*/
|
|
297
|
+
releaseEntity(entity: unknown, opts?: { asset?: unknown }): { programs: number; container: 'unloaded' | 'in-use' | null };
|
|
286
298
|
unexclude(el: Element): void;
|
|
287
299
|
/**
|
|
288
300
|
* The core window's `TileHandle.firstWoven`: when it is safe to reveal the canvas. Resolves
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@displayxr/inline3d",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.48.0",
|
|
4
4
|
"description": "Turn any HTML <canvas> into a 3D window on a spatial display, inside an ordinary web page, for the DisplayXR Browser. Dependency-free; progressive enhancement (falls back to plain 2D everywhere else).",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"types": "./index.d.ts",
|
|
@@ -74,6 +74,7 @@
|
|
|
74
74
|
"llms.txt",
|
|
75
75
|
"docs/making-3d-look-right.md",
|
|
76
76
|
"docs/woven-canvas-rules.md",
|
|
77
|
+
"docs/content-lifecycle-rules.md",
|
|
77
78
|
"js/inline3d.js",
|
|
78
79
|
"js/inline3d-undock.js",
|
|
79
80
|
"js/inline3d-display-metrics.js",
|
|
@@ -108,6 +109,9 @@
|
|
|
108
109
|
"js/inline3d-model-entry.js",
|
|
109
110
|
"js/inline3d-model-playcanvas.js",
|
|
110
111
|
"js/inline3d-pc-look.js",
|
|
112
|
+
"js/inline3d-pc-auto-release.js",
|
|
113
|
+
"js/inline3d-pc-release.js",
|
|
114
|
+
"js/inline3d-pc-shader-cache.js",
|
|
111
115
|
"js/inline3d-playcanvas-engine.js",
|
|
112
116
|
"js/inline3d-player.js",
|
|
113
117
|
"js/inline3d-camera.js",
|
|
@@ -116,6 +120,13 @@
|
|
|
116
120
|
"js/camera/",
|
|
117
121
|
"js/call/",
|
|
118
122
|
"js/lift/",
|
|
123
|
+
"signaling/README.md",
|
|
124
|
+
"signaling/dev-server.mjs",
|
|
125
|
+
"signaling/room.mjs",
|
|
126
|
+
"signaling/limits.mjs",
|
|
127
|
+
"signaling/turn-budget.mjs",
|
|
128
|
+
"signaling/keys.mjs",
|
|
129
|
+
"signaling/gate.mjs",
|
|
119
130
|
"dist/",
|
|
120
131
|
"index.d.ts",
|
|
121
132
|
"three.d.ts",
|