@drawcall/lod 0.5.1 → 0.5.3

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.
@@ -7,14 +7,14 @@ export interface Layer {
7
7
  }
8
8
  /** What the worker encodes: RGBA8 rows of a mip level, `width` wide and `height` high. */
9
9
  export interface Job {
10
- data: Uint8Array;
10
+ data: Uint8Array<ArrayBuffer>;
11
11
  width: number;
12
12
  height: number;
13
13
  srgb: boolean;
14
14
  }
15
15
  /** What the worker answers a job with. */
16
16
  export type Reply = {
17
- file: Uint8Array;
17
+ file: Uint8Array<ArrayBuffer>;
18
18
  } | {
19
19
  error: string;
20
20
  };
@@ -1,5 +1,5 @@
1
1
  import { read, write } from "ktx-parse";
2
- import { inNode } from "../../node-process.js";
2
+ import { inNode, nodeOnly } from "../../node-process.js";
3
3
  // Read when the module loads, which also happens in Node, as the vitecache plugin builds levels; Node before 21 has no navigator.
4
4
  const cores = globalThis.navigator?.hardwareConcurrency ?? 4;
5
5
  /**
@@ -53,9 +53,7 @@ function strips(data, size, srgb, parts) {
53
53
  }
54
54
  return jobs;
55
55
  }
56
- async function encodeJob(job) {
57
- if (inNode)
58
- return (await import("./uastc.js")).encode(job);
56
+ function encodeJob(job) {
59
57
  return new Promise((resolve, reject) => {
60
58
  queue.push({ job, resolve, reject });
61
59
  queue.sort((a, b) => b.job.width * b.job.height - a.job.width * a.job.height);
@@ -66,12 +64,12 @@ async function encodeJob(job) {
66
64
  /** One worker, with its own encoder instance, that takes queued jobs until none is left and then ends. */
