@solidrt/2d 0.0.71 → 0.0.72
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/AGENTS.md +211 -42
- package/README.md +7 -3
- package/examples/README.md +1 -1
- package/examples/camera-probe.tsx +4 -4
- package/examples/markers.tsx +3 -3
- package/package.json +5 -4
- package/src/animation.ts +26 -5
- package/src/atlas.ts +8 -4
- package/src/camera.ts +2 -2
- package/src/components/camera2d.tsx +2 -2
- package/src/components/group.tsx +15 -3
- package/src/components/shots.tsx +2 -2
- package/src/components/sprite-layer.tsx +40 -12
- package/src/components/sprite.tsx +31 -3
- package/src/components/text2d.tsx +2 -2
- package/src/components/tile-layer.tsx +21 -7
- package/src/components/view2d.tsx +2 -2
- package/src/dispatch.ts +10 -6
- package/src/frames.ts +1 -1
- package/src/glsl.ts +399 -0
- package/src/index.ts +14 -6
- package/src/layer.ts +240 -101
- package/src/material.ts +368 -0
- package/src/records.ts +135 -32
- package/src/style.ts +98 -0
- package/src/tiles.ts +119 -24
- package/src/views.ts +17 -9
- package/src/shaders.ts +0 -323
package/src/records.ts
CHANGED
|
@@ -15,12 +15,15 @@
|
|
|
15
15
|
// static layer publishes nothing and therefore costs nothing.
|
|
16
16
|
//
|
|
17
17
|
// The record vocabulary is @solidrt/3d's record mesh's, one dimension
|
|
18
|
-
// down: `records(layer)` is the mirror
|
|
19
|
-
//
|
|
20
|
-
//
|
|
21
|
-
//
|
|
22
|
-
//
|
|
23
|
-
//
|
|
18
|
+
// down: `records(layer, stream?)` is the mirror (stream 0 the layer's
|
|
19
|
+
// own record, 1.. the material's style streams), `updateRecords(layer, {
|
|
20
|
+
// stream?, first?, count? })` publishes a range, `setRecordCount(layer,
|
|
21
|
+
// n)` dials the drawn prefix, `setInstanceStyle(layer, sprite, values)`
|
|
22
|
+
// and `instanceAttribute(layer, name)` write the material's per-sprite
|
|
23
|
+
// style record (style.ts). The POPULATION differs by kind and keeps its
|
|
24
|
+
// own verbs - a record mesh's records are opaque data (setRecords), a
|
|
25
|
+
// record layer's are sprites (addSprite/destroySprite) - so the copy-in
|
|
26
|
+
// here is `records(layer).set(src)` followed by updateRecords.
|
|
24
27
|
//
|
|
25
28
|
// This is NOT the default live layer - that is layer.ts, where sprites are
|
|
26
29
|
// spatial arena nodes core producers can reach. Use this when a JS loop
|
|
@@ -36,13 +39,16 @@
|
|
|
36
39
|
import { getOwner, onBeforeRender, onCleanup, runWithOwner } from "@solidrt/core"
|
|
37
40
|
import { checkScreenSize, createRecordStream, destroyBuffer, screenSizeScale } from "@solidrt/core/gpu"
|
|
38
41
|
import type { BufferId } from "@solidrt/core/gpu"
|
|
42
|
+
import type { AttributeAccess, ShaderParams } from "@solidrt/core/gpu"
|
|
39
43
|
import { checkAtlases, frameIndex } from "./atlas.ts"
|
|
40
44
|
import type { Atlas } from "./atlas.ts"
|
|
41
45
|
import { fullFrame, isFrame, writeFrame } from "./frames.ts"
|
|
42
|
-
import {
|
|
46
|
+
import { atlasBindings, RECORD_ATTRIBUTES } from "./glsl.ts"
|
|
47
|
+
import { checkParams, checkTint, readFrame } from "./layer.ts"
|
|
43
48
|
import type { LayerBase, Sprite, SpriteLayer, SpriteLayerOptions, SpriteOptions, SpriteState } from "./layer.ts"
|
|
49
|
+
import { materialSamplers, unlit } from "./material.ts"
|
|
44
50
|
import { pointInSprite } from "./pick.ts"
|
|
45
|
-
import {
|
|
51
|
+
import { blankStyle, createStyleStreams, growStyle, shiftStyle, writeStyle } from "./style.ts"
|
|
46
52
|
import { createViews } from "./views.ts"
|
|
47
53
|
|
|
48
54
|
// Floats per instance record:
|
|
@@ -92,12 +98,20 @@ export type RecordLayer = LayerBase & {
|
|
|
92
98
|
_order: SpriteState[]
|
|
93
99
|
}
|
|
94
100
|
|
|
95
|
-
/** Options of `updateRecords`: the
|
|
96
|
-
*
|
|
97
|
-
|
|
101
|
+
/** Options of `updateRecords`: the stream (default 0, the layer's own
|
|
102
|
+
* record; 1.. the material's style streams in its instanceBuffers
|
|
103
|
+
* order) and the record range (default the whole mirror, capacity
|
|
104
|
+
* wide). */
|
|
105
|
+
export type UpdateRecordsOptions = { stream?: number; first?: number; count?: number }
|
|
98
106
|
|
|
99
107
|
/**
|
|
100
|
-
* The layer's record mirror - the raw power path.
|
|
108
|
+
* The layer's record mirror - the raw power path. Stream 0 (the default)
|
|
109
|
+
* is the layer's own record; stream 1.. are the material's style
|
|
110
|
+
* streams in its `instanceBuffers` order, each a Float32Array over an
|
|
111
|
+
* all-float layout and bytes otherwise (write those through
|
|
112
|
+
* instanceAttribute's codecs, or a typed view of your own over the
|
|
113
|
+
* format), slot-indexed like the layer's own, blank (the material's
|
|
114
|
+
* instanceStyle) until written. On a records layer the
|
|
101
115
|
* layout per sprite is INSTANCE_FLOATS floats: [cx, cy, w, h, u0, v0, u1,
|
|
102
116
|
* v1, rot, tintR, tintG, tintB, tintA, minScreenPx, maxScreenPx, atlas,
|
|
103
117
|
* outlineR, outlineG, outlineB, outlineWidth], record i at i *
|
|
@@ -110,9 +124,9 @@ export type UpdateRecordsOptions = { first?: number; count?: number }
|
|
|
110
124
|
* records shift.
|
|
111
125
|
*
|
|
112
126
|
* On a node layer (createSpriteLayer) the records are its STYLE records,
|
|
113
|
-
*
|
|
127
|
+
* SPRITE_FLOATS floats per sprite slot: [u0, v0, u1, v1, tintR, tintG,
|
|
114
128
|
* tintB, tintA, renderOrder, minScreenPx, maxScreenPx, atlas, outlineR,
|
|
115
|
-
* outlineG, outlineB, outlineWidth], slot i at i *
|
|
129
|
+
* outlineG, outlineB, outlineWidth], slot i at i * SPRITE_FLOATS (a
|
|
116
130
|
* sprite's slot is fixed for its life; a freed slot recycles) - the pose
|
|
117
131
|
* lives in the core and is written through setSprite. The 3d
|
|
118
132
|
* `records(instancedMesh)`, one dimension down: a bulk restyle (a palette
|
|
@@ -124,12 +138,67 @@ export type UpdateRecordsOptions = { first?: number; count?: number }
|
|
|
124
138
|
* replaces the array, and a hoisted reference becomes a dead copy whose
|
|
125
139
|
* writes publish nothing. @solidrt/3d's records(mesh).
|
|
126
140
|
*/
|
|
127
|
-
export function records(layer: RecordLayer | SpriteLayer): Float32Array
|
|
128
|
-
|
|
141
|
+
export function records(layer: RecordLayer | SpriteLayer): Float32Array
|
|
142
|
+
export function records(layer: RecordLayer | SpriteLayer, stream: number): ArrayBufferView
|
|
143
|
+
export function records(layer: RecordLayer | SpriteLayer, stream = 0): ArrayBufferView {
|
|
144
|
+
if (stream === 0) return layer._records
|
|
145
|
+
return styleStream("records", layer, stream).data
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
// The material style stream at index `stream` (1-based after the layer's
|
|
149
|
+
// own record), or a throw naming the range.
|
|
150
|
+
function styleStream(verb: string, layer: RecordLayer | SpriteLayer, stream: number) {
|
|
151
|
+
let s = layer._styles[stream - 1]
|
|
152
|
+
if (!Number.isInteger(stream) || stream < 1 || s === undefined) {
|
|
153
|
+
throw new Error(`${verb}: stream ${stream} is out of range; the layer has record streams 0..${layer._styles.length} (0 its own record, then the material's instance buffers)`)
|
|
154
|
+
}
|
|
155
|
+
return s
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* Write a sprite's style record - the material's per-sprite data, its
|
|
160
|
+
* FIRST instance buffer (any format; `values` one per component, in
|
|
161
|
+
* attribute order, as the shader sees them: a unorm8x4 tint as 0..1) -
|
|
162
|
+
* marking the slot for the next flush; a frame-rate path like
|
|
163
|
+
* setSprite, any number of sprites restyled between two frames
|
|
164
|
+
* costing one coalesced buffer write. Throws on a material without an
|
|
165
|
+
* instance buffer or a record of the wrong length; a no-op on a
|
|
166
|
+
* destroyed sprite. instanceAttribute + updateRecords is the same
|
|
167
|
+
* write by attribute name, and the only way into a second stream.
|
|
168
|
+
* @solidrt/3d's setInstanceStyle(instance, values).
|
|
169
|
+
*/
|
|
170
|
+
export function setInstanceStyle(layer: RecordLayer | SpriteLayer, sprite: Sprite, values: ArrayLike<number>): void {
|
|
171
|
+
if (sprite.layer !== layer) {
|
|
172
|
+
if (sprite.layer === null) return
|
|
173
|
+
throw new Error("setInstanceStyle: the sprite belongs to another layer")
|
|
174
|
+
}
|
|
175
|
+
let style = layer._styles[0]
|
|
176
|
+
if (style === undefined) throw new Error("setInstanceStyle: the layer's material declares no instance buffer (no style record)")
|
|
177
|
+
writeStyle(style, sprite._slot, values, "setInstanceStyle")
|
|
178
|
+
layer._schedule()
|
|
129
179
|
}
|
|
130
180
|
|
|
131
181
|
/**
|
|
132
|
-
*
|
|
182
|
+
* The accessor for the material's per-sprite attribute `name` over the
|
|
183
|
+
* layer's style streams: slot in (a sprite's `_slot`), component values
|
|
184
|
+
* as the shader sees them (a unorm8x4 reads and writes as 0..1),
|
|
185
|
+
* encoded through the format's codec. Write through it, then
|
|
186
|
+
* updateRecords the range of that stream; the accessor stays valid
|
|
187
|
+
* across growth. Null when no stream carries the name (the layer's own
|
|
188
|
+
* record fields are records(layer)'s, not reachable here).
|
|
189
|
+
* @solidrt/3d's instanceAttribute(mesh, name).
|
|
190
|
+
*/
|
|
191
|
+
export function instanceAttribute(layer: RecordLayer | SpriteLayer, name: string): AttributeAccess | null {
|
|
192
|
+
for (let s of layer._styles) {
|
|
193
|
+
let i = s.layout.findIndex(a => a.name === name)
|
|
194
|
+
if (i >= 0) return s.fields[i]!
|
|
195
|
+
}
|
|
196
|
+
return null
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
/**
|
|
200
|
+
* Publish records [first, first + count) of a mirror (`stream` 0 the
|
|
201
|
+
* layer's own record, 1.. the material's style streams) at the layer's
|
|
133
202
|
* next flush - the partial rewrite a population stepped in JS wants (ten
|
|
134
203
|
* moved records of ten thousand cost ten); the whole mirror by default.
|
|
135
204
|
* Write the mirror first, through records(), then call this. The range
|
|
@@ -141,13 +210,17 @@ export function records(layer: RecordLayer | SpriteLayer): Float32Array {
|
|
|
141
210
|
* updateRecords(mesh).
|
|
142
211
|
*/
|
|
143
212
|
export function updateRecords(layer: RecordLayer | SpriteLayer, options: UpdateRecordsOptions = {}): void {
|
|
144
|
-
let
|
|
213
|
+
let index = options.stream ?? 0
|
|
214
|
+
let stream = index === 0 ? layer._stream : styleStream("updateRecords", layer, index).stream
|
|
215
|
+
let capacity = stream.capacity
|
|
145
216
|
let first = options.first ?? 0
|
|
146
217
|
let count = options.count ?? capacity - first
|
|
147
218
|
if (!Number.isInteger(first) || !Number.isInteger(count) || first < 0 || count < 0 || first + count > capacity) {
|
|
148
219
|
throw new Error("updateRecords: range [" + first + ", " + (first + count) + ") is outside the layer's " + capacity + " records")
|
|
149
220
|
}
|
|
150
|
-
if (count
|
|
221
|
+
if (count === 0) return
|
|
222
|
+
stream.mark(first, first + count)
|
|
223
|
+
layer._schedule()
|
|
151
224
|
}
|
|
152
225
|
|
|
153
226
|
/**
|
|
@@ -190,6 +263,12 @@ export function createRecordLayer(atlases: Atlas[], opts?: RecordLayerOptions):
|
|
|
190
263
|
let stream = createRecordStream(RECORD_BYTES, capacity, { label: `${label}-records`, autoFree: false })
|
|
191
264
|
let tint = opts?.tint ?? [1, 1, 1, 1]
|
|
192
265
|
checkTint("createRecordLayer", tint)
|
|
266
|
+
let material = opts?.material ?? unlit()
|
|
267
|
+
checkAtlases("createRecordLayer", atlases, materialSamplers(material))
|
|
268
|
+
let params: ShaderParams = { ...material.params, ...opts?.params }
|
|
269
|
+
// The material's style streams, after the layer's own record: the
|
|
270
|
+
// same publish, the same growth, the same shift on destroy.
|
|
271
|
+
let styles = createStyleStreams(material, capacity, label)
|
|
193
272
|
let orderBy = opts?.orderBy
|
|
194
273
|
let instanceOrder =
|
|
195
274
|
orderBy === undefined
|
|
@@ -198,7 +277,9 @@ export function createRecordLayer(atlases: Atlas[], opts?: RecordLayerOptions):
|
|
|
198
277
|
? { field: Y_FIELD_OFFSET }
|
|
199
278
|
: { field: orderBy.field, descending: orderBy.descending }
|
|
200
279
|
let ordered = instanceOrder !== undefined
|
|
201
|
-
let gpu =
|
|
280
|
+
let gpu = material.pipeline(atlases, [RECORD_ATTRIBUTES], label)
|
|
281
|
+
// The instance buffers in pipeline order after the quad.
|
|
282
|
+
let buffers = () => [stream.buffer, ...styles.map(s => s.stream.buffer)]
|
|
202
283
|
|
|
203
284
|
let disposed = false
|
|
204
285
|
let scheduled = false
|
|
@@ -233,18 +314,20 @@ export function createRecordLayer(atlases: Atlas[], opts?: RecordLayerOptions):
|
|
|
233
314
|
let live = layer._order.length
|
|
234
315
|
let count = drawn()
|
|
235
316
|
if (grownFrom.length > 0) {
|
|
236
|
-
// The
|
|
317
|
+
// The replacements are filled whole before the swap (the entry must
|
|
237
318
|
// never point at an unwritten buffer): the live prefix, or under
|
|
238
319
|
// orderBy the drawn one, which is all the gather may see.
|
|
239
320
|
stream.publish(true, ordered ? count : live)
|
|
240
|
-
|
|
321
|
+
for (let s of styles) s.stream.publish(true, ordered ? count : live)
|
|
322
|
+
views.setBuffers(buffers())
|
|
241
323
|
views.setCount(count)
|
|
242
324
|
if (ordered) {
|
|
243
325
|
// That publish landed BEFORE the swap, so the order had not yet
|
|
244
|
-
// followed to the grown
|
|
326
|
+
// followed to the grown buffers and it went out ungathered. One
|
|
245
327
|
// more publish, now under the swapped-in order, restores key
|
|
246
328
|
// order - growth frames only.
|
|
247
329
|
stream.publish(true, count)
|
|
330
|
+
for (let s of styles) s.stream.publish(true, count)
|
|
248
331
|
}
|
|
249
332
|
for (let old of grownFrom) destroyBuffer(old)
|
|
250
333
|
grownFrom.length = 0
|
|
@@ -253,12 +336,18 @@ export function createRecordLayer(atlases: Atlas[], opts?: RecordLayerOptions):
|
|
|
253
336
|
}
|
|
254
337
|
if (ordered) {
|
|
255
338
|
// The gathered set IS the drawn prefix: a dirty record or a moved
|
|
256
|
-
// dial republishes it whole
|
|
257
|
-
|
|
339
|
+
// dial republishes it whole, every stream alike (the core gathers
|
|
340
|
+
// them under one permutation).
|
|
341
|
+
if (stream.dirty || count !== published) {
|
|
342
|
+
stream.publish(true, count)
|
|
343
|
+
for (let s of styles) s.stream.publish(true, count)
|
|
344
|
+
}
|
|
258
345
|
} else {
|
|
259
346
|
// The dirty range, clipped to the live sprites (a record past them
|
|
260
|
-
// is never drawn): one partial write at its byte offset
|
|
347
|
+
// is never drawn): one partial write at its byte offset, per
|
|
348
|
+
// dirty stream.
|
|
261
349
|
stream.publish(false, live)
|
|
350
|
+
for (let s of styles) s.stream.publish(false, live)
|
|
262
351
|
}
|
|
263
352
|
if (count !== published) {
|
|
264
353
|
views.setCount(count)
|
|
@@ -324,16 +413,18 @@ export function createRecordLayer(atlases: Atlas[], opts?: RecordLayerOptions):
|
|
|
324
413
|
label,
|
|
325
414
|
pipeline: gpu.pipeline,
|
|
326
415
|
quad: gpu.quad,
|
|
327
|
-
|
|
328
|
-
buffers
|
|
416
|
+
textures: { ...atlasBindings(atlases.map(a => a.texture)), ...material.textures },
|
|
417
|
+
buffers,
|
|
329
418
|
count: () => published,
|
|
330
419
|
tint: () => tint,
|
|
420
|
+
params: () => params,
|
|
331
421
|
pick: (x, y, zoom) => layer.pick(x, y, zoom),
|
|
332
422
|
order: instanceOrder,
|
|
333
423
|
})
|
|
334
424
|
|
|
335
425
|
let layer: RecordLayer = {
|
|
336
426
|
atlases,
|
|
427
|
+
material,
|
|
337
428
|
get count() {
|
|
338
429
|
return layer._order.length
|
|
339
430
|
},
|
|
@@ -343,6 +434,12 @@ export function createRecordLayer(atlases: Atlas[], opts?: RecordLayerOptions):
|
|
|
343
434
|
tint = next
|
|
344
435
|
views.setTint(next)
|
|
345
436
|
},
|
|
437
|
+
setParams(next) {
|
|
438
|
+
if (disposed) return
|
|
439
|
+
checkParams("setParams", next)
|
|
440
|
+
Object.assign(params, next)
|
|
441
|
+
views.setParams(next)
|
|
442
|
+
},
|
|
346
443
|
createView(vopts) {
|
|
347
444
|
if (disposed) throw new Error("createView: layer is disposed")
|
|
348
445
|
return views.create(vopts)
|
|
@@ -376,7 +473,7 @@ export function createRecordLayer(atlases: Atlas[], opts?: RecordLayerOptions):
|
|
|
376
473
|
for (let old of grownFrom) destroyBuffer(old)
|
|
377
474
|
grownFrom.length = 0
|
|
378
475
|
stream.destroy()
|
|
379
|
-
|
|
476
|
+
for (let s of styles) s.stream.destroy()
|
|
380
477
|
},
|
|
381
478
|
_add(opts) {
|
|
382
479
|
if (opts?.parent) {
|
|
@@ -400,9 +497,11 @@ export function createRecordLayer(atlases: Atlas[], opts?: RecordLayerOptions):
|
|
|
400
497
|
let atlas = checkWrite("addSprite", record)
|
|
401
498
|
let index = layer._order.length
|
|
402
499
|
if (index + 1 > stream.capacity) {
|
|
403
|
-
// Growth replaces the
|
|
404
|
-
// fills the
|
|
405
|
-
|
|
500
|
+
// Growth replaces the mirrors and the buffers (doubling); the
|
|
501
|
+
// flush fills the replacements whole and swaps them in.
|
|
502
|
+
let next = stream.capacity * 2
|
|
503
|
+
grownFrom.push(stream.grow(next))
|
|
504
|
+
for (let s of styles) grownFrom.push(growStyle(s, next))
|
|
406
505
|
layer._records = new Float32Array(stream.bytes.buffer)
|
|
407
506
|
}
|
|
408
507
|
let sprite: SpriteState = { layer, node: null, _slot: index, _x: 0, _y: 0, _w: 0, _h: 0, _rot: 0, _flipX: false, _flipY: false, _visible: true, _parent: null }
|
|
@@ -411,6 +510,8 @@ export function createRecordLayer(atlases: Atlas[], opts?: RecordLayerOptions):
|
|
|
411
510
|
// No outline until a raw writer sets one: the slot may hold a
|
|
412
511
|
// shifted-out sprite's record otherwise.
|
|
413
512
|
layer._records.fill(0, index * INSTANCE_FLOATS + OUTLINE_FIELD_OFFSET, index * INSTANCE_FLOATS + OUTLINE_FIELD_OFFSET + 4)
|
|
513
|
+
// The material's style slot starts blank for the same reason.
|
|
514
|
+
for (let s of styles) blankStyle(s, index)
|
|
414
515
|
markRecords(layer, index, index + 1)
|
|
415
516
|
return sprite
|
|
416
517
|
},
|
|
@@ -450,6 +551,7 @@ export function createRecordLayer(atlases: Atlas[], opts?: RecordLayerOptions):
|
|
|
450
551
|
let r = layer._records
|
|
451
552
|
let last = order.length - 1
|
|
452
553
|
r.copyWithin(index * INSTANCE_FLOATS, (index + 1) * INSTANCE_FLOATS, order.length * INSTANCE_FLOATS)
|
|
554
|
+
if (index < last) for (let s of styles) shiftStyle(s, index + 1, order.length)
|
|
453
555
|
order.splice(index, 1)
|
|
454
556
|
for (let i = index; i < order.length; i++) order[i]!._slot = i
|
|
455
557
|
if (index < last) markRecords(layer, index, last)
|
|
@@ -462,6 +564,7 @@ export function createRecordLayer(atlases: Atlas[], opts?: RecordLayerOptions):
|
|
|
462
564
|
},
|
|
463
565
|
_stream: stream,
|
|
464
566
|
_records: new Float32Array(stream.bytes.buffer),
|
|
567
|
+
_styles: styles,
|
|
465
568
|
_dial: Infinity,
|
|
466
569
|
_order: [],
|
|
467
570
|
}
|
package/src/style.ts
ADDED
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
// A material's style streams on a layer: one core record stream per
|
|
2
|
+
// instance buffer the material declares (createRecordStream: the GPU
|
|
3
|
+
// buffer, the byte mirror, the dirty range and the range-or-whole
|
|
4
|
+
// publish), bound after the layer's own records, each slot starting as
|
|
5
|
+
// the material's blank record. The node layer and the records layer
|
|
6
|
+
// hold them beside their own stream and publish them in the same flush
|
|
7
|
+
// (the tile layer bakes per chunk and keeps plain arrays, tiles.ts);
|
|
8
|
+
// the shared verbs (setInstanceStyle, instanceAttribute, records,
|
|
9
|
+
// updateRecords in records.ts) write through the accessors here. The
|
|
10
|
+
// codecs are core's (vertex.ts), the same ones @solidrt/3d's instance
|
|
11
|
+
// streams encode through.
|
|
12
|
+
import { createRecordStream, isFloatLayout, layoutFields, layoutStride, layoutView, writeRecord } from "@solidrt/core/gpu"
|
|
13
|
+
import type { AttributeAccess, BufferId, RecordStream, VertexAttribute } from "@solidrt/core/gpu"
|
|
14
|
+
import { materialBlank } from "./material.ts"
|
|
15
|
+
import type { Material } from "./material.ts"
|
|
16
|
+
|
|
17
|
+
/** One style stream: the layout, the core stream behind it, the blank a
|
|
18
|
+
* fresh slot copies, the accessors (valid across growth), and the
|
|
19
|
+
* mirror's view as the layout wants it (re-derived by growth: read it
|
|
20
|
+
* at use time). */
|
|
21
|
+
export type StyleStream = {
|
|
22
|
+
layout: VertexAttribute[]
|
|
23
|
+
stream: RecordStream
|
|
24
|
+
blank: Uint8Array
|
|
25
|
+
fields: AttributeAccess[]
|
|
26
|
+
/** A Float32Array over an all-float layout, bytes otherwise. */
|
|
27
|
+
data: ArrayBufferView
|
|
28
|
+
/** The float view writeRecord stores through on an all-float layout,
|
|
29
|
+
* null otherwise. */
|
|
30
|
+
_floats: Float32Array | null
|
|
31
|
+
_view: DataView
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/** The material's style streams for a layer of `capacity` slots: empty
|
|
35
|
+
* without instance buffers. */
|
|
36
|
+
export function createStyleStreams(material: Material, capacity: number, label: string): StyleStream[] {
|
|
37
|
+
let out: StyleStream[] = []
|
|
38
|
+
material.instanceBuffers?.forEach((buffer, i) => {
|
|
39
|
+
let layout = buffer.attributes
|
|
40
|
+
let stream = createRecordStream(layoutStride(layout), capacity, { label: `${label}-style${i}`, autoFree: false })
|
|
41
|
+
let style: StyleStream = {
|
|
42
|
+
layout,
|
|
43
|
+
stream,
|
|
44
|
+
blank: materialBlank(material, i),
|
|
45
|
+
fields: [],
|
|
46
|
+
data: new Uint8Array(0),
|
|
47
|
+
_floats: null,
|
|
48
|
+
_view: new DataView(new ArrayBuffer(0)),
|
|
49
|
+
}
|
|
50
|
+
viewStyle(style)
|
|
51
|
+
style.fields = layoutFields(layout, () => style._view)
|
|
52
|
+
// Every slot starts blank: a material's instanceStyle is what an
|
|
53
|
+
// unwritten sprite shows, not zeros.
|
|
54
|
+
for (let slot = 0; slot < capacity; slot++) stream.bytes.set(style.blank, slot * stream.stride)
|
|
55
|
+
out.push(style)
|
|
56
|
+
})
|
|
57
|
+
return out
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// Point the views at the stream's current mirror.
|
|
61
|
+
function viewStyle(style: StyleStream): void {
|
|
62
|
+
let buffer = style.stream.bytes.buffer
|
|
63
|
+
style.data = layoutView(style.layout, buffer)
|
|
64
|
+
style._floats = isFloatLayout(style.layout) ? (style.data as Float32Array) : null
|
|
65
|
+
style._view = new DataView(buffer)
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/** Grow a style stream to `next` slots (the new slots blank) and return
|
|
69
|
+
* the old buffer for the caller to destroy once its draws point at the
|
|
70
|
+
* new one. */
|
|
71
|
+
export function growStyle(style: StyleStream, next: number): BufferId {
|
|
72
|
+
let previous = style.stream.capacity
|
|
73
|
+
let old = style.stream.grow(next)
|
|
74
|
+
for (let slot = previous; slot < next; slot++) style.stream.bytes.set(style.blank, slot * style.stream.stride)
|
|
75
|
+
viewStyle(style)
|
|
76
|
+
return old
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/** Reset slot `slot` to the blank record and mark it. */
|
|
80
|
+
export function blankStyle(style: StyleStream, slot: number): void {
|
|
81
|
+
style.stream.bytes.set(style.blank, slot * style.stream.stride)
|
|
82
|
+
style.stream.mark(slot, slot + 1)
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/** Encode `values` (one per component, in layout order, as the shader
|
|
86
|
+
* sees them) into slot `slot` and mark it. */
|
|
87
|
+
export function writeStyle(style: StyleStream, slot: number, values: ArrayLike<number>, site: string): void {
|
|
88
|
+
writeRecord(style.layout, style.fields, style._floats, slot, values, site)
|
|
89
|
+
style.stream.mark(slot, slot + 1)
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/** Shift slots [from, to) down to `from - 1` (a records layer's destroy)
|
|
93
|
+
* and mark the moved range. */
|
|
94
|
+
export function shiftStyle(style: StyleStream, from: number, to: number): void {
|
|
95
|
+
let stride = style.stream.stride
|
|
96
|
+
style.stream.bytes.copyWithin((from - 1) * stride, from * stride, to * stride)
|
|
97
|
+
style.stream.mark(from - 1, to - 1)
|
|
98
|
+
}
|