@solidrt/3d 0.0.70 → 0.0.71
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 +6 -3
- package/package.json +3 -3
- package/src/mesh.ts +56 -61
- package/src/scene.ts +1 -1
package/AGENTS.md
CHANGED
|
@@ -1430,7 +1430,10 @@ makes an INSTANCED material: the vertex stage reads the attributes as
|
|
|
1430
1430
|
`in` variables beside the layout's own, and each drawn instance gets one
|
|
1431
1431
|
record from each of the mesh's instance buffers. An instance buffer IS a
|
|
1432
1432
|
vertex stream stepped per instance: on the mesh it is an `InstanceStream`
|
|
1433
|
-
(the layout
|
|
1433
|
+
(the layout over the core's record stream - `createRecordStream` in
|
|
1434
|
+
`@solidrt/core/gpu`: the GPU buffer, the byte mirror, the dirty range
|
|
1435
|
+
and the range-or-whole publish, the one model @solidrt/2d's layers run
|
|
1436
|
+
on too), written through
|
|
1434
1437
|
`instanceAttribute(mesh, name)` - the accessor `geometryAttribute`
|
|
1435
1438
|
returns, record index in, values as the shader sees them, the codec
|
|
1436
1439
|
packing the bytes - or in bulk through the mirror itself, `records(mesh,
|
|
@@ -3078,8 +3081,8 @@ successors) as ordinary content, the model-loading split repeated:
|
|
|
3078
3081
|
rebuild.
|
|
3079
3082
|
- Record writes are mirrored, not immediate: `setInstanceStyle`,
|
|
3080
3083
|
`setRecords` and an `instanceAttribute` write land in the stream's
|
|
3081
|
-
`data` and the scene's sync publishes
|
|
3082
|
-
marks it for accessor writes) - so a write on a mesh outside any scene
|
|
3084
|
+
`data` (the core record stream's mirror) and the scene's sync publishes
|
|
3085
|
+
its dirty range (`updateRecords` marks it for accessor writes) - so a write on a mesh outside any scene
|
|
3083
3086
|
shows once it is added (the dirty range waits), and reading the GPU
|
|
3084
3087
|
buffer back between the write and the sync lags the mirror. The sync
|
|
3085
3088
|
runs in the publish pass of core's `onBeforeRender`, ahead of the
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@solidrt/3d",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.71",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"funding": "https://github.com/sponsors/wellawaretech",
|
|
6
6
|
"author": "Antoine van Wel",
|
|
@@ -20,10 +20,10 @@
|
|
|
20
20
|
"AGENTS.md"
|
|
21
21
|
],
|
|
22
22
|
"devDependencies": {
|
|
23
|
-
"@solidrt/test": "0.0.
|
|
23
|
+
"@solidrt/test": "0.0.71"
|
|
24
24
|
},
|
|
25
25
|
"peerDependencies": {
|
|
26
26
|
"@solidjs/signals": "2.0.0-rc.13",
|
|
27
|
-
"@solidrt/core": "0.0.
|
|
27
|
+
"@solidrt/core": "0.0.71"
|
|
28
28
|
}
|
|
29
29
|
}
|
package/src/mesh.ts
CHANGED
|
@@ -3,8 +3,8 @@
|
|
|
3
3
|
// setter write paths that keep a live scene's draw entries in step. The
|
|
4
4
|
// scene side is reached through the node's SceneHooks (node.ts).
|
|
5
5
|
|
|
6
|
-
import {
|
|
7
|
-
import type { BufferId, DrawId, ShaderParams, TextureBindings, TextureId, VertexAttribute, VertexBufferLayout } from "@solidrt/core/gpu"
|
|
6
|
+
import { createBuffer, createRecordStream, destroyBuffer, destroyTexture, transferRecords } from "@solidrt/core/gpu"
|
|
7
|
+
import type { BufferId, DrawId, RecordStream, ShaderParams, TextureBindings, TextureId, VertexAttribute, VertexBufferLayout } from "@solidrt/core/gpu"
|
|
8
8
|
import { checkCubeKnobs } from "./environment.ts"
|
|
9
9
|
import type { EnvironmentOptions } from "./environment.ts"
|
|
10
10
|
import { geometryBounds, isFloatLayout, layoutKey, layoutStride, plane, vertexBytes, vertexView, VERTEX_FORMATS } from "./geometry.ts"
|
|
@@ -189,15 +189,22 @@ export type InstanceStream = {
|
|
|
189
189
|
layout: VertexAttribute[]
|
|
190
190
|
/** Bytes per record: the layout's stride. */
|
|
191
191
|
stride: number
|
|
192
|
+
/** The GPU buffer the entry binds; `stream.buffer` where a stream
|
|
193
|
+
* exists (growth re-points both), the engine-held buffer otherwise. */
|
|
192
194
|
buffer: BufferId
|
|
195
|
+
/** The core record stream behind the mirror (createRecordStream: the
|
|
196
|
+
* buffer, the byte mirror, the dirty range and the publish), or null
|
|
197
|
+
* where no JS mirror exists: the transfer form hands the records to
|
|
198
|
+
* the engine, the indexed order's id stream is the engine's to fill. */
|
|
199
|
+
stream: RecordStream | null
|
|
200
|
+
/** The mirror as the layout's view (a Float32Array over an all-float
|
|
201
|
+
* layout, bytes otherwise): `stream.bytes` viewed, re-derived on growth;
|
|
202
|
+
* zero-length without a stream. */
|
|
193
203
|
data: ArrayBufferView
|
|
194
204
|
/** The record a fresh (or recycled) instance slot starts with: the
|
|
195
205
|
* material's instanceStyle encoded, on the first stream of an
|
|
196
206
|
* instanced mesh; zeros otherwise. */
|
|
197
207
|
blank: Uint8Array
|
|
198
|
-
/** The byte range [lo, hi) of `data` written since the last publish,
|
|
199
|
-
* or null. */
|
|
200
|
-
dirty: [number, number] | null
|
|
201
208
|
/** The DataView the accessors read through; follows `data`. */
|
|
202
209
|
_view: DataView
|
|
203
210
|
/** All-float32 layout: `data` is a Float32Array and a record write is
|
|
@@ -384,23 +391,25 @@ function streamLabel(label: string | undefined, index: number): string | undefin
|
|
|
384
391
|
return label === undefined ? undefined : label + "-" + index
|
|
385
392
|
}
|
|
386
393
|
|
|
387
|
-
// A stream of `capacity` zeroed records over `layout
|
|
388
|
-
// sized and unwritten (the mirror is the truth,
|
|
394
|
+
// A stream of `capacity` zeroed records over `layout` on a core record
|
|
395
|
+
// stream; its GPU buffer is sized and unwritten (the mirror is the truth,
|
|
396
|
+
// publishes follow the dirty range).
|
|
389
397
|
function makeStream(layout: VertexAttribute[], capacity: number, blank: Uint8Array | null, label: string | undefined): InstanceStream {
|
|
390
398
|
let stride = layoutStride(layout)
|
|
399
|
+
let records = createRecordStream(stride, capacity, { autoFree: false, label })
|
|
391
400
|
let stream: InstanceStream = {
|
|
392
401
|
layout,
|
|
393
402
|
stride,
|
|
394
|
-
buffer:
|
|
403
|
+
buffer: records.buffer,
|
|
404
|
+
stream: records,
|
|
395
405
|
data: new Uint8Array(0),
|
|
396
406
|
blank: blank ?? new Uint8Array(stride),
|
|
397
|
-
dirty: null,
|
|
398
407
|
_view: new DataView(new ArrayBuffer(0)),
|
|
399
408
|
_floats: isFloatLayout(layout),
|
|
400
409
|
_components: layoutComponents(layout),
|
|
401
410
|
_fields: [],
|
|
402
411
|
}
|
|
403
|
-
viewStream(stream,
|
|
412
|
+
viewStream(stream, records.bytes.buffer)
|
|
404
413
|
stream._fields = streamFields(stream)
|
|
405
414
|
return stream
|
|
406
415
|
}
|
|
@@ -416,9 +425,9 @@ function makeIndexStream(layout: VertexAttribute[], capacity: number, group: num
|
|
|
416
425
|
layout,
|
|
417
426
|
stride,
|
|
418
427
|
buffer: createBuffer(Math.ceil(capacity / group) * stride, { autoFree: false, label }),
|
|
428
|
+
stream: null,
|
|
419
429
|
data: new Uint8Array(0),
|
|
420
430
|
blank: new Uint8Array(stride),
|
|
421
|
-
dirty: null,
|
|
422
431
|
_view: new DataView(new ArrayBuffer(0)),
|
|
423
432
|
_floats: false,
|
|
424
433
|
_components: layoutComponents(layout),
|
|
@@ -442,9 +451,9 @@ function makeTransferredStream(layout: VertexAttribute[], records: Uint8Array, o
|
|
|
442
451
|
buffer: ordered
|
|
443
452
|
? createBuffer(records.byteLength, { autoFree: false, label })
|
|
444
453
|
: createBuffer(records, { autoFree: false, label }),
|
|
454
|
+
stream: null,
|
|
445
455
|
data: new Uint8Array(0),
|
|
446
456
|
blank: new Uint8Array(stride),
|
|
447
|
-
dirty: null,
|
|
448
457
|
_view: new DataView(new ArrayBuffer(0)),
|
|
449
458
|
_floats: isFloatLayout(layout),
|
|
450
459
|
_components: layoutComponents(layout),
|
|
@@ -475,21 +484,18 @@ function writeRecord(stream: InstanceStream, i: number, values: ArrayLike<number
|
|
|
475
484
|
// One record of `layout` encoded from `values`, the bytes a blank slot copies.
|
|
476
485
|
function encodeRecord(layout: VertexAttribute[], values: ArrayLike<number>, site: string): Uint8Array {
|
|
477
486
|
let stride = layoutStride(layout)
|
|
478
|
-
let one: InstanceStream = { layout, stride, buffer: 0 as BufferId, data: new Uint8Array(0), blank: new Uint8Array(0),
|
|
487
|
+
let one: InstanceStream = { layout, stride, buffer: 0 as BufferId, stream: null, data: new Uint8Array(0), blank: new Uint8Array(0), _view: new DataView(new ArrayBuffer(0)), _floats: isFloatLayout(layout), _components: layoutComponents(layout), _fields: [] }
|
|
479
488
|
viewStream(one, new ArrayBuffer(stride))
|
|
480
489
|
one._fields = streamFields(one)
|
|
481
490
|
writeRecord(one, 0, values, site)
|
|
482
491
|
return vertexBytes(one.data)
|
|
483
492
|
}
|
|
484
493
|
|
|
485
|
-
//
|
|
486
|
-
//
|
|
494
|
+
// Mark records [lo, hi) of a stream's mirror and ask the scene to publish
|
|
495
|
+
// them at the next sync.
|
|
487
496
|
function markRecords(mesh: Mesh, stream: InstanceStream, lo: number, hi: number): void {
|
|
488
|
-
if (stream.
|
|
489
|
-
|
|
490
|
-
if (lo < stream.dirty[0]) stream.dirty[0] = lo
|
|
491
|
-
if (hi > stream.dirty[1]) stream.dirty[1] = hi
|
|
492
|
-
}
|
|
497
|
+
if (stream.stream === null) throw new Error("markRecords: the stream has no mirror (its records were transferred to the engine)")
|
|
498
|
+
stream.stream.mark(lo, hi)
|
|
493
499
|
mesh._scene?._setRecords(mesh)
|
|
494
500
|
}
|
|
495
501
|
|
|
@@ -869,7 +875,7 @@ export function setInstanceStyle(instance: InstanceNode, values: ArrayLike<numbe
|
|
|
869
875
|
let stream = l._instances.streams[0]
|
|
870
876
|
if (stream === undefined) continue
|
|
871
877
|
writeRecord(stream, instance._slot, values, "setInstanceStyle")
|
|
872
|
-
markRecords(l, stream, instance._slot
|
|
878
|
+
markRecords(l, stream, instance._slot, instance._slot + 1)
|
|
873
879
|
}
|
|
874
880
|
}
|
|
875
881
|
|
|
@@ -944,34 +950,23 @@ export function updateRecords(mesh: InstancedMesh | RecordMesh, options: UpdateR
|
|
|
944
950
|
if (!Number.isInteger(first) || !Number.isInteger(count) || first < 0 || count < 0 || first + count > inst.capacity) {
|
|
945
951
|
throw new Error("updateRecords: range [" + first + ", " + (first + count) + ") is outside the mesh's " + inst.capacity + " records")
|
|
946
952
|
}
|
|
947
|
-
if (count > 0) markRecords(mesh, stream, first
|
|
953
|
+
if (count > 0) markRecords(mesh, stream, first, first + count)
|
|
948
954
|
}
|
|
949
955
|
|
|
950
956
|
/** Publish a mesh's pending record writes, one buffer write per dirty
|
|
951
|
-
* stream (the scene calls it from its sync)
|
|
952
|
-
* the
|
|
953
|
-
*
|
|
954
|
-
*
|
|
957
|
+
* stream (the scene calls it from its sync): the dirty record range, up
|
|
958
|
+
* to the capacity (a record mesh writes ahead of its count). An ordered
|
|
959
|
+
* mesh publishes the LIVE record set whole through the write lease - the
|
|
960
|
+
* engine gathers it into key order during the copy, and a byte range has
|
|
961
|
+
* no stable position under a permutation (writeBuffer throws on an
|
|
962
|
+
* ordered buffer). */
|
|
955
963
|
export function publishRecords(mesh: Mesh): void {
|
|
956
964
|
let inst = mesh._instances
|
|
957
965
|
if (inst === null) return
|
|
958
|
-
|
|
959
|
-
for (let s of inst.streams) {
|
|
960
|
-
if (s.dirty === null) continue
|
|
961
|
-
s.dirty = null
|
|
962
|
-
let bytes = inst.count * s.stride
|
|
963
|
-
if (bytes === 0) continue
|
|
964
|
-
let block = beginBufferWrite(s.buffer)
|
|
965
|
-
new Uint8Array(block.buffer, 0, bytes).set(vertexBytes(s.data).subarray(0, bytes))
|
|
966
|
-
endBufferWrite(s.buffer, bytes)
|
|
967
|
-
}
|
|
968
|
-
return
|
|
969
|
-
}
|
|
966
|
+
let ordered = inst.order !== null
|
|
970
967
|
for (let s of inst.streams) {
|
|
971
|
-
if (s.
|
|
972
|
-
|
|
973
|
-
s.dirty = null
|
|
974
|
-
writeBuffer(s.buffer, vertexBytes(s.data).subarray(lo, hi), lo)
|
|
968
|
+
if (s.stream === null || !s.stream.dirty) continue
|
|
969
|
+
s.stream.publish(ordered, ordered ? inst.count : inst.capacity)
|
|
975
970
|
}
|
|
976
971
|
}
|
|
977
972
|
|
|
@@ -984,7 +979,7 @@ export function markLiveRecords(mesh: Mesh): void {
|
|
|
984
979
|
// A transferred mesh has no mirror; the engine's own (buffer-lifetime)
|
|
985
980
|
// mirror seeds a re-attached entry instead.
|
|
986
981
|
if (inst === null || inst.count === 0 || inst.transfer) return
|
|
987
|
-
for (let s of inst.streams) markRecords(mesh, s, 0, inst.count
|
|
982
|
+
for (let s of inst.streams) markRecords(mesh, s, 0, inst.count)
|
|
988
983
|
}
|
|
989
984
|
|
|
990
985
|
/**
|
|
@@ -1033,7 +1028,7 @@ export function addInstance(mesh: InstancedMesh, update?: TransformUpdate, paren
|
|
|
1033
1028
|
// A recycled slot must not wear its last occupant's records.
|
|
1034
1029
|
for (let s of li.streams) {
|
|
1035
1030
|
vertexBytes(s.data).set(s.blank, slot * s.stride)
|
|
1036
|
-
markRecords(l, s, slot
|
|
1031
|
+
markRecords(l, s, slot, slot + 1)
|
|
1037
1032
|
}
|
|
1038
1033
|
}
|
|
1039
1034
|
if (parent._scene) enterScene(instance, parent._scene)
|
|
@@ -1069,14 +1064,13 @@ export function reparentInstance(instance: InstanceNode, parent: SceneNode): voi
|
|
|
1069
1064
|
|
|
1070
1065
|
// Grow the population's buffers to `next` records: replacements (never a
|
|
1071
1066
|
// resize). An instanced mesh's live matrix records move to the new buffer
|
|
1072
|
-
// in one core call and republish at the next flush; every stream
|
|
1073
|
-
//
|
|
1074
|
-
// re-points, then the old buffers, which
|
|
1075
|
-
// are freed.
|
|
1067
|
+
// in one core call and republish at the next flush; every stream grows
|
|
1068
|
+
// into a replacement with its mirror copied over and marked whole, so the
|
|
1069
|
+
// scene republishes it; the entry re-points, then the old buffers, which
|
|
1070
|
+
// the entry held alive until now, are freed.
|
|
1076
1071
|
function growInstances(mesh: InstancedMesh | RecordMesh, next: number): void {
|
|
1077
1072
|
let inst: MeshInstances = mesh._instances
|
|
1078
1073
|
if (inst.transfer) throw new Error("growInstances: a transferred population is fixed at its creation records")
|
|
1079
|
-
let previous = inst.capacity
|
|
1080
1074
|
inst.capacity = next
|
|
1081
1075
|
let freed: BufferId[] = []
|
|
1082
1076
|
if (inst.matrix !== null) {
|
|
@@ -1085,15 +1079,13 @@ function growInstances(mesh: InstancedMesh | RecordMesh, next: number): void {
|
|
|
1085
1079
|
// Out of a scene nothing is bound, and retargeting an empty source throws.
|
|
1086
1080
|
if (inst.nodes !== null && inst.nodes.slots.some(n => n !== null && n._node !== null)) spatial.retargetRecords(freed[0]!, inst.matrix)
|
|
1087
1081
|
}
|
|
1088
|
-
inst.streams
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
viewStream(s,
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
markRecords(mesh, s, 0, previous * s.stride)
|
|
1096
|
-
})
|
|
1082
|
+
for (let s of inst.streams) {
|
|
1083
|
+
if (s.stream === null) throw new Error("growInstances: a stream without a mirror cannot grow")
|
|
1084
|
+
freed.push(s.stream.grow(next))
|
|
1085
|
+
s.buffer = s.stream.buffer
|
|
1086
|
+
viewStream(s, s.stream.bytes.buffer)
|
|
1087
|
+
mesh._scene?._setRecords(mesh)
|
|
1088
|
+
}
|
|
1097
1089
|
mesh._scene?._setBuffer(mesh)
|
|
1098
1090
|
for (let b of freed) destroyBuffer(b)
|
|
1099
1091
|
if (inst.morph !== null) replacePopulationMorph(mesh, populationMorph(mesh.geometry, next, inst.label), true)
|
|
@@ -1180,7 +1172,7 @@ export function createRecordMesh(
|
|
|
1180
1172
|
function copyRecords(mesh: RecordMesh, records: ArrayBufferView, written: number): void {
|
|
1181
1173
|
let stream = mesh._instances.streams[0]!
|
|
1182
1174
|
vertexBytes(stream.data).set(vertexBytes(records))
|
|
1183
|
-
if (written > 0) markRecords(mesh, stream, 0, written
|
|
1175
|
+
if (written > 0) markRecords(mesh, stream, 0, written)
|
|
1184
1176
|
}
|
|
1185
1177
|
|
|
1186
1178
|
/**
|
|
@@ -1220,7 +1212,7 @@ export function setRecordCount(mesh: RecordMesh, count: number): void {
|
|
|
1220
1212
|
// old population's nearest records, a growth its stale tail. A
|
|
1221
1213
|
// transferred mesh has no mirror to publish from; the engine follows
|
|
1222
1214
|
// the entry's instance count into its own mirror's prefix instead.
|
|
1223
|
-
if (inst.order !== null && !inst.transfer && n > 0) for (let s of inst.streams) markRecords(mesh, s, 0, n
|
|
1215
|
+
if (inst.order !== null && !inst.transfer && n > 0) for (let s of inst.streams) markRecords(mesh, s, 0, n)
|
|
1224
1216
|
mesh._scene?._setCount(mesh)
|
|
1225
1217
|
}
|
|
1226
1218
|
|
|
@@ -1259,7 +1251,10 @@ export function disposeInstances(mesh: InstancedMesh | RecordMesh): void {
|
|
|
1259
1251
|
spatial.flush()
|
|
1260
1252
|
}
|
|
1261
1253
|
if (inst.matrix !== null) destroyBuffer(inst.matrix)
|
|
1262
|
-
for (let s of inst.streams)
|
|
1254
|
+
for (let s of inst.streams) {
|
|
1255
|
+
if (s.stream !== null) s.stream.destroy()
|
|
1256
|
+
else destroyBuffer(s.buffer)
|
|
1257
|
+
}
|
|
1263
1258
|
if (inst.morph !== null) destroyTexture(inst.morph.texture)
|
|
1264
1259
|
for (let t of inst.textures) destroyTexture(t)
|
|
1265
1260
|
;(mesh as Mesh)._instances = null
|
package/src/scene.ts
CHANGED
|
@@ -2071,7 +2071,7 @@ export function createScene(width: number, height: number, opts?: SceneOptions):
|
|
|
2071
2071
|
for (let v of views) attachView(v, mesh)
|
|
2072
2072
|
// Record writes made while the mesh was out of a scene kept their
|
|
2073
2073
|
// dirty ranges: publish them now.
|
|
2074
|
-
if (inst !== null && inst.streams.some(s => s.
|
|
2074
|
+
if (inst !== null && inst.streams.some(s => s.stream !== null && s.stream.dirty)) recordsDirty.add(mesh)
|
|
2075
2075
|
// Picking: an ordinary mesh's node carries its geometry's shape,
|
|
2076
2076
|
// which is its box in the core index (following every
|
|
2077
2077
|
// updateVertices) and its triangle narrowphase. An instanced mesh's
|