67
65
  async function work() {
68
66
  workers++;
69
- let worker;
67
+ let encoder;
70
68
  try {
71
69
  for (let task = queue.shift(); task; task = queue.shift()) {
72
70
  try {
73
- worker ??= await start();
74
- const reply = await run(worker, task.job);
71
+ encoder ??= await start();
72
+ const reply = await encoder.run(task.job);
75
73
  if ("file" in reply)
76
74
  task.resolve(reply.file);
77
75
  else
@@ -80,19 +78,36 @@ async function work() {
80
78
  catch (error) {
81
79
  // The worker failed to load or crashed; the next job gets a fresh one.
82
80
  task.reject(error);
83
- worker?.terminate();
84
- worker = undefined;
81
+ encoder?.terminate();
82
+ encoder = undefined;
85
83
  }
86
84
  }
87
85
  }
88
86
  finally {
89
- worker?.terminate();
87
+ encoder?.terminate();
90
88
  workers--;
91
89
  }
92
90
  }
93
91
  async function start() {
94
92
  const { url } = await import("./worker.js");
95
- return new Worker(url, { type: "module" });
93
+ if (inNode) {
94
+ const [{ Worker }, { once }] = await Promise.all([
95
+ nodeOnly("node:worker_threads"),
96
+ nodeOnly("node:events"),
97
+ ]);
98
+ const thread = new Worker(new URL(url), { workerData: url });
99
+ return {
100
+ // the thread's next message, or its error
101
+ async run(job) {
102
+ thread.postMessage(job, [job.data.buffer]);
103
+ const reply = (await once(thread, "message"))[0];
104
+ return reply;
105
+ },
106
+ terminate: () => void thread.terminate(),
107
+ };
108
+ }
109
+ const worker = new Worker(url, { type: "module" });
110
+ return { run: (job) => run(worker, job), terminate: () => worker.terminate() };
96
111
  }
97
112
  /** `job` on `worker`; rejects when the worker fails rather than answers, as one that does not load does. */
98
113
  function run(worker, job) {
@@ -1 +1 @@
1
- {"version":3,"file":"encode.js","sourceRoot":"","sources":["../../../src/impostor/ktx2/encode.ts"],"names":[],"mappings":"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","sourcesContent":["import { read, write } from \"ktx-parse\";\nimport { inNode } from \"../../node-process.js\";\n\n/** One map's RGBA8 mip levels, `size >> level` square. */\nexport interface Layer {\n levels: Uint8Array[];\n size: number;\n /** Whether the texels are sRGB-encoded colour; otherwise linear data. */\n srgb: boolean;\n}\n\n/** What the worker encodes: RGBA8 rows of a mip level, `width` wide and `height` high. */\nexport interface Job {\n data: Uint8Array;\n width: number;\n height: number;\n srgb: boolean;\n}\n\n/** What the worker answers a job with. */\nexport type Reply = { file: Uint8Array } | { error: string };\n\ninterface Task {\n job: Job;\n resolve(file: Uint8Array): void;\n reject(error: unknown): void;\n}\n\n// Read when the module loads, which also happens in Node, as the vitecache plugin builds levels; Node before 21 has no navigator.\nconst cores = globalThis.navigator?.hardwareConcurrency ?? 4;\n/**\n * A worker per core but one, since a cold ladder's encodes take far longer than its bakes; at most 8, since each holds\n * 100 to 250 MB of WebAssembly memory.\n */\nconst LIMIT = Math.max(1, Math.min(8, cores - 1));\n/** Largest job first, so that the smaller ones fill the other workers meanwhile. */\nconst queue: Task[] = [];\n/** How many encoder workers run: a process-wide pool, as each holds its own WASM memory and LIMIT bounds them all. */\nlet workers = 0;\n\n/**\n * One map as a KTX2 file of Basis UASTC. Each level encodes as `parts` strips of rows, by default one per worker, so\n * that a map's top level, which holds three quarters of its texels, takes all workers; the strips' and levels' files\n * are then joined. Takes about a second per million texels and worker.\n */\nexport async function encodeLayer({ levels, size, srgb }: Layer, parts = LIMIT): Promise<Uint8Array> {\n const files = await Promise.all(levels.map((data, m) => Promise.all(strips(data, size >> m, srgb, parts).map((job) => encodeJob(job).then(read)))));\n const first = files[0]?.[0];\n if (!first) throw new Error(\"encodeImpostorAsync: a map has no mip levels\");\n // UASTC encodes each 4x4 block on its own, and a level's Zstandard data may be several frames, which decode as one:\n // a level's strips, each a multiple of four rows high but the last, concatenate to data that decodes as the whole level's.\n first.levels = files.map((parts) => {\n const levels = parts.map((file) => {\n const [level] = file.levels;\n if (!level) throw new Error(\"encodeImpostorAsync: the encoder wrote a file without levels\");\n return level;\n });\n const levelData = new Uint8Array(levels.reduce((sum, level) => sum + level.levelData.length, 0));\n let offset = 0;\n for (const level of levels) {\n levelData.set(level.levelData, offset);\n offset += level.levelData.length;\n }\n return { levelData, uncompressedByteLength: levels.reduce((sum, level) => sum + level.uncompressedByteLength, 0) };\n });\n first.pixelHeight = size;\n first.levelCount = files.length;\n return write(first);\n}\n\n/** A `size`² level's strips: at most `parts`, each a multiple of four rows high but perhaps the last. */\nfunction strips(data: Uint8Array, size: number, srgb: boolean, parts: number): Job[] {\n const rows = 4 * Math.ceil(size / parts / 4);\n const jobs: Job[] = [];\n // Sliced: each job owns its buffer, which moves to its worker.\n for (let y = 0; y < size; y += rows) {\n const height = Math.min(rows, size - y);\n jobs.push({ data: data.slice(4 * size * y, 4 * size * (y + height)), width: size, height, srgb });\n }\n return jobs;\n}\n\nasync function encodeJob(job: Job): Promise<Uint8Array> {\n if (inNode) return (await import(\"./uastc.js\")).encode(job);\n return new Promise((resolve, reject) => {\n queue.push({ job, resolve, reject });\n queue.sort((a, b) => b.job.width * b.job.height - a.job.width * a.job.height);\n if (workers < LIMIT) void work();\n });\n}\n\n/** One worker, with its own encoder instance, that takes queued jobs until none is left and then ends. */\nasync function work(): Promise<void> {\n workers++;\n let worker: Worker | undefined;\n try {\n for (let task = queue.shift(); task; task = queue.shift()) {\n try {\n worker ??= await start();\n const reply = await run(worker, task.job);\n if (\"file\" in reply) task.resolve(reply.file);\n else task.reject(new Error(`encodeImpostorAsync: ${reply.error}`));\n } catch (error) {\n // The worker failed to load or crashed; the next job gets a fresh one.\n task.reject(error);\n worker?.terminate();\n worker = undefined;\n }\n }\n } finally {\n worker?.terminate();\n workers--;\n }\n}\n\nasync function start(): Promise<Worker> {\n const { url } = await import(\"./worker.js\");\n return new Worker(url, { type: \"module\" });\n}\n\n/** `job` on `worker`; rejects when the worker fails rather than answers, as one that does not load does. */\nfunction run(worker: Worker, job: Job): Promise<Reply> {\n return new Promise((resolve, reject) => {\n const settle = () => {\n worker.removeEventListener(\"message\", message);\n worker.removeEventListener(\"error\", error);\n worker.removeEventListener(\"messageerror\", error);\n };\n const message = (event: MessageEvent<Reply>) => {\n settle();\n resolve(event.data);\n };\n const error = (event: Event) => {\n settle();\n const why = event instanceof ErrorEvent ? event.message : `${event.type} event`;\n reject(new Error(`encodeImpostorAsync: the encoder worker failed (${why})`));\n };\n worker.addEventListener(\"message\", message);\n worker.addEventListener(\"error\", error);\n worker.addEventListener(\"messageerror\", error);\n worker.postMessage(job, [job.data.buffer]);\n });\n}\n"]}
1
+ {"version":3,"file":"encode.js","sourceRoot":"","sources":["../../../src/impostor/ktx2/encode.ts"],"names":[],"mappings":"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","sourcesContent":["import { read, write } from \"ktx-parse\";\nimport { inNode, nodeOnly } from \"../../node-process.js\";\n\n/** One map's RGBA8 mip levels, `size >> level` square. */\nexport interface Layer {\n levels: Uint8Array[];\n size: number;\n /** Whether the texels are sRGB-encoded colour; otherwise linear data. */\n srgb: boolean;\n}\n\n/** What the worker encodes: RGBA8 rows of a mip level, `width` wide and `height` high. */\nexport interface Job {\n data: Uint8Array<ArrayBuffer>;\n width: number;\n height: number;\n srgb: boolean;\n}\n\n/** What the worker answers a job with. */\nexport type Reply = { file: Uint8Array<ArrayBuffer> } | { error: string };\n\ninterface Task {\n job: Job;\n resolve(file: Uint8Array): void;\n reject(error: unknown): void;\n}\n\n// Read when the module loads, which also happens in Node, as the vitecache plugin builds levels; Node before 21 has no navigator.\nconst cores = globalThis.navigator?.hardwareConcurrency ?? 4;\n/**\n * A worker per core but one, since a cold ladder's encodes take far longer than its bakes; at most 8, since each holds\n * 100 to 250 MB of WebAssembly memory.\n */\nconst LIMIT = Math.max(1, Math.min(8, cores - 1));\n/** Largest job first, so that the smaller ones fill the other workers meanwhile. */\nconst queue: Task[] = [];\n/** How many encoder workers run: a process-wide pool, as each holds its own WASM memory and LIMIT bounds them all. */\nlet workers = 0;\n\n/**\n * One map as a KTX2 file of Basis UASTC. Each level encodes as `parts` strips of rows, by default one per worker, so\n * that a map's top level, which holds three quarters of its texels, takes all workers; the strips' and levels' files\n * are then joined. Takes about a second per million texels and worker.\n */\nexport async function encodeLayer({ levels, size, srgb }: Layer, parts = LIMIT): Promise<Uint8Array> {\n const files = await Promise.all(levels.map((data, m) => Promise.all(strips(data, size >> m, srgb, parts).map((job) => encodeJob(job).then(read)))));\n const first = files[0]?.[0];\n if (!first) throw new Error(\"encodeImpostorAsync: a map has no mip levels\");\n // UASTC encodes each 4x4 block on its own, and a level's Zstandard data may be several frames, which decode as one:\n // a level's strips, each a multiple of four rows high but the last, concatenate to data that decodes as the whole level's.\n first.levels = files.map((parts) => {\n const levels = parts.map((file) => {\n const [level] = file.levels;\n if (!level) throw new Error(\"encodeImpostorAsync: the encoder wrote a file without levels\");\n return level;\n });\n const levelData = new Uint8Array(levels.reduce((sum, level) => sum + level.levelData.length, 0));\n let offset = 0;\n for (const level of levels) {\n levelData.set(level.levelData, offset);\n offset += level.levelData.length;\n }\n return { levelData, uncompressedByteLength: levels.reduce((sum, level) => sum + level.uncompressedByteLength, 0) };\n });\n first.pixelHeight = size;\n first.levelCount = files.length;\n return write(first);\n}\n\n/** A `size`² level's strips: at most `parts`, each a multiple of four rows high but perhaps the last. */\nfunction strips(data: Uint8Array, size: number, srgb: boolean, parts: number): Job[] {\n const rows = 4 * Math.ceil(size / parts / 4);\n const jobs: Job[] = [];\n // Sliced: each job owns its buffer, which moves to its worker.\n for (let y = 0; y < size; y += rows) {\n const height = Math.min(rows, size - y);\n jobs.push({ data: data.slice(4 * size * y, 4 * size * (y + height)), width: size, height, srgb });\n }\n return jobs;\n}\n\nfunction encodeJob(job: Job): Promise<Uint8Array> {\n return new Promise((resolve, reject) => {\n queue.push({ job, resolve, reject });\n queue.sort((a, b) => b.job.width * b.job.height - a.job.width * a.job.height);\n if (workers < LIMIT) void work();\n });\n}\n\n/** An encoder worker: a module worker in browsers, a worker thread in Node (see `inNode`). */\ninterface Encoder {\n run(job: Job): Promise<Reply>;\n terminate(): void;\n}\n\n/** One worker, with its own encoder instance, that takes queued jobs until none is left and then ends. */\nasync function work(): Promise<void> {\n workers++;\n let encoder: Encoder | undefined;\n try {\n for (let task = queue.shift(); task; task = queue.shift()) {\n try {\n encoder ??= await start();\n const reply = await encoder.run(task.job);\n if (\"file\" in reply) task.resolve(reply.file);\n else task.reject(new Error(`encodeImpostorAsync: ${reply.error}`));\n } catch (error) {\n // The worker failed to load or crashed; the next job gets a fresh one.\n task.reject(error);\n encoder?.terminate();\n encoder = undefined;\n }\n }\n } finally {\n encoder?.terminate();\n workers--;\n }\n}\n\nasync function start(): Promise<Encoder> {\n const { url } = await import(\"./worker.js\");\n if (inNode) {\n const [{ Worker }, { once }] = await Promise.all([\n nodeOnly<typeof import(\"node:worker_threads\")>(\"node:worker_threads\"),\n nodeOnly<typeof import(\"node:events\")>(\"node:events\"),\n ]);\n const thread = new Worker(new URL(url), { workerData: url });\n return {\n // the thread's next message, or its error\n async run(job) {\n thread.postMessage(job, [job.data.buffer]);\n const reply: Reply = (await once(thread, \"message\"))[0];\n return reply;\n },\n terminate: () => void thread.terminate(),\n };\n }\n const worker = new Worker(url, { type: \"module\" });\n return { run: (job) => run(worker, job), terminate: () => worker.terminate() };\n}\n\n/** `job` on `worker`; rejects when the worker fails rather than answers, as one that does not load does. */\nfunction run(worker: Worker, job: Job): Promise<Reply> {\n return new Promise((resolve, reject) => {\n const settle = () => {\n worker.removeEventListener(\"message\", message);\n worker.removeEventListener(\"error\", error);\n worker.removeEventListener(\"messageerror\", error);\n };\n const message = (event: MessageEvent<Reply>) => {\n settle();\n resolve(event.data);\n };\n const error = (event: Event) => {\n settle();\n const why = event instanceof ErrorEvent ? event.message : `${event.type} event`;\n reject(new Error(`encodeImpostorAsync: the encoder worker failed (${why})`));\n };\n worker.addEventListener(\"message\", message);\n worker.addEventListener(\"error\", error);\n worker.addEventListener(\"messageerror\", error);\n worker.postMessage(job, [job.data.buffer]);\n });\n}\n"]}
@@ -4,4 +4,4 @@ import type { Job } from "./encode.js";
4
4
  * without RDO, which would trade normal accuracy for size. The encoder's own mips would filter level 0, so each baked
5
5
  * level is encoded on its own (see `encodeLayer`).
6
6
  */
7
- export declare function encode({ data, width, height, srgb }: Job): Promise<Uint8Array>;
7
+ export declare function encode({ data, width, height, srgb }: Job): Promise<Uint8Array<ArrayBuffer>>;
@@ -1 +1 @@
1
- {"version":3,"file":"uastc.js","sourceRoot":"","sources":["../../../src/impostor/ktx2/uastc.ts"],"names":[],"mappings":"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","sourcesContent":["import { inflateSync } from \"fflate\";\nimport type { Job } from \"./encode.js\";\nimport { ENCODER_JS, ENCODER_WASM } from \"./wasm/encoder.js\";\n\n/** The members of Basis's encoder bindings (embind, see basisu's webgl/encoder) that `encode` uses. */\ninterface BasisEncoder {\n setCreateKTX2File(on: boolean): void;\n setUASTC(on: boolean): void;\n setTexType(type: number): void;\n setSliceSourceImage(slice: number, data: Uint8Array, width: number, height: number, format: number): boolean;\n setPerceptual(on: boolean): void;\n setKTX2AndBasisSRGBTransferFunc(on: boolean): void;\n setMipGen(on: boolean): void;\n setPackUASTCFlags(flags: number): void;\n setKTX2UASTCSupercompression(on: boolean): void;\n encode(out: Uint8Array): number;\n delete(): void;\n}\ninterface Basis {\n BasisEncoder: new () => BasisEncoder;\n initializeBasis(): void;\n}\n\n/** basisu's encoder module, instantiated once: a process-wide WASM module, as three's decoders are. */\nlet basis: Promise<Basis> | undefined;\n\n/**\n * basisu's pack flags: level 1, which encodes about twice as fast as the default level 2 and loses under 0.1 dB on\n * albedo and normal maps (level 0 loses 1 to 4 dB), with the fastest ETC1 hints (128), which only transcodes to ETC1\n * and ETC2 read; three picks those only on a GPU without ASTC and BC texture compression, which WebGPU adapters on\n * desktops and most phones have.\n */\nconst PACK_FLAGS = 1 | 128;\n\n/**\n * Rows of a mip level as a KTX2 file of UASTC LDR 4x4 with Zstandard supercompression, at level 1 (see PACK_FLAGS) and\n * without RDO, which would trade normal accuracy for size. The encoder's own mips would filter level 0, so each baked\n * level is encoded on its own (see `encodeLayer`).\n */\nexport async function encode({ data, width, height, srgb }: Job): Promise<Uint8Array> {\n const encoder = new (await (basis ??= load())).BasisEncoder();\n try {\n encoder.setCreateKTX2File(true);\n encoder.setUASTC(true);\n // basisu's enums: texture type 0 is a 2D texture; image format 0 is raw RGBA8\n encoder.setTexType(0);\n if (!encoder.setSliceSourceImage(0, data, width, height, 0)) throw new Error(`the Basis encoder took no ${width}x${height} image`);\n encoder.setPerceptual(srgb);\n encoder.setKTX2AndBasisSRGBTransferFunc(srgb);\n encoder.setMipGen(false);\n encoder.setPackUASTCFlags(PACK_FLAGS);\n encoder.setKTX2UASTCSupercompression(true);\n const out = new Uint8Array(4 * width * height + 65536);\n const length = encoder.encode(out);\n if (length === 0) throw new Error(`the Basis encoder failed on a ${width}x${height} image`);\n return out.slice(0, length);\n } finally {\n encoder.delete();\n }\n}\n\n/**\n * The encoder's Emscripten module (see scripts/basis.ts), started on its binary. Imported from a data: URL, which\n * browsers and Node both import, unlike a blob: URL.\n */\nasync function load(): Promise<Basis> {\n const url = `data:text/javascript,${encodeURIComponent(new TextDecoder().decode(inflate(ENCODER_JS)))}`;\n const factory: unknown = (await import(/* @vite-ignore */ url)).default;\n if (typeof factory !== \"function\") throw new Error(\"the Basis encoder module has no factory\");\n // the module names its binary relative to its own URL, which a data: URL's is not: named, then given. It prints every\n // image's progress, which nothing needs.\n const basis: unknown = await factory({ wasmBinary: inflate(ENCODER_WASM), locateFile: (name: string) => name, print: () => {} });\n if (!isBasis(basis)) throw new Error(\"the Basis encoder module lacks its bindings\");\n basis.initializeBasis();\n return basis;\n}\n\nfunction isBasis(value: unknown): value is Basis {\n return (\n typeof value === \"object\" &&\n value !== null &&\n typeof Reflect.get(value, \"BasisEncoder\") === \"function\" &&\n typeof Reflect.get(value, \"initializeBasis\") === \"function\"\n );\n}\n\nconst inflate = (base64: string) => inflateSync(Uint8Array.from(atob(base64), (c) => c.charCodeAt(0)));\n"]}
1
+ {"version":3,"file":"uastc.js","sourceRoot":"","sources":["../../../src/impostor/ktx2/uastc.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,QAAQ,CAAC;AAErC,OAAO,EAAE,UAAU,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AAqB7D,uGAAuG;AACvG,IAAI,KAAiC,CAAC;AAEtC;;;;;GAKG;AACH,MAAM,UAAU,GAAG,CAAC,GAAG,GAAG,CAAC;AAE3B;;;;GAIG;AACH,MAAM,CAAC,KAAK,UAAU,MAAM,CAAC,EAAE,IAAI,EAAE,KAAK,EAAE,MAAM,EAAE,IAAI,EAAO;IAC7D,MAAM,OAAO,GAAG,IAAI,CAAC,MAAM,CAAC,KAAK,KAAK,IAAI,EAAE,CAAC,CAAC,CAAC,YAAY,EAAE,CAAC;IAC9D,IAAI,CAAC;QACH,OAAO,CAAC,iBAAiB,CAAC,IAAI,CAAC,CAAC;QAChC,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC;QACvB,8EAA8E;QAC9E,OAAO,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC;QACtB,IAAI,CAAC,OAAO,CAAC,mBAAmB,CAAC,CAAC,EAAE,IAAI,EAAE,KAAK,EAAE,MAAM,EAAE,CAAC,CAAC;YAAE,MAAM,IAAI,KAAK,CAAC,6BAA6B,KAAK,IAAI,MAAM,QAAQ,CAAC,CAAC;QACnI,OAAO,CAAC,aAAa,CAAC,IAAI,CAAC,CAAC;QAC5B,OAAO,CAAC,+BAA+B,CAAC,IAAI,CAAC,CAAC;QAC9C,OAAO,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC;QACzB,OAAO,CAAC,iBAAiB,CAAC,UAAU,CAAC,CAAC;QACtC,OAAO,CAAC,4BAA4B,CAAC,IAAI,CAAC,CAAC;QAC3C,MAAM,GAAG,GAAG,IAAI,UAAU,CAAC,CAAC,GAAG,KAAK,GAAG,MAAM,GAAG,KAAK,CAAC,CAAC;QACvD,MAAM,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;QACnC,IAAI,MAAM,KAAK,CAAC;YAAE,MAAM,IAAI,KAAK,CAAC,iCAAiC,KAAK,IAAI,MAAM,QAAQ,CAAC,CAAC;QAC5F,OAAO,GAAG,CAAC,KAAK,CAAC,CAAC,EAAE,MAAM,CAAC,CAAC;IAC9B,CAAC;YAAS,CAAC;QACT,OAAO,CAAC,MAAM,EAAE,CAAC;IACnB,CAAC;AACH,CAAC;AAED;;;GAGG;AACH,KAAK,UAAU,IAAI;IACjB,MAAM,GAAG,GAAG,wBAAwB,kBAAkB,CAAC,IAAI,WAAW,EAAE,CAAC,MAAM,CAAC,OAAO,CAAC,UAAU,CAAC,CAAC,CAAC,EAAE,CAAC;IACxG,MAAM,OAAO,GAAY,CAAC,MAAM,MAAM,CAAC,kBAAkB,CAAC,GAAG,CAAC,CAAC,CAAC,OAAO,CAAC;IACxE,IAAI,OAAO,OAAO,KAAK,UAAU;QAAE,MAAM,IAAI,KAAK,CAAC,yCAAyC,CAAC,CAAC;IAC9F,sHAAsH;IACtH,yCAAyC;IACzC,MAAM,KAAK,GAAY,MAAM,OAAO,CAAC,EAAE,UAAU,EAAE,OAAO,CAAC,YAAY,CAAC,EAAE,UAAU,EAAE,CAAC,IAAY,EAAE,EAAE,CAAC,IAAI,EAAE,KAAK,EAAE,GAAG,EAAE,GAAE,CAAC,EAAE,CAAC,CAAC;IACjI,IAAI,CAAC,OAAO,CAAC,KAAK,CAAC;QAAE,MAAM,IAAI,KAAK,CAAC,6CAA6C,CAAC,CAAC;IACpF,KAAK,CAAC,eAAe,EAAE,CAAC;IACxB,OAAO,KAAK,CAAC;AACf,CAAC;AAED,SAAS,OAAO,CAAC,KAAc;IAC7B,OAAO,CACL,OAAO,KAAK,KAAK,QAAQ;QACzB,KAAK,KAAK,IAAI;QACd,OAAO,OAAO,CAAC,GAAG,CAAC,KAAK,EAAE,cAAc,CAAC,KAAK,UAAU;QACxD,OAAO,OAAO,CAAC,GAAG,CAAC,KAAK,EAAE,iBAAiB,CAAC,KAAK,UAAU,CAC5D,CAAC;AACJ,CAAC;AAED,MAAM,OAAO,GAAG,CAAC,MAAc,EAAE,EAAE,CAAC,WAAW,CAAC,UAAU,CAAC,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC","sourcesContent":["import { inflateSync } from \"fflate\";\nimport type { Job } from \"./encode.js\";\nimport { ENCODER_JS, ENCODER_WASM } from \"./wasm/encoder.js\";\n\n/** The members of Basis's encoder bindings (embind, see basisu's webgl/encoder) that `encode` uses. */\ninterface BasisEncoder {\n setCreateKTX2File(on: boolean): void;\n setUASTC(on: boolean): void;\n setTexType(type: number): void;\n setSliceSourceImage(slice: number, data: Uint8Array, width: number, height: number, format: number): boolean;\n setPerceptual(on: boolean): void;\n setKTX2AndBasisSRGBTransferFunc(on: boolean): void;\n setMipGen(on: boolean): void;\n setPackUASTCFlags(flags: number): void;\n setKTX2UASTCSupercompression(on: boolean): void;\n encode(out: Uint8Array): number;\n delete(): void;\n}\ninterface Basis {\n BasisEncoder: new () => BasisEncoder;\n initializeBasis(): void;\n}\n\n/** basisu's encoder module, instantiated once: a process-wide WASM module, as three's decoders are. */\nlet basis: Promise<Basis> | undefined;\n\n/**\n * basisu's pack flags: level 1, which encodes about twice as fast as the default level 2 and loses under 0.1 dB on\n * albedo and normal maps (level 0 loses 1 to 4 dB), with the fastest ETC1 hints (128), which only transcodes to ETC1\n * and ETC2 read; three picks those only on a GPU without ASTC and BC texture compression, which WebGPU adapters on\n * desktops and most phones have.\n */\nconst PACK_FLAGS = 1 | 128;\n\n/**\n * Rows of a mip level as a KTX2 file of UASTC LDR 4x4 with Zstandard supercompression, at level 1 (see PACK_FLAGS) and\n * without RDO, which would trade normal accuracy for size. The encoder's own mips would filter level 0, so each baked\n * level is encoded on its own (see `encodeLayer`).\n */\nexport async function encode({ data, width, height, srgb }: Job): Promise<Uint8Array<ArrayBuffer>> {\n const encoder = new (await (basis ??= load())).BasisEncoder();\n try {\n encoder.setCreateKTX2File(true);\n encoder.setUASTC(true);\n // basisu's enums: texture type 0 is a 2D texture; image format 0 is raw RGBA8\n encoder.setTexType(0);\n if (!encoder.setSliceSourceImage(0, data, width, height, 0)) throw new Error(`the Basis encoder took no ${width}x${height} image`);\n encoder.setPerceptual(srgb);\n encoder.setKTX2AndBasisSRGBTransferFunc(srgb);\n encoder.setMipGen(false);\n encoder.setPackUASTCFlags(PACK_FLAGS);\n encoder.setKTX2UASTCSupercompression(true);\n const out = new Uint8Array(4 * width * height + 65536);\n const length = encoder.encode(out);\n if (length === 0) throw new Error(`the Basis encoder failed on a ${width}x${height} image`);\n return out.slice(0, length);\n } finally {\n encoder.delete();\n }\n}\n\n/**\n * The encoder's Emscripten module (see scripts/basis.ts), started on its binary. Imported from a data: URL, which\n * browsers and Node both import, unlike a blob: URL.\n */\nasync function load(): Promise<Basis> {\n const url = `data:text/javascript,${encodeURIComponent(new TextDecoder().decode(inflate(ENCODER_JS)))}`;\n const factory: unknown = (await import(/* @vite-ignore */ url)).default;\n if (typeof factory !== \"function\") throw new Error(\"the Basis encoder module has no factory\");\n // the module names its binary relative to its own URL, which a data: URL's is not: named, then given. It prints every\n // image's progress, which nothing needs.\n const basis: unknown = await factory({ wasmBinary: inflate(ENCODER_WASM), locateFile: (name: string) => name, print: () => {} });\n if (!isBasis(basis)) throw new Error(\"the Basis encoder module lacks its bindings\");\n basis.initializeBasis();\n return basis;\n}\n\nfunction isBasis(value: unknown): value is Basis {\n return (\n typeof value === \"object\" &&\n value !== null &&\n typeof Reflect.get(value, \"BasisEncoder\") === \"function\" &&\n typeof Reflect.get(value, \"initializeBasis\") === \"function\"\n );\n}\n\nconst inflate = (base64: string) => inflateSync(Uint8Array.from(atob(base64), (c) => c.charCodeAt(0)));\n"]}
@@ -1,10 +1,22 @@
1
1
  import { encode } from "./uastc.js";
2
+ import { inNode, nodeOnly } from "../../node-process.js";
2
3
  export const url = import.meta.url;
4
+ /** `job`'s reply, and the buffers it moves. */
5
+ async function answer(job) {
6
+ try {
7
+ const file = await encode(job);
8
+ return [{ file }, [file.buffer]];
9
+ }
10
+ catch (error) {
11
+ return [{ error: error instanceof Error ? error.message : String(error) }, []];
12
+ }
13
+ }
3
14
  const scope = Reflect.get(globalThis, "WorkerGlobalScope");
4
- if (typeof scope === "function" && globalThis instanceof scope) {
5
- addEventListener("message", (event) => {
6
- encode(event.data).then((file) => reply({ file }, [file.buffer]), (error) => reply({ error: error instanceof Error ? error.message : String(error) }));
7
- });
15
+ if (typeof scope === "function" && globalThis instanceof scope)
16
+ addEventListener("message", (event) => answer(event.data).then(([reply, transfer]) => postMessage(reply, { transfer })));
17
+ if (inNode) {
18
+ const { parentPort, workerData } = await nodeOnly("node:worker_threads");
19
+ const port = workerData === url ? parentPort : null;
20
+ port?.on("message", (job) => answer(job).then(([reply, transfer]) => port.postMessage(reply, transfer)));
8
21
  }
9
- const reply = (message, transfer = []) => postMessage(message, { transfer });
10
22
  //# sourceMappingURL=worker.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"worker.js","sourceRoot":"","sources":["../../../src/impostor/ktx2/worker.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,MAAM,EAAE,MAAM,YAAY,CAAC;AAEpC,MAAM,CAAC,MAAM,GAAG,GAAG,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC;AAEnC,MAAM,KAAK,GAAY,OAAO,CAAC,GAAG,CAAC,UAAU,EAAE,mBAAmB,CAAC,CAAC;AACpE,IAAI,OAAO,KAAK,KAAK,UAAU,IAAI,UAAU,YAAY,KAAK,EAAE,CAAC;IAC/D,gBAAgB,CAAC,SAAS,EAAE,CAAC,KAAwB,EAAE,EAAE;QACvD,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,IAAI,CACrB,CAAC,IAAI,EAAE,EAAE,CAAC,KAAK,CAAC,EAAE,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,EACxC,CAAC,KAAc,EAAE,EAAE,CAAC,KAAK,CAAC,EAAE,KAAK,EAAE,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,EAAE,CAAC,CAC7F,CAAC;IACJ,CAAC,CAAC,CAAC;AACL,CAAC;AAED,MAAM,KAAK,GAAG,CAAC,OAAc,EAAE,WAA2B,EAAE,EAAE,EAAE,CAAC,WAAW,CAAC,OAAO,EAAE,EAAE,QAAQ,EAAE,CAAC,CAAC","sourcesContent":["// The worker an impostor map encodes in. The page imports this module only for its URL, at which it then starts it as\n// a worker: unlike `new URL(\"./file\", import.meta.url)`, a module's own URL holds wherever a bundler moves it.\nimport type { Job, Reply } from \"./encode.js\";\nimport { encode } from \"./uastc.js\";\n\nexport const url = import.meta.url;\n\nconst scope: unknown = Reflect.get(globalThis, \"WorkerGlobalScope\");\nif (typeof scope === \"function\" && globalThis instanceof scope) {\n addEventListener(\"message\", (event: MessageEvent<Job>) => {\n encode(event.data).then(\n (file) => reply({ file }, [file.buffer]),\n (error: unknown) => reply({ error: error instanceof Error ? error.message : String(error) }),\n );\n });\n}\n\nconst reply = (message: Reply, transfer: Transferable[] = []) => postMessage(message, { transfer });\n"]}
1
+ {"version":3,"file":"worker.js","sourceRoot":"","sources":["../../../src/impostor/ktx2/worker.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,MAAM,EAAE,MAAM,YAAY,CAAC;AACpC,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,uBAAuB,CAAC;AAEzD,MAAM,CAAC,MAAM,GAAG,GAAG,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC;AAEnC,+CAA+C;AAC/C,KAAK,UAAU,MAAM,CAAC,GAAQ;IAC5B,IAAI,CAAC;QACH,MAAM,IAAI,GAAG,MAAM,MAAM,CAAC,GAAG,CAAC,CAAC;QAC/B,OAAO,CAAC,EAAE,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC;IACnC,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,OAAO,CAAC,EAAE,KAAK,EAAE,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,EAAE,EAAE,EAAE,CAAC,CAAC;IACjF,CAAC;AACH,CAAC;AAED,MAAM,KAAK,GAAY,OAAO,CAAC,GAAG,CAAC,UAAU,EAAE,mBAAmB,CAAC,CAAC;AACpE,IAAI,OAAO,KAAK,KAAK,UAAU,IAAI,UAAU,YAAY,KAAK;IAC5D,gBAAgB,CAAC,SAAS,EAAE,CAAC,KAAwB,EAAE,EAAE,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,KAAK,EAAE,QAAQ,CAAC,EAAE,EAAE,CAAC,WAAW,CAAC,KAAK,EAAE,EAAE,QAAQ,EAAE,CAAC,CAAC,CAAC,CAAC;AAE9I,IAAI,MAAM,EAAE,CAAC;IACX,MAAM,EAAE,UAAU,EAAE,UAAU,EAAE,GAAG,MAAM,QAAQ,CAAuC,qBAAqB,CAAC,CAAC;IAC/G,MAAM,IAAI,GAAG,UAAU,KAAK,GAAG,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,IAAI,CAAC;IACpD,IAAI,EAAE,EAAE,CAAC,SAAS,EAAE,CAAC,GAAQ,EAAE,EAAE,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,KAAK,EAAE,QAAQ,CAAC,EAAE,EAAE,CAAC,IAAI,CAAC,WAAW,CAAC,KAAK,EAAE,QAAQ,CAAC,CAAC,CAAC,CAAC;AAChH,CAAC","sourcesContent":["// The worker an impostor map encodes in. The page imports this module only for its URL, at which it then starts it as\n// a worker: unlike `new URL(\"./file\", import.meta.url)`, a module's own URL holds wherever a bundler moves it. Node\n// starts it as a worker thread, given that URL as its workerData.\nimport type { Job, Reply } from \"./encode.js\";\nimport { encode } from \"./uastc.js\";\nimport { inNode, nodeOnly } from \"../../node-process.js\";\n\nexport const url = import.meta.url;\n\n/** `job`'s reply, and the buffers it moves. */\nasync function answer(job: Job): Promise<[Reply, ArrayBuffer[]]> {\n try {\n const file = await encode(job);\n return [{ file }, [file.buffer]];\n } catch (error) {\n return [{ error: error instanceof Error ? error.message : String(error) }, []];\n }\n}\n\nconst scope: unknown = Reflect.get(globalThis, \"WorkerGlobalScope\");\nif (typeof scope === \"function\" && globalThis instanceof scope)\n addEventListener(\"message\", (event: MessageEvent<Job>) => answer(event.data).then(([reply, transfer]) => postMessage(reply, { transfer })));\n\nif (inNode) {\n const { parentPort, workerData } = await nodeOnly<typeof import(\"node:worker_threads\")>(\"node:worker_threads\");\n const port = workerData === url ? parentPort : null;\n port?.on(\"message\", (job: Job) => answer(job).then(([reply, transfer]) => port.postMessage(reply, transfer)));\n}\n"]}
@@ -30,8 +30,9 @@ export declare class Compiler {
30
30
  * Prepare the same target and MRT as the camera's draw, even if normal rendering has since restored the canvas, and
31
31
  * the shadow maps of `compileFor`, for each mesh that casts. r186 uploads a texture synchronously inside initTexture,
32
32
  * and creates a pipeline it meets while rendering synchronously, so each texture and draw is prepared here first.
33
- * Textures upload paced across frames (see `paced`); every draw then compiles at once, as do the draws of every
34
- * other level the renderer prepares, so their pipelines are created side by side.
33
+ * Textures upload paced across frames (see `paced`); every draw then compiles at once, but for those of one
34
+ * multi-material mesh, as do the draws of every other level the renderer prepares, so their pipelines are created
35
+ * side by side.
35
36
  */
36
37
  compileAsync(object: Object3D, context: RenderContext, { shadows, isDisposed }: CompileFor): Promise<void>;
37
38
  /** Run `upload`, which answers the bytes it uploaded, after the next frame if those since the last one reached `FRAME_UPLOAD`. */
@@ -32,8 +32,9 @@ export class Compiler {
32
32
  * Prepare the same target and MRT as the camera's draw, even if normal rendering has since restored the canvas, and
33
33
  * the shadow maps of `compileFor`, for each mesh that casts. r186 uploads a texture synchronously inside initTexture,
34
34
  * and creates a pipeline it meets while rendering synchronously, so each texture and draw is prepared here first.
35
- * Textures upload paced across frames (see `paced`); every draw then compiles at once, as do the draws of every
36
- * other level the renderer prepares, so their pipelines are created side by side.
35
+ * Textures upload paced across frames (see `paced`); every draw then compiles at once, but for those of one
36
+ * multi-material mesh, as do the draws of every other level the renderer prepares, so their pipelines are created
37
+ * side by side.
37
38
  */
38
39
  async compileAsync(object, context, { shadows, isDisposed }) {
39
40
  const { renderer } = this;
@@ -55,11 +56,18 @@ export class Compiler {
55
56
  if (mesh.castShadow)
56
57
  for (const { camera, target, overrideMaterial, depth } of shadows.filter((map) => map.scene === context.scene))
57
58
  targets.push({ context: { ...context, camera, target, mrt: null, cubeFace: 0, mipLevel: 0, depth }, overrideMaterial });
59
+ if (!Array.isArray(mesh.material)) {
60
+ for (const target of targets)
61
+ draws.push(this.compileDraw(mesh, target, mesh.geometry.groups, isDisposed));
62
+ continue;
63
+ }
58
64
  // r186 starts only the first material before yielding; isolate one draw so setup sees its captured target/MRT.
59
- const parts = Array.isArray(mesh.material) ? mesh.geometry.groups.map((group) => [group]) : [mesh.geometry.groups];
65
+ // The draws of one mesh compile in turn: with their compiles overlapping, r186 creates some of their pipelines
66
+ // with another draw's bind group layout, which WebGPU rejects.
67
+ let turn = Promise.resolve();
60
68
  for (const target of targets)
61
- for (const part of parts)
62
- draws.push(this.compileDraw(mesh, target, part, isDisposed));
69
+ for (const group of mesh.geometry.groups)
70
+ draws.push((turn = turn.then(() => this.compileDraw(mesh, target, [group], isDisposed))));
63
71
  }
64
72
  await Promise.all(draws);
65
73
  }
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/levels/compile/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAE3D,OAAO,EAAE,WAAW,EAAE,KAAK,EAAE,cAAc,EAAsB,MAAM,gBAAgB,CAAC;AAUxF,MAAM,SAAS,GAAG,IAAI,OAAO,EAAsB,CAAC;AAEpD,oHAAoH;AACpH,MAAM,UAAU,UAAU,CAAC,QAAkB;IAC3C,IAAI,QAAQ,GAAG,SAAS,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;IACvC,IAAI,CAAC,QAAQ;QAAE,SAAS,CAAC,GAAG,CAAC,QAAQ,EAAE,CAAC,QAAQ,GAAG,IAAI,QAAQ,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC;IAC5E,OAAO,QAAQ,CAAC;AAClB,CAAC;AAED;;;GAGG;AACH,MAAM,OAAO,QAAQ;IAYU;IAX7B,qCAAqC;IAC7B,QAAQ,GAAG,CAAC,CAAC;IACrB,4CAA4C;IACpC,OAAO,GAAG,OAAO,CAAC,OAAO,EAAE,CAAC;IACpC;;;;OAIG;IACc,MAAM,GAAG,IAAI,GAAG,EAAyB,CAAC;IAE3D,YAA6B,QAAkB;QAAlB,aAAQ,GAAR,QAAQ,CAAU;QAC7C,cAAc,CAAC,QAAQ,CAAC,CAAC;IAC3B,CAAC;IAED;;;;;;OAMG;IACH,KAAK,CAAC,YAAY,CAAC,MAAgB,EAAE,OAAsB,EAAE,EAAE,OAAO,EAAE,UAAU,EAAc;QAC9F,MAAM,EAAE,QAAQ,EAAE,GAAG,IAAI,CAAC;QAC1B,MAAM,EAAE,MAAM,EAAE,GAAG,QAAQ,CAAC,IAAI,CAAC;QACjC,KAAK,MAAM,OAAO,IAAI,QAAQ,CAAC,MAAM,CAAC;YACpC,MAAM,IAAI,CAAC,KAAK,CAAC,GAAG,EAAE;gBACpB,MAAM,KAAK,GAAG,MAAM,CAAC,QAAQ,CAAC;gBAC9B,QAAQ,CAAC,WAAW,CAAC,OAAO,CAAC,CAAC;gBAC9B,OAAO,MAAM,CAAC,QAAQ,KAAK,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC;YAC1D,CAAC,CAAC,CAAC;QACL,MAAM,MAAM,GAAW,EAAE,CAAC;QAC1B,MAAM,CAAC,QAAQ,CAAC,CAAC,IAAI,EAAE,EAAE;YACvB,IAAI,MAAM,CAAC,IAAI,CAAC;gBAAE,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACtC,CAAC,CAAC,CAAC;QACH,MAAM,KAAK,GAAoB,EAAE,CAAC;QAClC,KAAK,MAAM,IAAI,IAAI,MAAM,EAAE,CAAC;YAC1B,MAAM,OAAO,GAAoB,CAAC,EAAE,OAAO,EAAE,gBAAgB,EAAE,IAAI,EAAE,CAAC,CAAC;YACvE,IAAI,IAAI,CAAC,UAAU;gBACjB,KAAK,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,gBAAgB,EAAE,KAAK,EAAE,IAAI,OAAO,CAAC,MAAM,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,GAAG,CAAC,KAAK,KAAK,OAAO,CAAC,KAAK,CAAC;oBAC5G,OAAO,CAAC,IAAI,CAAC,EAAE,OAAO,EAAE,EAAE,GAAG,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,GAAG,EAAE,IAAI,EAAE,QAAQ,EAAE,CAAC,EAAE,QAAQ,EAAE,CAAC,EAAE,KAAK,EAAE,EAAE,gBAAgB,EAAE,CAAC,CAAC;YAC5H,+GAA+G;YAC/G,MAAM,KAAK,GAAG,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC;YACnH,KAAK,MAAM,MAAM,IAAI,OAAO;gBAAE,KAAK,MAAM,IAAI,IAAI,KAAK;oBAAE,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,WAAW,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,UAAU,CAAC,CAAC,CAAC;QACvH,CAAC;QACD,MAAM,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC;IAC3B,CAAC;IAED,kIAAkI;IAC1H,KAAK,CAAC,MAAoB;QAChC,MAAM,GAAG,GAAG,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,KAAK,IAAI,EAAE;YACvC,IAAI,IAAI,CAAC,QAAQ,IAAI,YAAY;gBAAE,MAAM,KAAK,EAAE,CAAC;YACjD,MAAM,KAAK,GAAG,MAAM,EAAE,CAAC;YACvB,4EAA4E;YAC5E,IAAI,KAAK,GAAG,CAAC,IAAI,IAAI,CAAC,QAAQ,KAAK,CAAC;gBAAE,KAAK,KAAK,EAAE,CAAC,IAAI,CAAC,GAAG,EAAE,CAAC,KAAK,CAAC,IAAI,CAAC,QAAQ,GAAG,CAAC,CAAC,CAAC,CAAC;YACxF,IAAI,CAAC,QAAQ,IAAI,KAAK,CAAC;QACzB,CAAC,CAAC,CAAC;QACH,IAAI,CAAC,OAAO,GAAG,GAAG,CAAC,KAAK,CAAC,GAAG,EAAE,GAAE,CAAC,CAAC,CAAC;QACnC,OAAO,GAAG,CAAC;IACb,CAAC;IAED,+DAA+D;IACvD,WAAW,CAAC,IAAU,EAAE,MAAqB,EAAE,IAAa,EAAE,UAAoC;QACxG,MAAM,EAAE,MAAM,EAAE,GAAG,IAAI,CAAC;QACxB,MAAM,QAAQ,GAAG,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,aAAa,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC;QAC3G,MAAM,GAAG,GAAG;YACV,QAAQ,CAAC,CAAC,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC,MAAM;YACjC,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,QAAQ,CAAC,UAAU,CAAC,CAAC,IAAI,EAAE,CAAC,IAAI,EAAE;YACnD,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC,CAAC,QAAQ;YACnE,MAAM,CAAC,gBAAgB,CAAC,CAAC,CAAC,MAAM,CAAC,gBAAgB,CAAC,IAAI,CAAC,CAAC,CAAC,QAAQ;YACjE,MAAM,CAAC,OAAO,CAAC,KAAK;SACrB,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;QACZ,MAAM,KAAK,GAAG,GAAG,EAAE;YACjB,8FAA8F;YAC9F,MAAM,KAAK,GAAG,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC;YACpC,IAAI,KAAK,IAAI,UAAU,CAAC,KAAK,CAAC;gBAAE,OAAO,OAAO,CAAC,OAAO,EAAE,CAAC;YACzD,MAAM,MAAM,GAAG,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC;YACpC,IAAI,CAAC;gBACH,IAAI,CAAC,QAAQ,CAAC,MAAM,GAAG,IAAI,CAAC;gBAC5B,OAAO,WAAW,CAAC,IAAI,CAAC,QAAQ,EAAE,IAAI,EAAE,MAAM,CAAC,CAAC;YAClD,CAAC;oBAAS,CAAC;gBACT,IAAI,CAAC,QAAQ,CAAC,MAAM,GAAG,MAAM,CAAC;YAChC,CAAC;QACH,CAAC,CAAC;QACF,MAAM,KAAK,GAAG,MAAM,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;QAC9B,MAAM,GAAG,GAAG,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,KAAK,EAAE,CAAC;QAChD,MAAM,OAAO,GAAG,GAAG,CAAC,IAAI,CACtB,GAAG,EAAE,GAAE,CAAC,EACR,GAAG,EAAE,GAAE,CAAC,CACT,CAAC;QACF,MAAM,CAAC,GAAG,CAAC,GAAG,EAAE,OAAO,CAAC,CAAC;QACzB,KAAK,OAAO,CAAC,IAAI,CAAC,GAAG,EAAE;YACrB,IAAI,MAAM,CAAC,GAAG,CAAC,GAAG,CAAC,KAAK,OAAO;gBAAE,MAAM,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;QACtD,CAAC,CAAC,CAAC;QACH,OAAO,GAAG,CAAC;IACb,CAAC;CACF;AAED;;;;GAIG;AACH,MAAM,YAAY,GAAG,EAAE,GAAG,CAAC,IAAI,EAAE,CAAC;AAKlC,MAAM,GAAG,GAAG,CAAC,MAAgB,EAAE,EAAE,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,KAAK,EAAE,KAAK,EAAE,EAAE,CAAC,KAAK,GAAG,KAAK,EAAE,CAAC,CAAC,CAAC;AAEpF;;;;GAIG;AACH,SAAS,OAAO,CAAC,OAAgB;IAC/B,MAAM,MAAM,GAAc,OAAO,CAAC,OAAO,IAAI,EAAE,CAAC;IAChD,MAAM,IAAI,GAAG,KAAK,CAAC,OAAO,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC;IAC1C,IAAI,MAAM,CAAC,MAAM,GAAG,CAAC,IAAI,CAAC,CAAC,IAAI,IAAI,OAAO,CAAC,GAAG,CAAC,OAAO,EAAE,qBAAqB,CAAC,KAAK,IAAI,CAAC;QAAE,OAAO,OAAO,CAAC,MAAM,CAAC,CAAC;IACjH,MAAM,KAAK,GAAG,OAAO,CAAC,OAAO,CAAC,KAAK,CAAC,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;IACvD,OAAO,OAAO,CAAC,eAAe,IAAI,MAAM,CAAC,MAAM,KAAK,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,KAAK,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC;AAC7F,CAAC;AAED,0GAA0G;AAC1G,SAAS,OAAO,CAAC,KAAc;IAC7B,IAAI,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC;QAAE,OAAO,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC;IACzD,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,KAAK,IAAI;QAAE,OAAO,CAAC,CAAC;IAC1D,IAAI,SAAS,CAAC,KAAK,CAAC;QAAE,OAAO,OAAO,CAAC,KAAK,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC;IAC1F,MAAM,MAAM,GAAY,OAAO,CAAC,GAAG,CAAC,KAAK,EAAE,QAAQ,CAAC,CAAC;IACrD,IAAI,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC;QAAE,OAAO,OAAO,CAAC,MAAM,CAAC,CAAC;IAClD,MAAM,IAAI,GAAY,OAAO,CAAC,GAAG,CAAC,KAAK,EAAE,MAAM,CAAC,CAAC;IACjD,IAAI,WAAW,CAAC,MAAM,CAAC,IAAI,CAAC;QAAE,OAAO,IAAI,CAAC,UAAU,CAAC;IACrD,MAAM,SAAS,GAAG,CAAC,IAAY,EAAE,EAAE;QACjC,MAAM,KAAK,GAAY,OAAO,CAAC,GAAG,CAAC,KAAK,EAAE,IAAI,CAAC,CAAC;QAChD,OAAO,OAAO,KAAK,KAAK,QAAQ,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC;IAC/C,CAAC,CAAC;IACF,OAAO,SAAS,CAAC,OAAO,CAAC,GAAG,SAAS,CAAC,QAAQ,CAAC,GAAG,SAAS,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;AAC3E,CAAC;AAED;;;GAGG;AACH,SAAS,QAAQ,CAAC,MAAgB;IAChC,MAAM,KAAK,GAAG,IAAI,GAAG,EAAW,CAAC;IACjC,MAAM,OAAO,GAAG,IAAI,GAAG,EAAW,CAAC;IACnC,MAAM,KAAK,GAAG,CAAC,KAAc,EAAE,EAAE;QAC/B,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,KAAK,IAAI,IAAI,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC;YAAE,OAAO;QAC9E,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC;QACnB,IAAI,SAAS,CAAC,KAAK,CAAC;YAAE,KAAK,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC;aAClC,IAAI,MAAM,CAAC,KAAK,CAAC,EAAE,CAAC;YACvB,KAAK,CAAC,OAAO,CAAC,GAAG,CAAC,KAAK,EAAE,OAAO,CAAC,CAAC,CAAC;YACnC,KAAK,MAAM,KAAK,IAAI,KAAK,CAAC,WAAW,EAAE;gBAAE,KAAK,CAAC,KAAK,CAAC,CAAC;QACxD,CAAC;aAAM,IAAI,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC;YAAE,KAAK,MAAM,KAAK,IAAI,KAAK;gBAAE,KAAK,CAAC,KAAK,CAAC,CAAC;aACpE,IAAI,CAAC,MAAM,CAAC,SAAS,EAAE,IAAI,CAAC,CAAC,QAAQ,CAAC,MAAM,CAAC,cAAc,CAAC,KAAK,CAAC,CAAC;YACtE,KAAK,MAAM,CAAC,GAAG,EAAE,KAAK,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC;gBAAE,IAAI,GAAG,KAAK,UAAU;oBAAE,KAAK,CAAC,KAAK,CAAC,CAAC;IAC3F,CAAC,CAAC;IACF,MAAM,CAAC,QAAQ,CAAC,CAAC,IAAI,EAAE,EAAE;QACvB,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC;YAAE,OAAO;QAC1B,4DAA4D;QAC5D,KAAK,MAAM,QAAQ,IAAI,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC,IAAI,EAAE;YAAE,KAAK,MAAM,CAAC,GAAG,EAAE,KAAK,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,QAAQ,CAAC;gBAAE,IAAI,GAAG,KAAK,UAAU;oBAAE,KAAK,CAAC,KAAK,CAAC,CAAC;IAC3I,CAAC,CAAC,CAAC;IACH,OAAO,KAAK,CAAC;AACf,CAAC","sourcesContent":["import type { Mesh, Object3D, Renderer, Texture } from \"three/webgpu\";\nimport { isMesh, isNode, isTexture } from \"../../three.js\";\nimport type { RenderContext, ShadowMap, Target } from \"./contexts.js\";\nimport { compileMesh, frame, sharePipelines, type CompileTarget } from \"./pipelines.js\";\n\n/** What a compile is for besides its context: shadow maps, and which targets were disposed. */\nexport interface CompileFor {\n /** The shadow maps the scene's level meshes draw into: each mesh that casts compiles for them too. */\n shadows: readonly ShadowMap[];\n /** Whether `target` was disposed: no draw compiles for it, as that would make its textures again. */\n isDisposed(target: Target): boolean;\n}\n\nconst compilers = new WeakMap<Renderer, Compiler>();\n\n/** What `renderer` prepares levels with: one Compiler per renderer, made the first time a level compiles for it. */\nexport function compilerOf(renderer: Renderer): Compiler {\n let compiler = compilers.get(renderer);\n if (!compiler) compilers.set(renderer, (compiler = new Compiler(renderer)));\n return compiler;\n}\n\n/**\n * What a renderer prepares for the levels arriving: the levels share its queue's budget and its pipeline cache, which\n * it shares the readiness of (see `sharePipelines`).\n */\nexport class Compiler {\n /** Uploaded since the last frame. */\n private uploaded = 0;\n /** The last upload: uploads run in turn. */\n private uploads = Promise.resolve();\n /**\n * The last draw compiled for each shader build: material, attributes, target, shadow map and nesting. three starts a\n * build for each draw that misses its cache, even one another draw is building, so a draw that builds alike waits\n * for the last one and finds its build cached.\n */\n private readonly builds = new Map<string, Promise<void>>();\n\n constructor(private readonly renderer: Renderer) {\n sharePipelines(renderer);\n }\n\n /**\n * Prepare the same target and MRT as the camera's draw, even if normal rendering has since restored the canvas, and\n * the shadow maps of `compileFor`, for each mesh that casts. r186 uploads a texture synchronously inside initTexture,\n * and creates a pipeline it meets while rendering synchronously, so each texture and draw is prepared here first.\n * Textures upload paced across frames (see `paced`); every draw then compiles at once, as do the draws of every\n * other level the renderer prepares, so their pipelines are created side by side.\n */\n async compileAsync(object: Object3D, context: RenderContext, { shadows, isDisposed }: CompileFor): Promise<void> {\n const { renderer } = this;\n const { memory } = renderer.info;\n for (const texture of textures(object))\n await this.paced(() => {\n const known = memory.textures;\n renderer.initTexture(texture);\n return memory.textures === known ? 0 : bytesOf(texture);\n });\n const meshes: Mesh[] = [];\n object.traverse((node) => {\n if (isMesh(node)) meshes.push(node);\n });\n const draws: Promise<void>[] = [];\n for (const mesh of meshes) {\n const targets: CompileTarget[] = [{ context, overrideMaterial: null }];\n if (mesh.castShadow)\n for (const { camera, target, overrideMaterial, depth } of shadows.filter((map) => map.scene === context.scene))\n targets.push({ context: { ...context, camera, target, mrt: null, cubeFace: 0, mipLevel: 0, depth }, overrideMaterial });\n // r186 starts only the first material before yielding; isolate one draw so setup sees its captured target/MRT.\n const parts = Array.isArray(mesh.material) ? mesh.geometry.groups.map((group) => [group]) : [mesh.geometry.groups];\n for (const target of targets) for (const part of parts) draws.push(this.compileDraw(mesh, target, part, isDisposed));\n }\n await Promise.all(draws);\n }\n\n /** Run `upload`, which answers the bytes it uploaded, after the next frame if those since the last one reached `FRAME_UPLOAD`. */\n private paced(upload: () => number): Promise<void> {\n const run = this.uploads.then(async () => {\n if (this.uploaded >= FRAME_UPLOAD) await frame();\n const bytes = upload();\n // The next frame empties the budget, whether or not an upload waits for it.\n if (bytes > 0 && this.uploaded === 0) void frame().then(() => void (this.uploaded = 0));\n this.uploaded += bytes;\n });\n this.uploads = run.catch(() => {});\n return run;\n }\n\n /** Compile the draw of `mesh`'s groups `part` for `target`. */\n private compileDraw(mesh: Mesh, target: CompileTarget, part: Group[], isDisposed: CompileFor[\"isDisposed\"]): Promise<void> {\n const { builds } = this;\n const material = Array.isArray(mesh.material) ? mesh.material[part[0]?.materialIndex ?? 0] : mesh.material;\n const key = [\n material ? material.uuid : \"none\",\n Object.keys(mesh.geometry.attributes).sort().join(),\n target.context.target ? target.context.target.texture.id : \"canvas\",\n target.overrideMaterial ? target.overrideMaterial.uuid : \"camera\",\n target.context.depth,\n ].join(\" \");\n const start = () => {\n // Uploads and alike builds may have taken frames: a target disposed meanwhile stays disposed.\n const drawn = target.context.target;\n if (drawn && isDisposed(drawn)) return Promise.resolve();\n const groups = mesh.geometry.groups;\n try {\n mesh.geometry.groups = part;\n return compileMesh(this.renderer, mesh, target);\n } finally {\n mesh.geometry.groups = groups;\n }\n };\n const alike = builds.get(key);\n const own = alike ? alike.then(start) : start();\n const settled = own.then(\n () => {},\n () => {},\n );\n builds.set(key, settled);\n void settled.then(() => {\n if (builds.get(key) === settled) builds.delete(key);\n });\n return own;\n }\n}\n\n/**\n * What texture uploads may add in one frame, in bytes: a 2048² RGBA texture. Uploads run on the GPU's queue before the\n * frame that follows them, which waits for them all. A draw's geometry is not paced: its buffers copy in far less\n * time than a texture of the same size and its mipmaps.\n */\nconst FRAME_UPLOAD = 16 * 2 ** 20;\n\n/** A draw's range of a geometry, as three's groups hold it. */\ntype Group = Mesh[\"geometry\"][\"groups\"][number];\n\nconst sum = (values: number[]) => values.reduce((total, value) => total + value, 0);\n\n/**\n * The bytes `texture` uploads, as three 0.186.1 uploads it: its mip levels', or its image's and the mipmaps three\n * generates from it. An uncompressed cube's image is its six faces, and its mip levels are those after the first, with\n * six images each; a compressed cube's faces carry their own mip levels, unless it has some.\n */\nfunction bytesOf(texture: Texture): number {\n const levels: unknown[] = texture.mipmaps ?? [];\n const cube = Array.isArray(texture.image);\n if (levels.length > 0 && (!cube || Reflect.get(texture, \"isCompressedTexture\") === true)) return bytesIn(levels);\n const bytes = bytesIn(texture.image) + bytesIn(levels);\n return texture.generateMipmaps && levels.length === 0 ? Math.ceil((bytes * 4) / 3) : bytes;\n}\n\n/** The bytes of an image, a mip level, a cube's face or a list of them: its data's, or four per texel. */\nfunction bytesIn(image: unknown): number {\n if (Array.isArray(image)) return sum(image.map(bytesIn));\n if (typeof image !== \"object\" || image === null) return 0;\n if (isTexture(image)) return bytesIn(image.mipmaps?.length ? image.mipmaps : image.image);\n const images: unknown = Reflect.get(image, \"images\");\n if (Array.isArray(images)) return bytesIn(images);\n const data: unknown = Reflect.get(image, \"data\");\n if (ArrayBuffer.isView(data)) return data.byteLength;\n const dimension = (name: string) => {\n const value: unknown = Reflect.get(image, name);\n return typeof value === \"number\" ? value : 1;\n };\n return dimension(\"width\") * dimension(\"height\") * dimension(\"depth\") * 4;\n}\n\n/**\n * The textures `object`'s meshes draw: their materials', those their nodes sample, and those a material keeps in plain\n * objects and arrays for nodes it makes while it builds (an impostor's atlases, a baked mesh's rooms).\n */\nfunction textures(object: Object3D): Set<Texture> {\n const found = new Set<Texture>();\n const visited = new Set<unknown>();\n const visit = (value: unknown) => {\n if (typeof value !== \"object\" || value === null || visited.has(value)) return;\n visited.add(value);\n if (isTexture(value)) found.add(value);\n else if (isNode(value)) {\n visit(Reflect.get(value, \"value\"));\n for (const child of value.getChildren()) visit(child);\n } else if (Array.isArray(value)) for (const entry of value) visit(entry);\n else if ([Object.prototype, null].includes(Object.getPrototypeOf(value)))\n for (const [key, entry] of Object.entries(value)) if (key !== \"userData\") visit(entry);\n };\n object.traverse((node) => {\n if (!isMesh(node)) return;\n // userData is the application's, not what a material binds.\n for (const material of [node.material].flat()) for (const [key, value] of Object.entries(material)) if (key !== \"userData\") visit(value);\n });\n return found;\n}\n"]}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/levels/compile/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAE3D,OAAO,EAAE,WAAW,EAAE,KAAK,EAAE,cAAc,EAAsB,MAAM,gBAAgB,CAAC;AAUxF,MAAM,SAAS,GAAG,IAAI,OAAO,EAAsB,CAAC;AAEpD,oHAAoH;AACpH,MAAM,UAAU,UAAU,CAAC,QAAkB;IAC3C,IAAI,QAAQ,GAAG,SAAS,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;IACvC,IAAI,CAAC,QAAQ;QAAE,SAAS,CAAC,GAAG,CAAC,QAAQ,EAAE,CAAC,QAAQ,GAAG,IAAI,QAAQ,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC;IAC5E,OAAO,QAAQ,CAAC;AAClB,CAAC;AAED;;;GAGG;AACH,MAAM,OAAO,QAAQ;IAYU;IAX7B,qCAAqC;IAC7B,QAAQ,GAAG,CAAC,CAAC;IACrB,4CAA4C;IACpC,OAAO,GAAG,OAAO,CAAC,OAAO,EAAE,CAAC;IACpC;;;;OAIG;IACc,MAAM,GAAG,IAAI,GAAG,EAAyB,CAAC;IAE3D,YAA6B,QAAkB;QAAlB,aAAQ,GAAR,QAAQ,CAAU;QAC7C,cAAc,CAAC,QAAQ,CAAC,CAAC;IAC3B,CAAC;IAED;;;;;;;OAOG;IACH,KAAK,CAAC,YAAY,CAAC,MAAgB,EAAE,OAAsB,EAAE,EAAE,OAAO,EAAE,UAAU,EAAc;QAC9F,MAAM,EAAE,QAAQ,EAAE,GAAG,IAAI,CAAC;QAC1B,MAAM,EAAE,MAAM,EAAE,GAAG,QAAQ,CAAC,IAAI,CAAC;QACjC,KAAK,MAAM,OAAO,IAAI,QAAQ,CAAC,MAAM,CAAC;YACpC,MAAM,IAAI,CAAC,KAAK,CAAC,GAAG,EAAE;gBACpB,MAAM,KAAK,GAAG,MAAM,CAAC,QAAQ,CAAC;gBAC9B,QAAQ,CAAC,WAAW,CAAC,OAAO,CAAC,CAAC;gBAC9B,OAAO,MAAM,CAAC,QAAQ,KAAK,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC;YAC1D,CAAC,CAAC,CAAC;QACL,MAAM,MAAM,GAAW,EAAE,CAAC;QAC1B,MAAM,CAAC,QAAQ,CAAC,CAAC,IAAI,EAAE,EAAE;YACvB,IAAI,MAAM,CAAC,IAAI,CAAC;gBAAE,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACtC,CAAC,CAAC,CAAC;QACH,MAAM,KAAK,GAAoB,EAAE,CAAC;QAClC,KAAK,MAAM,IAAI,IAAI,MAAM,EAAE,CAAC;YAC1B,MAAM,OAAO,GAAoB,CAAC,EAAE,OAAO,EAAE,gBAAgB,EAAE,IAAI,EAAE,CAAC,CAAC;YACvE,IAAI,IAAI,CAAC,UAAU;gBACjB,KAAK,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,gBAAgB,EAAE,KAAK,EAAE,IAAI,OAAO,CAAC,MAAM,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,GAAG,CAAC,KAAK,KAAK,OAAO,CAAC,KAAK,CAAC;oBAC5G,OAAO,CAAC,IAAI,CAAC,EAAE,OAAO,EAAE,EAAE,GAAG,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,GAAG,EAAE,IAAI,EAAE,QAAQ,EAAE,CAAC,EAAE,QAAQ,EAAE,CAAC,EAAE,KAAK,EAAE,EAAE,gBAAgB,EAAE,CAAC,CAAC;YAC5H,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,QAAQ,CAAC,EAAE,CAAC;gBAClC,KAAK,MAAM,MAAM,IAAI,OAAO;oBAAE,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,WAAW,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,CAAC,QAAQ,CAAC,MAAM,EAAE,UAAU,CAAC,CAAC,CAAC;gBAC3G,SAAS;YACX,CAAC;YACD,+GAA+G;YAC/G,+GAA+G;YAC/G,+DAA+D;YAC/D,IAAI,IAAI,GAAG,OAAO,CAAC,OAAO,EAAE,CAAC;YAC7B,KAAK,MAAM,MAAM,IAAI,OAAO;gBAC1B,KAAK,MAAM,KAAK,IAAI,IAAI,CAAC,QAAQ,CAAC,MAAM;oBAAE,KAAK,CAAC,IAAI,CAAC,CAAC,IAAI,GAAG,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,WAAW,CAAC,IAAI,EAAE,MAAM,EAAE,CAAC,KAAK,CAAC,EAAE,UAAU,CAAC,CAAC,CAAC,CAAC,CAAC;QACxI,CAAC;QACD,MAAM,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC;IAC3B,CAAC;IAED,kIAAkI;IAC1H,KAAK,CAAC,MAAoB;QAChC,MAAM,GAAG,GAAG,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,KAAK,IAAI,EAAE;YACvC,IAAI,IAAI,CAAC,QAAQ,IAAI,YAAY;gBAAE,MAAM,KAAK,EAAE,CAAC;YACjD,MAAM,KAAK,GAAG,MAAM,EAAE,CAAC;YACvB,4EAA4E;YAC5E,IAAI,KAAK,GAAG,CAAC,IAAI,IAAI,CAAC,QAAQ,KAAK,CAAC;gBAAE,KAAK,KAAK,EAAE,CAAC,IAAI,CAAC,GAAG,EAAE,CAAC,KAAK,CAAC,IAAI,CAAC,QAAQ,GAAG,CAAC,CAAC,CAAC,CAAC;YACxF,IAAI,CAAC,QAAQ,IAAI,KAAK,CAAC;QACzB,CAAC,CAAC,CAAC;QACH,IAAI,CAAC,OAAO,GAAG,GAAG,CAAC,KAAK,CAAC,GAAG,EAAE,GAAE,CAAC,CAAC,CAAC;QACnC,OAAO,GAAG,CAAC;IACb,CAAC;IAED,+DAA+D;IACvD,WAAW,CAAC,IAAU,EAAE,MAAqB,EAAE,IAAa,EAAE,UAAoC;QACxG,MAAM,EAAE,MAAM,EAAE,GAAG,IAAI,CAAC;QACxB,MAAM,QAAQ,GAAG,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,aAAa,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC;QAC3G,MAAM,GAAG,GAAG;YACV,QAAQ,CAAC,CAAC,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC,MAAM;YACjC,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,QAAQ,CAAC,UAAU,CAAC,CAAC,IAAI,EAAE,CAAC,IAAI,EAAE;YACnD,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC,CAAC,QAAQ;YACnE,MAAM,CAAC,gBAAgB,CAAC,CAAC,CAAC,MAAM,CAAC,gBAAgB,CAAC,IAAI,CAAC,CAAC,CAAC,QAAQ;YACjE,MAAM,CAAC,OAAO,CAAC,KAAK;SACrB,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;QACZ,MAAM,KAAK,GAAG,GAAG,EAAE;YACjB,8FAA8F;YAC9F,MAAM,KAAK,GAAG,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC;YACpC,IAAI,KAAK,IAAI,UAAU,CAAC,KAAK,CAAC;gBAAE,OAAO,OAAO,CAAC,OAAO,EAAE,CAAC;YACzD,MAAM,MAAM,GAAG,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC;YACpC,IAAI,CAAC;gBACH,IAAI,CAAC,QAAQ,CAAC,MAAM,GAAG,IAAI,CAAC;gBAC5B,OAAO,WAAW,CAAC,IAAI,CAAC,QAAQ,EAAE,IAAI,EAAE,MAAM,CAAC,CAAC;YAClD,CAAC;oBAAS,CAAC;gBACT,IAAI,CAAC,QAAQ,CAAC,MAAM,GAAG,MAAM,CAAC;YAChC,CAAC;QACH,CAAC,CAAC;QACF,MAAM,KAAK,GAAG,MAAM,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;QAC9B,MAAM,GAAG,GAAG,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,KAAK,EAAE,CAAC;QAChD,MAAM,OAAO,GAAG,GAAG,CAAC,IAAI,CACtB,GAAG,EAAE,GAAE,CAAC,EACR,GAAG,EAAE,GAAE,CAAC,CACT,CAAC;QACF,MAAM,CAAC,GAAG,CAAC,GAAG,EAAE,OAAO,CAAC,CAAC;QACzB,KAAK,OAAO,CAAC,IAAI,CAAC,GAAG,EAAE;YACrB,IAAI,MAAM,CAAC,GAAG,CAAC,GAAG,CAAC,KAAK,OAAO;gBAAE,MAAM,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;QACtD,CAAC,CAAC,CAAC;QACH,OAAO,GAAG,CAAC;IACb,CAAC;CACF;AAED;;;;GAIG;AACH,MAAM,YAAY,GAAG,EAAE,GAAG,CAAC,IAAI,EAAE,CAAC;AAKlC,MAAM,GAAG,GAAG,CAAC,MAAgB,EAAE,EAAE,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,KAAK,EAAE,KAAK,EAAE,EAAE,CAAC,KAAK,GAAG,KAAK,EAAE,CAAC,CAAC,CAAC;AAEpF;;;;GAIG;AACH,SAAS,OAAO,CAAC,OAAgB;IAC/B,MAAM,MAAM,GAAc,OAAO,CAAC,OAAO,IAAI,EAAE,CAAC;IAChD,MAAM,IAAI,GAAG,KAAK,CAAC,OAAO,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC;IAC1C,IAAI,MAAM,CAAC,MAAM,GAAG,CAAC,IAAI,CAAC,CAAC,IAAI,IAAI,OAAO,CAAC,GAAG,CAAC,OAAO,EAAE,qBAAqB,CAAC,KAAK,IAAI,CAAC;QAAE,OAAO,OAAO,CAAC,MAAM,CAAC,CAAC;IACjH,MAAM,KAAK,GAAG,OAAO,CAAC,OAAO,CAAC,KAAK,CAAC,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;IACvD,OAAO,OAAO,CAAC,eAAe,IAAI,MAAM,CAAC,MAAM,KAAK,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,KAAK,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC;AAC7F,CAAC;AAED,0GAA0G;AAC1G,SAAS,OAAO,CAAC,KAAc;IAC7B,IAAI,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC;QAAE,OAAO,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC;IACzD,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,KAAK,IAAI;QAAE,OAAO,CAAC,CAAC;IAC1D,IAAI,SAAS,CAAC,KAAK,CAAC;QAAE,OAAO,OAAO,CAAC,KAAK,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC;IAC1F,MAAM,MAAM,GAAY,OAAO,CAAC,GAAG,CAAC,KAAK,EAAE,QAAQ,CAAC,CAAC;IACrD,IAAI,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC;QAAE,OAAO,OAAO,CAAC,MAAM,CAAC,CAAC;IAClD,MAAM,IAAI,GAAY,OAAO,CAAC,GAAG,CAAC,KAAK,EAAE,MAAM,CAAC,CAAC;IACjD,IAAI,WAAW,CAAC,MAAM,CAAC,IAAI,CAAC;QAAE,OAAO,IAAI,CAAC,UAAU,CAAC;IACrD,MAAM,SAAS,GAAG,CAAC,IAAY,EAAE,EAAE;QACjC,MAAM,KAAK,GAAY,OAAO,CAAC,GAAG,CAAC,KAAK,EAAE,IAAI,CAAC,CAAC;QAChD,OAAO,OAAO,KAAK,KAAK,QAAQ,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC;IAC/C,CAAC,CAAC;IACF,OAAO,SAAS,CAAC,OAAO,CAAC,GAAG,SAAS,CAAC,QAAQ,CAAC,GAAG,SAAS,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;AAC3E,CAAC;AAED;;;GAGG;AACH,SAAS,QAAQ,CAAC,MAAgB;IAChC,MAAM,KAAK,GAAG,IAAI,GAAG,EAAW,CAAC;IACjC,MAAM,OAAO,GAAG,IAAI,GAAG,EAAW,CAAC;IACnC,MAAM,KAAK,GAAG,CAAC,KAAc,EAAE,EAAE;QAC/B,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,KAAK,IAAI,IAAI,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC;YAAE,OAAO;QAC9E,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC;QACnB,IAAI,SAAS,CAAC,KAAK,CAAC;YAAE,KAAK,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC;aAClC,IAAI,MAAM,CAAC,KAAK,CAAC,EAAE,CAAC;YACvB,KAAK,CAAC,OAAO,CAAC,GAAG,CAAC,KAAK,EAAE,OAAO,CAAC,CAAC,CAAC;YACnC,KAAK,MAAM,KAAK,IAAI,KAAK,CAAC,WAAW,EAAE;gBAAE,KAAK,CAAC,KAAK,CAAC,CAAC;QACxD,CAAC;aAAM,IAAI,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC;YAAE,KAAK,MAAM,KAAK,IAAI,KAAK;gBAAE,KAAK,CAAC,KAAK,CAAC,CAAC;aACpE,IAAI,CAAC,MAAM,CAAC,SAAS,EAAE,IAAI,CAAC,CAAC,QAAQ,CAAC,MAAM,CAAC,cAAc,CAAC,KAAK,CAAC,CAAC;YACtE,KAAK,MAAM,CAAC,GAAG,EAAE,KAAK,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC;gBAAE,IAAI,GAAG,KAAK,UAAU;oBAAE,KAAK,CAAC,KAAK,CAAC,CAAC;IAC3F,CAAC,CAAC;IACF,MAAM,CAAC,QAAQ,CAAC,CAAC,IAAI,EAAE,EAAE;QACvB,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC;YAAE,OAAO;QAC1B,4DAA4D;QAC5D,KAAK,MAAM,QAAQ,IAAI,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC,IAAI,EAAE;YAAE,KAAK,MAAM,CAAC,GAAG,EAAE,KAAK,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,QAAQ,CAAC;gBAAE,IAAI,GAAG,KAAK,UAAU;oBAAE,KAAK,CAAC,KAAK,CAAC,CAAC;IAC3I,CAAC,CAAC,CAAC;IACH,OAAO,KAAK,CAAC;AACf,CAAC","sourcesContent":["import type { Mesh, Object3D, Renderer, Texture } from \"three/webgpu\";\nimport { isMesh, isNode, isTexture } from \"../../three.js\";\nimport type { RenderContext, ShadowMap, Target } from \"./contexts.js\";\nimport { compileMesh, frame, sharePipelines, type CompileTarget } from \"./pipelines.js\";\n\n/** What a compile is for besides its context: shadow maps, and which targets were disposed. */\nexport interface CompileFor {\n /** The shadow maps the scene's level meshes draw into: each mesh that casts compiles for them too. */\n shadows: readonly ShadowMap[];\n /** Whether `target` was disposed: no draw compiles for it, as that would make its textures again. */\n isDisposed(target: Target): boolean;\n}\n\nconst compilers = new WeakMap<Renderer, Compiler>();\n\n/** What `renderer` prepares levels with: one Compiler per renderer, made the first time a level compiles for it. */\nexport function compilerOf(renderer: Renderer): Compiler {\n let compiler = compilers.get(renderer);\n if (!compiler) compilers.set(renderer, (compiler = new Compiler(renderer)));\n return compiler;\n}\n\n/**\n * What a renderer prepares for the levels arriving: the levels share its queue's budget and its pipeline cache, which\n * it shares the readiness of (see `sharePipelines`).\n */\nexport class Compiler {\n /** Uploaded since the last frame. */\n private uploaded = 0;\n /** The last upload: uploads run in turn. */\n private uploads = Promise.resolve();\n /**\n * The last draw compiled for each shader build: material, attributes, target, shadow map and nesting. three starts a\n * build for each draw that misses its cache, even one another draw is building, so a draw that builds alike waits\n * for the last one and finds its build cached.\n */\n private readonly builds = new Map<string, Promise<void>>();\n\n constructor(private readonly renderer: Renderer) {\n sharePipelines(renderer);\n }\n\n /**\n * Prepare the same target and MRT as the camera's draw, even if normal rendering has since restored the canvas, and\n * the shadow maps of `compileFor`, for each mesh that casts. r186 uploads a texture synchronously inside initTexture,\n * and creates a pipeline it meets while rendering synchronously, so each texture and draw is prepared here first.\n * Textures upload paced across frames (see `paced`); every draw then compiles at once, but for those of one\n * multi-material mesh, as do the draws of every other level the renderer prepares, so their pipelines are created\n * side by side.\n */\n async compileAsync(object: Object3D, context: RenderContext, { shadows, isDisposed }: CompileFor): Promise<void> {\n const { renderer } = this;\n const { memory } = renderer.info;\n for (const texture of textures(object))\n await this.paced(() => {\n const known = memory.textures;\n renderer.initTexture(texture);\n return memory.textures === known ? 0 : bytesOf(texture);\n });\n const meshes: Mesh[] = [];\n object.traverse((node) => {\n if (isMesh(node)) meshes.push(node);\n });\n const draws: Promise<void>[] = [];\n for (const mesh of meshes) {\n const targets: CompileTarget[] = [{ context, overrideMaterial: null }];\n if (mesh.castShadow)\n for (const { camera, target, overrideMaterial, depth } of shadows.filter((map) => map.scene === context.scene))\n targets.push({ context: { ...context, camera, target, mrt: null, cubeFace: 0, mipLevel: 0, depth }, overrideMaterial });\n if (!Array.isArray(mesh.material)) {\n for (const target of targets) draws.push(this.compileDraw(mesh, target, mesh.geometry.groups, isDisposed));\n continue;\n }\n // r186 starts only the first material before yielding; isolate one draw so setup sees its captured target/MRT.\n // The draws of one mesh compile in turn: with their compiles overlapping, r186 creates some of their pipelines\n // with another draw's bind group layout, which WebGPU rejects.\n let turn = Promise.resolve();\n for (const target of targets)\n for (const group of mesh.geometry.groups) draws.push((turn = turn.then(() => this.compileDraw(mesh, target, [group], isDisposed))));\n }\n await Promise.all(draws);\n }\n\n /** Run `upload`, which answers the bytes it uploaded, after the next frame if those since the last one reached `FRAME_UPLOAD`. */\n private paced(upload: () => number): Promise<void> {\n const run = this.uploads.then(async () => {\n if (this.uploaded >= FRAME_UPLOAD) await frame();\n const bytes = upload();\n // The next frame empties the budget, whether or not an upload waits for it.\n if (bytes > 0 && this.uploaded === 0) void frame().then(() => void (this.uploaded = 0));\n this.uploaded += bytes;\n });\n this.uploads = run.catch(() => {});\n return run;\n }\n\n /** Compile the draw of `mesh`'s groups `part` for `target`. */\n private compileDraw(mesh: Mesh, target: CompileTarget, part: Group[], isDisposed: CompileFor[\"isDisposed\"]): Promise<void> {\n const { builds } = this;\n const material = Array.isArray(mesh.material) ? mesh.material[part[0]?.materialIndex ?? 0] : mesh.material;\n const key = [\n material ? material.uuid : \"none\",\n Object.keys(mesh.geometry.attributes).sort().join(),\n target.context.target ? target.context.target.texture.id : \"canvas\",\n target.overrideMaterial ? target.overrideMaterial.uuid : \"camera\",\n target.context.depth,\n ].join(\" \");\n const start = () => {\n // Uploads and alike builds may have taken frames: a target disposed meanwhile stays disposed.\n const drawn = target.context.target;\n if (drawn && isDisposed(drawn)) return Promise.resolve();\n const groups = mesh.geometry.groups;\n try {\n mesh.geometry.groups = part;\n return compileMesh(this.renderer, mesh, target);\n } finally {\n mesh.geometry.groups = groups;\n }\n };\n const alike = builds.get(key);\n const own = alike ? alike.then(start) : start();\n const settled = own.then(\n () => {},\n () => {},\n );\n builds.set(key, settled);\n void settled.then(() => {\n if (builds.get(key) === settled) builds.delete(key);\n });\n return own;\n }\n}\n\n/**\n * What texture uploads may add in one frame, in bytes: a 2048² RGBA texture. Uploads run on the GPU's queue before the\n * frame that follows them, which waits for them all. A draw's geometry is not paced: its buffers copy in far less\n * time than a texture of the same size and its mipmaps.\n */\nconst FRAME_UPLOAD = 16 * 2 ** 20;\n\n/** A draw's range of a geometry, as three's groups hold it. */\ntype Group = Mesh[\"geometry\"][\"groups\"][number];\n\nconst sum = (values: number[]) => values.reduce((total, value) => total + value, 0);\n\n/**\n * The bytes `texture` uploads, as three 0.186.1 uploads it: its mip levels', or its image's and the mipmaps three\n * generates from it. An uncompressed cube's image is its six faces, and its mip levels are those after the first, with\n * six images each; a compressed cube's faces carry their own mip levels, unless it has some.\n */\nfunction bytesOf(texture: Texture): number {\n const levels: unknown[] = texture.mipmaps ?? [];\n const cube = Array.isArray(texture.image);\n if (levels.length > 0 && (!cube || Reflect.get(texture, \"isCompressedTexture\") === true)) return bytesIn(levels);\n const bytes = bytesIn(texture.image) + bytesIn(levels);\n return texture.generateMipmaps && levels.length === 0 ? Math.ceil((bytes * 4) / 3) : bytes;\n}\n\n/** The bytes of an image, a mip level, a cube's face or a list of them: its data's, or four per texel. */\nfunction bytesIn(image: unknown): number {\n if (Array.isArray(image)) return sum(image.map(bytesIn));\n if (typeof image !== \"object\" || image === null) return 0;\n if (isTexture(image)) return bytesIn(image.mipmaps?.length ? image.mipmaps : image.image);\n const images: unknown = Reflect.get(image, \"images\");\n if (Array.isArray(images)) return bytesIn(images);\n const data: unknown = Reflect.get(image, \"data\");\n if (ArrayBuffer.isView(data)) return data.byteLength;\n const dimension = (name: string) => {\n const value: unknown = Reflect.get(image, name);\n return typeof value === \"number\" ? value : 1;\n };\n return dimension(\"width\") * dimension(\"height\") * dimension(\"depth\") * 4;\n}\n\n/**\n * The textures `object`'s meshes draw: their materials', those their nodes sample, and those a material keeps in plain\n * objects and arrays for nodes it makes while it builds (an impostor's atlases, a baked mesh's rooms).\n */\nfunction textures(object: Object3D): Set<Texture> {\n const found = new Set<Texture>();\n const visited = new Set<unknown>();\n const visit = (value: unknown) => {\n if (typeof value !== \"object\" || value === null || visited.has(value)) return;\n visited.add(value);\n if (isTexture(value)) found.add(value);\n else if (isNode(value)) {\n visit(Reflect.get(value, \"value\"));\n for (const child of value.getChildren()) visit(child);\n } else if (Array.isArray(value)) for (const entry of value) visit(entry);\n else if ([Object.prototype, null].includes(Object.getPrototypeOf(value)))\n for (const [key, entry] of Object.entries(value)) if (key !== \"userData\") visit(entry);\n };\n object.traverse((node) => {\n if (!isMesh(node)) return;\n // userData is the application's, not what a material binds.\n for (const material of [node.material].flat()) for (const [key, value] of Object.entries(material)) if (key !== \"userData\") visit(value);\n });\n return found;\n}\n"]}
@@ -67,15 +67,23 @@ class SessionLoader extends BrowserCachedLodLoader {
67
67
  await super.dispose();
68
68
  await this.scripts.dispose();
69
69
  }
70
+ /**
71
+ * A path its manager served at the placeholder origin as that path, which `CachedLodLoader.loadAsync` gave below the
72
+ * app's base: keyed as in browsers whatever document the process has, such as the one a headless GLTS renderer
73
+ * installs, against which the placeholder origin would be foreign.
74
+ */
75
+ publicPath(url) {
76
+ return url.startsWith(`${placeholderOrigin}/`) ? url.slice(placeholderOrigin.length) : super.publicPath(url);
77
+ }
70
78
  /** Load `url` in the load's session, or, once `loadAsync` has resolved, in a session of its own. */
71
79
  loadAsset(url) {
72
80
  if (sessions.getStore())
73
81
  return super.loadAsset(url);
74
82
  return session({ read: new Set(), packages: new Set() }, () => super.loadAsset(url));
75
83
  }
76
- newBakeRenderer() {
84
+ async newBakeRenderer() {
77
85
  // Three's DOM canvas types exclude native canvases; its constructor checks the actual API.
78
- const renderer = Reflect.construct(WebGPURenderer, [{ canvas: canvas(), antialias: false }]);
86
+ const renderer = Reflect.construct(WebGPURenderer, [{ canvas: await canvas(), antialias: false }]);
79
87
  if (!(renderer instanceof WebGPURenderer))
80
88
  throw new Error("CachedLodLoader: three did not make a WebGPURenderer");
81
89
  return setUpBakeRenderer(renderer);
@@ -135,7 +143,7 @@ class ScriptLoader extends Loader {
135
143
  }
136
144
  }
137
145
  let users = 0;
138
- /** What sessions install while any runs: the canvas, and what restores the globals. */
146
+ /** What sessions install while any runs: the served fetch, and the canvas once one bakes. */
139
147
  let installed;
140
148
  /**
141
149
  * The restore of the last sessions to finish, which the next install waits for: @drawcall/webgpu saves and restores
@@ -147,14 +155,14 @@ let current;
147
155
  function canvas() {
148
156
  if (!current)
149
157
  throw new Error("CachedLodLoader: baking outside loadAsync");
150
- return current;
158
+ return (current.canvas ??= createCanvas(1, 1));
151
159
  }
152
160
  async function session(state, run) {
153
161
  users++;
154
162
  // A failed restore failed the session that ran it. Sessions running together share one install.
155
163
  const shared = (installed ??= restored.catch(() => { }).then(install));
156
164
  try {
157
- current = (await shared).canvas;
165
+ current = await shared;
158
166
  return await sessions.run(state, run);
159
167
  }
160
168
  finally {
@@ -166,16 +174,22 @@ async function session(state, run) {
166
174
  }
167
175
  }
168
176
  }
169
- /** A canvas of @drawcall/webgpu, which installs WebGPU, image decoding and workers while it lives, and the served fetch. */
170
- async function install() {
171
- const canvas = await createCanvas(1, 1);
177
+ /**
178
+ * The served fetch, and a canvas of @drawcall/webgpu once a session bakes, which installs WebGPU, image decoding and
179
+ * workers while it lives.
180
+ */
181
+ function install() {
172
182
  const { fetch } = globalThis;
173
183
  globalThis.fetch = (input, init) => serve(fetch, input, init);
174
- const restore = async () => {
175
- globalThis.fetch = fetch;
176
- await canvas.dispose();
184
+ const made = {
185
+ restore: async () => {
186
+ globalThis.fetch = fetch;
187
+ // A canvas that failed to make failed the bake that made it.
188
+ const canvas = await made.canvas?.catch(() => undefined);
189
+ await canvas?.dispose();
190
+ },
177
191
  };
178
- return { canvas, restore };
192
+ return made;
179
193
  }
180
194
  async function serve(fetch, input, init) {
181
195
  const url = new URL(input instanceof Request ? input.url : input);
@@ -1 +1 @@
1
- {"version":3,"file":"node.js","sourceRoot":"","sources":["../../src/loader/node.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAC;AACrD,OAAO,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAC5C,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AACzC,OAAO,EAAE,YAAY,EAAe,MAAM,kBAAkB,CAAC;AAE7D,OAAO,EAAE,OAAO,IAAI,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAC/D,OAAO,EAAE,MAAM,EAAE,cAAc,EAAE,cAAc,EAAiB,MAAM,cAAc,CAAC;AACrF,OAAO,EAAE,SAAS,EAAc,MAAM,WAAW,CAAC;AAClD,OAAO,EAAE,iBAAiB,EAAE,MAAM,YAAY,CAAC;AAE/C,OAAO,EAAE,eAAe,IAAI,sBAAsB,EAA+B,MAAM,YAAY,CAAC;AAEpG,OAAO,EAAE,aAAa,EAAE,iBAAiB,EAAE,MAAM,UAAU,CAAC;AAS5D,kFAAkF;AAClF,MAAM,QAAQ,GAAG,IAAI,iBAAiB,EAAW,CAAC;AAElD;;;;;;;;;;;;;GAaG;AACH,MAAM,OAAO,eAAgB,SAAQ,sBAAsB;IACzD,YAAY,OAAwB,EAAE,UAAkC,EAAE;QACxE,KAAK,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;IAC1B,CAAC;IAEQ,SAAS,CAAC,OAAmB;QACpC,MAAM,KAAK,GAAG,OAAO,OAAO,KAAK,QAAQ,CAAC,CAAC,CAAC,EAAE,GAAG,EAAE,OAAO,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC;QACvE,MAAM,GAAG,GAAG,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC,OAAO,CAAC,UAAU,CAAC,aAAa,CAAC,KAAK,CAAC,GAAG,EAAE,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QAC1F,OAAO,OAAO,CAAC,EAAE,IAAI,EAAE,IAAI,GAAG,EAAE,EAAE,QAAQ,EAAE,IAAI,GAAG,EAAE,EAAE,EAAE,KAAK,IAAI,EAAE;YAClE,MAAM,MAAM,GAAG,IAAI,aAAa,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;YAC/C,IAAI,CAAC;gBACH,OAAO,MAAM,MAAM,CAAC,SAAS,CAAC,EAAE,GAAG,KAAK,EAAE,GAAG,EAAE,CAAC,CAAC;YACnD,CAAC;oBAAS,CAAC;gBACT,MAAM,MAAM,CAAC,OAAO,EAAE,CAAC;YACzB,CAAC;QACH,CAAC,CAAC,CAAC;IACL,CAAC;IAED,qDAAqD;IAC5C,KAAK,CAAC,OAAO,KAAmB,CAAC;IAE1C,uFAAuF;IACpE,UAAU,CAAC,GAAW;QACvC,IAAI,CAAC,GAAG,CAAC,UAAU,CAAC,OAAO,CAAC;YAAE,OAAO,KAAK,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC;QAC3D,MAAM,IAAI,GAAG,SAAS,CAAC,GAAG,CAAC,CAAC;QAC5B,IAAI,CAAC,GAAG,CAAC,UAAU,CAAC,IAAI,CAAC;YAAE,MAAM,IAAI,KAAK,CAAC,uCAAuC,GAAG,8CAA8C,IAAI,EAAE,CAAC,CAAC;QAC3I,OAAO,IAAI,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,MAAM,CAAC,EAAE,CAAC;IACtC,CAAC;CACF;AAED,8GAA8G;AAC9G,MAAM,aAAc,SAAQ,sBAAsB;IACpB,SAAS,GAAG,IAAI,CAAC;IAC5B,OAAO,CAAe;IAEvC,YAAY,OAA+B;QACzC,KAAK,CAAC,cAAc,EAAE,EAAE,OAAO,CAAC,CAAC;QACjC,oCAAoC;QACpC,IAAI,CAAC,OAAO,GAAG,IAAI,YAAY,CAAC,IAAI,CAAC,OAAO,EAAE,SAAS,CAAC,GAAG,CAAC,CAAC,CAAC;QAC9D,IAAI,CAAC,OAAO,CAAC,UAAU,CAAC,UAAU,EAAE,IAAI,CAAC,OAAO,CAAC,CAAC;IACpD,CAAC;IAEQ,KAAK,CAAC,OAAO;QACpB,MAAM,KAAK,CAAC,OAAO,EAAE,CAAC;QACtB,MAAM,IAAI,CAAC,OAAO,CAAC,OAAO,EAAE,CAAC;IAC/B,CAAC;IAED,oGAAoG;IACjF,SAAS,CAAC,GAAW;QACtC,IAAI,QAAQ,CAAC,QAAQ,EAAE;YAAE,OAAO,KAAK,CAAC,SAAS,CAAC,GAAG,CAAC,CAAC;QACrD,OAAO,OAAO,CAAC,EAAE,IAAI,EAAE,IAAI,GAAG,EAAE,EAAE,QAAQ,EAAE,IAAI,GAAG,EAAE,EAAE,EAAE,GAAG,EAAE,CAAC,KAAK,CAAC,SAAS,CAAC,GAAG,CAAC,CAAC,CAAC;IACvF,CAAC;IAEkB,eAAe;QAChC,2FAA2F;QAC3F,MAAM,QAAQ,GAAY,OAAO,CAAC,SAAS,CAAC,cAAc,EAAE,CAAC,EAAE,MAAM,EAAE,MAAM,EAAE,EAAE,SAAS,EAAE,KAAK,EAAE,CAAC,CAAC,CAAC;QACtG,IAAI,CAAC,CAAC,QAAQ,YAAY,cAAc,CAAC;YAAE,MAAM,IAAI,KAAK,CAAC,sDAAsD,CAAC,CAAC;QACnH,OAAO,iBAAiB,CAAC,QAAQ,CAAC,CAAC;IACrC,CAAC;IAEkB,MAAM;QACvB,MAAM,KAAK,GAAG,QAAQ,CAAC,QAAQ,EAAE,CAAC;QAClC,IAAI,CAAC,KAAK;YAAE,MAAM,IAAI,KAAK,CAAC,sDAAsD,CAAC,CAAC;QACpF,yDAAyD;QACzD,MAAM,QAAQ,GAAG,CAAC,GAAG,IAAI,GAAG,CAAC,CAAC,OAAO,EAAE,GAAG,KAAK,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;QACnE,OAAO,CAAC,GAAG,CAAC,GAAG,KAAK,CAAC,IAAI,CAAC,CAAC,IAAI,EAAE,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC,EAAE,IAAI,EAAE,CAAC,CAAC,EAAE,GAAG,QAAQ,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC;IAC/G,CAAC;CACF;AAED,6FAA6F;AAC7F,SAAS,cAAc;IACrB,MAAM,OAAO,GAAG,IAAI,cAAc,EAAE,CAAC;IACrC,OAAO,CAAC,cAAc,CAAC,CAAC,GAAG,EAAE,EAAE;QAC7B,IAAI,CAAC,GAAG,CAAC,UAAU,CAAC,GAAG,CAAC,IAAI,GAAG,CAAC,UAAU,CAAC,IAAI,CAAC;YAAE,OAAO,GAAG,CAAC;QAC7D,IAAI,CAAC,QAAQ,CAAC,QAAQ,EAAE;YAAE,MAAM,IAAI,KAAK,CAAC,iCAAiC,GAAG,6CAA6C,CAAC,CAAC;QAC7H,OAAO,iBAAiB,GAAG,GAAG,CAAC;IACjC,CAAC,CAAC,CAAC;IACH,OAAO,OAAO,CAAC;AACjB,CAAC;AAED;;;GAGG;AACH,MAAM,YAAa,SAAQ,MAAgB;IAKtB;IAJX,MAAM,CAAkC;IAEhD,YACE,OAAuB,EACN,IAAY;QAE7B,KAAK,CAAC,OAAO,CAAC,CAAC;QAFE,SAAI,GAAJ,IAAI,CAAQ;IAG/B,CAAC;IAEQ,KAAK,CAAC,SAAS,CAAC,GAAW;QAClC,IAAI,CAAC,MAAM,KAAK,MAAM,CAAC,gBAAgB,CAAC,CAAC,IAAI,CAC3C,CAAC,EAAE,UAAU,EAAE,EAAE,EAAE,CAAC,IAAI,UAAU,CAAC,IAAI,CAAC,OAAO,EAAE,EAAE,OAAO,EAAE,CAAC,SAAS,EAAE,EAAE,CAAC,aAAa,CAAC,SAAS,EAAE,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,CAClH,CAAC;QACF,MAAM,KAAK,GAAG,MAAM,CAAC,MAAM,IAAI,CAAC,MAAM,CAAC,CAAC,SAAS,CAAC,GAAG,CAAC,CAAC;QACvD,MAAM,EAAE,QAAQ,EAAE,cAAc,EAAE,GAAG,KAAK,CAAC,MAAM,CAAC;QAClD,IAAI,cAAc,CAAC,MAAM,GAAG,CAAC;YAC3B,MAAM,IAAI,KAAK,CACb,uCAAuC,GAAG,iBAAiB,cAAc,CAAC,IAAI,CAAC,IAAI,CAAC,wEAAwE,CAC7J,CAAC;QACJ,MAAM,KAAK,GAAG,QAAQ,CAAC,QAAQ,EAAE,CAAC;QAClC,IAAI,CAAC,KAAK;YAAE,MAAM,IAAI,KAAK,CAAC,qBAAqB,GAAG,4BAA4B,CAAC,CAAC;QAClF,KAAK,MAAM,IAAI,IAAI,QAAQ;YAAE,KAAK,CAAC,QAAQ,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;QACtD,OAAO,KAAK,CAAC;IACf,CAAC;IAED,uGAAuG;IACvG,KAAK,CAAC,OAAO;QACX,MAAM,MAAM,GAAG,IAAI,CAAC,MAAM,CAAC;QAC3B,IAAI,CAAC,MAAM,GAAG,SAAS,CAAC;QACxB,iEAAiE;QACjE,MAAM,IAAI,GAAG,MAAM,MAAM,EAAE,KAAK,CAAC,GAAG,EAAE,CAAC,SAAS,CAAC,CAAC;QAClD,IAAI,EAAE,OAAO,EAAE,CAAC;IAClB,CAAC;CACF;AAED,IAAI,KAAK,GAAG,CAAC,CAAC;AACd,uFAAuF;AACvF,IAAI,SAAgF,CAAC;AACrF;;;GAGG;AACH,IAAI,QAAQ,GAAkB,OAAO,CAAC,OAAO,EAAE,CAAC;AAChD,IAAI,OAA2B,CAAC;AAEhC,0CAA0C;AAC1C,SAAS,MAAM;IACb,IAAI,CAAC,OAAO;QAAE,MAAM,IAAI,KAAK,CAAC,2CAA2C,CAAC,CAAC;IAC3E,OAAO,OAAO,CAAC;AACjB,CAAC;AAED,KAAK,UAAU,OAAO,CAAI,KAAc,EAAE,GAAqB;IAC7D,KAAK,EAAE,CAAC;IACR,gGAAgG;IAChG,MAAM,MAAM,GAAG,CAAC,SAAS,KAAK,QAAQ,CAAC,KAAK,CAAC,GAAG,EAAE,GAAE,CAAC,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC;IACtE,IAAI,CAAC;QACH,OAAO,GAAG,CAAC,MAAM,MAAM,CAAC,CAAC,MAAM,CAAC;QAChC,OAAO,MAAM,QAAQ,CAAC,GAAG,CAAC,KAAK,EAAE,GAAG,CAAC,CAAC;IACxC,CAAC;YAAS,CAAC;QACT,IAAI,EAAE,KAAK,KAAK,CAAC,EAAE,CAAC;YAClB,SAAS,GAAG,OAAO,GAAG,SAAS,CAAC;YAChC,yCAAyC;YACzC,QAAQ,GAAG,MAAM,CAAC,IAAI,CACpB,CAAC,EAAE,OAAO,EAAE,EAAE,EAAE,CAAC,OAAO,EAAE,EAC1B,GAAG,EAAE,GAAE,CAAC,CACT,CAAC;YACF,MAAM,QAAQ,CAAC;QACjB,CAAC;IACH,CAAC;AACH,CAAC;AAED,4HAA4H;AAC5H,KAAK,UAAU,OAAO;IACpB,MAAM,MAAM,GAAG,MAAM,YAAY,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;IACxC,MAAM,EAAE,KAAK,EAAE,GAAG,UAAU,CAAC;IAC7B,UAAU,CAAC,KAAK,GAAG,CAAC,KAAK,EAAE,IAAI,EAAE,EAAE,CAAC,KAAK,CAAC,KAAK,EAAE,KAAK,EAAE,IAAI,CAAC,CAAC;IAC9D,MAAM,OAAO,GAAG,KAAK,IAAI,EAAE;QACzB,UAAU,CAAC,KAAK,GAAG,KAAK,CAAC;QACzB,MAAM,MAAM,CAAC,OAAO,EAAE,CAAC;IACzB,CAAC,CAAC;IACF,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,CAAC;AAC7B,CAAC;AAED,KAAK,UAAU,KAAK,CAAC,KAA8B,EAAE,KAAwB,EAAE,IAAkB;IAC/F,MAAM,GAAG,GAAG,IAAI,GAAG,CAAC,KAAK,YAAY,OAAO,CAAC,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC;IAClE,MAAM,KAAK,GAAG,QAAQ,CAAC,QAAQ,EAAE,CAAC;IAClC,IAAI,CAAC,KAAK,IAAI,GAAG,CAAC,QAAQ,KAAK,OAAO,IAAI,GAAG,CAAC,QAAQ,KAAK,OAAO;QAAE,OAAO,KAAK,CAAC,KAAK,EAAE,IAAI,CAAC,CAAC;IAC9F,IAAI,GAAG,CAAC,MAAM,KAAK,iBAAiB;QAClC,MAAM,IAAI,KAAK,CAAC,0DAA0D,GAAG,CAAC,IAAI,kDAAkD,CAAC,CAAC;IACxI,MAAM,IAAI,GAAG,SAAS,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;IACrC,IAAI,CAAC;QACH,MAAM,KAAK,GAAG,MAAM,QAAQ,CAAC,aAAa,CAAC,IAAI,CAAC,CAAC,CAAC;QAClD,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;QACrB,OAAO,IAAI,QAAQ,CAAC,KAAK,CAAC,CAAC;IAC7B,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,IAAI,KAAK,YAAY,KAAK,IAAI,MAAM,IAAI,KAAK,IAAI,KAAK,CAAC,IAAI,KAAK,QAAQ;YAAE,OAAO,IAAI,QAAQ,CAAC,IAAI,EAAE,EAAE,MAAM,EAAE,GAAG,EAAE,CAAC,CAAC;QACrH,MAAM,KAAK,CAAC;IACd,CAAC;AACH,CAAC","sourcesContent":["import { AsyncLocalStorage } from \"node:async_hooks\";\nimport { readFile } from \"node:fs/promises\";\nimport { fileURLToPath } from \"node:url\";\nimport { createCanvas, type Canvas } from \"@drawcall/webgpu\";\nimport type { GLTSLoader } from \"@drawcall/glts\";\nimport { resolve as resolveImport } from \"import-meta-resolve\";\nimport { Loader, LoadingManager, WebGPURenderer, type Object3D } from \"three/webgpu\";\nimport { publicURL, type Input } from \"vitecache\";\nimport { setUpBakeRenderer } from \"../bake.js\";\nimport type { LodLevel } from \"../levels/index.js\";\nimport { CachedLodLoader as BrowserCachedLodLoader, type CachedLodLoaderOptions } from \"./index.js\";\nimport type { LodRequest } from \"./request.js\";\nimport { loaderPathURL, placeholderOrigin } from \"./url.js\";\n\ninterface Session {\n /** The `file:` URLs of the public files the session's loads read. */\n read: Set<string>;\n /** The installed packages the session's .glts sources import. */\n packages: Set<string>;\n}\n\n/** The session of each load, by its async context: the served fetch is global. */\nconst sessions = new AsyncLocalStorage<Session>();\n\n/**\n * CachedLodLoader in Node, as the vitecache plugin and the glts CLI run it: `loadAsync` builds and saves every level of\n * the ladder, each with the public files the ladder's loads read and the installed versions of three and of the\n * packages its .glts sources import. A load that reads anything else, such as a URL off the public directory or a module\n * a .glts source imports from a CDN, fails, since no stored level could be checked against it. Served paths such as\n * `/trees/oak.glts`, and `file:` URLs in the public directory, as a headless .glts script's manager resolves them to,\n * load from vitecache's public directory (see `publicURL`), and `.glts` sources through a GLTSLoader of the load's own;\n * bakes draw on a WebGPU device of @drawcall/webgpu. The manager only resolves the request's URL.\n *\n * Each `loadAsync` loads and bakes with what it makes for itself, which it frees before resolving, so loads may run\n * concurrently. The levels it returns load their stored bytes as in browsers, and none holds the source: the source\n * level, and a simplified level, which draws with the source's materials, load the source again when a camera needs\n * them, as the glts CLI's renders of a .glts script that draws a Lod do.\n */\nexport class CachedLodLoader extends BrowserCachedLodLoader {\n constructor(manager?: LoadingManager, options: CachedLodLoaderOptions = {}) {\n super(manager, options);\n }\n\n override loadAsync(request: LodRequest): Promise<LodLevel[]> {\n const input = typeof request === \"string\" ? { url: request } : request;\n const url = this.publicPath(this.manager.resolveURL(loaderPathURL(input.url, this.path)));\n return session({ read: new Set(), packages: new Set() }, async () => {\n const loader = new SessionLoader(this.options);\n try {\n return await loader.loadAsync({ ...input, url });\n } finally {\n await loader.dispose();\n }\n });\n }\n\n /** Nothing to free: each load frees what it made. */\n override async dispose(): Promise<void> {}\n\n /** A `file:` URL in the public directory as its served path; others as in browsers. */\n protected override publicPath(url: string): string {\n if (!url.startsWith(\"file:\")) return super.publicPath(url);\n const base = publicURL(\"/\");\n if (!url.startsWith(base)) throw new Error(`CachedLodLoader: cannot precompute \"${url}\", which lies outside the public directory ${base}`);\n return `/${url.slice(base.length)}`;\n }\n}\n\n/** One `loadAsync` of the Node loader: builds every level, with a GLTSLoader and bake renderer of its own. */\nclass SessionLoader extends BrowserCachedLodLoader {\n protected override readonly buildsAll = true;\n private readonly scripts: ScriptLoader;\n\n constructor(options: CachedLodLoaderOptions) {\n super(servingManager(), options);\n // Packages resolve as the app's do.\n this.scripts = new ScriptLoader(this.manager, publicURL(\"/\"));\n this.manager.addHandler(/\\.glts$/i, this.scripts);\n }\n\n override async dispose(): Promise<void> {\n await super.dispose();\n await this.scripts.dispose();\n }\n\n /** Load `url` in the load's session, or, once `loadAsync` has resolved, in a session of its own. */\n protected override loadAsset(url: string): Promise<Object3D> {\n if (sessions.getStore()) return super.loadAsset(url);\n return session({ read: new Set(), packages: new Set() }, () => super.loadAsset(url));\n }\n\n protected override newBakeRenderer(): Promise<WebGPURenderer> {\n // Three's DOM canvas types exclude native canvases; its constructor checks the actual API.\n const renderer: unknown = Reflect.construct(WebGPURenderer, [{ canvas: canvas(), antialias: false }]);\n if (!(renderer instanceof WebGPURenderer)) throw new Error(\"CachedLodLoader: three did not make a WebGPURenderer\");\n return setUpBakeRenderer(renderer);\n }\n\n protected override inputs(): Input[] {\n const state = sessions.getStore();\n if (!state) throw new Error(\"CachedLodLoader: a level was built outside loadAsync\");\n // three builds every level, whatever its source imports.\n const packages = [...new Set([\"three\", ...state.packages])].sort();\n return [...[...state.read].sort().map((path) => ({ path })), ...packages.map((name) => ({ package: name }))];\n }\n}\n\n/** A manager that serves root-relative paths from the public directory while a load runs. */\nfunction servingManager(): LoadingManager {\n const manager = new LoadingManager();\n manager.setURLModifier((url) => {\n if (!url.startsWith(\"/\") || url.startsWith(\"//\")) return url;\n if (!sessions.getStore()) throw new Error(`CachedLodLoader: cannot load \"${url}\" once the load that asked for it has ended`);\n return placeholderOrigin + url;\n });\n return manager;\n}\n\n/**\n * Loads `.glts` sources with a GLTSLoader made at the first load, resolving their packages as the app at `root` does,\n * and records the packages each imports.\n */\nclass ScriptLoader extends Loader<Object3D> {\n private loader: Promise<GLTSLoader> | undefined;\n\n constructor(\n manager: LoadingManager,\n private readonly root: string,\n ) {\n super(manager);\n }\n\n override async loadAsync(url: string): Promise<Object3D> {\n this.loader ??= import(\"@drawcall/glts\").then(\n ({ GLTSLoader }) => new GLTSLoader(this.manager, { resolve: (specifier) => resolveImport(specifier, this.root) }),\n );\n const scene = await (await this.loader).loadAsync(url);\n const { packages, runtimeModules } = scene.source;\n if (runtimeModules.length > 0)\n throw new Error(\n `CachedLodLoader: cannot precompute \"${url}\": it imports ${runtimeModules.join(\", \")} from a CDN at runtime, which a stored level cannot be checked against`,\n );\n const state = sessions.getStore();\n if (!state) throw new Error(`CachedLodLoader: \"${url}\" loaded outside loadAsync`);\n for (const name of packages) state.packages.add(name);\n return scene;\n }\n\n /** Free the GLTSLoader and the scenes it loaded, resolving once freed; the next load makes another. */\n async dispose(): Promise<void> {\n const loader = this.loader;\n this.loader = undefined;\n // A GLTSLoader that failed to load failed the load that made it.\n const made = await loader?.catch(() => undefined);\n made?.dispose();\n }\n}\n\nlet users = 0;\n/** What sessions install while any runs: the canvas, and what restores the globals. */\nlet installed: Promise<{ canvas: Canvas; restore: () => Promise<void> }> | undefined;\n/**\n * The restore of the last sessions to finish, which the next install waits for: @drawcall/webgpu saves and restores\n * the globals it installs per canvas, so two canvases' lives must not overlap.\n */\nlet restored: Promise<void> = Promise.resolve();\nlet current: Canvas | undefined;\n\n/** The canvas of the sessions running. */\nfunction canvas(): Canvas {\n if (!current) throw new Error(\"CachedLodLoader: baking outside loadAsync\");\n return current;\n}\n\nasync function session<T>(state: Session, run: () => Promise<T>): Promise<T> {\n users++;\n // A failed restore failed the session that ran it. Sessions running together share one install.\n const shared = (installed ??= restored.catch(() => {}).then(install));\n try {\n current = (await shared).canvas;\n return await sessions.run(state, run);\n } finally {\n if (--users === 0) {\n installed = current = undefined;\n // A failed install failed the run above.\n restored = shared.then(\n ({ restore }) => restore(),\n () => {},\n );\n await restored;\n }\n }\n}\n\n/** A canvas of @drawcall/webgpu, which installs WebGPU, image decoding and workers while it lives, and the served fetch. */\nasync function install(): Promise<{ canvas: Canvas; restore: () => Promise<void> }> {\n const canvas = await createCanvas(1, 1);\n const { fetch } = globalThis;\n globalThis.fetch = (input, init) => serve(fetch, input, init);\n const restore = async () => {\n globalThis.fetch = fetch;\n await canvas.dispose();\n };\n return { canvas, restore };\n}\n\nasync function serve(fetch: typeof globalThis.fetch, input: RequestInfo | URL, init?: RequestInit): Promise<Response> {\n const url = new URL(input instanceof Request ? input.url : input);\n const state = sessions.getStore();\n if (!state || url.protocol === \"data:\" || url.protocol === \"blob:\") return fetch(input, init);\n if (url.origin !== placeholderOrigin)\n throw new Error(`CachedLodLoader: cannot precompute a load that fetches ${url.href}, which a stored level cannot be checked against`);\n const file = publicURL(url.pathname);\n try {\n const bytes = await readFile(fileURLToPath(file));\n state.read.add(file);\n return new Response(bytes);\n } catch (error) {\n if (error instanceof Error && \"code\" in error && error.code === \"ENOENT\") return new Response(null, { status: 404 });\n throw error;\n }\n}\n"]}
1
+ {"version":3,"file":"node.js","sourceRoot":"","sources":["../../src/loader/node.ts"],"names":[],"mappings":"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","sourcesContent":["import { AsyncLocalStorage } from \"node:async_hooks\";\nimport { readFile } from \"node:fs/promises\";\nimport { fileURLToPath } from \"node:url\";\nimport { createCanvas, type Canvas } from \"@drawcall/webgpu\";\nimport type { GLTSLoader } from \"@drawcall/glts\";\nimport { resolve as resolveImport } from \"import-meta-resolve\";\nimport { Loader, LoadingManager, WebGPURenderer, type Object3D } from \"three/webgpu\";\nimport { publicURL, type Input } from \"vitecache\";\nimport { setUpBakeRenderer } from \"../bake.js\";\nimport type { LodLevel } from \"../levels/index.js\";\nimport { CachedLodLoader as BrowserCachedLodLoader, type CachedLodLoaderOptions } from \"./index.js\";\nimport type { LodRequest } from \"./request.js\";\nimport { loaderPathURL, placeholderOrigin } from \"./url.js\";\n\ninterface Session {\n /** The `file:` URLs of the public files the session's loads read. */\n read: Set<string>;\n /** The installed packages the session's .glts sources import. */\n packages: Set<string>;\n}\n\n/** The session of each load, by its async context: the served fetch is global. */\nconst sessions = new AsyncLocalStorage<Session>();\n\n/**\n * CachedLodLoader in Node, as the vitecache plugin and the glts CLI run it: `loadAsync` builds and saves every level of\n * the ladder, each with the public files the ladder's loads read and the installed versions of three and of the\n * packages its .glts sources import. A load that reads anything else, such as a URL off the public directory or a module\n * a .glts source imports from a CDN, fails, since no stored level could be checked against it. Served paths such as\n * `/trees/oak.glts`, and `file:` URLs in the public directory, as a headless .glts script's manager resolves them to,\n * load from vitecache's public directory (see `publicURL`), and `.glts` sources through a GLTSLoader of the load's own;\n * bakes draw on a WebGPU device of @drawcall/webgpu. The manager only resolves the request's URL.\n *\n * Each `loadAsync` loads and bakes with what it makes for itself, which it frees before resolving, so loads may run\n * concurrently. The levels it returns load their stored bytes as in browsers, and none holds the source: the source\n * level, and a simplified level, which draws with the source's materials, load the source again when a camera needs\n * them, as the glts CLI's renders of a .glts script that draws a Lod do.\n */\nexport class CachedLodLoader extends BrowserCachedLodLoader {\n constructor(manager?: LoadingManager, options: CachedLodLoaderOptions = {}) {\n super(manager, options);\n }\n\n override loadAsync(request: LodRequest): Promise<LodLevel[]> {\n const input = typeof request === \"string\" ? { url: request } : request;\n const url = this.publicPath(this.manager.resolveURL(loaderPathURL(input.url, this.path)));\n return session({ read: new Set(), packages: new Set() }, async () => {\n const loader = new SessionLoader(this.options);\n try {\n return await loader.loadAsync({ ...input, url });\n } finally {\n await loader.dispose();\n }\n });\n }\n\n /** Nothing to free: each load frees what it made. */\n override async dispose(): Promise<void> {}\n\n /** A `file:` URL in the public directory as its served path; others as in browsers. */\n protected override publicPath(url: string): string {\n if (!url.startsWith(\"file:\")) return super.publicPath(url);\n const base = publicURL(\"/\");\n if (!url.startsWith(base)) throw new Error(`CachedLodLoader: cannot precompute \"${url}\", which lies outside the public directory ${base}`);\n return `/${url.slice(base.length)}`;\n }\n}\n\n/** One `loadAsync` of the Node loader: builds every level, with a GLTSLoader and bake renderer of its own. */\nclass SessionLoader extends BrowserCachedLodLoader {\n protected override readonly buildsAll = true;\n private readonly scripts: ScriptLoader;\n\n constructor(options: CachedLodLoaderOptions) {\n super(servingManager(), options);\n // Packages resolve as the app's do.\n this.scripts = new ScriptLoader(this.manager, publicURL(\"/\"));\n this.manager.addHandler(/\\.glts$/i, this.scripts);\n }\n\n override async dispose(): Promise<void> {\n await super.dispose();\n await this.scripts.dispose();\n }\n\n /**\n * A path its manager served at the placeholder origin as that path, which `CachedLodLoader.loadAsync` gave below the\n * app's base: keyed as in browsers whatever document the process has, such as the one a headless GLTS renderer\n * installs, against which the placeholder origin would be foreign.\n */\n protected override publicPath(url: string): string {\n return url.startsWith(`${placeholderOrigin}/`) ? url.slice(placeholderOrigin.length) : super.publicPath(url);\n }\n\n /** Load `url` in the load's session, or, once `loadAsync` has resolved, in a session of its own. */\n protected override loadAsset(url: string): Promise<Object3D> {\n if (sessions.getStore()) return super.loadAsset(url);\n return session({ read: new Set(), packages: new Set() }, () => super.loadAsset(url));\n }\n\n protected override async newBakeRenderer(): Promise<WebGPURenderer> {\n // Three's DOM canvas types exclude native canvases; its constructor checks the actual API.\n const renderer: unknown = Reflect.construct(WebGPURenderer, [{ canvas: await canvas(), antialias: false }]);\n if (!(renderer instanceof WebGPURenderer)) throw new Error(\"CachedLodLoader: three did not make a WebGPURenderer\");\n return setUpBakeRenderer(renderer);\n }\n\n protected override inputs(): Input[] {\n const state = sessions.getStore();\n if (!state) throw new Error(\"CachedLodLoader: a level was built outside loadAsync\");\n // three builds every level, whatever its source imports.\n const packages = [...new Set([\"three\", ...state.packages])].sort();\n return [...[...state.read].sort().map((path) => ({ path })), ...packages.map((name) => ({ package: name }))];\n }\n}\n\n/** A manager that serves root-relative paths from the public directory while a load runs. */\nfunction servingManager(): LoadingManager {\n const manager = new LoadingManager();\n manager.setURLModifier((url) => {\n if (!url.startsWith(\"/\") || url.startsWith(\"//\")) return url;\n if (!sessions.getStore()) throw new Error(`CachedLodLoader: cannot load \"${url}\" once the load that asked for it has ended`);\n return placeholderOrigin + url;\n });\n return manager;\n}\n\n/**\n * Loads `.glts` sources with a GLTSLoader made at the first load, resolving their packages as the app at `root` does,\n * and records the packages each imports.\n */\nclass ScriptLoader extends Loader<Object3D> {\n private loader: Promise<GLTSLoader> | undefined;\n\n constructor(\n manager: LoadingManager,\n private readonly root: string,\n ) {\n super(manager);\n }\n\n override async loadAsync(url: string): Promise<Object3D> {\n this.loader ??= import(\"@drawcall/glts\").then(\n ({ GLTSLoader }) => new GLTSLoader(this.manager, { resolve: (specifier) => resolveImport(specifier, this.root) }),\n );\n const scene = await (await this.loader).loadAsync(url);\n const { packages, runtimeModules } = scene.source;\n if (runtimeModules.length > 0)\n throw new Error(\n `CachedLodLoader: cannot precompute \"${url}\": it imports ${runtimeModules.join(\", \")} from a CDN at runtime, which a stored level cannot be checked against`,\n );\n const state = sessions.getStore();\n if (!state) throw new Error(`CachedLodLoader: \"${url}\" loaded outside loadAsync`);\n for (const name of packages) state.packages.add(name);\n return scene;\n }\n\n /** Free the GLTSLoader and the scenes it loaded, resolving once freed; the next load makes another. */\n async dispose(): Promise<void> {\n const loader = this.loader;\n this.loader = undefined;\n // A GLTSLoader that failed to load failed the load that made it.\n const made = await loader?.catch(() => undefined);\n made?.dispose();\n }\n}\n\nlet users = 0;\n/** What sessions install while any runs: the served fetch, and the canvas once one bakes. */\nlet installed: Promise<Install> | undefined;\n/**\n * The restore of the last sessions to finish, which the next install waits for: @drawcall/webgpu saves and restores\n * the globals it installs per canvas, so two canvases' lives must not overlap.\n */\nlet restored: Promise<void> = Promise.resolve();\nlet current: Install | undefined;\n\ninterface Install {\n /**\n * The canvas bakes draw on, made by the first bake, so loads that only read stored levels or load sources leave the\n * process's globals alone, such as those of a glts host whose @drawcall/webgpu is another copy.\n */\n canvas?: Promise<Canvas>;\n restore: () => Promise<void>;\n}\n\n/** The canvas of the sessions running. */\nfunction canvas(): Promise<Canvas> {\n if (!current) throw new Error(\"CachedLodLoader: baking outside loadAsync\");\n return (current.canvas ??= createCanvas(1, 1));\n}\n\nasync function session<T>(state: Session, run: () => Promise<T>): Promise<T> {\n users++;\n // A failed restore failed the session that ran it. Sessions running together share one install.\n const shared = (installed ??= restored.catch(() => {}).then(install));\n try {\n current = await shared;\n return await sessions.run(state, run);\n } finally {\n if (--users === 0) {\n installed = current = undefined;\n // A failed install failed the run above.\n restored = shared.then(\n ({ restore }) => restore(),\n () => {},\n );\n await restored;\n }\n }\n}\n\n/**\n * The served fetch, and a canvas of @drawcall/webgpu once a session bakes, which installs WebGPU, image decoding and\n * workers while it lives.\n */\nfunction install(): Install {\n const { fetch } = globalThis;\n globalThis.fetch = (input, init) => serve(fetch, input, init);\n const made: Install = {\n restore: async () => {\n globalThis.fetch = fetch;\n // A canvas that failed to make failed the bake that made it.\n const canvas = await made.canvas?.catch(() => undefined);\n await canvas?.dispose();\n },\n };\n return made;\n}\n\nasync function serve(fetch: typeof globalThis.fetch, input: RequestInfo | URL, init?: RequestInit): Promise<Response> {\n const url = new URL(input instanceof Request ? input.url : input);\n const state = sessions.getStore();\n if (!state || url.protocol === \"data:\" || url.protocol === \"blob:\") return fetch(input, init);\n if (url.origin !== placeholderOrigin)\n throw new Error(`CachedLodLoader: cannot precompute a load that fetches ${url.href}, which a stored level cannot be checked against`);\n const file = publicURL(url.pathname);\n try {\n const bytes = await readFile(fileURLToPath(file));\n state.read.add(file);\n return new Response(bytes);\n } catch (error) {\n if (error instanceof Error && \"code\" in error && error.code === \"ENOENT\") return new Response(null, { status: 404 });\n throw error;\n }\n}\n"]}
@@ -1,5 +1,8 @@
1
1
  /**
2
- * Whether this runs in Node, as the vitecache plugin builds levels there. Work there stays on the calling thread: the
3
- * Worker @drawcall/webgpu installs runs no module workers and ends with its canvas, which a later load outlives.
2
+ * Whether this runs in Node, as the vitecache plugin builds levels there. The Worker @drawcall/webgpu installs there
3
+ * runs no module workers and ends with its canvas, which a later load outlives: work runs in Node's worker threads or
4
+ * on the calling thread.
4
5
  */
5
6
  export declare const inNode: boolean;
7
+ /** A module Node has and browsers do not, imported by a name bundlers do not see, so that they keep it out of pages. */
8
+ export declare function nodeOnly<T>(name: string): Promise<T>;
@@ -1,6 +1,11 @@
1
1
  /**
2
- * Whether this runs in Node, as the vitecache plugin builds levels there. Work there stays on the calling thread: the
3
- * Worker @drawcall/webgpu installs runs no module workers and ends with its canvas, which a later load outlives.
2
+ * Whether this runs in Node, as the vitecache plugin builds levels there. The Worker @drawcall/webgpu installs there
3
+ * runs no module workers and ends with its canvas, which a later load outlives: work runs in Node's worker threads or
4
+ * on the calling thread.
4
5
  */
5
6
  export const inNode = typeof process !== "undefined" && process.versions?.node !== undefined;
7
+ /** A module Node has and browsers do not, imported by a name bundlers do not see, so that they keep it out of pages. */
8
+ export function nodeOnly(name) {
9
+ return import(/* @vite-ignore */ name);
10
+ }
6
11
  //# sourceMappingURL=node-process.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"node-process.js","sourceRoot":"","sources":["../src/node-process.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,MAAM,CAAC,MAAM,MAAM,GAAG,OAAO,OAAO,KAAK,WAAW,IAAI,OAAO,CAAC,QAAQ,EAAE,IAAI,KAAK,SAAS,CAAC","sourcesContent":["/**\n * Whether this runs in Node, as the vitecache plugin builds levels there. Work there stays on the calling thread: the\n * Worker @drawcall/webgpu installs runs no module workers and ends with its canvas, which a later load outlives.\n */\nexport const inNode = typeof process !== \"undefined\" && process.versions?.node !== undefined;\n"]}
1
+ {"version":3,"file":"node-process.js","sourceRoot":"","sources":["../src/node-process.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,MAAM,CAAC,MAAM,MAAM,GAAG,OAAO,OAAO,KAAK,WAAW,IAAI,OAAO,CAAC,QAAQ,EAAE,IAAI,KAAK,SAAS,CAAC;AAE7F,wHAAwH;AACxH,MAAM,UAAU,QAAQ,CAAI,IAAY;IACtC,OAAO,MAAM,CAAC,kBAAkB,CAAC,IAAI,CAAC,CAAC;AACzC,CAAC","sourcesContent":["/**\n * Whether this runs in Node, as the vitecache plugin builds levels there. The Worker @drawcall/webgpu installs there\n * runs no module workers and ends with its canvas, which a later load outlives: work runs in Node's worker threads or\n * on the calling thread.\n */\nexport const inNode = typeof process !== \"undefined\" && process.versions?.node !== undefined;\n\n/** A module Node has and browsers do not, imported by a name bundlers do not see, so that they keep it out of pages. */\nexport function nodeOnly<T>(name: string): Promise<T> {\n return import(/* @vite-ignore */ name);\n}\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@drawcall/lod",
3
- "version": "0.5.1",
3
+ "version": "0.5.3",
4
4
  "type": "module",
5
5
  "description": "Level of detail for three.js: octahedral impostors, simplified and baked meshes, with dithered switching by on-screen size. WebGPURenderer only.",
6
6
  "keywords": [
@@ -39,7 +39,7 @@
39
39
  },
40
40
  "peerDependencies": {
41
41
  "@drawcall/glts": "^6.0.0",
42
- "three": ">=0.186.0",
42
+ "three": ">=0.186.0 <0.187.0",
43
43
  "typegpu": "^0.12.9",
44
44
  "vitecache": "^0.5.0"
45
45
  },