Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
97f19b4
fix: make editable glTF vertex buffers writable
nmfisher Aug 26, 2026
73be184
fix: reject incompatible vertex buffer operations
nmfisher Aug 26, 2026
edaa749
refactor: model vertex buffer storage natively
nmfisher Aug 26, 2026
34f1988
Merge edaa7492225e4fad1725b86f4afd505cf06a72dd into 01f4c19e63eb391bd…
nmfisher Aug 26, 2026
ac601d1
chore: update generated artifacts + format (CI)
github-actions[bot] Aug 26, 2026
e2d950d
docs: clarify vertex buffer capability guards
nmfisher Aug 26, 2026
5c3b6c2
refactor: make vertex buffer metadata explicit
nmfisher Aug 26, 2026
da53ec7
Merge 5c3b6c27a0e6f770f8dd398ee934df525a9dea09 into 01f4c19e63eb391bd…
nmfisher Aug 26, 2026
904aa18
chore: update generated artifacts + format (CI)
github-actions[bot] Aug 26, 2026
f82644a
fix: allow highlights with preserved geometry
nmfisher Aug 26, 2026
891bfab
test: preserve wireframe golden artifact names
nmfisher Aug 26, 2026
6d6a79c
test: refresh reviewed golden baseline
nmfisher Aug 26, 2026
3e8536f
refactor!: request asset geometry capabilities
nmfisher Aug 26, 2026
8a0a9e0
Merge 3e8536fe791e6f7af9addd9794b0ff709602705a into 01f4c19e63eb391bd…
nmfisher Aug 26, 2026
7852393
chore: update generated artifacts + format (CI)
github-actions[bot] Aug 26, 2026
a5ca614
fix: make geometry capabilities truthful
nmfisher Aug 26, 2026
4f5d09a
Merge a5ca6141abc65bd8e918dae4d15e6b15ab396ae7 into 01f4c19e63eb391bd…
nmfisher Aug 26, 2026
78f0a85
chore: update generated artifacts + format (CI)
github-actions[bot] Aug 26, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/run-dart-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -389,7 +389,7 @@ jobs:
github-token: ${{ github.token }}
# To refresh every baseline, first push a capture-only commit,
# inspect its dart-*-output-* artifacts, then update this run ID.
run-id: 32575493711
run-id: 32812764648
name: ${{ matrix.artifact }}
path: thermion_dart/test/golden-baseline

Expand Down
26 changes: 20 additions & 6 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,16 +40,30 @@
- Apply overlapping custom morph animations oldest-first so the most recently
added animation has final priority for shared targets. Active animations
continue to overwrite manual weights on their next update.
- Fix topology-preserving glTF assets so their vertex streams can be updated
through `VertexBuffer.setBufferAt`; editable buffers no longer use
the `BufferObject` backing reserved for unwelded smooth/flat shading swaps.
Buffer updates, flat shading, and stencil highlighting now throw actionable
errors when used with incompatible buffer storage or asset capabilities.
Stencil highlighting accepts both editable and unwelded glTF geometry, plus
procedural geometry, because its silhouette pass only requires preserved
vertex/index buffers; flat shading remains unwelded-only.
- Expose native `VertexBuffer.storageMode` metadata and first-class
`BufferObject` creation, upload, and attachment APIs. `supportsSetBufferAt`
is now derived from immutable metadata supplied by the native builder or
owning asset, without a global pointer registry or duplicated glTF load
state in Dart. Asset-owned vertex buffers are explicitly borrowed.

### Breaking changes
- Replace the `rebuildVertices` in `ThermionViewer.loadGltf`,
`ThermionViewer.loadGltfFromBuffer`, and `FilamentApp.loadGltfFromBuffer` with
`vertexBufferMode: VertexBufferMode.unwelded`. Use
`VertexBufferMode.editable` when mutable indexed topology is required.
`original` leaves gltfio geometry untouched, `unwelded` creates per-triangle
vertices with barycentric coordinates, and `editable` exposes mutable vertex
buffers while preserving source vertex order, indices, and morph-target
compatibility.
`requiredGeometryCapabilities`. An empty set leaves gltfio geometry untouched;
requesting `barycentrics` or `uniqueTriangleCorners` creates per-triangle vertices, while
requesting `writableVertices`, `preservedTopology`, or `accessibleGeometryBuffers`
preserves source vertex order, indices, and morph-target compatibility in
directly writable buffers.
Assets report the complete capability set actually provided through
`ThermionAsset.geometryCapabilities`.
- remove the unused `FilamentApp.createColorGrading` — it returned a raw
pointer nobody could destroy; use `View.createColorGradingBuilder().build()`
instead.
Expand Down
148 changes: 140 additions & 8 deletions thermion_dart/lib/src/bindings/src/thermion_dart_ffi.g.dart
Original file line number Diff line number Diff line change
Expand Up @@ -614,6 +614,7 @@ external ffi.Pointer<ffi.Char> NameComponentManager_getName(
ffi.Pointer<ffi.Pointer<TMaterialInstance>>,
ffi.Int,
ffi.UnsignedInt,
ffi.UnsignedInt,
Aabb3,
)
>(isLeaf: true)
Expand All @@ -624,6 +625,7 @@ external ffi.Pointer<TSceneAsset> SceneAsset_createFromBuffers(
ffi.Pointer<ffi.Pointer<TMaterialInstance>> materialInstances,
int materialInstanceCount,
int tPrimitiveType,
int vertexBufferStorageMode,
Aabb3 boundingBox,
);

