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!