@woosh/meep-engine 2.72.0 → 2.74.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 +38 -189
- package/build/meep.min.js +1 -1
- package/build/meep.module.js +38 -189
- package/package.json +3 -2
- package/src/engine/asset/loaders/image/codec/NativeImageDecoder.js +2 -1
- package/src/engine/asset/loaders/image/codec/ThreadedImageDecoder.js +4 -5
- package/src/engine/asset/loaders/image/png/PNGReader.js +23 -19
- package/src/engine/graphics/ecs/camera/FrustumProjector.js +31 -182
- 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/NOTES.md +11 -1
- package/src/engine/graphics/texture/virtual/v2/ShaderUsage.js +7 -5
- package/src/engine/graphics/texture/virtual/v2/SparseTexture.js +291 -0
- package/src/engine/graphics/texture/virtual/v2/TileLoader.js +232 -0
- package/src/engine/graphics/texture/virtual/v2/UsageMetadata.js +118 -134
- package/src/engine/graphics/texture/virtual/v2/VirtualTextureManager.js +73 -9
- package/src/engine/graphics/texture/virtual/v2/debug/ResidencyDebugView.js +58 -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} +27 -23
- package/src/engine/graphics/texture/virtual/v2/prototype.js +62 -19
- package/src/engine/graphics/texture/virtual/v2/tile/TextureTile.js +31 -0
- 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 +22 -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/tile_address_to_finger_print.js +35 -0
- package/src/view/CSS_ABSOLUTE_POSITIONING.js +5 -0
- package/src/engine/graphics/clouds/MaterialTransformer.js +0 -0
- package/src/engine/graphics/clouds/TerrainCloudsPlugin.js +0 -0
- package/src/engine/graphics/clouds/cs_build_fragment_shader.js +0 -0
- package/src/engine/graphics/clouds/cs_build_vertex_shader.js +0 -0
- package/src/engine/graphics/ecs/camera/TiltCameraController.js +0 -69
- package/src/engine/graphics/ecs/camera/is_valid_distance_value.js +0 -11
- package/src/engine/graphics/texture/virtual/v2/UsageDebugView.js +0 -51
|
@@ -6,190 +6,41 @@
|
|
|
6
6
|
import { Vector3 as ThreeVector3 } from 'three';
|
|
7
7
|
|
|
8
8
|
|
|
9
|
-
const
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
new ThreeVector3(-1, -1, 0),
|
|
9
|
+
const corners = [
|
|
10
|
+
new ThreeVector3(-1, -1, 0),
|
|
11
|
+
new ThreeVector3(-1, -1, 0),
|
|
13
12
|
|
|
14
|
-
|
|
15
|
-
|
|
13
|
+
new ThreeVector3(-1, 1, 0),
|
|
14
|
+
new ThreeVector3(-1, 1, 0),
|
|
16
15
|
|
|
17
|
-
|
|
18
|
-
|
|
16
|
+
new ThreeVector3(1, -1, 0),
|
|
17
|
+
new ThreeVector3(1, -1, 0),
|
|
19
18
|
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
19
|
+
new ThreeVector3(1, 1, 0),
|
|
20
|
+
new ThreeVector3(1, 1, 0)
|
|
21
|
+
];
|
|
23
22
|
|
|
24
23
|
|
|
25
|
-
/**
|
|
26
|
-
*
|
|
27
|
-
* @param {Number} nearZ
|
|
28
|
-
* @param {Number} farZ
|
|
29
|
-
* @param {THREE.Camera} camera
|
|
30
|
-
* @param {THREE.Matrix4} matrixWorldInverse
|
|
31
|
-
* @param {function} callback
|
|
32
|
-
*/
|
|
33
|
-
function projectInWorldSpace(nearZ, farZ, camera, callback) {
|
|
34
|
-
corners[0].set(-1, -1, nearZ);
|
|
35
|
-
corners[1].set(-1, -1, farZ);
|
|
36
|
-
|
|
37
|
-
corners[2].set(-1, 1, nearZ);
|
|
38
|
-
corners[3].set(-1, 1, farZ);
|
|
39
|
-
|
|
40
|
-
corners[4].set(1, -1, nearZ);
|
|
41
|
-
corners[5].set(1, -1, farZ);
|
|
42
|
-
|
|
43
|
-
corners[6].set(1, 1, nearZ);
|
|
44
|
-
corners[7].set(1, 1, farZ);
|
|
45
|
-
|
|
46
|
-
//compute corners of view frustum in light space
|
|
47
|
-
let x0 = Number.POSITIVE_INFINITY;
|
|
48
|
-
let y0 = Number.POSITIVE_INFINITY;
|
|
49
|
-
let z0 = Number.POSITIVE_INFINITY;
|
|
50
|
-
|
|
51
|
-
let x1 = Number.NEGATIVE_INFINITY;
|
|
52
|
-
let y1 = Number.NEGATIVE_INFINITY;
|
|
53
|
-
let z1 = Number.NEGATIVE_INFINITY;
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
for (let i = 0; i < 8; i++) {
|
|
57
|
-
const corner = corners[i];
|
|
58
|
-
corner.unproject(camera);
|
|
59
|
-
|
|
60
|
-
if (corner.x < x0) {
|
|
61
|
-
x0 = corner.x;
|
|
62
|
-
}
|
|
63
|
-
if (corner.x > x1) {
|
|
64
|
-
x1 = corner.x;
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
if (corner.y < y0) {
|
|
68
|
-
y0 = corner.y;
|
|
69
|
-
}
|
|
70
|
-
if (corner.y > y1) {
|
|
71
|
-
y1 = corner.y;
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
if (corner.z < z0) {
|
|
75
|
-
z0 = corner.z;
|
|
76
|
-
}
|
|
77
|
-
if (corner.z > z1) {
|
|
78
|
-
z1 = corner.z;
|
|
79
|
-
}
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
callback(x0, y0, z0, x1, y1, z1);
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
return projectInWorldSpace;
|
|
86
|
-
})();
|
|
87
|
-
|
|
88
|
-
const project = (function () {
|
|
89
|
-
const corners = [
|
|
90
|
-
new ThreeVector3(-1, -1, 0),
|
|
91
|
-
new ThreeVector3(-1, -1, 0),
|
|
92
|
-
|
|
93
|
-
new ThreeVector3(-1, 1, 0),
|
|
94
|
-
new ThreeVector3(-1, 1, 0),
|
|
95
|
-
|
|
96
|
-
new ThreeVector3(1, -1, 0),
|
|
97
|
-
new ThreeVector3(1, -1, 0),
|
|
98
|
-
|
|
99
|
-
new ThreeVector3(1, 1, 0),
|
|
100
|
-
new ThreeVector3(1, 1, 0)
|
|
101
|
-
];
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
/**
|
|
105
|
-
*
|
|
106
|
-
* @param {Number} nearZ
|
|
107
|
-
* @param {Number} farZ
|
|
108
|
-
* @param {THREE.Camera} camera
|
|
109
|
-
* @param {THREE.Matrix4} matrixWorldInverse
|
|
110
|
-
* @param {AABB3} result
|
|
111
|
-
*/
|
|
112
|
-
function project(nearZ, farZ, camera, matrixWorldInverse, result) {
|
|
113
|
-
corners[0].set(-1, -1, nearZ);
|
|
114
|
-
corners[1].set(-1, -1, farZ);
|
|
115
|
-
|
|
116
|
-
corners[2].set(-1, 1, nearZ);
|
|
117
|
-
corners[3].set(-1, 1, farZ);
|
|
118
|
-
|
|
119
|
-
corners[4].set(1, -1, nearZ);
|
|
120
|
-
corners[5].set(1, -1, farZ);
|
|
121
|
-
|
|
122
|
-
corners[6].set(1, 1, nearZ);
|
|
123
|
-
corners[7].set(1, 1, farZ);
|
|
124
|
-
|
|
125
|
-
//compute corners of view frustum in light space
|
|
126
|
-
let x0 = Number.POSITIVE_INFINITY;
|
|
127
|
-
let y0 = Number.POSITIVE_INFINITY;
|
|
128
|
-
let z0 = Number.POSITIVE_INFINITY;
|
|
129
|
-
|
|
130
|
-
let x1 = Number.NEGATIVE_INFINITY;
|
|
131
|
-
let y1 = Number.NEGATIVE_INFINITY;
|
|
132
|
-
let z1 = Number.NEGATIVE_INFINITY;
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
for (let i = 0; i < 8; i++) {
|
|
136
|
-
const corner = corners[i];
|
|
137
|
-
corner.unproject(camera);
|
|
138
|
-
corner.applyMatrix4(matrixWorldInverse);
|
|
139
|
-
|
|
140
|
-
if (corner.x < x0) {
|
|
141
|
-
x0 = corner.x;
|
|
142
|
-
}
|
|
143
|
-
if (corner.x > x1) {
|
|
144
|
-
x1 = corner.x;
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
if (corner.y < y0) {
|
|
148
|
-
y0 = corner.y;
|
|
149
|
-
}
|
|
150
|
-
if (corner.y > y1) {
|
|
151
|
-
y1 = corner.y;
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
if (corner.z < z0) {
|
|
155
|
-
z0 = corner.z;
|
|
156
|
-
}
|
|
157
|
-
if (corner.z > z1) {
|
|
158
|
-
z1 = corner.z;
|
|
159
|
-
}
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
result.setBounds(x0, y0, z0, x1, y1, z1);
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
return project;
|
|
166
|
-
})();
|
|
167
|
-
|
|
168
24
|
/**
|
|
169
25
|
*
|
|
170
|
-
* @param
|
|
171
|
-
* @param
|
|
172
|
-
* @param
|
|
173
|
-
* @param
|
|
174
|
-
* @param
|
|
175
|
-
* @param _z1
|
|
176
|
-
* @param {Camera} camera
|
|
177
|
-
* @param callback
|
|
26
|
+
* @param {Number} nearZ
|
|
27
|
+
* @param {Number} farZ
|
|
28
|
+
* @param {THREE.Camera} camera
|
|
29
|
+
* @param {THREE.Matrix4} matrixWorldInverse
|
|
30
|
+
* @param {AABB3} result
|
|
178
31
|
*/
|
|
179
|
-
function
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
new ThreeVector3(_x0, _y0, _z1),
|
|
32
|
+
function project(nearZ, farZ, camera, matrixWorldInverse, result) {
|
|
33
|
+
corners[0].set(-1, -1, nearZ);
|
|
34
|
+
corners[1].set(-1, -1, farZ);
|
|
183
35
|
|
|
184
|
-
|
|
185
|
-
|
|
36
|
+
corners[2].set(-1, 1, nearZ);
|
|
37
|
+
corners[3].set(-1, 1, farZ);
|
|
186
38
|
|
|
187
|
-
|
|
188
|
-
|
|
39
|
+
corners[4].set(1, -1, nearZ);
|
|
40
|
+
corners[5].set(1, -1, farZ);
|
|
189
41
|
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
];
|
|
42
|
+
corners[6].set(1, 1, nearZ);
|
|
43
|
+
corners[7].set(1, 1, farZ);
|
|
193
44
|
|
|
194
45
|
//compute corners of view frustum in light space
|
|
195
46
|
let x0 = Number.POSITIVE_INFINITY;
|
|
@@ -201,8 +52,10 @@ function unproject(_x0, _y0, _z0, _x1, _y1, _z1, camera, callback) {
|
|
|
201
52
|
let z1 = Number.NEGATIVE_INFINITY;
|
|
202
53
|
|
|
203
54
|
|
|
204
|
-
|
|
205
|
-
corner
|
|
55
|
+
for (let i = 0; i < 8; i++) {
|
|
56
|
+
const corner = corners[i];
|
|
57
|
+
corner.unproject(camera);
|
|
58
|
+
corner.applyMatrix4(matrixWorldInverse);
|
|
206
59
|
|
|
207
60
|
if (corner.x < x0) {
|
|
208
61
|
x0 = corner.x;
|
|
@@ -224,19 +77,15 @@ function unproject(_x0, _y0, _z0, _x1, _y1, _z1, camera, callback) {
|
|
|
224
77
|
if (corner.z > z1) {
|
|
225
78
|
z1 = corner.z;
|
|
226
79
|
}
|
|
227
|
-
}
|
|
80
|
+
}
|
|
228
81
|
|
|
229
|
-
|
|
82
|
+
result.setBounds(x0, y0, z0, x1, y1, z1);
|
|
230
83
|
}
|
|
231
84
|
|
|
85
|
+
|
|
232
86
|
export default {
|
|
233
87
|
/**
|
|
234
88
|
* @deprecated use non-threejs specific code
|
|
235
89
|
*/
|
|
236
90
|
project,
|
|
237
|
-
unproject,
|
|
238
|
-
/**
|
|
239
|
-
* @deprecated use non-threejs specific code
|
|
240
|
-
*/
|
|
241
|
-
projectInWorldSpace
|
|
242
91
|
};
|
|
@@ -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 = {
|
|
@@ -14,4 +14,14 @@ The original texture is mip-mapped, down to the size of a single tile, for the s
|
|
|
14
14
|
|
|
15
15
|
This is achieved by pre-rendering the scene with a special shader that records UV and mip-level information, which we call `Usage`, this is then analysed to build a list of used tiles (mip level, x, y) and by counting occurrence of this tile in the `Usage` texture we get a much more useful data structure.
|
|
16
16
|
|
|
17
|
-
Based on the `Usage` data, we populate our `Physical` texture of pages, and based on what's currently in the `Physical` texture - we populate the `Reference` texture that stores information about the entire mip pyramid, and for each tile contains a pointer to the `Physical` texture, where representative tile can be found.
|
|
17
|
+
Based on the `Usage` data, we populate our `Physical` texture of pages, and based on what's currently in the `Physical` texture - we populate the `Reference` texture that stores information about the entire mip pyramid, and for each tile contains a pointer to the `Physical` texture, where representative tile can be found.
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
## Considerations
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
### Filtering
|
|
24
|
+
|
|
25
|
+
Most papers suggest introducing a border into each tile of ~4 pixels.
|
|
26
|
+
4 pixel border on a 128x128 pixel tile is going to take up 12.1% of space, which is quite a lot.
|
|
27
|
+
We can do filtering in software, that is - sample individual pixels and perform interpolation inside the shader.
|
|
@@ -34,13 +34,15 @@ function fragment() {
|
|
|
34
34
|
|
|
35
35
|
float MTCalculateMipLevel( const in vec2 uv, const in float bias ) {
|
|
36
36
|
|
|
37
|
-
float scale_x = float(u_mt_tex.x)
|
|
38
|
-
float scale_y = float(u_mt_tex.y)
|
|
37
|
+
float scale_x = float(u_mt_tex.x) ;
|
|
38
|
+
float scale_y = float(u_mt_tex.y) ;
|
|
39
39
|
|
|
40
|
-
vec2 dx = dFdx( uv * scale_x);
|
|
41
|
-
vec2 dy = dFdy( uv * scale_y);
|
|
40
|
+
vec2 dx = dFdx( uv * scale_x)/ u_mt_viewport_size.x;
|
|
41
|
+
vec2 dy = dFdy( uv * scale_y)/ u_mt_viewport_size.y;
|
|
42
42
|
|
|
43
|
-
|
|
43
|
+
float d = max( dot( dx, dx ), dot( dy, dy ) );
|
|
44
|
+
|
|
45
|
+
return log2( d ) * 0.5 + bias;
|
|
44
46
|
}
|
|
45
47
|
|
|
46
48
|
uvec4 MTCalculateTileKeyRGBA( const in vec2 uv ) {
|
|
@@ -0,0 +1,291 @@
|
|
|
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 { passThrough, strictEquals } from "../../../../../core/function/Functions.js";
|
|
6
|
+
import { max2 } from "../../../../../core/math/max2.js";
|
|
7
|
+
import { min2 } from "../../../../../core/math/min2.js";
|
|
8
|
+
import { tile_address_to_finger_print } from "./tile/tile_address_to_finger_print.js";
|
|
9
|
+
import { TileLoader } from "./TileLoader.js";
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* How much extra data to store in cache (in bytes)
|
|
13
|
+
* @type {number}
|
|
14
|
+
*/
|
|
15
|
+
const DEFAULT_CACHE_SIZE = 16 * 1024 * 1024;
|
|
16
|
+
|
|
17
|
+
export class SparseTexture {
|
|
18
|
+
#page_texture = new WebGLRenderTarget(1, 1);
|
|
19
|
+
|
|
20
|
+
#page_texture_size = [1, 1];
|
|
21
|
+
|
|
22
|
+
#version = 0;
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Maximum number of new page assignments per single update cycle
|
|
26
|
+
* This helps prevent thrashing and keep performance overhead of updating pages predictable
|
|
27
|
+
* @type {number}
|
|
28
|
+
*/
|
|
29
|
+
#cycle_assignment_limit = 2;
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
get page_texture_resolution() {
|
|
33
|
+
return this.#page_texture_size;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
*
|
|
38
|
+
* @type {TextureTile[]}
|
|
39
|
+
*/
|
|
40
|
+
#resident_tiles = [];
|
|
41
|
+
|
|
42
|
+
get resident_tiles() {
|
|
43
|
+
return this.#resident_tiles;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
#page_slot_occupancy = new BitSet();
|
|
47
|
+
|
|
48
|
+
#tile_resolution = 128;
|
|
49
|
+
|
|
50
|
+
get tile_resolution() {
|
|
51
|
+
return this.#tile_resolution;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
#residency_tile_capacity = 0;
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Must be >=0
|
|
58
|
+
* When 0 - tiles that would not fit onto the page will not be loaded
|
|
59
|
+
* When >0 - same fraction of extra tiles will be fetching and put into cache, resulting in prefetching
|
|
60
|
+
* @type {number}
|
|
61
|
+
*/
|
|
62
|
+
#prefetch_factor = 0.33;
|
|
63
|
+
|
|
64
|
+
#loader = new TileLoader();
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
*
|
|
68
|
+
* @type {AssetManager|null}
|
|
69
|
+
*/
|
|
70
|
+
#asset_manager = null;
|
|
71
|
+
|
|
72
|
+
constructor() {
|
|
73
|
+
this.#loader.on.loaded.add(this.#handle_tile_loaded, this);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
*
|
|
78
|
+
* @param {TextureTile} tile
|
|
79
|
+
*/
|
|
80
|
+
#handle_tile_loaded(tile) {
|
|
81
|
+
const finderPrint = tile.finder_print;
|
|
82
|
+
|
|
83
|
+
if (this.#tile_cache.has(finderPrint)) {
|
|
84
|
+
// already loaded
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
this.#tile_cache.put(finderPrint, tile);
|
|
89
|
+
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
*
|
|
94
|
+
* @param {string} v
|
|
95
|
+
*/
|
|
96
|
+
set path(v) {
|
|
97
|
+
this.#loader.path = v;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
set asset_manager(v) {
|
|
101
|
+
this.#asset_manager = v
|
|
102
|
+
|
|
103
|
+
this.#loader.asset_manager = v;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
#update_residency_capacity() {
|
|
107
|
+
const tile_resolution = this.#tile_resolution;
|
|
108
|
+
const size = this.#page_texture_size;
|
|
109
|
+
|
|
110
|
+
const x = size[0] / tile_resolution;
|
|
111
|
+
const y = size[1] / tile_resolution;
|
|
112
|
+
|
|
113
|
+
this.#residency_tile_capacity = x * y;
|
|
114
|
+
|
|
115
|
+
this.#loader.queue_limit = max2(16, this.#residency_tile_capacity);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
*
|
|
121
|
+
* @param {number} v
|
|
122
|
+
*/
|
|
123
|
+
set tile_resolution(v) {
|
|
124
|
+
this.#tile_resolution = v;
|
|
125
|
+
|
|
126
|
+
this.#update_residency_capacity();
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Tiles that are not currently resident live here
|
|
131
|
+
* key is the tile's "fingerprint"
|
|
132
|
+
* @type {Cache<number,TextureTile>}
|
|
133
|
+
*/
|
|
134
|
+
#tile_cache = new Cache({
|
|
135
|
+
keyHashFunction: passThrough,
|
|
136
|
+
keyEqualityFunction: strictEquals,
|
|
137
|
+
maxWeight: DEFAULT_CACHE_SIZE,
|
|
138
|
+
valueWeigher: () => this.#tile_resolution * this.#tile_resolution * 4
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
#find_eviction_target_index() {
|
|
142
|
+
// find least-recently used
|
|
143
|
+
const tiles = this.#resident_tiles;
|
|
144
|
+
const count = tiles.length;
|
|
145
|
+
|
|
146
|
+
let min = Infinity;
|
|
147
|
+
let min_index = -1;
|
|
148
|
+
|
|
149
|
+
for (let i = 0; i < count; i++) {
|
|
150
|
+
const tile = tiles[i];
|
|
151
|
+
|
|
152
|
+
if (tile.last_used_time < min) {
|
|
153
|
+
min = tile.last_used_time;
|
|
154
|
+
min_index = i;
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
return min_index;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
#evict_one() {
|
|
162
|
+
const index = this.#find_eviction_target_index();
|
|
163
|
+
|
|
164
|
+
if (index === -1) {
|
|
165
|
+
return false;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
return this.#remove_resident_tile(index);
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/**
|
|
172
|
+
*
|
|
173
|
+
* @param {number} index
|
|
174
|
+
* @returns {boolean}
|
|
175
|
+
*/
|
|
176
|
+
#remove_resident_tile(index) {
|
|
177
|
+
const [removed] = this.#resident_tiles.splice(index, 1);
|
|
178
|
+
|
|
179
|
+
const pageSlot = removed.page_slot;
|
|
180
|
+
|
|
181
|
+
// mark slot as empty for next tile
|
|
182
|
+
this.#page_slot_occupancy.clear(pageSlot);
|
|
183
|
+
|
|
184
|
+
removed.page_slot = -1;
|
|
185
|
+
|
|
186
|
+
// push into cache
|
|
187
|
+
this.#tile_cache.put(removed.finder_print, removed);
|
|
188
|
+
|
|
189
|
+
return true;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
/**
|
|
193
|
+
*
|
|
194
|
+
* @param {TextureTile} tile
|
|
195
|
+
*/
|
|
196
|
+
#make_resident(tile) {
|
|
197
|
+
|
|
198
|
+
if (this.#residency_tile_capacity === 0) {
|
|
199
|
+
throw new Error('Capacity is 0');
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
let slot = this.#page_slot_occupancy.nextClearBit(0);
|
|
203
|
+
|
|
204
|
+
while (slot >= this.#residency_tile_capacity) {
|
|
205
|
+
this.#evict_one();
|
|
206
|
+
|
|
207
|
+
slot = this.#page_slot_occupancy.nextClearBit(0);
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
tile.page_slot = slot;
|
|
211
|
+
|
|
212
|
+
this.#page_slot_occupancy.set(slot, true);
|
|
213
|
+
this.#resident_tiles.push(tile);
|
|
214
|
+
|
|
215
|
+
return true;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
/**
|
|
219
|
+
*
|
|
220
|
+
* @param {number[]} v
|
|
221
|
+
*/
|
|
222
|
+
set page_texture_size(v) {
|
|
223
|
+
array_copy(
|
|
224
|
+
v, 0,
|
|
225
|
+
this.#page_texture_size, 0,
|
|
226
|
+
2
|
|
227
|
+
);
|
|
228
|
+
|
|
229
|
+
this.#page_texture.dispose();
|
|
230
|
+
this.#page_texture.setSize(this.#page_texture_size[0], this.#page_texture_size[1]);
|
|
231
|
+
|
|
232
|
+
this.#update_residency_capacity();
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
/**
|
|
236
|
+
*
|
|
237
|
+
* @param {UsageMetadata} usage
|
|
238
|
+
*/
|
|
239
|
+
update_usage(usage) {
|
|
240
|
+
const usage_occupancy = usage.occupancy;
|
|
241
|
+
const usage_occupancy_count = usage.occupancy_count;
|
|
242
|
+
|
|
243
|
+
|
|
244
|
+
// Usage occupancy is already sorted by priority, so there's no point is trying to fetch more than t
|
|
245
|
+
let fetch_limit = this.#residency_tile_capacity * (1 + this.#prefetch_factor);
|
|
246
|
+
|
|
247
|
+
let writes_remaining = min2(
|
|
248
|
+
max2(1, this.#cycle_assignment_limit),
|
|
249
|
+
this.#residency_tile_capacity
|
|
250
|
+
);
|
|
251
|
+
|
|
252
|
+
let remaining_slots = this.#residency_tile_capacity;
|
|
253
|
+
|
|
254
|
+
for (let i = 0; i < usage_occupancy_count && fetch_limit > 0; i++) {
|
|
255
|
+
const tile_address = usage_occupancy[i];
|
|
256
|
+
|
|
257
|
+
const fingerPrint = tile_address_to_finger_print(tile_address);
|
|
258
|
+
|
|
259
|
+
const tile = this.#tile_cache.get(fingerPrint);
|
|
260
|
+
if (tile === null) {
|
|
261
|
+
this.#loader.enqueue(fingerPrint);
|
|
262
|
+
} else if (remaining_slots > 0) {
|
|
263
|
+
// found in cache, and we have some slots remaining to write into
|
|
264
|
+
|
|
265
|
+
|
|
266
|
+
// touch to prevent eviction
|
|
267
|
+
tile.last_used_time = this.#version;
|
|
268
|
+
|
|
269
|
+
if (tile.page_slot === -1 && writes_remaining > 0) {
|
|
270
|
+
// tile is not active, and we can write it
|
|
271
|
+
writes_remaining--;
|
|
272
|
+
this.#make_resident(tile);
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
fetch_limit--;
|
|
276
|
+
remaining_slots--;
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
|
|
281
|
+
this.#loader.update_usage(usage);
|
|
282
|
+
|
|
283
|
+
this.#version++;
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
dispose() {
|
|
287
|
+
this.#page_texture.dispose();
|
|
288
|
+
this.#tile_cache.clear();
|
|
289
|
+
this.#page_slot_occupancy.reset();
|
|
290
|
+
}
|
|
291
|
+
}
|