Expand All @@ -633,15 +635,15 @@ external ffi.Pointer<TSceneAsset> SceneAsset_createFromBuffers(
ffi.Pointer<TGltfAssetLoader>,
ffi.Pointer<TNameComponentManager>,
ffi.Pointer<TFilamentAsset>,
ffi.UnsignedInt,
ffi.Uint32,
)
>(isLeaf: true)
external ffi.Pointer<TSceneAsset> SceneAsset_createFromFilamentAsset(
ffi.Pointer<TEngine> tEngine,
ffi.Pointer<TGltfAssetLoader> tAssetLoader,
ffi.Pointer<TNameComponentManager> tNameComponentManager,
ffi.Pointer<TFilamentAsset> tFilamentAsset,
int vertexBufferMode,
int requiredGeometryCapabilities,
);

@ffi.Native<ffi.Pointer<TFilamentAsset> Function(ffi.Pointer<TSceneAsset>)>(isLeaf: true)
Expand Down Expand Up @@ -698,12 +700,21 @@ external ffi.Pointer<TSceneAsset> SceneAsset_createInstance(
@ffi.Native<Aabb3 Function(ffi.Pointer<TSceneAsset>)>(isLeaf: true)
external Aabb3 SceneAsset_getBoundingBox(ffi.Pointer<TSceneAsset> asset);

@ffi.Native<ffi.Uint32 Function(ffi.Pointer<TSceneAsset>)>(isLeaf: true)
external int SceneAsset_getGeometryCapabilities(ffi.Pointer<TSceneAsset> asset);

@ffi.Native<ffi.Bool Function(ffi.Pointer<TSceneAsset>)>(isLeaf: true)
external bool SceneAsset_supportsFlatShading(ffi.Pointer<TSceneAsset> asset);

@ffi.Native<ffi.Pointer<TVertexBuffer> Function(ffi.Pointer<TSceneAsset>, ffi.Int)>(isLeaf: true)
external ffi.Pointer<TVertexBuffer> SceneAsset_getVertexBuffer(
ffi.Pointer<TSceneAsset> tSceneAsset,
int primitiveIndex,
);

@ffi.Native<ffi.UnsignedInt Function(ffi.Pointer<TSceneAsset>, ffi.Int)>(isLeaf: true)
external int SceneAsset_getVertexBufferStorageMode(ffi.Pointer<TSceneAsset> tSceneAsset, int primitiveIndex);

@ffi.Native<ffi.Pointer<TIndexBuffer> Function(ffi.Pointer<TSceneAsset>, ffi.Int)>(isLeaf: true)
external ffi.Pointer<TIndexBuffer> SceneAsset_getIndexBuffer(ffi.Pointer<TSceneAsset> tSceneAsset, int primitiveIndex);

Expand Down Expand Up @@ -1396,6 +1407,12 @@ external void VertexBufferBuilder_bufferCount(ffi.Pointer<TVertexBufferBuilder>
@ffi.Native<ffi.Void Function(ffi.Pointer<TVertexBufferBuilder>, ffi.Uint32)>(isLeaf: true)
external void VertexBufferBuilder_vertexCount(ffi.Pointer<TVertexBufferBuilder> builder, int count);

@ffi.Native<ffi.Void Function(ffi.Pointer<TVertexBufferBuilder>, ffi.Bool)>(isLeaf: true)
external void VertexBufferBuilder_enableBufferObjects(ffi.Pointer<TVertexBufferBuilder> builder, bool enabled);

@ffi.Native<ffi.UnsignedInt Function(ffi.Pointer<TVertexBufferBuilder>)>(isLeaf: true)
external int VertexBufferBuilder_getStorageMode(ffi.Pointer<TVertexBufferBuilder> builder);

@ffi.Native<
ffi.Void Function(
ffi.Pointer<TVertexBufferBuilder>,
Expand Down Expand Up @@ -1449,6 +1466,16 @@ external void VertexBuffer_setBufferAt(
int byteOffset,
);

@ffi.Native<ffi.Void Function(ffi.Pointer<TEngine>, ffi.Pointer<TVertexBuffer>, ffi.Uint8, ffi.Pointer<TBufferObject>)>(
isLeaf: true,
)
external void VertexBuffer_setBufferObjectAt(
ffi.Pointer<TEngine> engine,
ffi.Pointer<TVertexBuffer> buffer,
int bufferIndex,
ffi.Pointer<TBufferObject> bufferObject,
);

@ffi.Native<ffi.Void Function(ffi.Pointer<TEngine>, ffi.Pointer<TVertexBuffer>)>(isLeaf: true)
external void VertexBuffer_destroy(ffi.Pointer<TEngine> engine, ffi.Pointer<TVertexBuffer> buffer);

Expand Down Expand Up @@ -1487,6 +1514,35 @@ external void IndexBuffer_setBuffer(
@ffi.Native<ffi.Void Function(ffi.Pointer<TEngine>, ffi.Pointer<TIndexBuffer>)>(isLeaf: true)
external void IndexBuffer_destroy(ffi.Pointer<TEngine> engine, ffi.Pointer<TIndexBuffer> buffer);

@ffi.Native<ffi.Pointer<TBufferObjectBuilder> Function()>(isLeaf: true)
external ffi.Pointer<TBufferObjectBuilder> BufferObjectBuilder_create();

@ffi.Native<ffi.Void Function(ffi.Pointer<TBufferObjectBuilder>, ffi.Uint32)>(isLeaf: true)
external void BufferObjectBuilder_size(ffi.Pointer<TBufferObjectBuilder> builder, int sizeInBytes);

@ffi.Native<ffi.Pointer<TBufferObject> Function(ffi.Pointer<TBufferObjectBuilder>, ffi.Pointer<TEngine>)>(isLeaf: true)
external ffi.Pointer<TBufferObject> BufferObjectBuilder_build(
ffi.Pointer<TBufferObjectBuilder> builder,
ffi.Pointer<TEngine> engine,
);

@ffi.Native<ffi.Void Function(ffi.Pointer<TBufferObjectBuilder>)>(isLeaf: true)
external void BufferObjectBuilder_destroy(ffi.Pointer<TBufferObjectBuilder> builder);

@ffi.Native<
ffi.Void Function(ffi.Pointer<TEngine>, ffi.Pointer<TBufferObject>, ffi.Pointer<ffi.Void>, ffi.Size, ffi.Uint32)
>(isLeaf: true)
external void BufferObject_setBuffer(
ffi.Pointer<TEngine> engine,
ffi.Pointer<TBufferObject> buffer,
ffi.Pointer<ffi.Void> data,
int sizeInBytes,
int byteOffset,
);

@ffi.Native<ffi.Void Function(ffi.Pointer<TEngine>, ffi.Pointer<TBufferObject>)>(isLeaf: true)
external void BufferObject_destroy(ffi.Pointer<TEngine> engine, ffi.Pointer<TBufferObject> buffer);

@ffi.Native<double4x4 Function(ffi.Pointer<TTransformManager>, EntityId)>(isLeaf: true)
external double4x4 TransformManager_getLocalTransform(ffi.Pointer<TTransformManager> tTransformManager, int entityId);

Expand Down Expand Up @@ -2695,7 +2751,7 @@ external void SceneAsset_destroyRenderThread(
ffi.Pointer<TGltfAssetLoader>,
ffi.Pointer<TNameComponentManager>,
ffi.Pointer<TFilamentAsset>,
ffi.UnsignedInt,
ffi.Uint32,
ffi.Pointer<ffi.NativeFunction<ffi.Void Function(ffi.Pointer<TSceneAsset>)>>,
)
>(isLeaf: true)
Expand All @@ -2704,7 +2760,7 @@ external void SceneAsset_createFromFilamentAssetRenderThread(
ffi.Pointer<TGltfAssetLoader> tAssetLoader,
ffi.Pointer<TNameComponentManager> tNameComponentManager,
ffi.Pointer<TFilamentAsset> tFilamentAsset,
int vertexBufferMode,
int requiredGeometryCapabilities,
ffi.Pointer<ffi.NativeFunction<ffi.Void Function(ffi.Pointer<TSceneAsset>)>> onComplete,
);

Expand All @@ -2716,6 +2772,7 @@ external void SceneAsset_createFromFilamentAssetRenderThread(
ffi.Pointer<ffi.Pointer<TMaterialInstance>>,
ffi.Int,
ffi.UnsignedInt,
ffi.UnsignedInt,
Aabb3,
ffi.Pointer<ffi.NativeFunction<ffi.Void Function(ffi.Pointer<TSceneAsset>)>>,
)
Expand All @@ -2727,6 +2784,7 @@ external void SceneAsset_createFromBuffersRenderThread(
ffi.Pointer<ffi.Pointer<TMaterialInstance>> materialInstances,
int materialInstanceCount,
int tPrimitiveType,
int vertexBufferStorageMode,
Aabb3 boundingBox,
ffi.Pointer<ffi.NativeFunction<ffi.Void Function(ffi.Pointer<TSceneAsset>)>> callback,
);
Expand Down Expand Up @@ -3429,6 +3487,67 @@ external void VertexBuffer_setBufferAtRenderThread(
VoidCallback onComplete,
);

@ffi.Native<
ffi.Void Function(
ffi.Pointer<TEngine>,
ffi.Pointer<TVertexBuffer>,
ffi.Uint8,
ffi.Pointer<TBufferObject>,
ffi.Uint32,
VoidCallback,
)
>(isLeaf: true)
external void VertexBuffer_setBufferObjectAtRenderThread(
ffi.Pointer<TEngine> tEngine,
ffi.Pointer<TVertexBuffer> tBuffer,
int bufferIndex,
ffi.Pointer<TBufferObject> tBufferObject,
int requestId,
VoidCallback onComplete,
);

@ffi.Native<
ffi.Void Function(
ffi.Pointer<TBufferObjectBuilder>,
ffi.Pointer<TEngine>,
ffi.Pointer<ffi.NativeFunction<ffi.Void Function(ffi.Pointer<TBufferObject>)>>,
)
>(isLeaf: true)
external void BufferObjectBuilder_buildRenderThread(
ffi.Pointer<TBufferObjectBuilder> tBuilder,
ffi.Pointer<TEngine> tEngine,
ffi.Pointer<ffi.NativeFunction<ffi.Void Function(ffi.Pointer<TBufferObject>)>> onComplete,
);

@ffi.Native<
ffi.Void Function(
ffi.Pointer<TEngine>,
ffi.Pointer<TBufferObject>,
ffi.Pointer<ffi.Void>,
ffi.Size,
ffi.Uint32,
ffi.Uint32,
VoidCallback,
)
>(isLeaf: true)
external void BufferObject_setBufferRenderThread(
ffi.Pointer<TEngine> tEngine,
ffi.Pointer<TBufferObject> tBuffer,
ffi.Pointer<ffi.Void> data,
int sizeInBytes,
int byteOffset,
int requestId,
VoidCallback onComplete,
);

@ffi.Native<ffi.Void Function(ffi.Pointer<TEngine>, ffi.Pointer<TBufferObject>, ffi.Uint32, VoidCallback)>(isLeaf: true)
external void BufferObject_destroyRenderThread(
ffi.Pointer<TEngine> tEngine,
ffi.Pointer<TBufferObject> tBuffer,
int requestId,
VoidCallback onComplete,
);

@ffi.Native<
ffi.Void Function(
ffi.Pointer<TIndexBufferBuilder>,
Expand Down Expand Up @@ -4801,6 +4920,10 @@ final class TVertexBufferBuilder extends ffi.Opaque {}

final class TIndexBufferBuilder extends ffi.Opaque {}

final class TBufferObject extends ffi.Opaque {}

final class TBufferObjectBuilder extends ffi.Opaque {}

final class TSurfaceOrientation extends ffi.Opaque {}

final class TSurfaceOrientationBuilder extends ffi.Opaque {}
Expand Down Expand Up @@ -4901,10 +5024,19 @@ sealed class TSceneAssetType {
static const SCENE_ASSET_TYPE_GIZMO = 6;
}

sealed class TVertexBufferMode {
static const VERTEX_BUFFER_MODE_ORIGINAL = 0;
static const VERTEX_BUFFER_MODE_UNWELDED = 1;
static const VERTEX_BUFFER_MODE_EDITABLE = 2;
sealed class TVertexBufferStorageMode {
static const VERTEX_BUFFER_STORAGE_MODE_UNKNOWN = 0;
static const VERTEX_BUFFER_STORAGE_MODE_DIRECT = 1;
static const VERTEX_BUFFER_STORAGE_MODE_BUFFER_OBJECTS = 2;
}

sealed class TSceneAssetGeometryCapability {
static const SCENE_ASSET_GEOMETRY_CAPABILITY_NONE = 0;
static const SCENE_ASSET_GEOMETRY_CAPABILITY_BARYCENTRICS = 1;
static const SCENE_ASSET_GEOMETRY_CAPABILITY_WRITABLE_VERTICES = 2;
static const SCENE_ASSET_GEOMETRY_CAPABILITY_ACCESSIBLE_GEOMETRY_BUFFERS = 4;
static const SCENE_ASSET_GEOMETRY_CAPABILITY_PRESERVED_TOPOLOGY = 8;
static const SCENE_ASSET_GEOMETRY_CAPABILITY_UNIQUE_TRIANGLE_CORNERS = 16;
}

sealed class TFeatureLevel {
Expand Down
Loading