From 832e13a2beb8492468e1b650e6c860df8c4ada77 Mon Sep 17 00:00:00 2001 From: codydorsettlynn Date: Mon, 13 Jul 2026 12:43:42 +0800 Subject: [PATCH] fix: crash on scene teardown after changeMaterialTextureMap MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit changeMaterialTextureMap duplicated the entity's MaterialInstance and accumulated every duplicate until the RenderableManager was destroyed. Because JS wrappers release in nondeterministic order at scene teardown, a duplicate could be destroyed while still attached to its renderable, aborting with filament's "destroying MaterialInstance which is still in use by Renderable" precondition. The Texture created per call was never destroyed at all (permanent GPU memory leak). Track instances and textures per (entity, primitive) slot instead. The duplicate's deleter now detaches a still-attached instance first by restoring the slot's original asset-owned instance, then destroys the duplicate on the renderer thread — satisfying both filament teardown preconditions in any release order. Superseded instances and textures of a slot are destroyed eagerly when replaced. Co-Authored-By: Claude Fable 5 --- examples/Shared/src/ChangeMaterials.tsx | 14 +++-- package/cpp/core/RNFRenderableManagerImpl.cpp | 58 ++++++++++++++++--- package/cpp/core/RNFRenderableManagerImpl.h | 16 ++++- 3 files changed, 74 insertions(+), 14 deletions(-) diff --git a/examples/Shared/src/ChangeMaterials.tsx b/examples/Shared/src/ChangeMaterials.tsx index f3a715c1..22a09029 100644 --- a/examples/Shared/src/ChangeMaterials.tsx +++ b/examples/Shared/src/ChangeMaterials.tsx @@ -9,7 +9,11 @@ import RocketGlb from '@assets/rocket.glb' const baseColorBlueImage = require('@assets/rocket_BaseColor_Blue.png') function Renderer() { - const [showBlue, setShowBlue] = React.useState(false) + // Regression test for a crash on scene teardown: after changing a texture map, navigating back + // from this screen must not abort with filament's "destroying MaterialInstance which is still + // in use by Renderable" precondition. Every press re-applies the texture map, which also + // exercises destroying the superseded material instance and texture of a slot. + const [applyCount, setApplyCount] = React.useState(0) const blueBaseColorBuffer = useBuffer({ source: baseColorBlueImage }) const materialName = 'Toy Ship' @@ -20,18 +24,18 @@ function Renderer() { - {blueBaseColorBuffer != null && showBlue && ( - <> + {blueBaseColorBuffer != null && applyCount > 0 && ( + - + )}