@woosh/meep-engine 2.73.0 → 2.75.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/build/bundle-worker-image-decoder.js +1 -1
- package/build/meep.cjs +185 -189
- package/build/meep.min.js +1 -1
- package/build/meep.module.js +185 -189
- package/package.json +1 -1
- package/src/core/binary/UINT32_MAX.js +5 -0
- package/src/core/bvh2/bvh3/BVH.js +44 -2
- package/src/core/bvh2/bvh3/BVH.spec.js +45 -0
- package/src/core/bvh2/bvh3/build_triangle_morton_codes.js +73 -0
- package/src/core/bvh2/bvh3/ebvh_build_for_geometry_morton.js +5 -101
- package/src/core/bvh2/bvh3/ebvh_build_hierarchy.js +59 -0
- package/src/core/bvh2/bvh3/query/bvh_query_user_data_nearest_to_point.js +31 -32
- package/src/core/bvh2/bvh3/query/bvh_query_user_data_nearest_to_point.spec.js +64 -0
- package/src/core/collection/SCRATCH_UINT32_TRAVERSAL_STACK.js +1 -0
- package/src/core/geom/3d/aabb/aabb3_signed_distance_sqr_to_point.js +1 -0
- package/src/core/geom/3d/aabb/aabb3_unsigned_distance_sqr_to_point.js +36 -0
- package/src/core/process/worker/OnDemandWorkerManager.js +5 -1
- package/src/engine/asset/loaders/ArrayBufferLoader.js +13 -15
- package/src/engine/asset/loaders/image/ImageDecoderWorker.js +1 -1
- package/src/engine/asset/loaders/image/ImageRGBADataLoader.js +5 -7
- package/src/engine/asset/loaders/image/codec/NativeImageDecoder.js +2 -1
- package/src/engine/asset/loaders/image/codec/ThreadedImageDecoder.js +5 -6
- package/src/engine/asset/loaders/image/png/PNG.js +339 -332
- package/src/engine/asset/loaders/image/png/PNGReader.js +77 -30
- package/src/engine/asset/loaders/image/png/prototypePNG.js +13 -4
- package/src/engine/graphics/generate_halton_jitter.js +21 -0
- package/src/engine/graphics/render/buffer/simple-fx/taa/TemporalSupersamplingRenderPlugin.js +3 -20
- package/src/engine/graphics/texture/virtual/v2/PageTexture.js +335 -0
- package/src/engine/graphics/texture/virtual/v2/ResidentTileTexture.js +46 -0
- package/src/engine/graphics/texture/virtual/v2/{TileLoader.js → VirtualTextureTileLoader.js} +33 -13
- package/src/engine/graphics/texture/virtual/v2/{UsageMetadata.js → VirtualTextureUsage.js} +68 -22
- package/src/engine/graphics/texture/virtual/v2/{VirtualTextureManager.js → VirtualTextureUsageUpdater.js} +66 -9
- package/src/engine/graphics/texture/virtual/v2/debug/ResidencyDebugView.js +70 -0
- package/src/engine/graphics/texture/virtual/v2/debug/UsageDebugView.js +63 -0
- package/src/engine/graphics/texture/virtual/v2/{UsagePyramidDebugView.js → debug/UsagePyramidDebugView.js} +31 -40
- package/src/engine/graphics/texture/virtual/v2/prototype.js +98 -54
- package/src/engine/graphics/texture/virtual/v2/{TextureTile.js → tile/VirtualTextureTile.js} +3 -3
- package/src/engine/graphics/texture/virtual/v2/tile/compose_finger_print.js +23 -0
- package/src/engine/graphics/texture/virtual/v2/tile/compose_tile_address.js +26 -0
- package/src/engine/graphics/texture/virtual/v2/tile/decompose_finger_print.js +12 -0
- package/src/engine/graphics/texture/virtual/v2/tile/finger_print_to_tile_address.js +16 -0
- package/src/engine/graphics/texture/virtual/v2/{tile_index_to_finger_print.js → tile/tile_address_to_finger_print.js} +7 -3
- package/src/view/CSS_ABSOLUTE_POSITIONING.js +5 -0
- package/src/engine/graphics/texture/virtual/v2/SparseTexture.js +0 -182
- package/src/engine/graphics/texture/virtual/v2/UsageDebugView.js +0 -64
- package/src/engine/graphics/texture/virtual/v2/compose_finger_print.js +0 -13
- package/src/engine/graphics/texture/virtual/v2/finger_print_to_tile_index.js +0 -37
- /package/src/engine/graphics/texture/virtual/v2/{ShaderUsage.js → VirtualTextureUsageShader.js} +0 -0
|
@@ -2,13 +2,13 @@
|
|
|
2
2
|
"use strict";
|
|
3
3
|
|
|
4
4
|
import zlib from 'pako';
|
|
5
|
-
|
|
6
|
-
import { PNG } from './PNG.js';
|
|
7
5
|
import { BinaryBuffer } from "../../../../../core/binary/BinaryBuffer.js";
|
|
8
6
|
import { isArrayEqualStrict } from "../../../../../core/collection/array/isArrayEqualStrict.js";
|
|
9
|
-
import { PNG_HEADER_BYTES } from "./PNG_HEADER_BYTES.js";
|
|
10
7
|
import { crc } from "./crc.js";
|
|
11
8
|
|
|
9
|
+
import { PNG } from './PNG.js';
|
|
10
|
+
import { PNG_HEADER_BYTES } from "./PNG_HEADER_BYTES.js";
|
|
11
|
+
|
|
12
12
|
/**
|
|
13
13
|
*
|
|
14
14
|
* @param {Uint8Array} buffer
|
|
@@ -140,6 +140,8 @@ PNGReader.prototype.decodeChunk = function () {
|
|
|
140
140
|
|
|
141
141
|
}
|
|
142
142
|
|
|
143
|
+
// console.log(`chunk: ${type}`);
|
|
144
|
+
|
|
143
145
|
|
|
144
146
|
switch (type) {
|
|
145
147
|
case 'IHDR':
|
|
@@ -157,8 +159,11 @@ PNGReader.prototype.decodeChunk = function () {
|
|
|
157
159
|
case 'IEND':
|
|
158
160
|
this.decodeIEND(chunk);
|
|
159
161
|
break;
|
|
162
|
+
case 'sRGB':
|
|
163
|
+
this.decodesRGB(chunk);
|
|
164
|
+
break;
|
|
160
165
|
default:
|
|
161
|
-
|
|
166
|
+
console.warn(`Unsupported block ${type}`);
|
|
162
167
|
break;
|
|
163
168
|
}
|
|
164
169
|
|
|
@@ -166,6 +171,19 @@ PNGReader.prototype.decodeChunk = function () {
|
|
|
166
171
|
|
|
167
172
|
};
|
|
168
173
|
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
* https://www.w3.org/TR/2003/REC-PNG-20031110/#11sRGB
|
|
177
|
+
* @param {Uint8Array} chunk
|
|
178
|
+
*/
|
|
179
|
+
PNGReader.prototype.decodesRGB = function (chunk) {
|
|
180
|
+
|
|
181
|
+
const rendering_intent = readUInt8(chunk, 0);
|
|
182
|
+
|
|
183
|
+
// TODO add metadata to the PNG representation
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
|
|
169
187
|
/**
|
|
170
188
|
* https://www.w3.org/TR/PNG/#11tEXt
|
|
171
189
|
* @param {Uint8Array} chunk
|
|
@@ -217,7 +235,7 @@ PNGReader.prototype.decodeiEXt = function (chunk) {
|
|
|
217
235
|
* Interlace method 1 byte
|
|
218
236
|
*/
|
|
219
237
|
PNGReader.prototype.decodeIHDR = function (chunk) {
|
|
220
|
-
|
|
238
|
+
const png = this.png;
|
|
221
239
|
|
|
222
240
|
png.setWidth(readUInt32(chunk, 0));
|
|
223
241
|
png.setHeight(readUInt32(chunk, 4));
|
|
@@ -263,7 +281,7 @@ PNGReader.prototype.decodeIEND = function () {
|
|
|
263
281
|
* Uncompress IDAT chunks
|
|
264
282
|
*/
|
|
265
283
|
PNGReader.prototype.decodePixels = function () {
|
|
266
|
-
|
|
284
|
+
const png = this.png;
|
|
267
285
|
|
|
268
286
|
const inflator = new zlib.Inflate();
|
|
269
287
|
|
|
@@ -292,21 +310,27 @@ PNGReader.prototype.decodePixels = function () {
|
|
|
292
310
|
|
|
293
311
|
PNGReader.prototype.interlaceNone = function (data) {
|
|
294
312
|
|
|
295
|
-
|
|
313
|
+
const png = this.png;
|
|
314
|
+
|
|
315
|
+
const depth = png.bitDepth;
|
|
316
|
+
const bits_per_line = png.colors * depth;
|
|
317
|
+
const bytes_per_pixel = bits_per_line / 8;
|
|
296
318
|
|
|
297
|
-
|
|
298
|
-
|
|
319
|
+
const width = png.width;
|
|
320
|
+
const height = png.height;
|
|
299
321
|
|
|
300
322
|
// color bytes per row
|
|
301
|
-
const color_bytes_per_row = bytes_per_pixel *
|
|
323
|
+
const color_bytes_per_row = Math.ceil(bytes_per_pixel * width);
|
|
324
|
+
|
|
325
|
+
const output_bytes_per_row = Math.ceil(bytes_per_pixel) * width;
|
|
302
326
|
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
327
|
+
const pixels = new Uint8Array(output_bytes_per_row * height);
|
|
328
|
+
|
|
329
|
+
let offset = 0;
|
|
306
330
|
|
|
307
331
|
const data_length = data.length;
|
|
308
332
|
|
|
309
|
-
for (
|
|
333
|
+
for (let i = 0; i < data_length; i += color_bytes_per_row + 1) {
|
|
310
334
|
|
|
311
335
|
const scanline_address = i + 1;
|
|
312
336
|
|
|
@@ -316,7 +340,38 @@ PNGReader.prototype.interlaceNone = function (data) {
|
|
|
316
340
|
|
|
317
341
|
switch (header) {
|
|
318
342
|
case 0:
|
|
319
|
-
|
|
343
|
+
// NONE
|
|
344
|
+
if (depth === 1) {
|
|
345
|
+
for (let x = 0; x < output_bytes_per_row; x++) {
|
|
346
|
+
const q = x >>> 4;
|
|
347
|
+
const datum = data[q + scanline_address];
|
|
348
|
+
const shift = ((x) & 0x7);
|
|
349
|
+
const out_value = (datum >>> shift) & 0x1;
|
|
350
|
+
pixels[offset + x] = out_value;
|
|
351
|
+
}
|
|
352
|
+
} else if (depth === 2) {
|
|
353
|
+
for (let x = 0; x < output_bytes_per_row; x++) {
|
|
354
|
+
const q = x >>> 2;
|
|
355
|
+
const datum = data[q + scanline_address];
|
|
356
|
+
const shift = ((~x) & 0x3) << 1;
|
|
357
|
+
const out_value = (datum >>> shift) & 0x3;
|
|
358
|
+
pixels[offset + x] = out_value;
|
|
359
|
+
}
|
|
360
|
+
} else if (depth === 4) {
|
|
361
|
+
for (let x = 0; x < output_bytes_per_row; x++) {
|
|
362
|
+
const q = x >>> 1;
|
|
363
|
+
const datum = data[q + scanline_address];
|
|
364
|
+
const shift = ((~x) & 0x1) << 2;
|
|
365
|
+
const out_value = (datum >>> shift) & 0xF;
|
|
366
|
+
pixels[offset + x] = out_value;
|
|
367
|
+
}
|
|
368
|
+
} else if (depth === 8) {
|
|
369
|
+
for (let x = 0; x < output_bytes_per_row; x++) {
|
|
370
|
+
pixels[offset + x] = data[x + scanline_address];
|
|
371
|
+
}
|
|
372
|
+
} else {
|
|
373
|
+
throw new Error(`unsupported bit depth ${depth}`)
|
|
374
|
+
}
|
|
320
375
|
break;
|
|
321
376
|
case 1:
|
|
322
377
|
this.unFilterSub(data, scanline_address, pixels, bytes_per_pixel, offset, color_bytes_per_row);
|
|
@@ -334,7 +389,7 @@ PNGReader.prototype.interlaceNone = function (data) {
|
|
|
334
389
|
throw new Error(`unknown filtered scanline type '${header}'`);
|
|
335
390
|
}
|
|
336
391
|
|
|
337
|
-
offset +=
|
|
392
|
+
offset += output_bytes_per_row;
|
|
338
393
|
|
|
339
394
|
}
|
|
340
395
|
|
|
@@ -352,14 +407,6 @@ PNGReader.prototype.interlaceAdam7 = function (data) {
|
|
|
352
407
|
|
|
353
408
|
// Unfiltering
|
|
354
409
|
|
|
355
|
-
/**
|
|
356
|
-
* No filtering, direct copy
|
|
357
|
-
*/
|
|
358
|
-
PNGReader.prototype.unFilterNone = function (scanline, scanline_offset, pixels, bpp, of, length) {
|
|
359
|
-
for (let i = 0; i < length; i++) {
|
|
360
|
-
pixels[of + i] = scanline[i + scanline_offset];
|
|
361
|
-
}
|
|
362
|
-
};
|
|
363
410
|
|
|
364
411
|
/**
|
|
365
412
|
* The Sub() filter transmits the difference between each byte and the value
|
|
@@ -367,7 +414,7 @@ PNGReader.prototype.unFilterNone = function (scanline, scanline_offset, pixels,
|
|
|
367
414
|
* Sub(x) = Raw(x) + Raw(x - bpp)
|
|
368
415
|
*/
|
|
369
416
|
PNGReader.prototype.unFilterSub = function (scanline, scanline_offset, pixels, bpp, of, length) {
|
|
370
|
-
|
|
417
|
+
let i = 0;
|
|
371
418
|
|
|
372
419
|
for (; i < bpp; i++) {
|
|
373
420
|
pixels[of + i] = scanline[i + scanline_offset];
|
|
@@ -387,7 +434,7 @@ PNGReader.prototype.unFilterSub = function (scanline, scanline_offset, pixels, b
|
|
|
387
434
|
* Up(x) = Raw(x) + Prior(x)
|
|
388
435
|
*/
|
|
389
436
|
PNGReader.prototype.unFilterUp = function (scanline, scanline_offset, pixels, bpp, of, length) {
|
|
390
|
-
|
|
437
|
+
let i = 0, byte, prev;
|
|
391
438
|
// Prior(x) is 0 for all x on the first scanline
|
|
392
439
|
if ((of - length) < 0) for (; i < length; i++) {
|
|
393
440
|
pixels[of + i] = scanline[i + scanline_offset];
|
|
@@ -406,7 +453,7 @@ PNGReader.prototype.unFilterUp = function (scanline, scanline_offset, pixels, bp
|
|
|
406
453
|
* Average(x) = Raw(x) + floor((Raw(x-bpp)+Prior(x))/2)
|
|
407
454
|
*/
|
|
408
455
|
PNGReader.prototype.unFilterAverage = function (scanline, scanline_offset, pixels, bpp, of, length) {
|
|
409
|
-
|
|
456
|
+
let i = 0, byte, prev, prior;
|
|
410
457
|
if ((of - length) < 0) {
|
|
411
458
|
// Prior(x) == 0 && Raw(x - bpp) == 0
|
|
412
459
|
for (; i < bpp; i++) {
|
|
@@ -460,7 +507,7 @@ PNGReader.prototype.unFilterAverage = function (scanline, scanline_offset, pixel
|
|
|
460
507
|
* end
|
|
461
508
|
*/
|
|
462
509
|
PNGReader.prototype.unFilterPaeth = function (scanline, scanline_offset, pixels, bpp, of, length) {
|
|
463
|
-
|
|
510
|
+
let i = 0, raw, a, b, c, p, pa, pb, pc, pr;
|
|
464
511
|
if ((of - length) < 0) {
|
|
465
512
|
// Prior(x) == 0 && Raw(x - bpp) == 0
|
|
466
513
|
for (; i < bpp; i++) {
|
|
@@ -525,7 +572,7 @@ PNGReader.prototype.parse = function () {
|
|
|
525
572
|
this.decodeHeader();
|
|
526
573
|
|
|
527
574
|
while (this.buffer.position < this.buffer.length) {
|
|
528
|
-
|
|
575
|
+
const type = this.decodeChunk();
|
|
529
576
|
// stop after IHDR chunk, or after IEND
|
|
530
577
|
// if (type === 'IHDR' && options.data === false || type === 'IEND') break;
|
|
531
578
|
if (type === 'IEND') {
|
|
@@ -533,7 +580,7 @@ PNGReader.prototype.parse = function () {
|
|
|
533
580
|
}
|
|
534
581
|
}
|
|
535
582
|
|
|
536
|
-
|
|
583
|
+
const png = this.png;
|
|
537
584
|
|
|
538
585
|
this.decodePixels();
|
|
539
586
|
|
|
@@ -1,13 +1,22 @@
|
|
|
1
|
+
import { Sampler2D } from "../../../../graphics/texture/sampler/Sampler2D.js";
|
|
1
2
|
import sampler2D2Canvas from "../../../../graphics/texture/sampler/Sampler2D2Canvas.js";
|
|
3
|
+
import { AssetRequestScope } from "../../../AssetRequestScope.js";
|
|
2
4
|
import { ArrayBufferLoader } from "../../ArrayBufferLoader.js";
|
|
3
5
|
import { PNGReader } from "./PNGReader.js";
|
|
4
|
-
import { Sampler2D } from "../../../../graphics/texture/sampler/Sampler2D.js";
|
|
5
6
|
|
|
6
7
|
const array_loader = new ArrayBufferLoader();
|
|
7
8
|
|
|
8
9
|
|
|
10
|
+
// const path = 'moicon/ISO 7010 - Safety Signs (3)/ISO 7010 - Safety Signs/Emergency/400px/E001 – Emergency exit (left hand).png';
|
|
11
|
+
// const path = 'data/textures/utility/vt/uv_map_reference/5-2-1.png';
|
|
12
|
+
// const path = 'data/textures/utility/vt/uv_map_reference/6-62-60.png';
|
|
13
|
+
// const path = 'data/textures/utility/vt/uv_map_reference/5-11-5.png';
|
|
14
|
+
// const path = 'data/textures/utility/vt/uv_map_reference/1-0-0.png';
|
|
15
|
+
// const path = 'data/textures/utility/vt/uv_map_reference/5-3-0.png';
|
|
16
|
+
// const path = 'data/textures/utility/vt/uv_map_reference/5-21-14.png';
|
|
17
|
+
const path = 'data/textures/utility/vt/uv_map_reference/6-39-33.png';
|
|
9
18
|
|
|
10
|
-
array_loader.load(
|
|
19
|
+
array_loader.load(new AssetRequestScope(), path, (asset) => {
|
|
11
20
|
|
|
12
21
|
const array = asset.create();
|
|
13
22
|
|
|
@@ -19,9 +28,9 @@ array_loader.load('moicon/ISO 7010 - Safety Signs (3)/ISO 7010 - Safety Signs/Em
|
|
|
19
28
|
|
|
20
29
|
const uint8Data = png.getUint8Data();
|
|
21
30
|
|
|
22
|
-
const sampler = new Sampler2D(uint8Data.data,uint8Data.itemSize,png.getWidth(), png.getHeight());
|
|
31
|
+
const sampler = new Sampler2D(uint8Data.data, uint8Data.itemSize, png.getWidth(), png.getHeight());
|
|
23
32
|
|
|
24
|
-
const canvas = sampler2D2Canvas(sampler,1,0);
|
|
33
|
+
const canvas = sampler2D2Canvas(sampler, 1, 0);
|
|
25
34
|
|
|
26
35
|
document.body.appendChild(canvas);
|
|
27
36
|
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { assert } from "../../core/assert.js";
|
|
2
|
+
import { halton_sequence } from "../../core/math/statistics/halton_sequence.js";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Generates a number of 2d jitter offsets in range -1...1
|
|
6
|
+
* @param {number} point_count
|
|
7
|
+
* @return {Float32Array}
|
|
8
|
+
*/
|
|
9
|
+
export function generate_halton_jitter(point_count) {
|
|
10
|
+
assert.isNonNegativeInteger(point_count, 'point_count');
|
|
11
|
+
|
|
12
|
+
const result = new Float32Array(point_count * 2);
|
|
13
|
+
|
|
14
|
+
for (let i = 0; i < point_count; i++) {
|
|
15
|
+
const i2 = i * 2;
|
|
16
|
+
result[i2] = halton_sequence(2, i) * 2 - 1;
|
|
17
|
+
result[i2 + 1] = halton_sequence(3, i) * 2 - 1;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
return result;
|
|
21
|
+
}
|
package/src/engine/graphics/render/buffer/simple-fx/taa/TemporalSupersamplingRenderPlugin.js
CHANGED
|
@@ -1,23 +1,6 @@
|
|
|
1
|
-
import { EnginePlugin } from "../../../../../plugin/EnginePlugin.js";
|
|
2
|
-
import { halton_sequence } from "../../../../../../core/math/statistics/halton_sequence.js";
|
|
3
1
|
import { array_copy } from "../../../../../../core/collection/array/array_copy.js";
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
* Generates a number of 2d jitter offsets in range -1...1
|
|
7
|
-
* @param {number} point_count
|
|
8
|
-
* @return {Float32Array}
|
|
9
|
-
*/
|
|
10
|
-
function generate_jitter(point_count) {
|
|
11
|
-
const result = new Float32Array(point_count * 2);
|
|
12
|
-
|
|
13
|
-
for (let i = 0; i < point_count; i++) {
|
|
14
|
-
const i2 = i * 2;
|
|
15
|
-
result[i2] = halton_sequence(2, i) * 2 - 1;
|
|
16
|
-
result[i2 + 1] = halton_sequence(3, i) * 2 - 1;
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
return result;
|
|
20
|
-
}
|
|
2
|
+
import { EnginePlugin } from "../../../../../plugin/EnginePlugin.js";
|
|
3
|
+
import { generate_halton_jitter } from "../../../../generate_halton_jitter.js";
|
|
21
4
|
|
|
22
5
|
export class TemporalSupersamplingRenderPlugin extends EnginePlugin {
|
|
23
6
|
constructor() {
|
|
@@ -31,7 +14,7 @@ export class TemporalSupersamplingRenderPlugin extends EnginePlugin {
|
|
|
31
14
|
this.__frame_index = 0;
|
|
32
15
|
|
|
33
16
|
this.__sample_count = 16;
|
|
34
|
-
this.__jitter_offset =
|
|
17
|
+
this.__jitter_offset = generate_halton_jitter(this.__sample_count);
|
|
35
18
|
|
|
36
19
|
|
|
37
20
|
this.__projection_modifier = {
|
|
@@ -0,0 +1,335 @@
|
|
|
1
|
+
import { WebGLRenderTarget } from "three";
|
|
2
|
+
import { BitSet } from "../../../../../core/binary/BitSet.js";
|
|
3
|
+
import { Cache } from "../../../../../core/cache/Cache.js";
|
|
4
|
+
import { array_copy } from "../../../../../core/collection/array/array_copy.js";
|
|
5
|
+
import { HashMap } from "../../../../../core/collection/map/HashMap.js";
|
|
6
|
+
import { passThrough, strictEquals } from "../../../../../core/function/Functions.js";
|
|
7
|
+
import { max2 } from "../../../../../core/math/max2.js";
|
|
8
|
+
import { min2 } from "../../../../../core/math/min2.js";
|
|
9
|
+
import { tile_address_to_finger_print } from "./tile/tile_address_to_finger_print.js";
|
|
10
|
+
import { VirtualTextureTileLoader } from "./VirtualTextureTileLoader.js";
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* How much extra data to store in cache (in bytes)
|
|
14
|
+
* @type {number}
|
|
15
|
+
*/
|
|
16
|
+
const DEFAULT_CACHE_SIZE = 16 * 1024 * 1024;
|
|
17
|
+
|
|
18
|
+
export class PageTexture {
|
|
19
|
+
#page_texture = new WebGLRenderTarget(1, 1);
|
|
20
|
+
|
|
21
|
+
#page_texture_size = [1, 1];
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Used internally to track number of times `update` method was called
|
|
25
|
+
* @type {number}
|
|
26
|
+
*/
|
|
27
|
+
update_count = 0;
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Track updates to page content, whenever a tile is made resident or is removed - this number goes up by 1
|
|
31
|
+
* @type {number}
|
|
32
|
+
*/
|
|
33
|
+
version = 0;
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Maximum number of new page assignments per single update cycle
|
|
37
|
+
* This helps prevent thrashing and keep performance overhead of updating pages predictable
|
|
38
|
+
* @type {number}
|
|
39
|
+
*/
|
|
40
|
+
#cycle_assignment_limit = 2;
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
get page_texture_resolution() {
|
|
44
|
+
return this.#page_texture_size;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
*
|
|
49
|
+
* @type {VirtualTextureTile[]}
|
|
50
|
+
*/
|
|
51
|
+
#resident_tiles = [];
|
|
52
|
+
|
|
53
|
+
get resident_tiles() {
|
|
54
|
+
return this.#resident_tiles;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
*
|
|
59
|
+
* @type {HashMap<number, VirtualTextureTile>}
|
|
60
|
+
*/
|
|
61
|
+
#resident_tile_lookup = new HashMap({
|
|
62
|
+
keyHashFunction: passThrough,
|
|
63
|
+
keyEqualityFunction: strictEquals,
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
#page_slot_occupancy = new BitSet();
|
|
67
|
+
|
|
68
|
+
#tile_resolution = 128;
|
|
69
|
+
|
|
70
|
+
get tile_resolution() {
|
|
71
|
+
return this.#tile_resolution;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
#residency_tile_capacity = 0;
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Must be >=0
|
|
78
|
+
* When 0 - tiles that would not fit onto the page will not be loaded
|
|
79
|
+
* When >0 - same fraction of extra tiles will be fetching and put into cache, resulting in prefetching
|
|
80
|
+
* @type {number}
|
|
81
|
+
*/
|
|
82
|
+
#prefetch_factor = 0.33;
|
|
83
|
+
|
|
84
|
+
#loader = new VirtualTextureTileLoader();
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
*
|
|
88
|
+
* @type {AssetManager|null}
|
|
89
|
+
*/
|
|
90
|
+
#asset_manager = null;
|
|
91
|
+
|
|
92
|
+
constructor() {
|
|
93
|
+
this.#loader.on.loaded.add(this.#handle_tile_loaded, this);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
*
|
|
98
|
+
* @param {VirtualTextureTile} tile
|
|
99
|
+
*/
|
|
100
|
+
#handle_tile_loaded(tile) {
|
|
101
|
+
const finderPrint = tile.finder_print;
|
|
102
|
+
|
|
103
|
+
if (this.#tile_cache.has(finderPrint)) {
|
|
104
|
+
// already loaded
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
this.#tile_cache.put(finderPrint, tile);
|
|
109
|
+
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
*
|
|
114
|
+
* @param {string} v
|
|
115
|
+
*/
|
|
116
|
+
set path(v) {
|
|
117
|
+
this.#loader.path = v;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
set asset_manager(v) {
|
|
121
|
+
this.#asset_manager = v
|
|
122
|
+
|
|
123
|
+
this.#loader.asset_manager = v;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
#update_residency_capacity() {
|
|
127
|
+
const tile_resolution = this.#tile_resolution;
|
|
128
|
+
const size = this.#page_texture_size;
|
|
129
|
+
|
|
130
|
+
const x = size[0] / tile_resolution;
|
|
131
|
+
const y = size[1] / tile_resolution;
|
|
132
|
+
|
|
133
|
+
this.#residency_tile_capacity = x * y;
|
|
134
|
+
|
|
135
|
+
// Limit the queue to be able to load no more than an entire page, trying to load more would be wasteful as we wouldn't be able to use that data immediately anyway
|
|
136
|
+
this.#loader.queue_limit = max2(1, this.#residency_tile_capacity);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
*
|
|
142
|
+
* @param {number} v
|
|
143
|
+
*/
|
|
144
|
+
set tile_resolution(v) {
|
|
145
|
+
this.#tile_resolution = v;
|
|
146
|
+
|
|
147
|
+
this.#update_residency_capacity();
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* Tiles that are not currently resident live here
|
|
152
|
+
* key is the tile's "fingerprint"
|
|
153
|
+
* @type {Cache<number,VirtualTextureTile>}
|
|
154
|
+
*/
|
|
155
|
+
#tile_cache = new Cache({
|
|
156
|
+
keyHashFunction: passThrough,
|
|
157
|
+
keyEqualityFunction: strictEquals,
|
|
158
|
+
maxWeight: DEFAULT_CACHE_SIZE,
|
|
159
|
+
valueWeigher: () => this.#tile_resolution * this.#tile_resolution * 4
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
#find_eviction_target_index() {
|
|
163
|
+
// find least-recently used
|
|
164
|
+
const tiles = this.#resident_tiles;
|
|
165
|
+
const count = tiles.length;
|
|
166
|
+
|
|
167
|
+
let min = Infinity;
|
|
168
|
+
let min_index = -1;
|
|
169
|
+
|
|
170
|
+
for (let i = 0; i < count; i++) {
|
|
171
|
+
const tile = tiles[i];
|
|
172
|
+
|
|
173
|
+
if (tile.last_used_time < min) {
|
|
174
|
+
min = tile.last_used_time;
|
|
175
|
+
min_index = i;
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
return min_index;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
#evict_one() {
|
|
183
|
+
const index = this.#find_eviction_target_index();
|
|
184
|
+
|
|
185
|
+
if (index === -1) {
|
|
186
|
+
return false;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
return this.#remove_resident_tile(index);
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
/**
|
|
193
|
+
*
|
|
194
|
+
* @param {number} index
|
|
195
|
+
* @returns {boolean}
|
|
196
|
+
*/
|
|
197
|
+
#remove_resident_tile(index) {
|
|
198
|
+
const [removed] = this.#resident_tiles.splice(index, 1);
|
|
199
|
+
|
|
200
|
+
const pageSlot = removed.page_slot;
|
|
201
|
+
|
|
202
|
+
// mark slot as empty for next tile
|
|
203
|
+
this.#page_slot_occupancy.clear(pageSlot);
|
|
204
|
+
|
|
205
|
+
removed.page_slot = -1;
|
|
206
|
+
|
|
207
|
+
const fingerprint = removed.finder_print;
|
|
208
|
+
|
|
209
|
+
this.#resident_tile_lookup.delete(fingerprint);
|
|
210
|
+
|
|
211
|
+
// push into cache
|
|
212
|
+
this.#tile_cache.put(fingerprint, removed);
|
|
213
|
+
|
|
214
|
+
this.version++;
|
|
215
|
+
|
|
216
|
+
return true;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
/**
|
|
220
|
+
*
|
|
221
|
+
* @param {VirtualTextureTile} tile
|
|
222
|
+
*/
|
|
223
|
+
#make_resident(tile) {
|
|
224
|
+
|
|
225
|
+
if (this.#residency_tile_capacity === 0) {
|
|
226
|
+
throw new Error('Capacity is 0');
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
let slot = this.#page_slot_occupancy.nextClearBit(0);
|
|
230
|
+
|
|
231
|
+
while (slot >= this.#residency_tile_capacity) {
|
|
232
|
+
this.#evict_one();
|
|
233
|
+
|
|
234
|
+
slot = this.#page_slot_occupancy.nextClearBit(0);
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
tile.page_slot = slot;
|
|
238
|
+
tile.last_used_time = this.update_count;
|
|
239
|
+
|
|
240
|
+
this.#page_slot_occupancy.set(slot, true);
|
|
241
|
+
this.#resident_tiles.push(tile);
|
|
242
|
+
this.#resident_tile_lookup.set(tile.finder_print, tile);
|
|
243
|
+
|
|
244
|
+
this.version++;
|
|
245
|
+
|
|
246
|
+
return true;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
/**
|
|
250
|
+
*
|
|
251
|
+
* @param {number[]} v
|
|
252
|
+
*/
|
|
253
|
+
set page_texture_size(v) {
|
|
254
|
+
array_copy(
|
|
255
|
+
v, 0,
|
|
256
|
+
this.#page_texture_size, 0,
|
|
257
|
+
2
|
|
258
|
+
);
|
|
259
|
+
|
|
260
|
+
this.#page_texture.dispose();
|
|
261
|
+
this.#page_texture.setSize(this.#page_texture_size[0], this.#page_texture_size[1]);
|
|
262
|
+
|
|
263
|
+
this.#update_residency_capacity();
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
/**
|
|
267
|
+
*
|
|
268
|
+
* @param {VirtualTextureUsage} usage
|
|
269
|
+
*/
|
|
270
|
+
update_usage(usage) {
|
|
271
|
+
const usage_occupancy = usage.occupancy;
|
|
272
|
+
const usage_occupancy_count = usage.occupancy_count;
|
|
273
|
+
|
|
274
|
+
|
|
275
|
+
// Usage occupancy is already sorted by priority, so there's no point is trying to fetch more than t
|
|
276
|
+
let fetch_limit = this.#residency_tile_capacity * (1 + this.#prefetch_factor);
|
|
277
|
+
|
|
278
|
+
let writes_remaining = min2(
|
|
279
|
+
max2(1, this.#cycle_assignment_limit),
|
|
280
|
+
this.#residency_tile_capacity
|
|
281
|
+
);
|
|
282
|
+
|
|
283
|
+
let remaining_slots = this.#residency_tile_capacity;
|
|
284
|
+
|
|
285
|
+
for (let i = 0; i < usage_occupancy_count && fetch_limit > 0; i++) {
|
|
286
|
+
const tile_address = usage_occupancy[i];
|
|
287
|
+
|
|
288
|
+
const fingerPrint = tile_address_to_finger_print(tile_address);
|
|
289
|
+
|
|
290
|
+
let tile = this.#resident_tile_lookup.get(fingerPrint);
|
|
291
|
+
|
|
292
|
+
if (tile !== undefined) {
|
|
293
|
+
// already resident
|
|
294
|
+
|
|
295
|
+
// touch to prevent eviction
|
|
296
|
+
tile.last_used_time = this.update_count;
|
|
297
|
+
remaining_slots--;
|
|
298
|
+
fetch_limit--;
|
|
299
|
+
|
|
300
|
+
continue;
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
tile = this.#tile_cache.get(fingerPrint);
|
|
304
|
+
|
|
305
|
+
if (tile === null) {
|
|
306
|
+
this.#loader.enqueue(fingerPrint);
|
|
307
|
+
} else if (remaining_slots > 0) {
|
|
308
|
+
// found in cache, and we have some slots remaining to write into
|
|
309
|
+
|
|
310
|
+
if (writes_remaining > 0) {
|
|
311
|
+
// tile is not active, and we can write it
|
|
312
|
+
writes_remaining--;
|
|
313
|
+
this.#make_resident(tile);
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
fetch_limit--;
|
|
317
|
+
remaining_slots--;
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
|
|
322
|
+
this.#loader.update_usage(usage);
|
|
323
|
+
|
|
324
|
+
this.update_count++;
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
dispose() {
|
|
328
|
+
this.#page_texture.dispose();
|
|
329
|
+
this.#tile_cache.clear();
|
|
330
|
+
this.#page_slot_occupancy.reset();
|
|
331
|
+
|
|
332
|
+
this.#resident_tiles.splice(0, this.#resident_tiles.length);
|
|
333
|
+
this.#resident_tile_lookup.clear();
|
|
334
|
+
}
|
|
335
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { compose_tile_address } from "./tile/compose_tile_address.js";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Represents entire mip pyramid of tiles, where each tile slot points to an actual resident tile
|
|
5
|
+
*/
|
|
6
|
+
export class ResidentTileTexture {
|
|
7
|
+
#resolution = 0;
|
|
8
|
+
#pyramid = new Uint32Array(0);
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
*
|
|
13
|
+
* @param {number} resolution
|
|
14
|
+
*/
|
|
15
|
+
set resolution(resolution) {
|
|
16
|
+
this.#resolution = resolution;
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
//max mip
|
|
20
|
+
const mip_level = Math.log2(resolution);
|
|
21
|
+
|
|
22
|
+
compose_tile_address(mip_level, 0, 0);
|
|
23
|
+
|
|
24
|
+
this.#pyramid = new Uint32Array(mip_level);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
get resolution() {
|
|
28
|
+
return this.#resolution;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
*
|
|
33
|
+
* @param {PageTexture} page
|
|
34
|
+
*/
|
|
35
|
+
set residency(page) {
|
|
36
|
+
|
|
37
|
+
const tiles = page.resident_tiles;
|
|
38
|
+
const tile_count = tiles.length;
|
|
39
|
+
|
|
40
|
+
for (let i = 0; i < tile_count; i++) {
|
|
41
|
+
const tile = tiles[i];
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
}
|