three-base 2.0.192 → 2.0.195
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/EventMap.d.ts +7 -0
- package/EventMap.ts +8 -0
- package/MktMesh/MktMesh.d.ts +35 -1
- package/MktMesh/MktMesh.js +54 -1
- package/MktMesh/MktMesh.ts +54 -1
- package/MktMesh/panoBitmapTexture.d.ts +5 -2
- package/MktMesh/panoBitmapTexture.js +14 -4
- package/MktMesh/panoBitmapTexture.ts +17 -4
- package/MktMesh/panoTileStitcher.d.ts +35 -4
- package/MktMesh/panoTileStitcher.js +90 -27
- package/MktMesh/panoTileStitcher.ts +95 -23
- package/package.json +1 -1
- package/threeLoader.js +2 -0
- package/threeLoader.ts +2 -0
package/EventMap.d.ts
CHANGED
|
@@ -8,6 +8,12 @@ export interface LoadingEvent {
|
|
|
8
8
|
export interface OnLoadEvent {
|
|
9
9
|
type: "onLoad";
|
|
10
10
|
}
|
|
11
|
+
export interface LoaderProgressEvent {
|
|
12
|
+
type: "progress";
|
|
13
|
+
url: string;
|
|
14
|
+
loaded: number;
|
|
15
|
+
total: number;
|
|
16
|
+
}
|
|
11
17
|
export interface BeforeRenderEvent {
|
|
12
18
|
type: "beforeRender";
|
|
13
19
|
}
|
|
@@ -53,6 +59,7 @@ export interface MeshBaseEventMap extends Object3DEventMap {
|
|
|
53
59
|
export interface ThreeLoaderEventMap extends Object3DEventMap {
|
|
54
60
|
"loadding": LoadingEvent;
|
|
55
61
|
"onLoad": OnLoadEvent;
|
|
62
|
+
"progress": LoaderProgressEvent;
|
|
56
63
|
}
|
|
57
64
|
export interface ThreeBaseEventTypeMap extends Object3DEventMap {
|
|
58
65
|
"loadding": LoadingEvent;
|
package/EventMap.ts
CHANGED
|
@@ -29,6 +29,13 @@ import type { Object3DEventMap, Vector3 } from "three";
|
|
|
29
29
|
export interface LoadedEvent { type: "loaded"; }
|
|
30
30
|
export interface LoadingEvent { type: "loadding"; }
|
|
31
31
|
export interface OnLoadEvent { type: "onLoad"; }
|
|
32
|
+
// 资源加载进度:`DefaultLoadingManager.onProgress` 的三个数原样带出(url / 已完成项数 / 总项数)
|
|
33
|
+
export interface LoaderProgressEvent {
|
|
34
|
+
type: "progress";
|
|
35
|
+
url: string;
|
|
36
|
+
loaded: number;
|
|
37
|
+
total: number;
|
|
38
|
+
}
|
|
32
39
|
export interface BeforeRenderEvent { type: "beforeRender"; }
|
|
33
40
|
export interface AfterRenderEvent { type: "afterRender"; }
|
|
34
41
|
|
|
@@ -77,6 +84,7 @@ export interface MeshBaseEventMap extends Object3DEventMap {
|
|
|
77
84
|
export interface ThreeLoaderEventMap extends Object3DEventMap {
|
|
78
85
|
"loadding": LoadingEvent;
|
|
79
86
|
"onLoad": OnLoadEvent;
|
|
87
|
+
"progress": LoaderProgressEvent;
|
|
80
88
|
}
|
|
81
89
|
|
|
82
90
|
export interface ThreeBaseEventTypeMap extends Object3DEventMap {
|
package/MktMesh/MktMesh.d.ts
CHANGED
|
@@ -405,6 +405,40 @@ export default class MktMesh extends Mesh<BufferGeometry, ShaderMaterial> {
|
|
|
405
405
|
* @param projName 项目名(可选)——用于把落盘的 `{项目名}/x_0.jpg` 还原回真身OSS 地址
|
|
406
406
|
* @param onModelLoad 每只 GLB 落地时回调一次(可选)——按需渲染的宿主(编辑器)拿它标脏;每帧真画的预览不传 */
|
|
407
407
|
importData(data: MktMeshData, projName?: string, onModelLoad?: () => void): void;
|
|
408
|
-
/**
|
|
408
|
+
/**
|
|
409
|
+
* 把**存档里前 `PANO_IMPORT_PRIORITY` 个全景**按存档顺序登记进拼合器的**加载任务队列**
|
|
410
|
+
*(2026-10-29 需求「优先加载前 2 个全景素材」+ 2026-10-30 需求逐字:「给 mktmesh 内部加一个加载任务队列,
|
|
411
|
+
* 把模型和平面图排在最前面,然后按照顺序,依次把前 2 个全景的顺序把瓦片加载任务添加到任务队列里面,
|
|
412
|
+
* 第一个加载完了,就把这个加载任务从任务队列里面去掉,然后加载第二个;
|
|
413
|
+
* 如果后续再按照距离计算需要加载哪个全景的时候,同样地添加到加载任务队列的后面等待加载」)。
|
|
414
|
+
* 由两处渲染循环每帧点一次:基类 `onBeforeRender`(预览弹窗 / 发布页)与编辑器的 `updateFloorTexture`
|
|
415
|
+
*(`MktDevMesh` 覆写了 `onBeforeRender`,只点 `syncProjInvMatrix` + `tickProgressiveReveal`,库内那句在编辑态不跑)。
|
|
416
|
+
*
|
|
417
|
+
* 队列怎么排(`panoTileStitcher` 的 `_queue`,一处排队一张尺):
|
|
418
|
+
* ① **模型与平面图天然在最前面** —— 这两族的下载不在拼合器里,`importData` 当场同步发起
|
|
419
|
+
* (GLB 逐只 `loadModel` 不 throttle;平面那 4 片一次性 `_imgTileLoader.preload`),
|
|
420
|
+
* 而全景瓦片最早也要等下一帧的 `pump` 才发第一片 ⇒ 发起次序天然是「模型 → 平面图 → 全景」,
|
|
421
|
+
* ⛔ 这里不再把它们塞进队列重复管一遍(塞了就是第二把尺)。
|
|
422
|
+
* ② 本方法把存档 0 号、1 号依次 `prioritize` 进队列 ⇒ 拼合器每帧**只喂队首那一组**,
|
|
423
|
+
* 0 号 32 格全落定(画进 RT,或该格本就缺地址/判死)才出队、1 号才开始发第一片。
|
|
424
|
+
* 比 10-29 那版(只是把两批挪到在册顺序前面、额度仍被两批分摊)更硬:**第 1 颗独享额度,最快出全图**。
|
|
425
|
+
* ③ 名册按距离点到的其他全景走 `feedFaceMapping` 里那句 `ensure(t.tiles)`(不带队列)⇒ 只入册、
|
|
426
|
+
* 排在队列后面等,队列空了才按在册顺序补料(现行口径不变)。
|
|
427
|
+
* ⚠️⚠️ 为什么每帧点而⛔ 只在 `importData` 里点一记:预览每开一次都换渲染器,`_switchRenderer` 把在册组
|
|
428
|
+
* 整份作废(队列里的键也在同一刻失效);只在载入那次点名等于白点。本方法点在 `pump` 之后、名册逐槽
|
|
429
|
+
* `ensure` 之前 ⇒ 每帧把这两记任务补回队列头部(`prioritize` 幂等,已在队列里就不挪,所以 0 号恒在 1 号前)。
|
|
430
|
+
* ⚠️ 代价如实记:
|
|
431
|
+
* · 队列非空时**其他全景一片都不发** ⇒ 开机那几秒里,离注视点最近、但不在存档前两颗的那面墙会多灰一会儿。
|
|
432
|
+
* · 一颗「永远下不完」的瓦片(下载失败还没累计到判死)会压住队列;判死阈值是解码失败 3 次
|
|
433
|
+
* (`_dead`),退避重试期间队首不落定 ⇒ 第 2 颗跟着延后。真实数据里缺片是「地址为空/404」,
|
|
434
|
+
* 404 走三次即判死、那一格拼合涂灰 ⇒ 队列照样出清。
|
|
435
|
+
* · 这两颗全景若本来不在开机那一刻的名册里,它们的瓦片与拼合槽 RT 会被**提前**建出来
|
|
436
|
+
* (现行口径每颗全景 2 槽 × 6144² ≈ 384MB 显存)—— 组在运行中不淘汰,走进这两个景点迟早要建。
|
|
437
|
+
* ⚠️ 形态判据读 `this._panoTexMode`(整图形态那一档一张全景只有一张整图,走 `_texCache` 那条路,
|
|
438
|
+
* 走错档就是白下 32 片)⇒ 宿主若用自己那份形态变量(编辑器的 `store.panoTexMode`),
|
|
439
|
+
* 要在调 `importData` 之前先 `setPanoTexMode` 推进来,编辑器那侧已按此接线。
|
|
440
|
+
*/
|
|
441
|
+
prefetchHeadPanoramas(): void;
|
|
442
|
+
/** 释放 GPU 资源:瓦片缓存(**只有这里**清,2026-10-02 决定)、拼合器、平面图 / 模型 / 玻璃面三层、几何、材质 */
|
|
409
443
|
dispose(): this;
|
|
410
444
|
}
|
package/MktMesh/MktMesh.js
CHANGED
|
@@ -51,6 +51,9 @@ import { stripAssetHost, absAssetUrl } from "./assetUrl";
|
|
|
51
51
|
import { ImagePlane } from "./imagePlane";
|
|
52
52
|
import { ModelSlot } from "./modelSlot";
|
|
53
53
|
import { ImagePlaneTileLoader } from "./imageTileLoader";
|
|
54
|
+
/** 载入 JSON 时**优先**加载几个全景的素材(2026-10-29 需求「优先加载前 2 个全景图的素材,然后再加载其他的」)。
|
|
55
|
+
* 口径 = 存档 `textures` 数组的前 n 项;其余全景照常按每帧的槽位名册(离注视点最近 8 个)排队,只是排在它们后面。 */
|
|
56
|
+
const PANO_IMPORT_PRIORITY = 2;
|
|
54
57
|
export default class MktMesh extends Mesh {
|
|
55
58
|
/** 纹理列表(每个纹理的 url + 世界矩阵) */
|
|
56
59
|
textures = [];
|
|
@@ -247,6 +250,11 @@ export default class MktMesh extends Mesh {
|
|
|
247
250
|
this._glRenderer = renderer;
|
|
248
251
|
this.syncProjInvMatrix();
|
|
249
252
|
this.updateTexture(camera);
|
|
253
|
+
// ⭐ 2026-10-29 需求「载入 json 优先加载前 2 个全景图的素材,然后再加载其他的」:必须点在
|
|
254
|
+
// `updateTexture`(它里面那记 `pump` 是「第一次拿到 renderer ⇒ 在册组整份作废」的现场)之后、
|
|
255
|
+
// `feedFaceMapping`(名册逐槽入册)之前 —— 顺序倒过来就是「名册在前、我们垫底」= 静默失效。
|
|
256
|
+
// 编辑态那份在 `MktDevMesh` 的宿主循环里照同一相对位置点(⛔ 别在这儿加第三处)。
|
|
257
|
+
this.prefetchHeadPanoramas();
|
|
250
258
|
this.feedFaceMapping();
|
|
251
259
|
this.tickProgressiveReveal();
|
|
252
260
|
};
|
|
@@ -1360,8 +1368,53 @@ export default class MktMesh extends Mesh {
|
|
|
1360
1368
|
})));
|
|
1361
1369
|
this.setModels((data.models ?? []).map((d) => ({ ...d, url: d.url ? absAssetUrl(d.url, pn) : "" })), onModelLoad);
|
|
1362
1370
|
}
|
|
1371
|
+
/**
|
|
1372
|
+
* 把**存档里前 `PANO_IMPORT_PRIORITY` 个全景**按存档顺序登记进拼合器的**加载任务队列**
|
|
1373
|
+
*(2026-10-29 需求「优先加载前 2 个全景素材」+ 2026-10-30 需求逐字:「给 mktmesh 内部加一个加载任务队列,
|
|
1374
|
+
* 把模型和平面图排在最前面,然后按照顺序,依次把前 2 个全景的顺序把瓦片加载任务添加到任务队列里面,
|
|
1375
|
+
* 第一个加载完了,就把这个加载任务从任务队列里面去掉,然后加载第二个;
|
|
1376
|
+
* 如果后续再按照距离计算需要加载哪个全景的时候,同样地添加到加载任务队列的后面等待加载」)。
|
|
1377
|
+
* 由两处渲染循环每帧点一次:基类 `onBeforeRender`(预览弹窗 / 发布页)与编辑器的 `updateFloorTexture`
|
|
1378
|
+
*(`MktDevMesh` 覆写了 `onBeforeRender`,只点 `syncProjInvMatrix` + `tickProgressiveReveal`,库内那句在编辑态不跑)。
|
|
1379
|
+
*
|
|
1380
|
+
* 队列怎么排(`panoTileStitcher` 的 `_queue`,一处排队一张尺):
|
|
1381
|
+
* ① **模型与平面图天然在最前面** —— 这两族的下载不在拼合器里,`importData` 当场同步发起
|
|
1382
|
+
* (GLB 逐只 `loadModel` 不 throttle;平面那 4 片一次性 `_imgTileLoader.preload`),
|
|
1383
|
+
* 而全景瓦片最早也要等下一帧的 `pump` 才发第一片 ⇒ 发起次序天然是「模型 → 平面图 → 全景」,
|
|
1384
|
+
* ⛔ 这里不再把它们塞进队列重复管一遍(塞了就是第二把尺)。
|
|
1385
|
+
* ② 本方法把存档 0 号、1 号依次 `prioritize` 进队列 ⇒ 拼合器每帧**只喂队首那一组**,
|
|
1386
|
+
* 0 号 32 格全落定(画进 RT,或该格本就缺地址/判死)才出队、1 号才开始发第一片。
|
|
1387
|
+
* 比 10-29 那版(只是把两批挪到在册顺序前面、额度仍被两批分摊)更硬:**第 1 颗独享额度,最快出全图**。
|
|
1388
|
+
* ③ 名册按距离点到的其他全景走 `feedFaceMapping` 里那句 `ensure(t.tiles)`(不带队列)⇒ 只入册、
|
|
1389
|
+
* 排在队列后面等,队列空了才按在册顺序补料(现行口径不变)。
|
|
1390
|
+
* ⚠️⚠️ 为什么每帧点而⛔ 只在 `importData` 里点一记:预览每开一次都换渲染器,`_switchRenderer` 把在册组
|
|
1391
|
+
* 整份作废(队列里的键也在同一刻失效);只在载入那次点名等于白点。本方法点在 `pump` 之后、名册逐槽
|
|
1392
|
+
* `ensure` 之前 ⇒ 每帧把这两记任务补回队列头部(`prioritize` 幂等,已在队列里就不挪,所以 0 号恒在 1 号前)。
|
|
1393
|
+
* ⚠️ 代价如实记:
|
|
1394
|
+
* · 队列非空时**其他全景一片都不发** ⇒ 开机那几秒里,离注视点最近、但不在存档前两颗的那面墙会多灰一会儿。
|
|
1395
|
+
* · 一颗「永远下不完」的瓦片(下载失败还没累计到判死)会压住队列;判死阈值是解码失败 3 次
|
|
1396
|
+
* (`_dead`),退避重试期间队首不落定 ⇒ 第 2 颗跟着延后。真实数据里缺片是「地址为空/404」,
|
|
1397
|
+
* 404 走三次即判死、那一格拼合涂灰 ⇒ 队列照样出清。
|
|
1398
|
+
* · 这两颗全景若本来不在开机那一刻的名册里,它们的瓦片与拼合槽 RT 会被**提前**建出来
|
|
1399
|
+
* (现行口径每颗全景 2 槽 × 6144² ≈ 384MB 显存)—— 组在运行中不淘汰,走进这两个景点迟早要建。
|
|
1400
|
+
* ⚠️ 形态判据读 `this._panoTexMode`(整图形态那一档一张全景只有一张整图,走 `_texCache` 那条路,
|
|
1401
|
+
* 走错档就是白下 32 片)⇒ 宿主若用自己那份形态变量(编辑器的 `store.panoTexMode`),
|
|
1402
|
+
* 要在调 `importData` 之前先 `setPanoTexMode` 推进来,编辑器那侧已按此接线。
|
|
1403
|
+
*/
|
|
1404
|
+
prefetchHeadPanoramas() {
|
|
1405
|
+
const n = Math.min(PANO_IMPORT_PRIORITY, this.textures.length);
|
|
1406
|
+
// 正着点:0 号先入队 ⇒ 队首是它,落定后才轮到 1 号(`prioritize` 对已在队列里的键不挪位置)
|
|
1407
|
+
for (let i = 0; i < n; i++) {
|
|
1408
|
+
const info = this.textures[i];
|
|
1409
|
+
// 与 `feedFaceMapping` 里那一句同判据:真瓦片才进拼合器,整图形态进瓦片缓存
|
|
1410
|
+
if (mktPanoTiledFlag(info.tiles, this._panoTexMode) === 1)
|
|
1411
|
+
this._stitcher.prioritize(info.tiles);
|
|
1412
|
+
else if (info.url)
|
|
1413
|
+
this.ensureTileTexture(info.url, false);
|
|
1414
|
+
}
|
|
1415
|
+
}
|
|
1363
1416
|
// ───────────────────────── 释放 ─────────────────────────
|
|
1364
|
-
/** 释放 GPU
|
|
1417
|
+
/** 释放 GPU 资源:瓦片缓存(**只有这里**清,2026-10-02 决定)、拼合器、平面图 / 模型 / 玻璃面三层、几何、材质 */
|
|
1365
1418
|
dispose() {
|
|
1366
1419
|
// ⚠️ rt 虚拟键从不进 `_texCache`(拼合槽纹理由 `_stitcher` 自己持有并释放),这里清的都是本类下载的 Texture。
|
|
1367
1420
|
// ⭐ 甲(2026-10-05):`releasePanoTexture` 除 `dispose()` 外还要 `ImageBitmap.close()` —— 位图的原生内存
|
package/MktMesh/MktMesh.ts
CHANGED
|
@@ -72,6 +72,10 @@ import { ImagePlane, type ImagePlaneData } from "./imagePlane";
|
|
|
72
72
|
import { ModelSlot, type ModelSlotData } from "./modelSlot";
|
|
73
73
|
import { ImagePlaneTileLoader } from "./imageTileLoader";
|
|
74
74
|
|
|
75
|
+
/** 载入 JSON 时**优先**加载几个全景的素材(2026-10-29 需求「优先加载前 2 个全景图的素材,然后再加载其他的」)。
|
|
76
|
+
* 口径 = 存档 `textures` 数组的前 n 项;其余全景照常按每帧的槽位名册(离注视点最近 8 个)排队,只是排在它们后面。 */
|
|
77
|
+
const PANO_IMPORT_PRIORITY = 2;
|
|
78
|
+
|
|
75
79
|
export default class MktMesh extends Mesh<BufferGeometry, ShaderMaterial> {
|
|
76
80
|
/** 纹理列表(每个纹理的 url + 世界矩阵) */
|
|
77
81
|
textures: MktTextureInfo[] = [];
|
|
@@ -272,6 +276,11 @@ export default class MktMesh extends Mesh<BufferGeometry, ShaderMaterial> {
|
|
|
272
276
|
this._glRenderer = renderer;
|
|
273
277
|
this.syncProjInvMatrix();
|
|
274
278
|
this.updateTexture(camera as Camera);
|
|
279
|
+
// ⭐ 2026-10-29 需求「载入 json 优先加载前 2 个全景图的素材,然后再加载其他的」:必须点在
|
|
280
|
+
// `updateTexture`(它里面那记 `pump` 是「第一次拿到 renderer ⇒ 在册组整份作废」的现场)之后、
|
|
281
|
+
// `feedFaceMapping`(名册逐槽入册)之前 —— 顺序倒过来就是「名册在前、我们垫底」= 静默失效。
|
|
282
|
+
// 编辑态那份在 `MktDevMesh` 的宿主循环里照同一相对位置点(⛔ 别在这儿加第三处)。
|
|
283
|
+
this.prefetchHeadPanoramas();
|
|
275
284
|
this.feedFaceMapping();
|
|
276
285
|
this.tickProgressiveReveal();
|
|
277
286
|
};
|
|
@@ -1398,9 +1407,53 @@ export default class MktMesh extends Mesh<BufferGeometry, ShaderMaterial> {
|
|
|
1398
1407
|
);
|
|
1399
1408
|
}
|
|
1400
1409
|
|
|
1410
|
+
/**
|
|
1411
|
+
* 把**存档里前 `PANO_IMPORT_PRIORITY` 个全景**按存档顺序登记进拼合器的**加载任务队列**
|
|
1412
|
+
*(2026-10-29 需求「优先加载前 2 个全景素材」+ 2026-10-30 需求逐字:「给 mktmesh 内部加一个加载任务队列,
|
|
1413
|
+
* 把模型和平面图排在最前面,然后按照顺序,依次把前 2 个全景的顺序把瓦片加载任务添加到任务队列里面,
|
|
1414
|
+
* 第一个加载完了,就把这个加载任务从任务队列里面去掉,然后加载第二个;
|
|
1415
|
+
* 如果后续再按照距离计算需要加载哪个全景的时候,同样地添加到加载任务队列的后面等待加载」)。
|
|
1416
|
+
* 由两处渲染循环每帧点一次:基类 `onBeforeRender`(预览弹窗 / 发布页)与编辑器的 `updateFloorTexture`
|
|
1417
|
+
*(`MktDevMesh` 覆写了 `onBeforeRender`,只点 `syncProjInvMatrix` + `tickProgressiveReveal`,库内那句在编辑态不跑)。
|
|
1418
|
+
*
|
|
1419
|
+
* 队列怎么排(`panoTileStitcher` 的 `_queue`,一处排队一张尺):
|
|
1420
|
+
* ① **模型与平面图天然在最前面** —— 这两族的下载不在拼合器里,`importData` 当场同步发起
|
|
1421
|
+
* (GLB 逐只 `loadModel` 不 throttle;平面那 4 片一次性 `_imgTileLoader.preload`),
|
|
1422
|
+
* 而全景瓦片最早也要等下一帧的 `pump` 才发第一片 ⇒ 发起次序天然是「模型 → 平面图 → 全景」,
|
|
1423
|
+
* ⛔ 这里不再把它们塞进队列重复管一遍(塞了就是第二把尺)。
|
|
1424
|
+
* ② 本方法把存档 0 号、1 号依次 `prioritize` 进队列 ⇒ 拼合器每帧**只喂队首那一组**,
|
|
1425
|
+
* 0 号 32 格全落定(画进 RT,或该格本就缺地址/判死)才出队、1 号才开始发第一片。
|
|
1426
|
+
* 比 10-29 那版(只是把两批挪到在册顺序前面、额度仍被两批分摊)更硬:**第 1 颗独享额度,最快出全图**。
|
|
1427
|
+
* ③ 名册按距离点到的其他全景走 `feedFaceMapping` 里那句 `ensure(t.tiles)`(不带队列)⇒ 只入册、
|
|
1428
|
+
* 排在队列后面等,队列空了才按在册顺序补料(现行口径不变)。
|
|
1429
|
+
* ⚠️⚠️ 为什么每帧点而⛔ 只在 `importData` 里点一记:预览每开一次都换渲染器,`_switchRenderer` 把在册组
|
|
1430
|
+
* 整份作废(队列里的键也在同一刻失效);只在载入那次点名等于白点。本方法点在 `pump` 之后、名册逐槽
|
|
1431
|
+
* `ensure` 之前 ⇒ 每帧把这两记任务补回队列头部(`prioritize` 幂等,已在队列里就不挪,所以 0 号恒在 1 号前)。
|
|
1432
|
+
* ⚠️ 代价如实记:
|
|
1433
|
+
* · 队列非空时**其他全景一片都不发** ⇒ 开机那几秒里,离注视点最近、但不在存档前两颗的那面墙会多灰一会儿。
|
|
1434
|
+
* · 一颗「永远下不完」的瓦片(下载失败还没累计到判死)会压住队列;判死阈值是解码失败 3 次
|
|
1435
|
+
* (`_dead`),退避重试期间队首不落定 ⇒ 第 2 颗跟着延后。真实数据里缺片是「地址为空/404」,
|
|
1436
|
+
* 404 走三次即判死、那一格拼合涂灰 ⇒ 队列照样出清。
|
|
1437
|
+
* · 这两颗全景若本来不在开机那一刻的名册里,它们的瓦片与拼合槽 RT 会被**提前**建出来
|
|
1438
|
+
* (现行口径每颗全景 2 槽 × 6144² ≈ 384MB 显存)—— 组在运行中不淘汰,走进这两个景点迟早要建。
|
|
1439
|
+
* ⚠️ 形态判据读 `this._panoTexMode`(整图形态那一档一张全景只有一张整图,走 `_texCache` 那条路,
|
|
1440
|
+
* 走错档就是白下 32 片)⇒ 宿主若用自己那份形态变量(编辑器的 `store.panoTexMode`),
|
|
1441
|
+
* 要在调 `importData` 之前先 `setPanoTexMode` 推进来,编辑器那侧已按此接线。
|
|
1442
|
+
*/
|
|
1443
|
+
prefetchHeadPanoramas(): void {
|
|
1444
|
+
const n = Math.min(PANO_IMPORT_PRIORITY, this.textures.length);
|
|
1445
|
+
// 正着点:0 号先入队 ⇒ 队首是它,落定后才轮到 1 号(`prioritize` 对已在队列里的键不挪位置)
|
|
1446
|
+
for (let i = 0; i < n; i++) {
|
|
1447
|
+
const info = this.textures[i];
|
|
1448
|
+
// 与 `feedFaceMapping` 里那一句同判据:真瓦片才进拼合器,整图形态进瓦片缓存
|
|
1449
|
+
if (mktPanoTiledFlag(info.tiles, this._panoTexMode) === 1) this._stitcher.prioritize(info.tiles);
|
|
1450
|
+
else if (info.url) this.ensureTileTexture(info.url, false);
|
|
1451
|
+
}
|
|
1452
|
+
}
|
|
1453
|
+
|
|
1401
1454
|
// ───────────────────────── 释放 ─────────────────────────
|
|
1402
1455
|
|
|
1403
|
-
/** 释放 GPU
|
|
1456
|
+
/** 释放 GPU 资源:瓦片缓存(**只有这里**清,2026-10-02 决定)、拼合器、平面图 / 模型 / 玻璃面三层、几何、材质 */
|
|
1404
1457
|
dispose(): this {
|
|
1405
1458
|
// ⚠️ rt 虚拟键从不进 `_texCache`(拼合槽纹理由 `_stitcher` 自己持有并释放),这里清的都是本类下载的 Texture。
|
|
1406
1459
|
// ⭐ 甲(2026-10-05):`releasePanoTexture` 除 `dispose()` 外还要 `ImageBitmap.close()` —— 位图的原生内存
|
|
@@ -16,8 +16,11 @@ import { Texture } from "three";
|
|
|
16
16
|
* 明说「已经翻过了」。1×2 像素对照实测:`<img>`+flipY=true 与 bitmap(flipY)+flipY=false 逐行一致。
|
|
17
17
|
*/
|
|
18
18
|
/** 发起一张全景纹理的加载(下载 + 异步解码都在浏览器工作线程,主线程只拿结果)。
|
|
19
|
-
* 与 `TextureLoader.load` 同口径:**不许**把这一步排进 `threeUtils`
|
|
20
|
-
* 排队的是「上传那一句」,见两侧的 `_commitTileToShader`。
|
|
19
|
+
* 与 `TextureLoader.load` 同口径:**不许**把这一步排进 `threeUtils` 的全局队列(用户决定 2026-09-25),
|
|
20
|
+
* 排队的是「上传那一句」,见两侧的 `_commitTileToShader`。
|
|
21
|
+
* ⭐ 这一步走的是原生 `fetch`,不经过 three 的 `Loader`,所以它**自己**向 `DefaultLoadingManager`
|
|
22
|
+
* 报一次 `itemStart` / `itemEnd`(失败再多一次 `itemError`,与 `FileLoader` 的口径一致)——
|
|
23
|
+
* 否则页面拿到的加载状态只算模型和 `TextureLoader` 那些路,全景切片的下载完全不在里面。 */
|
|
21
24
|
export declare function loadPanoTexture(url: string): Promise<Texture>;
|
|
22
25
|
/** 释放一张由 `loadPanoTexture` 建出来的纹理:`dispose()` 只退显存,**ImageBitmap 的原生内存要 `close()`**
|
|
23
26
|
* (不像 `<img>` 能等 GC —— 一张 270MB 的位图挂着不关,切几轮全景就把进程吃爆)。
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Texture } from "three";
|
|
1
|
+
import { DefaultLoadingManager, Texture } from "three";
|
|
2
2
|
/**
|
|
3
3
|
* ⭐ 甲(皇上旨 2026-10-05「你试试甲」):一张全景图(整图 / 单片瓦片,现行 32 片/全景)的**异步解码**建纹理。
|
|
4
4
|
*
|
|
@@ -16,10 +16,14 @@ import { Texture } from "three";
|
|
|
16
16
|
* 明说「已经翻过了」。1×2 像素对照实测:`<img>`+flipY=true 与 bitmap(flipY)+flipY=false 逐行一致。
|
|
17
17
|
*/
|
|
18
18
|
/** 发起一张全景纹理的加载(下载 + 异步解码都在浏览器工作线程,主线程只拿结果)。
|
|
19
|
-
* 与 `TextureLoader.load` 同口径:**不许**把这一步排进 `threeUtils`
|
|
20
|
-
* 排队的是「上传那一句」,见两侧的 `_commitTileToShader`。
|
|
19
|
+
* 与 `TextureLoader.load` 同口径:**不许**把这一步排进 `threeUtils` 的全局队列(用户决定 2026-09-25),
|
|
20
|
+
* 排队的是「上传那一句」,见两侧的 `_commitTileToShader`。
|
|
21
|
+
* ⭐ 这一步走的是原生 `fetch`,不经过 three 的 `Loader`,所以它**自己**向 `DefaultLoadingManager`
|
|
22
|
+
* 报一次 `itemStart` / `itemEnd`(失败再多一次 `itemError`,与 `FileLoader` 的口径一致)——
|
|
23
|
+
* 否则页面拿到的加载状态只算模型和 `TextureLoader` 那些路,全景切片的下载完全不在里面。 */
|
|
21
24
|
export function loadPanoTexture(url) {
|
|
22
|
-
|
|
25
|
+
DefaultLoadingManager.itemStart(url);
|
|
26
|
+
const task = fetch(url)
|
|
23
27
|
.then((res) => {
|
|
24
28
|
if (!res.ok)
|
|
25
29
|
throw new Error(`HTTP ${res.status}`);
|
|
@@ -38,6 +42,12 @@ export function loadPanoTexture(url) {
|
|
|
38
42
|
tex.userData.url = url;
|
|
39
43
|
return tex;
|
|
40
44
|
});
|
|
45
|
+
// 一条 promise 只 settle 一次 ⇒ 开始/结束各报一次,两边都不会漏;reject 原样透给调用方。
|
|
46
|
+
task.then(() => DefaultLoadingManager.itemEnd(url), () => {
|
|
47
|
+
DefaultLoadingManager.itemError(url);
|
|
48
|
+
DefaultLoadingManager.itemEnd(url);
|
|
49
|
+
});
|
|
50
|
+
return task;
|
|
41
51
|
}
|
|
42
52
|
/** 释放一张由 `loadPanoTexture` 建出来的纹理:`dispose()` 只退显存,**ImageBitmap 的原生内存要 `close()`**
|
|
43
53
|
* (不像 `<img>` 能等 GC —— 一张 270MB 的位图挂着不关,切几轮全景就把进程吃爆)。
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Texture } from "three";
|
|
1
|
+
import { DefaultLoadingManager, Texture } from "three";
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* ⭐ 甲(皇上旨 2026-10-05「你试试甲」):一张全景图(整图 / 单片瓦片,现行 32 片/全景)的**异步解码**建纹理。
|
|
@@ -18,10 +18,14 @@ import { Texture } from "three";
|
|
|
18
18
|
*/
|
|
19
19
|
|
|
20
20
|
/** 发起一张全景纹理的加载(下载 + 异步解码都在浏览器工作线程,主线程只拿结果)。
|
|
21
|
-
* 与 `TextureLoader.load` 同口径:**不许**把这一步排进 `threeUtils`
|
|
22
|
-
* 排队的是「上传那一句」,见两侧的 `_commitTileToShader`。
|
|
21
|
+
* 与 `TextureLoader.load` 同口径:**不许**把这一步排进 `threeUtils` 的全局队列(用户决定 2026-09-25),
|
|
22
|
+
* 排队的是「上传那一句」,见两侧的 `_commitTileToShader`。
|
|
23
|
+
* ⭐ 这一步走的是原生 `fetch`,不经过 three 的 `Loader`,所以它**自己**向 `DefaultLoadingManager`
|
|
24
|
+
* 报一次 `itemStart` / `itemEnd`(失败再多一次 `itemError`,与 `FileLoader` 的口径一致)——
|
|
25
|
+
* 否则页面拿到的加载状态只算模型和 `TextureLoader` 那些路,全景切片的下载完全不在里面。 */
|
|
23
26
|
export function loadPanoTexture(url: string): Promise<Texture> {
|
|
24
|
-
|
|
27
|
+
DefaultLoadingManager.itemStart(url);
|
|
28
|
+
const task = fetch(url)
|
|
25
29
|
.then((res) => {
|
|
26
30
|
if (!res.ok) throw new Error(`HTTP ${res.status}`);
|
|
27
31
|
return res.blob();
|
|
@@ -41,6 +45,15 @@ export function loadPanoTexture(url: string): Promise<Texture> {
|
|
|
41
45
|
tex.userData.url = url;
|
|
42
46
|
return tex;
|
|
43
47
|
});
|
|
48
|
+
// 一条 promise 只 settle 一次 ⇒ 开始/结束各报一次,两边都不会漏;reject 原样透给调用方。
|
|
49
|
+
task.then(
|
|
50
|
+
() => DefaultLoadingManager.itemEnd(url),
|
|
51
|
+
() => {
|
|
52
|
+
DefaultLoadingManager.itemError(url);
|
|
53
|
+
DefaultLoadingManager.itemEnd(url);
|
|
54
|
+
}
|
|
55
|
+
);
|
|
56
|
+
return task;
|
|
44
57
|
}
|
|
45
58
|
|
|
46
59
|
/** 释放一张由 `loadPanoTexture` 建出来的纹理:`dispose()` 只退显存,**ImageBitmap 的原生内存要 `close()`**
|
|
@@ -7,8 +7,22 @@ import { type TileLayout } from "./mktShader";
|
|
|
7
7
|
export interface PanoStitcher {
|
|
8
8
|
/** 确保批次已在册并发起瓦片加载(幂等;同批已在册 ⇒ 什么都不做)。
|
|
9
9
|
* 新增可选 `layout` 参数:缺省 = PANO_TILE_LAYOUT(全景 8×4 / 2 槽 / 6144×6144,2026-10-27 需求);
|
|
10
|
-
* 图片平面传 `imageTileLayout(natW, natH)`(2×2 / 1 槽 / RT 宽高比=原图)。
|
|
10
|
+
* 图片平面传 `imageTileLayout(natW, natH)`(2×2 / 1 槽 / RT 宽高比=原图)。
|
|
11
|
+
* ⚠️ 2026-10-30「加载任务队列」:本方法只负责**入册**(新组追加到在册顺序末尾 ⇒ 排在队列里等),
|
|
12
|
+
* 点名优先的那一路换成交换 `prioritize()`(10-29 那版挂在第三个参 `front` 上的「挪到队首」已并进那里,
|
|
13
|
+
* ⛔ 一处排队只有 `prioritize` 一张尺)。 */
|
|
11
14
|
ensure(urls: string[], layout?: TileLayout): void;
|
|
15
|
+
/**
|
|
16
|
+
* 把某批瓦片登记进**加载任务队列**(2026-10-30 需求,逐字:「依次把前 2 个全景的顺序把瓦片加载任务添加到
|
|
17
|
+
* 任务队列里面,第一个加载完了,就把这个加载任务从任务队列里面去掉,然后加载第二个;后续按距离需要哪个全景
|
|
18
|
+
* 的时候同样添加到队列后面等待加载」)。
|
|
19
|
+
* 语义 = 先 `ensure` 入册(不在册就建组),再把批次键登记进队列**末尾**(已在队列里就不再挪,所以先登记的排前面)。
|
|
20
|
+
* 排队规则见 `_pumpDownloads`:**队首那组落定(32 格全画进 RT,或该格本就缺地址/判死)之前,队列里后面的组
|
|
21
|
+
* 和一切没登记的名册组一片都不发** ⇒ 第 1 颗全景独享全部下载额度、最快出全图。
|
|
22
|
+
* ⚠️ 消费方每帧点一次(`MktMesh.prefetchHeadPanoramas`):预览每开一次都换渲染器,`_switchRenderer` 会把在册组
|
|
23
|
+
* 整份作废 ⇒ 队列跟着清空,下一帧重新登记才能把顺序抢回来(只在 `importData` 里点一次等于白点)。
|
|
24
|
+
*/
|
|
25
|
+
prioritize(urls: string[], layout?: TileLayout): void;
|
|
12
26
|
/**
|
|
13
27
|
* 取某一拼合槽的纹理。**画过 ≥1 格就返回**(同日旨「需要先喂给 shader」)。
|
|
14
28
|
* ⭐ 2026-10-09 皇上旨「切换全景时上一个全景的内容要渲染成深灰」:**传了 `renderer` 且该槽一格都还没画时,
|
|
@@ -37,6 +51,10 @@ export interface PanoStitcher {
|
|
|
37
51
|
export declare class PanoTileStitcher implements PanoStitcher {
|
|
38
52
|
/** 批次组(键 = 32 张瓦片的第一张**非空**地址,与消费方同一个 `rtStitchBatchOf`) */
|
|
39
53
|
private _groups;
|
|
54
|
+
/** 加载任务队列(2026-10-30 需求):批次键的**串行带**,队首那组落定才开下一组。
|
|
55
|
+
* 由 `prioritize` 登记(存档里前 2 颗全景),`_pumpDownloads` 每帧出清;`_switchRenderer`/`dispose`/`disposeGroup`
|
|
56
|
+
* 跟着组一起拆。⛔ 这是「谁先下载」的唯一一把尺 —— 在册顺序(`_groups` 的 Map 序)只决定「同不带队列时」的补料次序。 */
|
|
57
|
+
private _queue;
|
|
40
58
|
/** 瓦片纹理(url → `{tex, uploaded}`)。画进自己那一格就立刻释放 ⇒ 只在册期间占显存/内存。 */
|
|
41
59
|
private _texs;
|
|
42
60
|
/** 仍在册的组还需要的地址(迟到的加载回调据此判断「这条已经没人要了,立刻释放」) */
|
|
@@ -75,6 +93,13 @@ export declare class PanoTileStitcher implements PanoStitcher {
|
|
|
75
93
|
* 但它的瓦片是 1024²、被画进 1536² 的格子 = 1.5× 放大 ⇒ 发虚,那类老点仍要重传一次。
|
|
76
94
|
* ⚠️ 每槽数组长度由 `RT_STITCH_SLOT_COUNT` 派生(⛔ 不许手写 `[null,null]` —— 槽数再变会漏改)。 */
|
|
77
95
|
ensure(urls: string[], layout?: TileLayout): void;
|
|
96
|
+
/** 登记进加载任务队列(语义与排队规则见接口上那一段)。已在队列里 ⇒ 不挪(先登记的仍在前)。 */
|
|
97
|
+
prioritize(urls: string[], layout?: TileLayout): void;
|
|
98
|
+
/** 地址数组 → 批次键(补齐到满批再取首条非空,与 `ensure` 里同一条尺 ⇒ 两处不会各算各的)。
|
|
99
|
+
* 超限或全空 ⇒ 空串(`ensure` 那一步同样不收,这里也不入队)。 */
|
|
100
|
+
private _batchKeyOf;
|
|
101
|
+
/** 这一组的每一格要么已经画进 RT,要么根本没有地址 / 地址已判死 ⇒ 该下的都下完了(队列出清判据)。 */
|
|
102
|
+
private _isSettled;
|
|
78
103
|
/** 取某一拼合槽的纹理:**画过 ≥1 格就交出去**(同日旨「需要先喂给 shader」⇒ 不再等 `cellsPerSlot` 格画满)。
|
|
79
104
|
* RT 建好时整张涂过缺失灰 ⇒ 交出去的那张里**没有未定义像素**,观感 = 该象限逐格显影。 */
|
|
80
105
|
peek(batch: string, slot: number, renderer?: WebGLRenderer | null): Texture | null;
|
|
@@ -86,14 +111,20 @@ export declare class PanoTileStitcher implements PanoStitcher {
|
|
|
86
111
|
progressStamp(): number;
|
|
87
112
|
/** 每帧驱动(三道工序,顺序固定:补下载 → 画已进 GPU 的 → 再传一张新的)。 */
|
|
88
113
|
pump(renderer: WebGLRenderer | null): void;
|
|
89
|
-
/**
|
|
114
|
+
/** 释放一整组(2 张 RT + 该组还没画掉的瓦片纹理);队列里那一记任务跟着撤(组没了还排着 = 队首永远等不到落定) */
|
|
90
115
|
disposeGroup(batch: string): void;
|
|
91
116
|
/** 释放全部(持有者 `dispose()` 时调用);共享的那套 quad/材质/空白纹理一并退掉 */
|
|
92
117
|
dispose(): void;
|
|
93
118
|
private _now;
|
|
94
|
-
/** 工序 1:按「槽 0 格 0 →
|
|
95
|
-
*
|
|
119
|
+
/** 工序 1:按「槽 0 格 0 → …」的顺序补齐还没发起的下载,受三道闸约束(每帧 2 张 / 下载中 ≤8 /
|
|
120
|
+
* 已解码待上传 ≤8)⇒ 一片瓦片终身只 `fetch` 一次(在册条目不重复发)。
|
|
121
|
+
* ⭐ 2026-10-30「加载任务队列」:队列非空时**只喂队首那一组**,它落定(`_isSettled`)就出队、下一帧轮到下一个;
|
|
122
|
+
* ⇒ 存档里第 1 颗全景独享额度下满,才轮到第 2 颗,之后才轮到名册按距离点到、排在队尾等着的那些全景。
|
|
123
|
+
* 队列为空 ⇒ 退回原来的在册顺序逐组补料(图片平面那一路不入队,照旧按在册顺序)。 */
|
|
96
124
|
private _pumpDownloads;
|
|
125
|
+
/** 某一组的取料循环:把「还没画、有地址、没判死、没发起过」的格子交给 `_loadTile`。
|
|
126
|
+
* 返回 false = 三道闸里有一道合上了 ⇒ 本帧别再往下看(`ctx` 是连扣的,跨组也接着算)。 */
|
|
127
|
+
private _issueCells;
|
|
97
128
|
/** 工序 2:把所有「已进 GPU 且还没画」的格子画进各自的 RT —— 一帧画多少格都行(旨②)。
|
|
98
129
|
* ⛔ 准入只看一件事:`uploaded`。没上传过的纹理当场画 = `texImage2D` 藏进 `render()` 里
|
|
99
130
|
* = 每帧一张新纹理那道闸形同虚设(这正是本轮要治的卡顿本体)。 */
|
|
@@ -41,6 +41,8 @@
|
|
|
41
41
|
*
|
|
42
42
|
* ── 每帧的三道工序(旨②那两句话逐条落地,顺序固定)────────────────────────────────
|
|
43
43
|
* 1. `_pumpDownloads()`:补发缺图的下载(每帧 ≤2 张、在册 ≤8、已解码待上传 ≤8)⇒ ⛔ 一批几十条 XHR 一起炸。
|
|
44
|
+
* ⭐ 2026-10-30 需求「加载任务队列」:`_queue` 非空时**只喂队首那一组**,那一组落定才出队开下一组
|
|
45
|
+
* ⇒ 存档前 2 颗全景是一颗一颗下的(第 1 颗下满才轮到第 2 颗),名册按距离点到的排在后面等。
|
|
44
46
|
* 2. `_drawPass()`:**只画「已经在 GPU 上的」瓦片**,一帧内画多少格都行(旨②「已经载入过的纹理,才可以
|
|
45
47
|
* 一次性全部渲染到 RT 上面去」)。画完立刻释放那片 ⇒ 显存峰值 = 一格的量。
|
|
46
48
|
* 3. `_uploadPass()`:**每帧至多一张**新纹理 `initTexture` 进 GPU(旨②「每一帧同样也是只载入一张新纹理
|
|
@@ -166,6 +168,10 @@ void main() {
|
|
|
166
168
|
export class PanoTileStitcher {
|
|
167
169
|
/** 批次组(键 = 32 张瓦片的第一张**非空**地址,与消费方同一个 `rtStitchBatchOf`) */
|
|
168
170
|
_groups = new Map();
|
|
171
|
+
/** 加载任务队列(2026-10-30 需求):批次键的**串行带**,队首那组落定才开下一组。
|
|
172
|
+
* 由 `prioritize` 登记(存档里前 2 颗全景),`_pumpDownloads` 每帧出清;`_switchRenderer`/`dispose`/`disposeGroup`
|
|
173
|
+
* 跟着组一起拆。⛔ 这是「谁先下载」的唯一一把尺 —— 在册顺序(`_groups` 的 Map 序)只决定「同不带队列时」的补料次序。 */
|
|
174
|
+
_queue = [];
|
|
169
175
|
/** 瓦片纹理(url → `{tex, uploaded}`)。画进自己那一格就立刻释放 ⇒ 只在册期间占显存/内存。 */
|
|
170
176
|
_texs = new Map();
|
|
171
177
|
/** 仍在册的组还需要的地址(迟到的加载回调据此判断「这条已经没人要了,立刻释放」) */
|
|
@@ -225,12 +231,12 @@ export class PanoTileStitcher {
|
|
|
225
231
|
const maxTiles = tilesPerBatchOf(L);
|
|
226
232
|
if (!urls || urls.length === 0 || urls.length > maxTiles)
|
|
227
233
|
return;
|
|
234
|
+
const batch = this._batchKeyOf(urls, L);
|
|
235
|
+
if (this._groups.has(batch))
|
|
236
|
+
return;
|
|
228
237
|
const u = urls.slice();
|
|
229
238
|
while (u.length < maxTiles)
|
|
230
239
|
u.push("");
|
|
231
|
-
const batch = rtStitchBatchOf(u);
|
|
232
|
-
if (this._groups.has(batch))
|
|
233
|
-
return;
|
|
234
240
|
const slotCount = L.slotCount;
|
|
235
241
|
const cellsPerSlot = cellsPerSlotOf(L);
|
|
236
242
|
this._groups.set(batch, {
|
|
@@ -246,6 +252,40 @@ export class PanoTileStitcher {
|
|
|
246
252
|
});
|
|
247
253
|
this._stamp++;
|
|
248
254
|
}
|
|
255
|
+
/** 登记进加载任务队列(语义与排队规则见接口上那一段)。已在队列里 ⇒ 不挪(先登记的仍在前)。 */
|
|
256
|
+
prioritize(urls, layout) {
|
|
257
|
+
this.ensure(urls, layout);
|
|
258
|
+
const batch = this._batchKeyOf(urls, layout ?? PANO_TILE_LAYOUT);
|
|
259
|
+
if (!batch || !this._groups.has(batch) || this._queue.includes(batch))
|
|
260
|
+
return;
|
|
261
|
+
this._queue.push(batch);
|
|
262
|
+
}
|
|
263
|
+
/** 地址数组 → 批次键(补齐到满批再取首条非空,与 `ensure` 里同一条尺 ⇒ 两处不会各算各的)。
|
|
264
|
+
* 超限或全空 ⇒ 空串(`ensure` 那一步同样不收,这里也不入队)。 */
|
|
265
|
+
_batchKeyOf(urls, L) {
|
|
266
|
+
const maxTiles = tilesPerBatchOf(L);
|
|
267
|
+
if (!urls || urls.length === 0 || urls.length > maxTiles)
|
|
268
|
+
return "";
|
|
269
|
+
const u = urls.slice(0, maxTiles);
|
|
270
|
+
while (u.length < maxTiles)
|
|
271
|
+
u.push("");
|
|
272
|
+
return rtStitchBatchOf(u);
|
|
273
|
+
}
|
|
274
|
+
/** 这一组的每一格要么已经画进 RT,要么根本没有地址 / 地址已判死 ⇒ 该下的都下完了(队列出清判据)。 */
|
|
275
|
+
_isSettled(g) {
|
|
276
|
+
for (let slot = 0; slot < g.layout.slotCount; slot++) {
|
|
277
|
+
const ids = g.slotTiles[slot];
|
|
278
|
+
for (let cell = 0; cell < g.cellsPerSlot; cell++) {
|
|
279
|
+
if (g.drawn[slot] & (1 << cell))
|
|
280
|
+
continue;
|
|
281
|
+
const url = g.urls[ids[cell]];
|
|
282
|
+
if (!url || this._dead.has(url))
|
|
283
|
+
continue;
|
|
284
|
+
return false;
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
return true;
|
|
288
|
+
}
|
|
249
289
|
/** 取某一拼合槽的纹理:**画过 ≥1 格就交出去**(同日旨「需要先喂给 shader」⇒ 不再等 `cellsPerSlot` 格画满)。
|
|
250
290
|
* RT 建好时整张涂过缺失灰 ⇒ 交出去的那张里**没有未定义像素**,观感 = 该象限逐格显影。 */
|
|
251
291
|
peek(batch, slot, renderer) {
|
|
@@ -297,13 +337,16 @@ export class PanoTileStitcher {
|
|
|
297
337
|
this._drawPass(renderer);
|
|
298
338
|
this._uploadPass(renderer);
|
|
299
339
|
}
|
|
300
|
-
/**
|
|
340
|
+
/** 释放一整组(2 张 RT + 该组还没画掉的瓦片纹理);队列里那一记任务跟着撤(组没了还排着 = 队首永远等不到落定) */
|
|
301
341
|
disposeGroup(batch) {
|
|
302
342
|
const g = this._groups.get(batch);
|
|
303
343
|
if (!g)
|
|
304
344
|
return;
|
|
305
345
|
this._dropGroupGpu(g);
|
|
306
346
|
this._groups.delete(batch);
|
|
347
|
+
const qi = this._queue.indexOf(batch);
|
|
348
|
+
if (qi >= 0)
|
|
349
|
+
this._queue.splice(qi, 1);
|
|
307
350
|
this._stamp++;
|
|
308
351
|
}
|
|
309
352
|
/** 释放全部(持有者 `dispose()` 时调用);共享的那套 quad/材质/空白纹理一并退掉 */
|
|
@@ -325,38 +368,58 @@ export class PanoTileStitcher {
|
|
|
325
368
|
_now() {
|
|
326
369
|
return typeof performance !== "undefined" ? performance.now() : Date.now();
|
|
327
370
|
}
|
|
328
|
-
/** 工序 1:按「槽 0 格 0 →
|
|
329
|
-
*
|
|
371
|
+
/** 工序 1:按「槽 0 格 0 → …」的顺序补齐还没发起的下载,受三道闸约束(每帧 2 张 / 下载中 ≤8 /
|
|
372
|
+
* 已解码待上传 ≤8)⇒ 一片瓦片终身只 `fetch` 一次(在册条目不重复发)。
|
|
373
|
+
* ⭐ 2026-10-30「加载任务队列」:队列非空时**只喂队首那一组**,它落定(`_isSettled`)就出队、下一帧轮到下一个;
|
|
374
|
+
* ⇒ 存档里第 1 颗全景独享额度下满,才轮到第 2 颗,之后才轮到名册按距离点到、排在队尾等着的那些全景。
|
|
375
|
+
* 队列为空 ⇒ 退回原来的在册顺序逐组补料(图片平面那一路不入队,照旧按在册顺序)。 */
|
|
330
376
|
_pumpDownloads() {
|
|
331
|
-
|
|
332
|
-
|
|
377
|
+
while (this._queue.length > 0) {
|
|
378
|
+
const head = this._groups.get(this._queue[0]);
|
|
379
|
+
if (head && !this._isSettled(head))
|
|
380
|
+
break;
|
|
381
|
+
this._queue.shift();
|
|
382
|
+
}
|
|
383
|
+
const ctx = { budget: PANO_STITCH_DOWNLOADS_PER_FRAME, inflight: 0, decoded: 0 };
|
|
333
384
|
for (const rec of this._texs.values()) {
|
|
334
385
|
if (rec.tex)
|
|
335
|
-
decoded++;
|
|
386
|
+
ctx.decoded++;
|
|
336
387
|
else
|
|
337
|
-
inflight++;
|
|
388
|
+
ctx.inflight++;
|
|
389
|
+
}
|
|
390
|
+
if (this._queue.length > 0) {
|
|
391
|
+
const head = this._groups.get(this._queue[0]);
|
|
392
|
+
if (head)
|
|
393
|
+
this._issueCells(head, ctx);
|
|
394
|
+
return;
|
|
338
395
|
}
|
|
339
|
-
let budget = PANO_STITCH_DOWNLOADS_PER_FRAME;
|
|
340
396
|
for (const g of this._groups.values()) {
|
|
341
|
-
if (budget <= 0 || inflight >= PANO_STITCH_MAX_INFLIGHT || decoded >= PANO_STITCH_MAX_DECODED_AHEAD)
|
|
397
|
+
if (ctx.budget <= 0 || ctx.inflight >= PANO_STITCH_MAX_INFLIGHT || ctx.decoded >= PANO_STITCH_MAX_DECODED_AHEAD)
|
|
342
398
|
return;
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
399
|
+
if (!this._issueCells(g, ctx))
|
|
400
|
+
return;
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
/** 某一组的取料循环:把「还没画、有地址、没判死、没发起过」的格子交给 `_loadTile`。
|
|
404
|
+
* 返回 false = 三道闸里有一道合上了 ⇒ 本帧别再往下看(`ctx` 是连扣的,跨组也接着算)。 */
|
|
405
|
+
_issueCells(g, ctx) {
|
|
406
|
+
for (let slot = 0; slot < g.layout.slotCount; slot++) {
|
|
407
|
+
const ids = g.slotTiles[slot];
|
|
408
|
+
for (let cell = 0; cell < g.cellsPerSlot; cell++) {
|
|
409
|
+
if (g.drawn[slot] & (1 << cell))
|
|
410
|
+
continue;
|
|
411
|
+
const url = g.urls[ids[cell]];
|
|
412
|
+
if (!url || this._dead.has(url))
|
|
413
|
+
continue;
|
|
414
|
+
if (this._texs.has(url))
|
|
415
|
+
continue;
|
|
416
|
+
this._loadTile(url);
|
|
417
|
+
ctx.inflight++;
|
|
418
|
+
if (--ctx.budget <= 0 || ctx.inflight >= PANO_STITCH_MAX_INFLIGHT || ctx.decoded >= PANO_STITCH_MAX_DECODED_AHEAD)
|
|
419
|
+
return false;
|
|
358
420
|
}
|
|
359
421
|
}
|
|
422
|
+
return true;
|
|
360
423
|
}
|
|
361
424
|
/** 工序 2:把所有「已进 GPU 且还没画」的格子画进各自的 RT —— 一帧画多少格都行(旨②)。
|
|
362
425
|
* ⛔ 准入只看一件事:`uploaded`。没上传过的纹理当场画 = `texImage2D` 藏进 `render()` 里
|
|
@@ -41,6 +41,8 @@
|
|
|
41
41
|
*
|
|
42
42
|
* ── 每帧的三道工序(旨②那两句话逐条落地,顺序固定)────────────────────────────────
|
|
43
43
|
* 1. `_pumpDownloads()`:补发缺图的下载(每帧 ≤2 张、在册 ≤8、已解码待上传 ≤8)⇒ ⛔ 一批几十条 XHR 一起炸。
|
|
44
|
+
* ⭐ 2026-10-30 需求「加载任务队列」:`_queue` 非空时**只喂队首那一组**,那一组落定才出队开下一组
|
|
45
|
+
* ⇒ 存档前 2 颗全景是一颗一颗下的(第 1 颗下满才轮到第 2 颗),名册按距离点到的排在后面等。
|
|
44
46
|
* 2. `_drawPass()`:**只画「已经在 GPU 上的」瓦片**,一帧内画多少格都行(旨②「已经载入过的纹理,才可以
|
|
45
47
|
* 一次性全部渲染到 RT 上面去」)。画完立刻释放那片 ⇒ 显存峰值 = 一格的量。
|
|
46
48
|
* 3. `_uploadPass()`:**每帧至多一张**新纹理 `initTexture` 进 GPU(旨②「每一帧同样也是只载入一张新纹理
|
|
@@ -238,8 +240,22 @@ interface StitchGroup {
|
|
|
238
240
|
export interface PanoStitcher {
|
|
239
241
|
/** 确保批次已在册并发起瓦片加载(幂等;同批已在册 ⇒ 什么都不做)。
|
|
240
242
|
* 新增可选 `layout` 参数:缺省 = PANO_TILE_LAYOUT(全景 8×4 / 2 槽 / 6144×6144,2026-10-27 需求);
|
|
241
|
-
* 图片平面传 `imageTileLayout(natW, natH)`(2×2 / 1 槽 / RT 宽高比=原图)。
|
|
243
|
+
* 图片平面传 `imageTileLayout(natW, natH)`(2×2 / 1 槽 / RT 宽高比=原图)。
|
|
244
|
+
* ⚠️ 2026-10-30「加载任务队列」:本方法只负责**入册**(新组追加到在册顺序末尾 ⇒ 排在队列里等),
|
|
245
|
+
* 点名优先的那一路换成交换 `prioritize()`(10-29 那版挂在第三个参 `front` 上的「挪到队首」已并进那里,
|
|
246
|
+
* ⛔ 一处排队只有 `prioritize` 一张尺)。 */
|
|
242
247
|
ensure(urls: string[], layout?: TileLayout): void;
|
|
248
|
+
/**
|
|
249
|
+
* 把某批瓦片登记进**加载任务队列**(2026-10-30 需求,逐字:「依次把前 2 个全景的顺序把瓦片加载任务添加到
|
|
250
|
+
* 任务队列里面,第一个加载完了,就把这个加载任务从任务队列里面去掉,然后加载第二个;后续按距离需要哪个全景
|
|
251
|
+
* 的时候同样添加到队列后面等待加载」)。
|
|
252
|
+
* 语义 = 先 `ensure` 入册(不在册就建组),再把批次键登记进队列**末尾**(已在队列里就不再挪,所以先登记的排前面)。
|
|
253
|
+
* 排队规则见 `_pumpDownloads`:**队首那组落定(32 格全画进 RT,或该格本就缺地址/判死)之前,队列里后面的组
|
|
254
|
+
* 和一切没登记的名册组一片都不发** ⇒ 第 1 颗全景独享全部下载额度、最快出全图。
|
|
255
|
+
* ⚠️ 消费方每帧点一次(`MktMesh.prefetchHeadPanoramas`):预览每开一次都换渲染器,`_switchRenderer` 会把在册组
|
|
256
|
+
* 整份作废 ⇒ 队列跟着清空,下一帧重新登记才能把顺序抢回来(只在 `importData` 里点一次等于白点)。
|
|
257
|
+
*/
|
|
258
|
+
prioritize(urls: string[], layout?: TileLayout): void;
|
|
243
259
|
/**
|
|
244
260
|
* 取某一拼合槽的纹理。**画过 ≥1 格就返回**(同日旨「需要先喂给 shader」)。
|
|
245
261
|
* ⭐ 2026-10-09 皇上旨「切换全景时上一个全景的内容要渲染成深灰」:**传了 `renderer` 且该槽一格都还没画时,
|
|
@@ -269,6 +285,10 @@ export interface PanoStitcher {
|
|
|
269
285
|
export class PanoTileStitcher implements PanoStitcher {
|
|
270
286
|
/** 批次组(键 = 32 张瓦片的第一张**非空**地址,与消费方同一个 `rtStitchBatchOf`) */
|
|
271
287
|
private _groups: Map<string, StitchGroup> = new Map();
|
|
288
|
+
/** 加载任务队列(2026-10-30 需求):批次键的**串行带**,队首那组落定才开下一组。
|
|
289
|
+
* 由 `prioritize` 登记(存档里前 2 颗全景),`_pumpDownloads` 每帧出清;`_switchRenderer`/`dispose`/`disposeGroup`
|
|
290
|
+
* 跟着组一起拆。⛔ 这是「谁先下载」的唯一一把尺 —— 在册顺序(`_groups` 的 Map 序)只决定「同不带队列时」的补料次序。 */
|
|
291
|
+
private _queue: string[] = [];
|
|
272
292
|
/** 瓦片纹理(url → `{tex, uploaded}`)。画进自己那一格就立刻释放 ⇒ 只在册期间占显存/内存。 */
|
|
273
293
|
private _texs: Map<string, TileRec> = new Map();
|
|
274
294
|
/** 仍在册的组还需要的地址(迟到的加载回调据此判断「这条已经没人要了,立刻释放」) */
|
|
@@ -330,10 +350,10 @@ private _uploadQueued: Set<TileRec> = new Set();
|
|
|
330
350
|
const L = layout ?? PANO_TILE_LAYOUT;
|
|
331
351
|
const maxTiles = tilesPerBatchOf(L);
|
|
332
352
|
if (!urls || urls.length === 0 || urls.length > maxTiles) return;
|
|
353
|
+
const batch = this._batchKeyOf(urls, L);
|
|
354
|
+
if (this._groups.has(batch)) return;
|
|
333
355
|
const u = urls.slice();
|
|
334
356
|
while (u.length < maxTiles) u.push("");
|
|
335
|
-
const batch = rtStitchBatchOf(u);
|
|
336
|
-
if (this._groups.has(batch)) return;
|
|
337
357
|
const slotCount = L.slotCount;
|
|
338
358
|
const cellsPerSlot = cellsPerSlotOf(L);
|
|
339
359
|
this._groups.set(batch, {
|
|
@@ -350,6 +370,38 @@ private _uploadQueued: Set<TileRec> = new Set();
|
|
|
350
370
|
this._stamp++;
|
|
351
371
|
}
|
|
352
372
|
|
|
373
|
+
/** 登记进加载任务队列(语义与排队规则见接口上那一段)。已在队列里 ⇒ 不挪(先登记的仍在前)。 */
|
|
374
|
+
prioritize(urls: string[], layout?: TileLayout): void {
|
|
375
|
+
this.ensure(urls, layout);
|
|
376
|
+
const batch = this._batchKeyOf(urls, layout ?? PANO_TILE_LAYOUT);
|
|
377
|
+
if (!batch || !this._groups.has(batch) || this._queue.includes(batch)) return;
|
|
378
|
+
this._queue.push(batch);
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
/** 地址数组 → 批次键(补齐到满批再取首条非空,与 `ensure` 里同一条尺 ⇒ 两处不会各算各的)。
|
|
382
|
+
* 超限或全空 ⇒ 空串(`ensure` 那一步同样不收,这里也不入队)。 */
|
|
383
|
+
private _batchKeyOf(urls: string[], L: TileLayout): string {
|
|
384
|
+
const maxTiles = tilesPerBatchOf(L);
|
|
385
|
+
if (!urls || urls.length === 0 || urls.length > maxTiles) return "";
|
|
386
|
+
const u = urls.slice(0, maxTiles);
|
|
387
|
+
while (u.length < maxTiles) u.push("");
|
|
388
|
+
return rtStitchBatchOf(u);
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
/** 这一组的每一格要么已经画进 RT,要么根本没有地址 / 地址已判死 ⇒ 该下的都下完了(队列出清判据)。 */
|
|
392
|
+
private _isSettled(g: StitchGroup): boolean {
|
|
393
|
+
for (let slot = 0; slot < g.layout.slotCount; slot++) {
|
|
394
|
+
const ids = g.slotTiles[slot];
|
|
395
|
+
for (let cell = 0; cell < g.cellsPerSlot; cell++) {
|
|
396
|
+
if (g.drawn[slot] & (1 << cell)) continue;
|
|
397
|
+
const url = g.urls[ids[cell]];
|
|
398
|
+
if (!url || this._dead.has(url)) continue;
|
|
399
|
+
return false;
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
return true;
|
|
403
|
+
}
|
|
404
|
+
|
|
353
405
|
/** 取某一拼合槽的纹理:**画过 ≥1 格就交出去**(同日旨「需要先喂给 shader」⇒ 不再等 `cellsPerSlot` 格画满)。
|
|
354
406
|
* RT 建好时整张涂过缺失灰 ⇒ 交出去的那张里**没有未定义像素**,观感 = 该象限逐格显影。 */
|
|
355
407
|
peek(batch: string, slot: number, renderer?: WebGLRenderer | null): Texture | null {
|
|
@@ -401,12 +453,14 @@ private _uploadQueued: Set<TileRec> = new Set();
|
|
|
401
453
|
this._uploadPass(renderer);
|
|
402
454
|
}
|
|
403
455
|
|
|
404
|
-
/**
|
|
456
|
+
/** 释放一整组(2 张 RT + 该组还没画掉的瓦片纹理);队列里那一记任务跟着撤(组没了还排着 = 队首永远等不到落定) */
|
|
405
457
|
disposeGroup(batch: string): void {
|
|
406
458
|
const g = this._groups.get(batch);
|
|
407
459
|
if (!g) return;
|
|
408
460
|
this._dropGroupGpu(g);
|
|
409
461
|
this._groups.delete(batch);
|
|
462
|
+
const qi = this._queue.indexOf(batch);
|
|
463
|
+
if (qi >= 0) this._queue.splice(qi, 1);
|
|
410
464
|
this._stamp++;
|
|
411
465
|
}
|
|
412
466
|
|
|
@@ -431,31 +485,49 @@ private _uploadQueued: Set<TileRec> = new Set();
|
|
|
431
485
|
return typeof performance !== "undefined" ? performance.now() : Date.now();
|
|
432
486
|
}
|
|
433
487
|
|
|
434
|
-
/** 工序 1:按「槽 0 格 0 →
|
|
435
|
-
*
|
|
488
|
+
/** 工序 1:按「槽 0 格 0 → …」的顺序补齐还没发起的下载,受三道闸约束(每帧 2 张 / 下载中 ≤8 /
|
|
489
|
+
* 已解码待上传 ≤8)⇒ 一片瓦片终身只 `fetch` 一次(在册条目不重复发)。
|
|
490
|
+
* ⭐ 2026-10-30「加载任务队列」:队列非空时**只喂队首那一组**,它落定(`_isSettled`)就出队、下一帧轮到下一个;
|
|
491
|
+
* ⇒ 存档里第 1 颗全景独享额度下满,才轮到第 2 颗,之后才轮到名册按距离点到、排在队尾等着的那些全景。
|
|
492
|
+
* 队列为空 ⇒ 退回原来的在册顺序逐组补料(图片平面那一路不入队,照旧按在册顺序)。 */
|
|
436
493
|
private _pumpDownloads(): void {
|
|
437
|
-
|
|
438
|
-
|
|
494
|
+
while (this._queue.length > 0) {
|
|
495
|
+
const head = this._groups.get(this._queue[0]);
|
|
496
|
+
if (head && !this._isSettled(head)) break;
|
|
497
|
+
this._queue.shift();
|
|
498
|
+
}
|
|
499
|
+
const ctx = { budget: PANO_STITCH_DOWNLOADS_PER_FRAME, inflight: 0, decoded: 0 };
|
|
439
500
|
for (const rec of this._texs.values()) {
|
|
440
|
-
if (rec.tex) decoded++;
|
|
441
|
-
else inflight++;
|
|
501
|
+
if (rec.tex) ctx.decoded++;
|
|
502
|
+
else ctx.inflight++;
|
|
503
|
+
}
|
|
504
|
+
if (this._queue.length > 0) {
|
|
505
|
+
const head = this._groups.get(this._queue[0]);
|
|
506
|
+
if (head) this._issueCells(head, ctx);
|
|
507
|
+
return;
|
|
442
508
|
}
|
|
443
|
-
let budget = PANO_STITCH_DOWNLOADS_PER_FRAME;
|
|
444
509
|
for (const g of this._groups.values()) {
|
|
445
|
-
if (budget <= 0 || inflight >= PANO_STITCH_MAX_INFLIGHT || decoded >= PANO_STITCH_MAX_DECODED_AHEAD) return;
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
510
|
+
if (ctx.budget <= 0 || ctx.inflight >= PANO_STITCH_MAX_INFLIGHT || ctx.decoded >= PANO_STITCH_MAX_DECODED_AHEAD) return;
|
|
511
|
+
if (!this._issueCells(g, ctx)) return;
|
|
512
|
+
}
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
/** 某一组的取料循环:把「还没画、有地址、没判死、没发起过」的格子交给 `_loadTile`。
|
|
516
|
+
* 返回 false = 三道闸里有一道合上了 ⇒ 本帧别再往下看(`ctx` 是连扣的,跨组也接着算)。 */
|
|
517
|
+
private _issueCells(g: StitchGroup, ctx: { budget: number; inflight: number; decoded: number }): boolean {
|
|
518
|
+
for (let slot = 0; slot < g.layout.slotCount; slot++) {
|
|
519
|
+
const ids = g.slotTiles[slot];
|
|
520
|
+
for (let cell = 0; cell < g.cellsPerSlot; cell++) {
|
|
521
|
+
if (g.drawn[slot] & (1 << cell)) continue;
|
|
522
|
+
const url = g.urls[ids[cell]];
|
|
523
|
+
if (!url || this._dead.has(url)) continue;
|
|
524
|
+
if (this._texs.has(url)) continue;
|
|
525
|
+
this._loadTile(url);
|
|
526
|
+
ctx.inflight++;
|
|
527
|
+
if (--ctx.budget <= 0 || ctx.inflight >= PANO_STITCH_MAX_INFLIGHT || ctx.decoded >= PANO_STITCH_MAX_DECODED_AHEAD) return false;
|
|
457
528
|
}
|
|
458
529
|
}
|
|
530
|
+
return true;
|
|
459
531
|
}
|
|
460
532
|
|
|
461
533
|
/** 工序 2:把所有「已进 GPU 且还没画」的格子画进各自的 RT —— 一帧画多少格都行(旨②)。
|
package/package.json
CHANGED
package/threeLoader.js
CHANGED
|
@@ -28,6 +28,8 @@ class ThreeLoader extends EventDispatcher {
|
|
|
28
28
|
};
|
|
29
29
|
// 资源加载进度
|
|
30
30
|
DefaultLoadingManager.onProgress = (url, loaded, total) => {
|
|
31
|
+
// 三个数一并转发:首页只认「还在不在加载」,弹窗一类要看进度
|
|
32
|
+
this.dispatchEvent({ type: "progress", url, loaded, total });
|
|
31
33
|
if (loaded < total) {
|
|
32
34
|
this.dispatchEvent({ type: "loadding" });
|
|
33
35
|
}
|
package/threeLoader.ts
CHANGED
|
@@ -32,6 +32,8 @@ class ThreeLoader extends EventDispatcher<ThreeLoaderEventMap> {
|
|
|
32
32
|
};
|
|
33
33
|
// 资源加载进度
|
|
34
34
|
DefaultLoadingManager.onProgress = (url: string, loaded: number, total: number) => {
|
|
35
|
+
// 三个数一并转发:首页只认「还在不在加载」,弹窗一类要看进度
|
|
36
|
+
this.dispatchEvent({ type: "progress", url, loaded, total });
|
|
35
37
|
if (loaded < total) {
|
|
36
38
|
this.dispatchEvent({ type: "loadding" });
|
|
37
39
|
}
|