@es3d/es3d 1.2.0 → 1.2.1
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/browser/es3d-stage.min.js +32 -32
- package/browser/es3d.min.js +73 -73
- package/package.json +1 -1
- package/src/render/geometry-pool.js +103 -41
- package/src/render/webgl2/webgl2-backend.js +68 -16
- package/src/render/webgpu/webgpu-backend.js +42 -11
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@es3d/es3d",
|
|
3
|
-
"version": "1.2.
|
|
3
|
+
"version": "1.2.1",
|
|
4
4
|
"description": "ES3D (Extensible Scalable 3D): a declarative, SVG-inspired 3D scene format with a WebGPU/WebGL2 reference renderer, editor and converter.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "Apache-2.0",
|
|
@@ -7,26 +7,93 @@
|
|
|
7
7
|
// vertex. That keeps the WebGL2 and WebGPU paths the same.
|
|
8
8
|
//
|
|
9
9
|
// The pool is CPU-side; backends upload the ranges appended since their last
|
|
10
|
-
// sync
|
|
10
|
+
// sync. The CPU copy is kept so a lost GPU context can be rebuilt.
|
|
11
|
+
//
|
|
12
|
+
// Growing and resetting are different events:
|
|
13
|
+
// - Growing keeps every byte and adds room. The CPU side adds a chunk (old
|
|
14
|
+
// chunks are never copied), and backends grow their buffers with a GPU-side
|
|
15
|
+
// copy of what they already hold, then upload only what is new. Vertex and
|
|
16
|
+
// index storage grow independently.
|
|
17
|
+
// - Resetting (reset(), a repack) throws the content away. It bumps
|
|
18
|
+
// `generation`, and backends reallocate and upload from zero.
|
|
11
19
|
|
|
12
20
|
export const VERTEX_FLOATS = 8; // position xyz, normal xyz, uv
|
|
13
21
|
export const VERTEX_BYTES = VERTEX_FLOATS * 4;
|
|
14
22
|
|
|
23
|
+
/**
|
|
24
|
+
* A growable typed array stored as chunks. Element `i` of the whole array is
|
|
25
|
+
* in the chunk whose `start <= i < start + length`. Growing appends a chunk
|
|
26
|
+
* and never moves existing data.
|
|
27
|
+
*/
|
|
28
|
+
class ChunkedArray {
|
|
29
|
+
constructor(Type, stride, capacity) {
|
|
30
|
+
this.Type = Type;
|
|
31
|
+
this.stride = stride; // array entries per element (8 floats per vertex, 1 per index)
|
|
32
|
+
this.chunks = [{ start: 0, length: capacity, data: new Type(capacity * stride) }];
|
|
33
|
+
this.capacity = capacity; // elements
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/** Make room for `count` elements in total. Returns true when it grew. */
|
|
37
|
+
reserve(count) {
|
|
38
|
+
if (count <= this.capacity) return false;
|
|
39
|
+
let cap = Math.max(1, this.capacity); // a zero capacity would never double
|
|
40
|
+
while (cap < count) cap *= 2;
|
|
41
|
+
const length = cap - this.capacity;
|
|
42
|
+
this.chunks.push({ start: this.capacity, length, data: new this.Type(length * this.stride) });
|
|
43
|
+
this.capacity = cap;
|
|
44
|
+
return true;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Visit the parts of elements [start, end) chunk by chunk:
|
|
49
|
+
* fn(firstElement, view), where view is the chunk's typed array restricted
|
|
50
|
+
* to that part.
|
|
51
|
+
*/
|
|
52
|
+
forEach(start, end, fn) {
|
|
53
|
+
const s = this.stride;
|
|
54
|
+
for (const c of this.chunks) {
|
|
55
|
+
const a = Math.max(start, c.start);
|
|
56
|
+
const b = Math.min(end, c.start + c.length);
|
|
57
|
+
if (a < b) fn(a, c.data.subarray((a - c.start) * s, (b - c.start) * s));
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
15
62
|
export class GeometryPool {
|
|
16
63
|
constructor(vertexCapacity = 1 << 16, indexCapacity = 1 << 17) {
|
|
17
|
-
this.
|
|
18
|
-
this.
|
|
64
|
+
this.vertexStore = new ChunkedArray(Float32Array, VERTEX_FLOATS, vertexCapacity);
|
|
65
|
+
this.indexStore = new ChunkedArray(Uint32Array, 1, indexCapacity);
|
|
19
66
|
this.vertexCount = 0;
|
|
20
67
|
this.indexCount = 0;
|
|
21
68
|
this.ranges = new Map(); // key object -> {firstIndex, indexCount}
|
|
22
69
|
this.edgeRanges = new Map();
|
|
23
70
|
this.normalRanges = new Map();
|
|
24
71
|
// dirty tracking for incremental uploads
|
|
25
|
-
this.generation = 0; // bumps on
|
|
72
|
+
this.generation = 0; // bumps when the content is thrown away (reset, repack); NOT on growth
|
|
26
73
|
this.uploadedVertices = 0;
|
|
27
74
|
this.uploadedIndices = 0;
|
|
28
75
|
}
|
|
29
76
|
|
|
77
|
+
/** Vertex capacity in vertices. Backends size their vertex buffer to at least this. */
|
|
78
|
+
get vertexCapacity() {
|
|
79
|
+
return this.vertexStore.capacity;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/** Index capacity in indices. */
|
|
83
|
+
get indexCapacity() {
|
|
84
|
+
return this.indexStore.capacity;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/** Capacity in bytes of the vertex storage. */
|
|
88
|
+
get vertexCapacityBytes() {
|
|
89
|
+
return this.vertexStore.capacity * VERTEX_BYTES;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/** Capacity in bytes of the index storage. */
|
|
93
|
+
get indexCapacityBytes() {
|
|
94
|
+
return this.indexStore.capacity * 4;
|
|
95
|
+
}
|
|
96
|
+
|
|
30
97
|
reset() {
|
|
31
98
|
this.vertexCount = 0;
|
|
32
99
|
this.indexCount = 0;
|
|
@@ -42,60 +109,55 @@ export class GeometryPool {
|
|
|
42
109
|
return this.vertexCount * VERTEX_BYTES + this.indexCount * 4;
|
|
43
110
|
}
|
|
44
111
|
|
|
112
|
+
/** Make room for more vertices and indices. Growing keeps what is stored and uploaded. */
|
|
45
113
|
ensure(extraV, extraI) {
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
let cap = this.vertices.length / VERTEX_FLOATS;
|
|
49
|
-
while (cap < this.vertexCount + extraV) cap *= 2;
|
|
50
|
-
const v = new Float32Array(cap * VERTEX_FLOATS);
|
|
51
|
-
v.set(this.vertices.subarray(0, this.vertexCount * VERTEX_FLOATS));
|
|
52
|
-
this.vertices = v;
|
|
53
|
-
grew = true;
|
|
54
|
-
}
|
|
55
|
-
if (this.indexCount + extraI > this.indices.length) {
|
|
56
|
-
let cap = this.indices.length;
|
|
57
|
-
while (cap < this.indexCount + extraI) cap *= 2;
|
|
58
|
-
const i = new Uint32Array(cap);
|
|
59
|
-
i.set(this.indices.subarray(0, this.indexCount));
|
|
60
|
-
this.indices = i;
|
|
61
|
-
grew = true;
|
|
62
|
-
}
|
|
63
|
-
if (grew) {
|
|
64
|
-
this.generation++;
|
|
65
|
-
this.uploadedVertices = 0;
|
|
66
|
-
this.uploadedIndices = 0;
|
|
67
|
-
}
|
|
114
|
+
this.vertexStore.reserve(this.vertexCount + extraV);
|
|
115
|
+
this.indexStore.reserve(this.indexCount + extraI);
|
|
68
116
|
}
|
|
69
117
|
|
|
70
118
|
appendVertices(mesh) {
|
|
71
119
|
const n = mesh.vertexCount;
|
|
72
120
|
const base = this.vertexCount;
|
|
73
|
-
const v = this.vertices;
|
|
74
121
|
const p = mesh.positions, nr = mesh.normals, t = mesh.uvs;
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
122
|
+
this.vertexStore.forEach(base, base + n, (first, v) => {
|
|
123
|
+
const count = v.length / VERTEX_FLOATS;
|
|
124
|
+
let o = 0;
|
|
125
|
+
for (let i = first - base, end = i + count; i < end; i++) {
|
|
126
|
+
v[o] = p[i * 3];
|
|
127
|
+
v[o + 1] = p[i * 3 + 1];
|
|
128
|
+
v[o + 2] = p[i * 3 + 2];
|
|
129
|
+
v[o + 3] = nr[i * 3];
|
|
130
|
+
v[o + 4] = nr[i * 3 + 1];
|
|
131
|
+
v[o + 5] = nr[i * 3 + 2];
|
|
132
|
+
v[o + 6] = t[i * 2];
|
|
133
|
+
v[o + 7] = t[i * 2 + 1];
|
|
134
|
+
o += VERTEX_FLOATS;
|
|
135
|
+
}
|
|
136
|
+
});
|
|
87
137
|
this.vertexCount += n;
|
|
88
138
|
return base;
|
|
89
139
|
}
|
|
90
140
|
|
|
91
141
|
appendIndices(src, base) {
|
|
92
142
|
const first = this.indexCount;
|
|
93
|
-
|
|
94
|
-
|
|
143
|
+
this.indexStore.forEach(first, first + src.length, (at, dst) => {
|
|
144
|
+
const off = at - first;
|
|
145
|
+
for (let i = 0; i < dst.length; i++) dst[i] = src[off + i] + base;
|
|
146
|
+
});
|
|
95
147
|
this.indexCount += src.length;
|
|
96
148
|
return first;
|
|
97
149
|
}
|
|
98
150
|
|
|
151
|
+
/** Visit stored vertices [start, end) chunk by chunk: fn(firstVertex, Float32Array). */
|
|
152
|
+
forEachVertices(start, end, fn) {
|
|
153
|
+
this.vertexStore.forEach(start, end, fn);
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/** Visit stored indices [start, end) chunk by chunk: fn(firstIndex, Uint32Array). */
|
|
157
|
+
forEachIndices(start, end, fn) {
|
|
158
|
+
this.indexStore.forEach(start, end, fn);
|
|
159
|
+
}
|
|
160
|
+
|
|
99
161
|
/** Range of a mesh's primitives (triangles or lines). */
|
|
100
162
|
meshRange(mesh) {
|
|
101
163
|
let r = this.ranges.get(mesh);
|
|
@@ -171,12 +171,8 @@ export class WebGL2Backend {
|
|
|
171
171
|
this.vao = gl.createVertexArray();
|
|
172
172
|
gl.bindVertexArray(this.vao);
|
|
173
173
|
gl.bindBuffer(gl.ARRAY_BUFFER, this.vertexBuffer);
|
|
174
|
-
gl.enableVertexAttribArray(
|
|
175
|
-
|
|
176
|
-
gl.enableVertexAttribArray(1);
|
|
177
|
-
gl.vertexAttribPointer(1, 3, gl.FLOAT, false, VERTEX_BYTES, 12);
|
|
178
|
-
gl.enableVertexAttribArray(2);
|
|
179
|
-
gl.vertexAttribPointer(2, 2, gl.FLOAT, false, VERTEX_BYTES, 24);
|
|
174
|
+
for (let k = 0; k < 3; k++) gl.enableVertexAttribArray(k);
|
|
175
|
+
this.bindVertexAttributes();
|
|
180
176
|
gl.bindBuffer(gl.ARRAY_BUFFER, this.instanceBuffer);
|
|
181
177
|
for (let k = 0; k < 4; k++) {
|
|
182
178
|
gl.enableVertexAttribArray(3 + k);
|
|
@@ -215,14 +211,28 @@ export class WebGL2Backend {
|
|
|
215
211
|
this.gl.deleteTexture(t);
|
|
216
212
|
}
|
|
217
213
|
|
|
218
|
-
/**
|
|
214
|
+
/** Point the VAO's vertex attributes at the vertex buffer bound to ARRAY_BUFFER. */
|
|
215
|
+
bindVertexAttributes() {
|
|
216
|
+
const gl = this.gl;
|
|
217
|
+
gl.vertexAttribPointer(0, 3, gl.FLOAT, false, VERTEX_BYTES, 0);
|
|
218
|
+
gl.vertexAttribPointer(1, 3, gl.FLOAT, false, VERTEX_BYTES, 12);
|
|
219
|
+
gl.vertexAttribPointer(2, 2, gl.FLOAT, false, VERTEX_BYTES, 24);
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
/**
|
|
223
|
+
* Upload geometry appended to the pool since the last sync.
|
|
224
|
+
*
|
|
225
|
+
* A new pool generation (reset, repack, rebuilt context) reallocates both
|
|
226
|
+
* buffers and uploads everything. A pool that only grew keeps its content:
|
|
227
|
+
* the buffer that ran out of room is replaced by a larger one, the bytes
|
|
228
|
+
* already uploaded are copied on the GPU, and only new data is sent.
|
|
229
|
+
*/
|
|
219
230
|
syncPool(pool) {
|
|
220
231
|
const gl = this.gl;
|
|
221
|
-
const vBytes = pool.
|
|
222
|
-
const iBytes = pool.
|
|
223
|
-
const realloc = pool.generation !== this.poolGeneration || vBytes !== this.vertexCapacity || iBytes !== this.indexCapacity;
|
|
232
|
+
const vBytes = pool.vertexCapacityBytes;
|
|
233
|
+
const iBytes = pool.indexCapacityBytes;
|
|
224
234
|
let uploaded = 0;
|
|
225
|
-
if (
|
|
235
|
+
if (pool.generation !== this.poolGeneration) {
|
|
226
236
|
gl.bindBuffer(gl.ARRAY_BUFFER, this.vertexBuffer);
|
|
227
237
|
gl.bufferData(gl.ARRAY_BUFFER, vBytes, gl.STATIC_DRAW);
|
|
228
238
|
gl.bindVertexArray(this.vao);
|
|
@@ -234,25 +244,67 @@ export class WebGL2Backend {
|
|
|
234
244
|
this.poolGeneration = pool.generation;
|
|
235
245
|
pool.uploadedVertices = 0;
|
|
236
246
|
pool.uploadedIndices = 0;
|
|
247
|
+
} else {
|
|
248
|
+
if (vBytes > this.vertexCapacity) {
|
|
249
|
+
const old = this.vertexBuffer;
|
|
250
|
+
const next = gl.createBuffer();
|
|
251
|
+
gl.bindBuffer(gl.ARRAY_BUFFER, next);
|
|
252
|
+
gl.bufferData(gl.ARRAY_BUFFER, vBytes, gl.STATIC_DRAW);
|
|
253
|
+
gl.bindVertexArray(this.vao);
|
|
254
|
+
this.bindVertexAttributes();
|
|
255
|
+
gl.bindVertexArray(null);
|
|
256
|
+
this.copyBuffer(old, next, pool.uploadedVertices * VERTEX_BYTES);
|
|
257
|
+
gl.deleteBuffer(old);
|
|
258
|
+
this.vertexBuffer = next;
|
|
259
|
+
this.vertexCapacity = vBytes;
|
|
260
|
+
}
|
|
261
|
+
if (iBytes > this.indexCapacity) {
|
|
262
|
+
// An index buffer must first be bound as ELEMENT_ARRAY_BUFFER (WebGL2
|
|
263
|
+
// types buffers on first bind); bind it through the VAO, which keeps it.
|
|
264
|
+
const old = this.indexBuffer;
|
|
265
|
+
const next = gl.createBuffer();
|
|
266
|
+
gl.bindVertexArray(this.vao);
|
|
267
|
+
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, next);
|
|
268
|
+
gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, iBytes, gl.STATIC_DRAW);
|
|
269
|
+
gl.bindVertexArray(null);
|
|
270
|
+
this.copyBuffer(old, next, pool.uploadedIndices * 4);
|
|
271
|
+
gl.deleteBuffer(old);
|
|
272
|
+
this.indexBuffer = next;
|
|
273
|
+
this.indexCapacity = iBytes;
|
|
274
|
+
}
|
|
237
275
|
}
|
|
238
276
|
const p = pool.pending();
|
|
239
277
|
if (p.vertexEnd > p.vertexStart) {
|
|
240
278
|
gl.bindBuffer(gl.ARRAY_BUFFER, this.vertexBuffer);
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
279
|
+
pool.forEachVertices(p.vertexStart, p.vertexEnd, (first, data) => {
|
|
280
|
+
gl.bufferSubData(gl.ARRAY_BUFFER, first * VERTEX_BYTES, data);
|
|
281
|
+
uploaded += data.byteLength;
|
|
282
|
+
});
|
|
244
283
|
}
|
|
245
284
|
if (p.indexEnd > p.indexStart) {
|
|
246
285
|
gl.bindVertexArray(this.vao);
|
|
247
286
|
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, this.indexBuffer);
|
|
248
|
-
|
|
287
|
+
pool.forEachIndices(p.indexStart, p.indexEnd, (first, data) => {
|
|
288
|
+
gl.bufferSubData(gl.ELEMENT_ARRAY_BUFFER, first * 4, data);
|
|
289
|
+
uploaded += data.byteLength;
|
|
290
|
+
});
|
|
249
291
|
gl.bindVertexArray(null);
|
|
250
|
-
uploaded += (p.indexEnd - p.indexStart) * 4;
|
|
251
292
|
}
|
|
252
293
|
pool.markUploaded();
|
|
253
294
|
return uploaded;
|
|
254
295
|
}
|
|
255
296
|
|
|
297
|
+
/** Copy the first `bytes` of one buffer into another, on the GPU. */
|
|
298
|
+
copyBuffer(from, to, bytes) {
|
|
299
|
+
if (bytes <= 0) return;
|
|
300
|
+
const gl = this.gl;
|
|
301
|
+
gl.bindBuffer(gl.COPY_READ_BUFFER, from);
|
|
302
|
+
gl.bindBuffer(gl.COPY_WRITE_BUFFER, to);
|
|
303
|
+
gl.copyBufferSubData(gl.COPY_READ_BUFFER, gl.COPY_WRITE_BUFFER, 0, 0, bytes);
|
|
304
|
+
gl.bindBuffer(gl.COPY_READ_BUFFER, null);
|
|
305
|
+
gl.bindBuffer(gl.COPY_WRITE_BUFFER, null);
|
|
306
|
+
}
|
|
307
|
+
|
|
256
308
|
uploadInstances(data, count) {
|
|
257
309
|
const gl = this.gl;
|
|
258
310
|
const bytes = count * 64;
|
|
@@ -254,30 +254,61 @@ export class WebGPUBackend {
|
|
|
254
254
|
});
|
|
255
255
|
}
|
|
256
256
|
|
|
257
|
+
/**
|
|
258
|
+
* Upload geometry appended to the pool since the last sync.
|
|
259
|
+
*
|
|
260
|
+
* A new pool generation (reset, repack) reallocates both buffers and uploads
|
|
261
|
+
* everything. A pool that only grew keeps its content: the buffer that ran
|
|
262
|
+
* out of room is replaced by a larger one, the bytes already uploaded are
|
|
263
|
+
* copied on the GPU, and only new data is sent.
|
|
264
|
+
*/
|
|
257
265
|
syncPool(pool) {
|
|
258
266
|
const d = this.device;
|
|
259
|
-
const vBytes = pool.
|
|
260
|
-
const iBytes = pool.
|
|
267
|
+
const vBytes = pool.vertexCapacityBytes;
|
|
268
|
+
const iBytes = pool.indexCapacityBytes;
|
|
269
|
+
const vUsage = GPUBufferUsage.VERTEX | GPUBufferUsage.COPY_DST | GPUBufferUsage.COPY_SRC;
|
|
270
|
+
const iUsage = GPUBufferUsage.INDEX | GPUBufferUsage.COPY_DST | GPUBufferUsage.COPY_SRC;
|
|
261
271
|
let uploaded = 0;
|
|
262
|
-
if (pool.generation !== this.poolGeneration || !this.vertexBuffer
|
|
272
|
+
if (pool.generation !== this.poolGeneration || !this.vertexBuffer) {
|
|
263
273
|
if (this.vertexBuffer) this.vertexBuffer.destroy();
|
|
264
274
|
if (this.indexBuffer) this.indexBuffer.destroy();
|
|
265
|
-
this.vertexBuffer = d.createBuffer({ size: pow2(vBytes, 1 << 16), usage:
|
|
266
|
-
this.indexBuffer = d.createBuffer({ size: pow2(iBytes, 1 << 16), usage:
|
|
275
|
+
this.vertexBuffer = d.createBuffer({ size: pow2(vBytes, 1 << 16), usage: vUsage });
|
|
276
|
+
this.indexBuffer = d.createBuffer({ size: pow2(iBytes, 1 << 16), usage: iUsage });
|
|
267
277
|
this.poolGeneration = pool.generation;
|
|
268
278
|
pool.uploadedVertices = 0;
|
|
269
279
|
pool.uploadedIndices = 0;
|
|
280
|
+
} else {
|
|
281
|
+
let encoder = null;
|
|
282
|
+
const grow = (old, bytes, usage, keep) => {
|
|
283
|
+
const next = d.createBuffer({ size: pow2(bytes, 1 << 16), usage });
|
|
284
|
+
if (keep > 0) {
|
|
285
|
+
encoder ||= d.createCommandEncoder();
|
|
286
|
+
encoder.copyBufferToBuffer(old, 0, next, 0, keep);
|
|
287
|
+
}
|
|
288
|
+
return next;
|
|
289
|
+
};
|
|
290
|
+
const oldV = this.vertexBuffer.size < vBytes ? this.vertexBuffer : null;
|
|
291
|
+
const oldI = this.indexBuffer.size < iBytes ? this.indexBuffer : null;
|
|
292
|
+
if (oldV) this.vertexBuffer = grow(oldV, vBytes, vUsage, pool.uploadedVertices * VERTEX_BYTES);
|
|
293
|
+
if (oldI) this.indexBuffer = grow(oldI, iBytes, iUsage, pool.uploadedIndices * 4);
|
|
294
|
+
// the copy is queued before the writes below, so the new data lands after it;
|
|
295
|
+
// destroying a buffer waits for submitted work that reads it
|
|
296
|
+
if (encoder) d.queue.submit([encoder.finish()]);
|
|
297
|
+
if (oldV) oldV.destroy();
|
|
298
|
+
if (oldI) oldI.destroy();
|
|
270
299
|
}
|
|
271
300
|
const p = pool.pending();
|
|
272
301
|
if (p.vertexEnd > p.vertexStart) {
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
302
|
+
pool.forEachVertices(p.vertexStart, p.vertexEnd, (first, src) => {
|
|
303
|
+
d.queue.writeBuffer(this.vertexBuffer, first * VERTEX_BYTES, src.buffer, src.byteOffset, src.byteLength);
|
|
304
|
+
uploaded += src.byteLength;
|
|
305
|
+
});
|
|
276
306
|
}
|
|
277
307
|
if (p.indexEnd > p.indexStart) {
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
308
|
+
pool.forEachIndices(p.indexStart, p.indexEnd, (first, src) => {
|
|
309
|
+
d.queue.writeBuffer(this.indexBuffer, first * 4, src.buffer, src.byteOffset, src.byteLength);
|
|
310
|
+
uploaded += src.byteLength;
|
|
311
|
+
});
|
|
281
312
|
}
|
|
282
313
|
pool.markUploaded();
|
|
283
314
|
return uploaded;
|