@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.
Files changed (48) hide show
  1. package/build/bundle-worker-image-decoder.js +1 -1
  2. package/build/meep.cjs +185 -189
  3. package/build/meep.min.js +1 -1
  4. package/build/meep.module.js +185 -189
  5. package/package.json +1 -1
  6. package/src/core/binary/UINT32_MAX.js +5 -0
  7. package/src/core/bvh2/bvh3/BVH.js +44 -2
  8. package/src/core/bvh2/bvh3/BVH.spec.js +45 -0
  9. package/src/core/bvh2/bvh3/build_triangle_morton_codes.js +73 -0
  10. package/src/core/bvh2/bvh3/ebvh_build_for_geometry_morton.js +5 -101
  11. package/src/core/bvh2/bvh3/ebvh_build_hierarchy.js +59 -0
  12. package/src/core/bvh2/bvh3/query/bvh_query_user_data_nearest_to_point.js +31 -32
  13. package/src/core/bvh2/bvh3/query/bvh_query_user_data_nearest_to_point.spec.js +64 -0
  14. package/src/core/collection/SCRATCH_UINT32_TRAVERSAL_STACK.js +1 -0
  15. package/src/core/geom/3d/aabb/aabb3_signed_distance_sqr_to_point.js +1 -0
  16. package/src/core/geom/3d/aabb/aabb3_unsigned_distance_sqr_to_point.js +36 -0
  17. package/src/core/process/worker/OnDemandWorkerManager.js +5 -1
  18. package/src/engine/asset/loaders/ArrayBufferLoader.js +13 -15
  19. package/src/engine/asset/loaders/image/ImageDecoderWorker.js +1 -1
  20. package/src/engine/asset/loaders/image/ImageRGBADataLoader.js +5 -7
  21. package/src/engine/asset/loaders/image/codec/NativeImageDecoder.js +2 -1
  22. package/src/engine/asset/loaders/image/codec/ThreadedImageDecoder.js +5 -6
  23. package/src/engine/asset/loaders/image/png/PNG.js +339 -332
  24. package/src/engine/asset/loaders/image/png/PNGReader.js +77 -30
  25. package/src/engine/asset/loaders/image/png/prototypePNG.js +13 -4
  26. package/src/engine/graphics/generate_halton_jitter.js +21 -0
  27. package/src/engine/graphics/render/buffer/simple-fx/taa/TemporalSupersamplingRenderPlugin.js +3 -20
  28. package/src/engine/graphics/texture/virtual/v2/PageTexture.js +335 -0
  29. package/src/engine/graphics/texture/virtual/v2/ResidentTileTexture.js +46 -0
  30. package/src/engine/graphics/texture/virtual/v2/{TileLoader.js → VirtualTextureTileLoader.js} +33 -13
  31. package/src/engine/graphics/texture/virtual/v2/{UsageMetadata.js → VirtualTextureUsage.js} +68 -22
  32. package/src/engine/graphics/texture/virtual/v2/{VirtualTextureManager.js → VirtualTextureUsageUpdater.js} +66 -9
  33. package/src/engine/graphics/texture/virtual/v2/debug/ResidencyDebugView.js +70 -0
  34. package/src/engine/graphics/texture/virtual/v2/debug/UsageDebugView.js +63 -0
  35. package/src/engine/graphics/texture/virtual/v2/{UsagePyramidDebugView.js → debug/UsagePyramidDebugView.js} +31 -40
  36. package/src/engine/graphics/texture/virtual/v2/prototype.js +98 -54
  37. package/src/engine/graphics/texture/virtual/v2/{TextureTile.js → tile/VirtualTextureTile.js} +3 -3
  38. package/src/engine/graphics/texture/virtual/v2/tile/compose_finger_print.js +23 -0
  39. package/src/engine/graphics/texture/virtual/v2/tile/compose_tile_address.js +26 -0
  40. package/src/engine/graphics/texture/virtual/v2/tile/decompose_finger_print.js +12 -0
  41. package/src/engine/graphics/texture/virtual/v2/tile/finger_print_to_tile_address.js +16 -0
  42. package/src/engine/graphics/texture/virtual/v2/{tile_index_to_finger_print.js → tile/tile_address_to_finger_print.js} +7 -3
  43. package/src/view/CSS_ABSOLUTE_POSITIONING.js +5 -0
  44. package/src/engine/graphics/texture/virtual/v2/SparseTexture.js +0 -182
  45. package/src/engine/graphics/texture/virtual/v2/UsageDebugView.js +0 -64
  46. package/src/engine/graphics/texture/virtual/v2/compose_finger_print.js +0 -13
  47. package/src/engine/graphics/texture/virtual/v2/finger_print_to_tile_index.js +0 -37
  48. /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
- //console.log(`Unsupported block ${type}`);
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
- var png = this.png;
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
- var png = this.png;
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
- var png = this.png;
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
- // bytes per pixel
298
- var bytes_per_pixel = Math.max(1, png.colors * png.bitDepth / 8);
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 * png.width;
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
- var pixels = new Uint8Array(bytes_per_pixel * png.width * png.height);
304
- // var scanline;
305
- var offset = 0;
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 (var i = 0; i < data_length; i += color_bytes_per_row + 1) {
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
- this.unFilterNone(data, scanline_address, pixels, bytes_per_pixel, offset, color_bytes_per_row);
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 += color_bytes_per_row;
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
- var i = 0;
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
- var i = 0, byte, prev;
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
- var i = 0, byte, prev, prior;
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
- var i = 0, raw, a, b, c, p, pa, pb, pc, pr;
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
- var type = this.decodeChunk();
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
- var png = this.png;
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('moicon/ISO 7010 - Safety Signs (3)/ISO 7010 - Safety Signs/Emergency/400px/E001 – Emergency exit (left hand).png',(asset)=>{
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
+ }
@@ -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 = generate_jitter(this.__sample_count);
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
+ }