@codexo/exojs-tilemap 0.15.3 → 0.16.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/README.md +33 -26
- package/dist/esm/ChunkSource.d.ts +33 -0
- package/dist/esm/ChunkSource.d.ts.map +1 -0
- package/dist/esm/ChunkStreamer.d.ts +98 -0
- package/dist/esm/ChunkStreamer.d.ts.map +1 -0
- package/dist/esm/ChunkStreamer.js +201 -0
- package/dist/esm/ChunkStreamer.js.map +1 -0
- package/dist/esm/ImageLayer.d.ts +11 -3
- package/dist/esm/ImageLayer.d.ts.map +1 -0
- package/dist/esm/ImageLayer.js +71 -64
- package/dist/esm/ImageLayer.js.map +1 -1
- package/dist/esm/ImageLayerNode.d.ts +89 -0
- package/dist/esm/ImageLayerNode.d.ts.map +1 -0
- package/dist/esm/ImageLayerNode.js +205 -0
- package/dist/esm/ImageLayerNode.js.map +1 -0
- package/dist/esm/MapObject.d.ts +70 -0
- package/dist/esm/MapObject.d.ts.map +1 -0
- package/dist/esm/MapObject.js +34 -0
- package/dist/esm/MapObject.js.map +1 -0
- package/dist/esm/MapObjectSpawner.d.ts +133 -0
- package/dist/esm/MapObjectSpawner.d.ts.map +1 -0
- package/dist/esm/MapObjectSpawner.js +147 -0
- package/dist/esm/MapObjectSpawner.js.map +1 -0
- package/dist/esm/MapSpawnSession.d.ts +53 -0
- package/dist/esm/MapSpawnSession.d.ts.map +1 -0
- package/dist/esm/MapSpawnSession.js +88 -0
- package/dist/esm/MapSpawnSession.js.map +1 -0
- package/dist/esm/MapWorld.d.ts +134 -0
- package/dist/esm/MapWorld.d.ts.map +1 -0
- package/dist/esm/MapWorld.js +150 -0
- package/dist/esm/MapWorld.js.map +1 -0
- package/dist/esm/MapWorldRuntime.d.ts +182 -0
- package/dist/esm/MapWorldRuntime.d.ts.map +1 -0
- package/dist/esm/MapWorldRuntime.js +261 -0
- package/dist/esm/MapWorldRuntime.js.map +1 -0
- package/dist/esm/ObjectLayer.d.ts +93 -7
- package/dist/esm/ObjectLayer.d.ts.map +1 -0
- package/dist/esm/ObjectLayer.js +184 -167
- package/dist/esm/ObjectLayer.js.map +1 -1
- package/dist/esm/SampledChunkSource.d.ts +43 -0
- package/dist/esm/SampledChunkSource.d.ts.map +1 -0
- package/dist/esm/SampledChunkSource.js +48 -0
- package/dist/esm/SampledChunkSource.js.map +1 -0
- package/dist/esm/TileAnimator.d.ts +12 -2
- package/dist/esm/TileAnimator.d.ts.map +1 -0
- package/dist/esm/TileAnimator.js +154 -165
- package/dist/esm/TileAnimator.js.map +1 -1
- package/dist/esm/TileChunk.d.ts +30 -5
- package/dist/esm/TileChunk.d.ts.map +1 -0
- package/dist/esm/TileChunk.js +190 -188
- package/dist/esm/TileChunk.js.map +1 -1
- package/dist/esm/TileChunkNode.d.ts +18 -3
- package/dist/esm/TileChunkNode.d.ts.map +1 -0
- package/dist/esm/TileChunkNode.js +104 -85
- package/dist/esm/TileChunkNode.js.map +1 -1
- package/dist/esm/TileLayer.d.ts +104 -24
- package/dist/esm/TileLayer.d.ts.map +1 -0
- package/dist/esm/TileLayer.js +511 -469
- package/dist/esm/TileLayer.js.map +1 -1
- package/dist/esm/TileLayerNode.d.ts +52 -11
- package/dist/esm/TileLayerNode.d.ts.map +1 -0
- package/dist/esm/TileLayerNode.js +224 -166
- package/dist/esm/TileLayerNode.js.map +1 -1
- package/dist/esm/TileMap.d.ts +96 -31
- package/dist/esm/TileMap.d.ts.map +1 -0
- package/dist/esm/TileMap.js +401 -357
- package/dist/esm/TileMap.js.map +1 -1
- package/dist/esm/TileMapBand.d.ts +42 -29
- package/dist/esm/TileMapBand.d.ts.map +1 -0
- package/dist/esm/TileMapBand.js +151 -158
- package/dist/esm/TileMapBand.js.map +1 -1
- package/dist/esm/TileMapNode.d.ts +34 -18
- package/dist/esm/TileMapNode.d.ts.map +1 -0
- package/dist/esm/TileMapNode.js +122 -109
- package/dist/esm/TileMapNode.js.map +1 -1
- package/dist/esm/TileMapView.d.ts +108 -47
- package/dist/esm/TileMapView.d.ts.map +1 -0
- package/dist/esm/TileMapView.js +385 -338
- package/dist/esm/TileMapView.js.map +1 -1
- package/dist/esm/TileSet.d.ts +15 -6
- package/dist/esm/TileSet.d.ts.map +1 -0
- package/dist/esm/TileSet.js +161 -181
- package/dist/esm/TileSet.js.map +1 -1
- package/dist/esm/WangSet.d.ts +5 -4
- package/dist/esm/WangSet.d.ts.map +1 -0
- package/dist/esm/WangSet.js +70 -78
- package/dist/esm/WangSet.js.map +1 -1
- package/dist/esm/WorkerSampledChunkSource.d.ts +95 -0
- package/dist/esm/WorkerSampledChunkSource.d.ts.map +1 -0
- package/dist/esm/WorkerSampledChunkSource.js +117 -0
- package/dist/esm/WorkerSampledChunkSource.js.map +1 -0
- package/dist/esm/autoTile.d.ts +10 -5
- package/dist/esm/autoTile.d.ts.map +1 -0
- package/dist/esm/autoTile.js +164 -205
- package/dist/esm/autoTile.js.map +1 -1
- package/dist/esm/chunkGeometry.d.ts +8 -7
- package/dist/esm/chunkGeometry.d.ts.map +1 -0
- package/dist/esm/chunkGeometry.js +83 -94
- package/dist/esm/chunkGeometry.js.map +1 -1
- package/dist/esm/index.d.ts +1 -0
- package/dist/esm/index.d.ts.map +1 -0
- package/dist/esm/index.js +26 -15
- package/dist/esm/nodeBounds.d.ts +20 -0
- package/dist/esm/nodeBounds.d.ts.map +1 -0
- package/dist/esm/nodeBounds.js +40 -0
- package/dist/esm/nodeBounds.js.map +1 -0
- package/dist/esm/pixelSnap.d.ts +4 -3
- package/dist/esm/pixelSnap.d.ts.map +1 -0
- package/dist/esm/pixelSnap.js +18 -16
- package/dist/esm/pixelSnap.js.map +1 -1
- package/dist/esm/public.d.ts +23 -3
- package/dist/esm/public.d.ts.map +1 -0
- package/dist/esm/tileCollision.d.ts +190 -0
- package/dist/esm/tileCollision.d.ts.map +1 -0
- package/dist/esm/tileCollision.js +367 -0
- package/dist/esm/tileCollision.js.map +1 -0
- package/dist/esm/tileWord.d.ts +12 -0
- package/dist/esm/tileWord.d.ts.map +1 -0
- package/dist/esm/tileWord.js +16 -0
- package/dist/esm/tileWord.js.map +1 -0
- package/dist/esm/tilemapExtension.d.ts +4 -3
- package/dist/esm/tilemapExtension.d.ts.map +1 -0
- package/dist/esm/tilemapExtension.js +47 -47
- package/dist/esm/tilemapExtension.js.map +1 -1
- package/dist/esm/tilemapSerializers.d.ts +5 -4
- package/dist/esm/tilemapSerializers.d.ts.map +1 -0
- package/dist/esm/tilemapSerializers.js +48 -41
- package/dist/esm/tilemapSerializers.js.map +1 -1
- package/dist/esm/types.d.ts +34 -18
- package/dist/esm/types.d.ts.map +1 -0
- package/dist/esm/types.js +111 -120
- package/dist/esm/types.js.map +1 -1
- package/dist/esm/webgl2/WebGl2TileChunkRenderer.d.ts +50 -2
- package/dist/esm/webgl2/WebGl2TileChunkRenderer.d.ts.map +1 -0
- package/dist/esm/webgl2/WebGl2TileChunkRenderer.js +366 -332
- package/dist/esm/webgl2/WebGl2TileChunkRenderer.js.map +1 -1
- package/dist/esm/webgl2/shaders/tile-chunk.frag.js +6 -0
- package/dist/esm/webgl2/shaders/tile-chunk.frag.js.map +1 -0
- package/dist/esm/webgl2/shaders/tile-chunk.vert.js +6 -0
- package/dist/esm/webgl2/shaders/tile-chunk.vert.js.map +1 -0
- package/dist/esm/webgpu/WebGpuTileChunkRenderer.d.ts +70 -4
- package/dist/esm/webgpu/WebGpuTileChunkRenderer.d.ts.map +1 -0
- package/dist/esm/webgpu/WebGpuTileChunkRenderer.js +496 -383
- package/dist/esm/webgpu/WebGpuTileChunkRenderer.js.map +1 -1
- package/dist/esm/webgpu/shaders/tile-chunk.wgsl.js +6 -0
- package/dist/esm/webgpu/shaders/tile-chunk.wgsl.js.map +1 -0
- package/package.json +8 -15
- package/dist/esm/index.js.map +0 -1
- package/dist/esm/register.d.ts +0 -1
- package/dist/esm/register.js +0 -25
- package/dist/esm/register.js.map +0 -1
|
@@ -1,390 +1,503 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { TILE_DIAGONAL_BIT, TILE_ROW_MASK } from "../tileWord.js";
|
|
2
|
+
import tile_chunk_default from "./shaders/tile-chunk.wgsl.js";
|
|
3
|
+
import { AbstractWebGpuRenderer, DataTexture, Texture, fillShaderSource, getWebGpuBlendState, packAffineMat4, packSnapViewport, packedGroupChanged, retainedGroupUniformBytes, stencilContentDepthStencilState } from "@codexo/exojs/renderer-sdk";
|
|
4
|
+
import { Matrix } from "@codexo/exojs";
|
|
2
5
|
|
|
3
|
-
|
|
6
|
+
//#region src/webgpu/WebGpuTileChunkRenderer.ts
|
|
4
7
|
const instanceStrideBytes = 32;
|
|
5
|
-
const wordsPerInstance = instanceStrideBytes / Uint32Array.BYTES_PER_ELEMENT;
|
|
6
|
-
const projectionByteLength =
|
|
8
|
+
const wordsPerInstance = instanceStrideBytes / Uint32Array.BYTES_PER_ELEMENT;
|
|
9
|
+
const projectionByteLength = 144;
|
|
7
10
|
const initialBatchCapacity = 256;
|
|
8
11
|
const indicesPerInstance = 6;
|
|
9
|
-
const quadIndices = new Uint16Array([
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
};
|
|
22
|
-
|
|
23
|
-
@group(0) @binding(0)
|
|
24
|
-
var<uniform> projection: ProjectionUniforms;
|
|
25
|
-
@group(0) @binding(1)
|
|
26
|
-
var<storage, read> transforms: array<TransformSlot>;
|
|
27
|
-
|
|
28
|
-
@group(1) @binding(0)
|
|
29
|
-
var tileTexture: texture_2d<f32>;
|
|
30
|
-
@group(1) @binding(1)
|
|
31
|
-
var tileSampler: sampler;
|
|
32
|
-
|
|
33
|
-
struct VertexInput {
|
|
34
|
-
@location(0) quadBounds: vec4<f32>, // x0, y0, x1, y1
|
|
35
|
-
@location(1) uvBounds: vec4<f32>, // uMin, vMin, uMax, vMax (flipX/Y + texture flipY baked)
|
|
36
|
-
@location(2) color: vec4<f32>, // RGBA tint (layer opacity in alpha)
|
|
37
|
-
@location(3) tileWord: u32, // transform row (bits 0..28) | diagonal (bit 29)
|
|
38
|
-
};
|
|
39
|
-
|
|
40
|
-
struct VertexOutput {
|
|
41
|
-
@builtin(position) position: vec4<f32>,
|
|
42
|
-
@location(0) texcoord: vec2<f32>,
|
|
43
|
-
@location(1) color: vec4<f32>,
|
|
44
|
-
};
|
|
45
|
-
|
|
46
|
-
@vertex
|
|
47
|
-
fn vertexMain(input: VertexInput, @builtin(vertex_index) vid: u32) -> VertexOutput {
|
|
48
|
-
var output: VertexOutput;
|
|
49
|
-
|
|
50
|
-
// vid 0..3 → TL, TR, BR, BL (matches static index buffer [0,1,2,0,2,3])
|
|
51
|
-
let cornerX = ((vid + 1u) >> 1u) & 1u;
|
|
52
|
-
let cornerY = vid >> 1u;
|
|
53
|
-
|
|
54
|
-
let localX = select(input.quadBounds.x, input.quadBounds.z, cornerX == 1u);
|
|
55
|
-
let localY = select(input.quadBounds.y, input.quadBounds.w, cornerY == 1u);
|
|
56
|
-
|
|
57
|
-
let row = input.tileWord & ${TILE_ROW_MASK}u;
|
|
58
|
-
let diagonal = (input.tileWord & ${TILE_DIAGONAL_BIT}u) != 0u;
|
|
59
|
-
|
|
60
|
-
let slot = transforms[row];
|
|
61
|
-
let worldX = slot.m0.x * localX + slot.m0.y * localY + slot.m1.x;
|
|
62
|
-
let worldY = slot.m0.z * localX + slot.m0.w * localY + slot.m1.y;
|
|
63
|
-
|
|
64
|
-
output.position = projection.matrix * vec4<f32>(worldX, worldY, 0.0, 1.0);
|
|
65
|
-
|
|
66
|
-
// Tile orientation: diagonal transposes the corner-coordinate axes; flipX/Y
|
|
67
|
-
// are baked into the UV corner ordering by the CPU writer.
|
|
68
|
-
var su = cornerX;
|
|
69
|
-
var sv = cornerY;
|
|
70
|
-
if (diagonal) {
|
|
71
|
-
let t = su;
|
|
72
|
-
su = sv;
|
|
73
|
-
sv = t;
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
let u = select(input.uvBounds.x, input.uvBounds.z, su == 1u);
|
|
77
|
-
let v = select(input.uvBounds.y, input.uvBounds.w, sv == 1u);
|
|
78
|
-
output.texcoord = vec2<f32>(u, v);
|
|
79
|
-
|
|
80
|
-
output.color = vec4(input.color.rgb * input.color.a, input.color.a);
|
|
81
|
-
|
|
82
|
-
return output;
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
@fragment
|
|
86
|
-
fn fragmentMain(input: VertexOutput) -> @location(0) vec4<f32> {
|
|
87
|
-
let sample = textureSample(tileTexture, tileSampler, input.texcoord);
|
|
88
|
-
return sample * input.color;
|
|
89
|
-
}
|
|
90
|
-
`;
|
|
12
|
+
const quadIndices = new Uint16Array([
|
|
13
|
+
0,
|
|
14
|
+
1,
|
|
15
|
+
2,
|
|
16
|
+
0,
|
|
17
|
+
2,
|
|
18
|
+
3
|
|
19
|
+
]);
|
|
20
|
+
const tileShaderSource = fillShaderSource(tile_chunk_default, {
|
|
21
|
+
tileRowMask: TILE_ROW_MASK,
|
|
22
|
+
tileDiagonalBit: TILE_DIAGONAL_BIT
|
|
23
|
+
});
|
|
91
24
|
/**
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
25
|
+
* Instanced WebGPU renderer for {@link TileChunkNode}, the parity counterpart of
|
|
26
|
+
* the WebGL2 renderer. Same instance layout and orientation handling; per-chunk
|
|
27
|
+
* transforms ride on the backend's shared transform storage buffer, and the
|
|
28
|
+
* instance buffer grows on demand rather than flushing in fixed runs.
|
|
29
|
+
* @internal
|
|
30
|
+
*/
|
|
31
|
+
var WebGpuTileChunkRenderer = class extends AbstractWebGpuRenderer {
|
|
32
|
+
/**
|
|
33
|
+
* Retained-batch capability opt-in: a tile chunk's per-flush
|
|
34
|
+
* instanced batches (fixed 32-byte layout, tile word at word 7) record and
|
|
35
|
+
* replay from group-owned resources. Pixel-snapped draws are excluded by
|
|
36
|
+
* the collect-time recordability predicate (and belt-and-braces poisoning
|
|
37
|
+
* in {@link render}); tile chunks have no custom-material path to exclude.
|
|
38
|
+
* @internal
|
|
39
|
+
*/
|
|
40
|
+
_supportsRetainedBatches = true;
|
|
41
|
+
_projectionData = new Float32Array(projectionByteLength / Float32Array.BYTES_PER_ELEMENT);
|
|
42
|
+
_writtenView = null;
|
|
43
|
+
_writtenViewUpdateId = -1;
|
|
44
|
+
_hasWrittenProjection = false;
|
|
45
|
+
_stagedGroupData = /* @__PURE__ */ new Float32Array(16);
|
|
46
|
+
_device = null;
|
|
47
|
+
_shaderModule = null;
|
|
48
|
+
_uniformBindGroupLayout = null;
|
|
49
|
+
_textureBindGroupLayout = null;
|
|
50
|
+
_pipelineLayout = null;
|
|
51
|
+
_uniformBuffer = null;
|
|
52
|
+
_transformBindGroup = null;
|
|
53
|
+
_transformStorageBuffer = null;
|
|
54
|
+
_indexBuffer = null;
|
|
55
|
+
_instanceBuffer = null;
|
|
56
|
+
_instanceBufferCapacity = 0;
|
|
57
|
+
_instanceStagingCapacity = 0;
|
|
58
|
+
_instanceData = /* @__PURE__ */ new ArrayBuffer(0);
|
|
59
|
+
_instanceFloat32 = new Float32Array(this._instanceData);
|
|
60
|
+
_instanceUint32 = new Uint32Array(this._instanceData);
|
|
61
|
+
_pipelines = /* @__PURE__ */ new Map();
|
|
62
|
+
_textureBindGroups = /* @__PURE__ */ new WeakMap();
|
|
63
|
+
_quadIndex = 0;
|
|
64
|
+
_maxNodeIndex = 0;
|
|
65
|
+
_batchNodeCount = 0;
|
|
66
|
+
_nodeBooked = false;
|
|
67
|
+
_currentBlendMode = null;
|
|
68
|
+
_currentTexture = null;
|
|
69
|
+
_passDraws = null;
|
|
70
|
+
_instancePassBytes = 0;
|
|
71
|
+
_stagedReplayGroupData = /* @__PURE__ */ new Float32Array(16);
|
|
72
|
+
_recordTextures = [null];
|
|
73
|
+
onConnect(backend) {
|
|
74
|
+
if (this._device) return;
|
|
75
|
+
this._device = backend.device;
|
|
76
|
+
this._shaderModule = this._device.createShaderModule({ code: tileShaderSource });
|
|
77
|
+
this._uniformBindGroupLayout = this._device.createBindGroupLayout({ entries: [{
|
|
78
|
+
binding: 0,
|
|
79
|
+
visibility: GPUShaderStage.VERTEX,
|
|
80
|
+
buffer: { type: "uniform" }
|
|
81
|
+
}, {
|
|
82
|
+
binding: 1,
|
|
83
|
+
visibility: GPUShaderStage.VERTEX,
|
|
84
|
+
buffer: { type: "read-only-storage" }
|
|
85
|
+
}] });
|
|
86
|
+
this._textureBindGroupLayout = this._device.createBindGroupLayout({ entries: [{
|
|
87
|
+
binding: 0,
|
|
88
|
+
visibility: GPUShaderStage.FRAGMENT,
|
|
89
|
+
texture: { sampleType: "float" }
|
|
90
|
+
}, {
|
|
91
|
+
binding: 1,
|
|
92
|
+
visibility: GPUShaderStage.FRAGMENT,
|
|
93
|
+
sampler: { type: "filtering" }
|
|
94
|
+
}] });
|
|
95
|
+
this._pipelineLayout = this._device.createPipelineLayout({ bindGroupLayouts: [this._uniformBindGroupLayout, this._textureBindGroupLayout] });
|
|
96
|
+
this._uniformBuffer = this._device.createBuffer({
|
|
97
|
+
size: projectionByteLength,
|
|
98
|
+
usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST
|
|
99
|
+
});
|
|
100
|
+
this._indexBuffer = this._device.createBuffer({
|
|
101
|
+
size: quadIndices.byteLength,
|
|
102
|
+
usage: GPUBufferUsage.INDEX | GPUBufferUsage.COPY_DST
|
|
103
|
+
});
|
|
104
|
+
this._device.queue.writeBuffer(this._indexBuffer, 0, quadIndices.buffer, quadIndices.byteOffset, quadIndices.byteLength);
|
|
105
|
+
}
|
|
106
|
+
onDisconnect() {
|
|
107
|
+
const coordinator = this._backend?._passCoordinator ?? null;
|
|
108
|
+
if (coordinator !== null && this._passDraws !== null && this._passDraws === coordinator.activePass) coordinator.endPass();
|
|
109
|
+
this._instanceBuffer?.destroy();
|
|
110
|
+
this._indexBuffer?.destroy();
|
|
111
|
+
this._uniformBuffer?.destroy();
|
|
112
|
+
this._pipelines.clear();
|
|
113
|
+
this._instanceBuffer = null;
|
|
114
|
+
this._indexBuffer = null;
|
|
115
|
+
this._transformBindGroup = null;
|
|
116
|
+
this._transformStorageBuffer = null;
|
|
117
|
+
this._textureBindGroups = /* @__PURE__ */ new WeakMap();
|
|
118
|
+
this._uniformBuffer = null;
|
|
119
|
+
this._pipelineLayout = null;
|
|
120
|
+
this._textureBindGroupLayout = null;
|
|
121
|
+
this._uniformBindGroupLayout = null;
|
|
122
|
+
this._shaderModule = null;
|
|
123
|
+
this._device = null;
|
|
124
|
+
this._backend = null;
|
|
125
|
+
this._instanceBufferCapacity = 0;
|
|
126
|
+
this._instanceStagingCapacity = 0;
|
|
127
|
+
this._instanceData = /* @__PURE__ */ new ArrayBuffer(0);
|
|
128
|
+
this._instanceFloat32 = new Float32Array(this._instanceData);
|
|
129
|
+
this._instanceUint32 = new Uint32Array(this._instanceData);
|
|
130
|
+
this._passDraws = null;
|
|
131
|
+
this._instancePassBytes = 0;
|
|
132
|
+
this._quadIndex = 0;
|
|
133
|
+
this._maxNodeIndex = 0;
|
|
134
|
+
this._batchNodeCount = 0;
|
|
135
|
+
this._nodeBooked = false;
|
|
136
|
+
this._currentBlendMode = null;
|
|
137
|
+
this._currentTexture = null;
|
|
138
|
+
this._writtenView = null;
|
|
139
|
+
this._writtenViewUpdateId = -1;
|
|
140
|
+
this._hasWrittenProjection = false;
|
|
141
|
+
}
|
|
142
|
+
render(node) {
|
|
143
|
+
const backend = this._backend;
|
|
144
|
+
if (backend === null) return;
|
|
145
|
+
const pages = node.pages;
|
|
146
|
+
this._nodeBooked = false;
|
|
147
|
+
if (pages.length === 0) return;
|
|
148
|
+
const blendMode = node.blendMode;
|
|
149
|
+
const tintRgba = node.tint.toRgba8();
|
|
150
|
+
const command = backend.activeDrawCommand;
|
|
151
|
+
const nodeIndex = command !== null ? command.nodeIndex : backend._pushTransform(node);
|
|
152
|
+
for (const page of pages) this._renderPage(backend, page.texture, page.quads, blendMode, tintRgba, nodeIndex);
|
|
153
|
+
}
|
|
154
|
+
_renderPage(backend, texture, quads, blendMode, tintRgba, nodeIndex) {
|
|
155
|
+
if (quads.length === 0) return;
|
|
156
|
+
if (texture.width === 0 || texture.height === 0) return;
|
|
157
|
+
if (texture instanceof Texture && !(texture instanceof DataTexture) && texture.source === null) return;
|
|
158
|
+
const blendModeChanged = this._currentBlendMode !== null && blendMode !== this._currentBlendMode;
|
|
159
|
+
const textureChanged = this._currentTexture !== null && texture !== this._currentTexture;
|
|
160
|
+
const willExceed = this._quadIndex + quads.length > this._instanceStagingCapacity && this._instanceStagingCapacity > 0;
|
|
161
|
+
if ((blendModeChanged || textureChanged || willExceed) && this._quadIndex > 0) this.flush();
|
|
162
|
+
this._currentBlendMode = blendMode;
|
|
163
|
+
this._currentTexture = texture;
|
|
164
|
+
backend.setBlendMode(blendMode);
|
|
165
|
+
this._ensureStagingCapacity(this._quadIndex + quads.length);
|
|
166
|
+
if (!this._nodeBooked) {
|
|
167
|
+
this._nodeBooked = true;
|
|
168
|
+
this._batchNodeCount++;
|
|
169
|
+
}
|
|
170
|
+
const f32 = this._instanceFloat32;
|
|
171
|
+
const u32 = this._instanceUint32;
|
|
172
|
+
const flipYTexture = texture.flipY;
|
|
173
|
+
const baseWord = nodeIndex & TILE_ROW_MASK;
|
|
174
|
+
for (const q of quads) {
|
|
175
|
+
const idx = this._quadIndex * wordsPerInstance;
|
|
176
|
+
f32[idx + 0] = q.x0;
|
|
177
|
+
f32[idx + 1] = q.y0;
|
|
178
|
+
f32[idx + 2] = q.x1;
|
|
179
|
+
f32[idx + 3] = q.y1;
|
|
180
|
+
const flipX = (q.orient & 1) !== 0;
|
|
181
|
+
const tileFlipY = (q.orient & 2) !== 0;
|
|
182
|
+
const diagonal = (q.orient & 4) !== 0;
|
|
183
|
+
const uA = flipX ? q.u1 : q.u0;
|
|
184
|
+
const uB = flipX ? q.u0 : q.u1;
|
|
185
|
+
let vA = tileFlipY ? q.v1 : q.v0;
|
|
186
|
+
let vB = tileFlipY ? q.v0 : q.v1;
|
|
187
|
+
if (flipYTexture) {
|
|
188
|
+
const swap = vA;
|
|
189
|
+
vA = vB;
|
|
190
|
+
vB = swap;
|
|
191
|
+
}
|
|
192
|
+
const uMin = uA * 65535 & 65535;
|
|
193
|
+
const vMin = vA * 65535 & 65535;
|
|
194
|
+
const uMax = uB * 65535 & 65535;
|
|
195
|
+
const vMax = vB * 65535 & 65535;
|
|
196
|
+
u32[idx + 4] = uMin | vMin << 16;
|
|
197
|
+
u32[idx + 5] = uMax | vMax << 16;
|
|
198
|
+
u32[idx + 6] = tintRgba;
|
|
199
|
+
u32[idx + 7] = (diagonal ? baseWord | TILE_DIAGONAL_BIT : baseWord) >>> 0;
|
|
200
|
+
this._quadIndex++;
|
|
201
|
+
}
|
|
202
|
+
if (nodeIndex > this._maxNodeIndex) this._maxNodeIndex = nodeIndex;
|
|
203
|
+
}
|
|
204
|
+
flush() {
|
|
205
|
+
const backend = this._backend;
|
|
206
|
+
const device = this._device;
|
|
207
|
+
const uniformBuffer = this._uniformBuffer;
|
|
208
|
+
if (!backend || !device || !uniformBuffer) return;
|
|
209
|
+
if (this._quadIndex === 0 && !backend.clearRequested) return;
|
|
210
|
+
const view = backend.view;
|
|
211
|
+
const viewportChanged = packSnapViewport(backend, this._projectionData, 32);
|
|
212
|
+
const projectionChanged = !this._hasWrittenProjection || this._writtenView !== view || this._writtenViewUpdateId !== view.updateId || viewportChanged || this._groupContentChanged(backend);
|
|
213
|
+
const scissor = backend.getScissorRect();
|
|
214
|
+
const maskClipsAll = scissor !== null && (scissor.width <= 0 || scissor.height <= 0);
|
|
215
|
+
const coordinator = backend._passCoordinator;
|
|
216
|
+
const texture = this._currentTexture;
|
|
217
|
+
const blendMode = this._currentBlendMode;
|
|
218
|
+
const indexBuffer = this._indexBuffer;
|
|
219
|
+
const willDraw = this._quadIndex > 0 && !maskClipsAll && texture !== null && blendMode !== null && indexBuffer !== null;
|
|
220
|
+
const flushBytes = this._quadIndex * instanceStrideBytes;
|
|
221
|
+
const targetInstanceBytes = this._instancePassBytes + flushBytes;
|
|
222
|
+
if (this._passDraws !== null && this._passDraws === coordinator.activePass && (projectionChanged || willDraw && targetInstanceBytes > this._instanceBufferCapacity) || willDraw && coordinator.passHasDraws && (backend._textureUploadWouldMutate(texture) || backend._transformStorageWouldGrow(this._maxNodeIndex + 1))) coordinator.endPass();
|
|
223
|
+
if (this._passDraws !== coordinator.activePass) {
|
|
224
|
+
this._passDraws = null;
|
|
225
|
+
this._instancePassBytes = 0;
|
|
226
|
+
}
|
|
227
|
+
if (projectionChanged) {
|
|
228
|
+
packAffineMat4(view.getTransform(), this._projectionData, 0);
|
|
229
|
+
packAffineMat4(backend.renderGroupTransform ?? Matrix.identity, this._projectionData, 16);
|
|
230
|
+
this._writtenView = view;
|
|
231
|
+
this._writtenViewUpdateId = view.updateId;
|
|
232
|
+
this._hasWrittenProjection = true;
|
|
233
|
+
device.queue.writeBuffer(uniformBuffer, 0, this._projectionData.buffer, this._projectionData.byteOffset, this._projectionData.byteLength);
|
|
234
|
+
}
|
|
235
|
+
if (willDraw || backend.clearRequested) {
|
|
236
|
+
const active = coordinator.acquirePass();
|
|
237
|
+
const pass = active.pass;
|
|
238
|
+
if (willDraw) {
|
|
239
|
+
const instanceBuffer = this._ensureInstanceBufferCapacity(device, targetInstanceBytes);
|
|
240
|
+
const instanceByteOffset = this._instancePassBytes;
|
|
241
|
+
device.queue.writeBuffer(instanceBuffer, instanceByteOffset, this._instanceData, 0, flushBytes);
|
|
242
|
+
const storage = backend.getTransformStorageBuffer(this._maxNodeIndex + 1);
|
|
243
|
+
const transformBindGroup = this._getOrCreateTransformBindGroup(device, uniformBuffer, storage.buffer);
|
|
244
|
+
const textureBindGroup = this._getOrCreateTextureBindGroup(device, backend, texture);
|
|
245
|
+
const stencil = coordinator.stencilActive;
|
|
246
|
+
const pipeline = this._getPipeline(blendMode, backend.renderTargetFormat, stencil);
|
|
247
|
+
pass.setPipeline(pipeline);
|
|
248
|
+
pass.setBindGroup(0, transformBindGroup);
|
|
249
|
+
pass.setBindGroup(1, textureBindGroup);
|
|
250
|
+
pass.setVertexBuffer(0, instanceBuffer, instanceByteOffset);
|
|
251
|
+
pass.setIndexBuffer(indexBuffer, "uint16");
|
|
252
|
+
pass.drawIndexed(indicesPerInstance, this._quadIndex, 0, 0, 0);
|
|
253
|
+
this._instancePassBytes = instanceByteOffset + flushBytes;
|
|
254
|
+
this._passDraws = active;
|
|
255
|
+
coordinator.markPassDraws();
|
|
256
|
+
backend.stats.batches++;
|
|
257
|
+
backend.stats.drawCalls++;
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
if (this._quadIndex > 0 && backend._retainedCaptureActive && blendMode !== null && texture !== null) {
|
|
261
|
+
this._recordTextures[0] = texture;
|
|
262
|
+
backend._recordRetainedBatch(this, this._instanceData, flushBytes, this._quadIndex, blendMode, this._recordTextures, 1, null, null, this._batchNodeCount);
|
|
263
|
+
}
|
|
264
|
+
this._quadIndex = 0;
|
|
265
|
+
this._maxNodeIndex = 0;
|
|
266
|
+
this._batchNodeCount = 0;
|
|
267
|
+
this._currentBlendMode = null;
|
|
268
|
+
this._currentTexture = null;
|
|
269
|
+
}
|
|
270
|
+
/**
|
|
271
|
+
* Whether the packed floats of the active group matrix differ from what the
|
|
272
|
+
* shared projection UBO currently holds at [16, 32). Stages the packed matrix
|
|
273
|
+
* into `_stagedGroupData` as a side effect (idempotent - safe to call more
|
|
274
|
+
* than once per flush).
|
|
275
|
+
*/
|
|
276
|
+
_groupContentChanged(backend) {
|
|
277
|
+
packAffineMat4(backend.renderGroupTransform ?? Matrix.identity, this._stagedGroupData, 0);
|
|
278
|
+
if (!this._hasWrittenProjection) return true;
|
|
279
|
+
return packedGroupChanged(this._stagedGroupData, this._projectionData, 16);
|
|
280
|
+
}
|
|
281
|
+
destroy() {
|
|
282
|
+
this.disconnect();
|
|
283
|
+
}
|
|
284
|
+
_getOrCreateTransformBindGroup(device, uniformBuffer, storageBuffer) {
|
|
285
|
+
if (this._transformBindGroup !== null && this._transformStorageBuffer === storageBuffer) return this._transformBindGroup;
|
|
286
|
+
this._transformStorageBuffer = storageBuffer;
|
|
287
|
+
this._transformBindGroup = device.createBindGroup({
|
|
288
|
+
layout: this._uniformBindGroupLayout,
|
|
289
|
+
entries: [{
|
|
290
|
+
binding: 0,
|
|
291
|
+
resource: { buffer: uniformBuffer }
|
|
292
|
+
}, {
|
|
293
|
+
binding: 1,
|
|
294
|
+
resource: { buffer: storageBuffer }
|
|
295
|
+
}]
|
|
296
|
+
});
|
|
297
|
+
return this._transformBindGroup;
|
|
298
|
+
}
|
|
299
|
+
/**
|
|
300
|
+
* Build (or reuse) the group(1) texture bind group for `texture`. The
|
|
301
|
+
* binding is resolved BEFORE the cache lookup on purpose: resolving is what
|
|
302
|
+
* syncs a dirty/mutated texture's content to the GPU, so it must run every
|
|
303
|
+
* flush/replay even when the bind group itself is served from cache.
|
|
304
|
+
*/
|
|
305
|
+
_getOrCreateTextureBindGroup(device, backend, texture) {
|
|
306
|
+
const { view, sampler } = backend.getTextureBinding(texture);
|
|
307
|
+
const cached = this._textureBindGroups.get(texture);
|
|
308
|
+
if (cached?.view === view && cached.sampler === sampler) return cached.group;
|
|
309
|
+
const group = device.createBindGroup({
|
|
310
|
+
layout: this._textureBindGroupLayout,
|
|
311
|
+
entries: [{
|
|
312
|
+
binding: 0,
|
|
313
|
+
resource: view
|
|
314
|
+
}, {
|
|
315
|
+
binding: 1,
|
|
316
|
+
resource: sampler
|
|
317
|
+
}]
|
|
318
|
+
});
|
|
319
|
+
this._textureBindGroups.set(texture, {
|
|
320
|
+
group,
|
|
321
|
+
view,
|
|
322
|
+
sampler
|
|
323
|
+
});
|
|
324
|
+
return group;
|
|
325
|
+
}
|
|
326
|
+
/** @internal See {@link WebGpuRetainedBatchReplayer._scanRetainedNodeIndexRange}. */
|
|
327
|
+
_scanRetainedNodeIndexRange(bytes, range) {
|
|
328
|
+
const words = new Uint32Array(bytes.buffer, bytes.byteOffset, bytes.byteLength / Uint32Array.BYTES_PER_ELEMENT);
|
|
329
|
+
for (let i = 7; i < words.length; i += 8) {
|
|
330
|
+
const row = words[i] & TILE_ROW_MASK;
|
|
331
|
+
if (row < range.min) range.min = row;
|
|
332
|
+
if (row > range.max) range.max = row;
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
/** @internal See {@link WebGpuRetainedBatchReplayer._rebaseRetainedNodeIndices} (group-local indices). */
|
|
336
|
+
_rebaseRetainedNodeIndices(bytes, base) {
|
|
337
|
+
const words = new Uint32Array(bytes.buffer, bytes.byteOffset, bytes.byteLength / Uint32Array.BYTES_PER_ELEMENT);
|
|
338
|
+
for (let i = 7; i < words.length; i += 8) {
|
|
339
|
+
const word = words[i];
|
|
340
|
+
const diagonal = word & TILE_DIAGONAL_BIT;
|
|
341
|
+
const row = word & TILE_ROW_MASK;
|
|
342
|
+
words[i] = (diagonal | row - base & TILE_ROW_MASK) >>> 0;
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
/**
|
|
346
|
+
* Replay one recorded batch from its group-owned bundle into the OPEN pass.
|
|
347
|
+
* Reuses only recorded DATA (instance bytes, transform rows, texture, blend
|
|
348
|
+
* mode); every piece of STATE is resolved live - pipeline via the
|
|
349
|
+
* `_getPipeline` cache, the group(1) texture bind group via the live
|
|
350
|
+
* texture-set cache (resolving re-syncs dirty content), and the group's
|
|
351
|
+
* 128-byte UBO (projection from the live view + the live composed group
|
|
352
|
+
* matrix) written only when its content changed. The same-frame
|
|
353
|
+
* double-replay hazard (one group under two views while the open pass
|
|
354
|
+
* already holds this bundle's draws) ends the pass first.
|
|
355
|
+
* @internal
|
|
356
|
+
*/
|
|
357
|
+
_replayRetainedBatch(payload) {
|
|
358
|
+
const backend = this._backend;
|
|
359
|
+
const device = this._device;
|
|
360
|
+
const bundle = payload.bundle;
|
|
361
|
+
if (!backend || !device || this._indexBuffer === null || !bundle.isReady) return;
|
|
362
|
+
this.flush();
|
|
363
|
+
const scissor = backend.getScissorRect();
|
|
364
|
+
if (scissor !== null && (scissor.width <= 0 || scissor.height <= 0)) return;
|
|
365
|
+
const coordinator = backend._passCoordinator;
|
|
366
|
+
if (coordinator.passHasDraws) {
|
|
367
|
+
for (const texture of payload.textures) if (backend._textureUploadWouldMutate(texture)) {
|
|
368
|
+
coordinator.endPass();
|
|
369
|
+
break;
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
const textureBindGroup = this._getOrCreateTextureBindGroup(device, backend, payload.textures[0]);
|
|
373
|
+
const view = backend.view;
|
|
374
|
+
const scratch = this._stagedReplayGroupData;
|
|
375
|
+
packAffineMat4(backend.renderGroupTransform ?? Matrix.identity, scratch, 0);
|
|
376
|
+
const viewportChanged = packSnapViewport(backend, bundle.uboData, 32);
|
|
377
|
+
let uboDirty = !bundle.uboWritten || bundle.uboView !== view || bundle.uboViewUpdateId !== view.updateId || viewportChanged;
|
|
378
|
+
if (!uboDirty) {
|
|
379
|
+
for (let i = 0; i < 16; i++) if (scratch[i] !== bundle.uboData[16 + i]) {
|
|
380
|
+
uboDirty = true;
|
|
381
|
+
break;
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
if (uboDirty) {
|
|
385
|
+
const activePass = coordinator.activePass;
|
|
386
|
+
if (activePass !== null && bundle.drawsInPass === activePass) coordinator.endPass();
|
|
387
|
+
packAffineMat4(view.getTransform(), bundle.uboData, 0);
|
|
388
|
+
bundle.uboData.set(scratch, 16);
|
|
389
|
+
bundle.uboView = view;
|
|
390
|
+
bundle.uboViewUpdateId = view.updateId;
|
|
391
|
+
bundle.uboWritten = true;
|
|
392
|
+
device.queue.writeBuffer(bundle.uniformBuffer, 0, bundle.uboData.buffer, bundle.uboData.byteOffset, retainedGroupUniformBytes);
|
|
393
|
+
}
|
|
394
|
+
const active = coordinator.acquirePass();
|
|
395
|
+
const pass = active.pass;
|
|
396
|
+
pass.setPipeline(this._getPipeline(payload.blendMode, backend.renderTargetFormat, coordinator.stencilActive));
|
|
397
|
+
pass.setBindGroup(0, bundle.getBindGroup(device, this._uniformBindGroupLayout, false));
|
|
398
|
+
pass.setBindGroup(1, textureBindGroup);
|
|
399
|
+
pass.setVertexBuffer(0, bundle.instanceBuffer, payload.byteOffset);
|
|
400
|
+
pass.setIndexBuffer(this._indexBuffer, "uint16");
|
|
401
|
+
pass.drawIndexed(indicesPerInstance, payload.instanceCount, 0, 0, 0);
|
|
402
|
+
bundle.drawsInPass = active;
|
|
403
|
+
coordinator.markPassDraws();
|
|
404
|
+
backend.stats.batches++;
|
|
405
|
+
backend.stats.drawCalls++;
|
|
406
|
+
}
|
|
407
|
+
_getPipeline(blendMode, format, stencil) {
|
|
408
|
+
const key = `${blendMode}:${format}:${stencil ? "s" : "n"}`;
|
|
409
|
+
const existing = this._pipelines.get(key);
|
|
410
|
+
if (existing) return existing;
|
|
411
|
+
if (!this._device || !this._shaderModule || !this._pipelineLayout) throw new Error("WebGpuTileChunkRenderer: renderer must be connected first.");
|
|
412
|
+
const descriptor = {
|
|
413
|
+
layout: this._pipelineLayout,
|
|
414
|
+
vertex: {
|
|
415
|
+
module: this._shaderModule,
|
|
416
|
+
entryPoint: "vertexMain",
|
|
417
|
+
buffers: [{
|
|
418
|
+
arrayStride: instanceStrideBytes,
|
|
419
|
+
stepMode: "instance",
|
|
420
|
+
attributes: [
|
|
421
|
+
{
|
|
422
|
+
shaderLocation: 0,
|
|
423
|
+
offset: 0,
|
|
424
|
+
format: "float32x4"
|
|
425
|
+
},
|
|
426
|
+
{
|
|
427
|
+
shaderLocation: 1,
|
|
428
|
+
offset: 16,
|
|
429
|
+
format: "unorm16x4"
|
|
430
|
+
},
|
|
431
|
+
{
|
|
432
|
+
shaderLocation: 2,
|
|
433
|
+
offset: 24,
|
|
434
|
+
format: "unorm8x4"
|
|
435
|
+
},
|
|
436
|
+
{
|
|
437
|
+
shaderLocation: 3,
|
|
438
|
+
offset: 28,
|
|
439
|
+
format: "uint32"
|
|
440
|
+
}
|
|
441
|
+
]
|
|
442
|
+
}]
|
|
443
|
+
},
|
|
444
|
+
fragment: {
|
|
445
|
+
module: this._shaderModule,
|
|
446
|
+
entryPoint: "fragmentMain",
|
|
447
|
+
targets: [{
|
|
448
|
+
format,
|
|
449
|
+
blend: getWebGpuBlendState(blendMode),
|
|
450
|
+
writeMask: GPUColorWrite.ALL
|
|
451
|
+
}]
|
|
452
|
+
},
|
|
453
|
+
primitive: { topology: "triangle-list" }
|
|
454
|
+
};
|
|
455
|
+
if (stencil) descriptor.depthStencil = stencilContentDepthStencilState();
|
|
456
|
+
const pipeline = this._device.createRenderPipeline(descriptor);
|
|
457
|
+
this._pipelines.set(key, pipeline);
|
|
458
|
+
return pipeline;
|
|
459
|
+
}
|
|
460
|
+
/**
|
|
461
|
+
* Grow the CPU staging array to hold `instanceCount` instances, carrying the
|
|
462
|
+
* quads packed so far. Flush-local: the array only ever holds the pending
|
|
463
|
+
* batch, so it is sized independently of the pass-scoped GPU buffer.
|
|
464
|
+
*/
|
|
465
|
+
_ensureStagingCapacity(instanceCount) {
|
|
466
|
+
if (instanceCount <= this._instanceStagingCapacity) return;
|
|
467
|
+
let nextCapacity = Math.max(this._instanceStagingCapacity, initialBatchCapacity);
|
|
468
|
+
while (nextCapacity < instanceCount) nextCapacity *= 2;
|
|
469
|
+
const oldData = this._instanceData;
|
|
470
|
+
const carryBytes = Math.min(this._quadIndex * instanceStrideBytes, oldData.byteLength);
|
|
471
|
+
const instanceData = /* @__PURE__ */ new ArrayBuffer(nextCapacity * instanceStrideBytes);
|
|
472
|
+
if (carryBytes > 0) new Uint8Array(instanceData).set(new Uint8Array(oldData, 0, carryBytes));
|
|
473
|
+
this._instanceStagingCapacity = nextCapacity;
|
|
474
|
+
this._instanceData = instanceData;
|
|
475
|
+
this._instanceFloat32 = new Float32Array(instanceData);
|
|
476
|
+
this._instanceUint32 = new Uint32Array(instanceData);
|
|
477
|
+
}
|
|
478
|
+
/**
|
|
479
|
+
* Resolve the GPU instance buffer for `requiredBytes` - the total this pass
|
|
480
|
+
* would reach by appending, not this flush's own bytes. Reallocation frees the
|
|
481
|
+
* buffer earlier draws in the open pass read, so the caller must have ended
|
|
482
|
+
* that pass (and restarted the cursor) before a growing call. Capacity
|
|
483
|
+
* doubles, so the split a growth costs converges away within a frame or two.
|
|
484
|
+
*/
|
|
485
|
+
_ensureInstanceBufferCapacity(device, requiredBytes) {
|
|
486
|
+
const existing = this._instanceBuffer;
|
|
487
|
+
if (existing !== null && requiredBytes <= this._instanceBufferCapacity) return existing;
|
|
488
|
+
const nextCapacity = Math.max(this._instanceBufferCapacity * 2, requiredBytes, initialBatchCapacity * instanceStrideBytes);
|
|
489
|
+
const instanceBuffer = device.createBuffer({
|
|
490
|
+
label: "tile-chunk:instance-buffer",
|
|
491
|
+
size: nextCapacity,
|
|
492
|
+
usage: GPUBufferUsage.VERTEX | GPUBufferUsage.COPY_DST
|
|
493
|
+
});
|
|
494
|
+
existing?.destroy();
|
|
495
|
+
this._instanceBufferCapacity = nextCapacity;
|
|
496
|
+
this._instanceBuffer = instanceBuffer;
|
|
497
|
+
return instanceBuffer;
|
|
498
|
+
}
|
|
499
|
+
};
|
|
388
500
|
|
|
501
|
+
//#endregion
|
|
389
502
|
export { WebGpuTileChunkRenderer };
|
|
390
|
-
//# sourceMappingURL=WebGpuTileChunkRenderer.js.map
|
|
503
|
+
//# sourceMappingURL=WebGpuTileChunkRenderer.js.map
|