Hi everyone,
I’m encountering a memory overflow issue in my visionOS app and I’d like to confirm if this is expected behavior or if I’m missing something in cleanup.
App Context
- The app showcases apartments in real scale using AR.
- Apartments are heavy USDZ models (hundreds of thousands of triangles, high-resolution textures).
- Users can walk inside the apartments, and performance is good even close to hardware limits.
Flow
- The app starts in a full immersive space (RealityView) for selecting the apartment.
- When an apartment is selected, a new ImmersiveSpace opens and the apartment scene loads.
- The scene includes multiple USDZ models, EnvironmentResources, and dynamic textures for skyboxes.
- When the user dismisses the experience, we attempt cleanup:
- Nulling out all entity references.
- Removing ModelComponents.
- Clearing cached textures and skyboxes.
- Forcing dictionaries/collections to empty.
Despite this cleanup, memory usage remains very high.
Problem
After dismissing the ImmersiveSpace, memory does not return to baseline. Check the attached screenshot of the profiling made using Instruments:
- Initial state: ~30MB (main menu).
- After loading models sequentially: ~3.3GB.
- Skybox textures bring it near ~4GB.
- After dismissing the experience (at ~01:00 mark): memory only drops slightly (to ~2.66GB).
- When loading the second apartment, memory continues to increase until ~5GB, at which point the app crashes due to memory pressure.
The issue is consistently visible under VM: IOSurface in Instruments. No leaks are detected.
So it looks like RealityKit (or lower-level frameworks) keeps caching meshes and textures, and does not free them when RealityView is ended. But for my use case, these resources should be fully released once the ImmersiveSpace is dismissed, since new apartments will load entirely different models and textures.
Cleanup Code Example
Here’s a simplified version of the cleanup I’m doing:
func clearAllRoomEntities() {
for (entityName, entity) in entityFromMarker {
entity.removeFromParent()
if let modelEntity = entity as? ModelEntity {
modelEntity.components.removeAll()
modelEntity.children.forEach { $0.removeFromParent() }
modelEntity.clearTexturesAndMaterials()
}
entityFromMarker[entityName] = nil
removeSkyboxPortals(from: entityName)
}
entityFromMarker.removeAll()
}
extension ModelEntity {
func clearTexturesAndMaterials() {
guard var modelComponent = self.model else { return }
for index in modelComponent.materials.indices {
removeTextures(from: &modelComponent.materials[index])
}
modelComponent.materials.removeAll()
self.model = modelComponent
self.model = nil
}
private func removeTextures(from material: inout any Material) {
if var pbr = material as? PhysicallyBasedMaterial {
pbr.baseColor.texture = nil
pbr.emissiveColor.texture = nil
pbr.metallic.texture = nil
pbr.roughness.texture = nil
pbr.normal.texture = nil
pbr.ambientOcclusion.texture = nil
pbr.clearcoat.texture = nil
material = pbr
} else if var simple = material as? SimpleMaterial {
simple.color.texture = nil
material = simple
}
}
}
Questions
- Is this expected RealityKit behavior (textures/meshes cached internally)?
- Is there a way to force RealityKit to release GPU resources tied to USDZ models when they’re no longer used?
- Should dismissing the ImmersiveSpace automatically free those IOSurfaces, or do I need to handle this differently?
- Any guidance, best practices, or confirmation would be hugely appreciated.
Thanks in advance!
Thanks to @Vision Pro Engineer answer, I could find the solution!
I tried checking for others references to the entities added in the ImmersiveSpace. Indeed, I could find some references and auxiliary entities that were not being explicitly removed from parent on dismiss. Also, I made sure to remove the rootEntity from the parent as I was not doing it before (I thought that removing the entities themselves would be enough).
It worked! Now the memory almost goes back to the state before the ImmersiveSpace was opened.
However, I did had to:
- force the
.removeFromParent()
for each non-nullable entities (MOST RELEVANT) ; - reset the arrays and dictionary referencing entities to [] or [:];
- nulling every reference for Textures, EnvironmentResources and Colliders etc (LESS RELEVANT);
I think that this should not be the expected behavior. I guess that most developers would assume that this clean up is supposed to be automatically made when the ImmersiveSpace is dismissed and/or when the RealityView is dismissed.
But thanks a lot for the help!