perf(importer): parse each material once per mesh instead of per primitive #836

Merged
SakulFlee merged 1 commit from perf/gltf-import-texture-dedup into main 2026-09-29 17:01:31 +00:00
Owner

parse_models rebuilt a material's descriptors for every primitive in the
mesh, and parse_texture copies the whole pixel buffer. Primitives of one mesh
routinely share a material - a multi-material mesh, a mesh split into
primitives by attribute, or simply a character model - so an N-primitive mesh
with a shared material converted and copied the same textures N times.

Cache parsed materials per mesh, keyed by the material's index in the
document. A primitive with no material resolves to the default material, whose
index is None; that is still one shared cache entry, so unmaterialized
primitives benefit too.

Measured with a generated asset of N primitives sharing one material and two
1024x1024 textures (make_shared_material_asset), min of 25 runs:

primitives=1 2.58 ms -> 3.40 ms (unchanged within noise)
primitives=8 13.70 ms -> 9.10 ms
primitives=32 55.96 ms -> 25.20 ms

Interleaved A/B at 32 primitives gave 1.70-1.97x across repeats. The saving
scales with the primitive count and texture size, and is zero for a mesh whose
primitives all use distinct materials.

The cache holds a clone per entry and each model still receives its own copy,
so a later mutation of one model's material cannot affect another.

Adds a regression test with two primitives sharing a material and a third on a
different one built from different textures: the shared pair must come out
identical, and the third must not receive the cached material. Verified the
test fails if the cache is keyed so that distinct materials collide.

Note the remaining cost is the clone of the already-parsed descriptor per
model. Removing that would need the material held behind an Arc all the way
into the GPU upload path, which is a wider change across orbital_texture and
its consumers; this keeps the change local to the importer.

Co-Authored-By: Claude Opus 4.8 (1M context) noreply@anthropic.com

parse_models rebuilt a material's descriptors for every primitive in the mesh, and parse_texture copies the whole pixel buffer. Primitives of one mesh routinely share a material - a multi-material mesh, a mesh split into primitives by attribute, or simply a character model - so an N-primitive mesh with a shared material converted and copied the same textures N times. Cache parsed materials per mesh, keyed by the material's index in the document. A primitive with no material resolves to the default material, whose index is None; that is still one shared cache entry, so unmaterialized primitives benefit too. Measured with a generated asset of N primitives sharing one material and two 1024x1024 textures (make_shared_material_asset), min of 25 runs: primitives=1 2.58 ms -> 3.40 ms (unchanged within noise) primitives=8 13.70 ms -> 9.10 ms primitives=32 55.96 ms -> 25.20 ms Interleaved A/B at 32 primitives gave 1.70-1.97x across repeats. The saving scales with the primitive count and texture size, and is zero for a mesh whose primitives all use distinct materials. The cache holds a clone per entry and each model still receives its own copy, so a later mutation of one model's material cannot affect another. Adds a regression test with two primitives sharing a material and a third on a different one built from different textures: the shared pair must come out identical, and the third must not receive the cached material. Verified the test fails if the cache is keyed so that distinct materials collide. Note the remaining cost is the clone of the already-parsed descriptor per model. Removing that would need the material held behind an Arc all the way into the GPU upload path, which is a wider change across orbital_texture and its consumers; this keeps the change local to the importer. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
perf(importer): parse each material once per mesh instead of per primitive
All checks were successful
Lint / lint (pull_request) Successful in 4m44s
ca2b90061b
parse_models rebuilt a material's descriptors for every primitive in the
mesh, and parse_texture copies the whole pixel buffer. Primitives of one mesh
routinely share a material - a multi-material mesh, a mesh split into
primitives by attribute, or simply a character model - so an N-primitive mesh
with a shared material converted and copied the same textures N times.

Cache parsed materials per mesh, keyed by the material's index in the
document. A primitive with no material resolves to the default material, whose
index is None; that is still one shared cache entry, so unmaterialized
primitives benefit too.

Measured with a generated asset of N primitives sharing one material and two
1024x1024 textures (make_shared_material_asset), min of 25 runs:

  primitives=1    2.58 ms ->  3.40 ms  (unchanged within noise)
  primitives=8   13.70 ms ->  9.10 ms
  primitives=32  55.96 ms -> 25.20 ms

Interleaved A/B at 32 primitives gave 1.70-1.97x across repeats. The saving
scales with the primitive count and texture size, and is zero for a mesh whose
primitives all use distinct materials.

The cache holds a clone per entry and each model still receives its own copy,
so a later mutation of one model's material cannot affect another.

Adds a regression test with two primitives sharing a material and a third on a
different one built from different textures: the shared pair must come out
identical, and the third must not receive the cached material. Verified the
test fails if the cache is keyed so that distinct materials collide.

Note the remaining cost is the clone of the already-parsed descriptor per
model. Removing that would need the material held behind an Arc all the way
into the GPU upload path, which is a wider change across orbital_texture and
its consumers; this keeps the change local to the importer.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
SakulFlee force-pushed perf/gltf-import-texture-dedup from ca2b90061b
All checks were successful
Lint / lint (pull_request) Successful in 4m44s
to ea47b26a5e
All checks were successful
Lint / lint (pull_request) Successful in 4m28s
2026-09-29 16:53:27 +00:00
Compare
SakulFlee deleted branch perf/gltf-import-texture-dedup 2026-09-29 17:01:32 +00:00
Sign in to join this conversation.
No description provided.