Designing Metal Resource Heaps for Smarter iOS Memory Management

A high-end iPhone can render complex scenes incredibly quickly, but fast GPU hardware does not remove the need for careful memory management.

Games still create shadow maps, visibility buffers, post-processing targets, particle data, and streaming resources that compete for the same system memory.

Designing Metal Resource Heaps helps teams manage this pressure at a lower level. Instead of treating textures and buffers as completely independent allocations, developers can group resources according to lifetime and usage.

Combined with aliasing, residency management, and profiling, heaps can turn transient GPU memory into a reusable system rather than a growing collection of allocations.

Design the Heap Around a Resource Lifetime Graph

The most important heap-design question is not, “How many textures do we have?”

It is, “Which textures need to exist at the same time?”

Imagine a renderer with shadow mapping, deferred lighting, ambient occlusion, bloom, depth-of-field, and tone mapping. Across the entire frame, it may create hundreds of megabytes of intermediate data.

But those resources are not necessarily live simultaneously.

Apple recommends using Metal heaps when transient resources are produced and consumed during each frame but are not all required together. Resources with non-overlapping lifetimes can share backing memory.

This maps naturally onto a frame graph.

The graph knows when a resource is created, which passes consume it, and when its final use occurs.

That data can drive heap placement automatically.

Instead of manually deciding that Texture X can reuse Texture Y’s memory, the allocator calculates lifetime overlap and finds compatible regions.

The more complex the renderer becomes, the more valuable this lifecyle-driven approach becomes.

Start With Automatic Heaps Before Going Fully Manual

Metal supports different heap placement strategies.

The default heap type is MTLHeapType.automatic, where Metal manages placement inside the heap. Apple notes that developers wanting more fine-grained control can use placement heaps and manage offsets more directly.

For many game engines, automatic heaps are a sensible starting point.

They let the renderer benefit from explicit memory pools and aliasing without immediately building a full GPU allocator.

Placement heaps become interesting when the engine already has strong lifetime analysis and needs deterministic control over where resources sit.

That flexibility also comes with responsibility.

Manual placement means your allocator needs to understand size, alignment, overlap, reuse, and synchronization correctly.

A sophisticated memory system can save significant space. A buggy one can create spectacularly confusing rendering corruption.

Move toward manual control only when profiling shows a real benefit.

Group Heaps by Behavior Rather Than by File Type

It may seem logical to create one texture heap and one buffer heap.

That can be too simplistic.

Resources often behave more similarly based on lifetime, storage mode, and synchronization requirements than based purely on whether they are buffers or textures.

For example, one heap might contain long-lived private GPU assets. Another handles transient render targets. A third is rebuilt when a level changes.

Apple’s historical heap best practices recommend separating aliasable and non-aliasable resources and, where useful, grouping similarly sized allocations to reduce fragmentation. It also notes that certain render-target types may benefit from separate heaps.

The general principle remains useful for modern engines.

Do not create a single enormous “everything heap.”

That makes ownership difficult and increases the chance that a temporary allocation pattern gradually fragments memory needed by long-lived resources.

Pools should reflect how data behaves.

Connect Heap Management With Residency

Allocation and residency are related but different problems.

A resource can have backing storage yet still need to become accessible, or resident, for GPU work.

Modern Metal provides residency sets that let developers group resource allocations-including buffers, textures, and complete heaps-and tell Metal when those allocations need GPU residency.

Apple notes that residency sets can lower CPU overhead compared with declaring many resources separately on command encoders.

This becomes interesting for large games with many resources.

Frequently used heaps can be associated with a command queue, while more specialized groups can be attached only when particular work needs them.

Apple also provides requestResidency() to prepare allocations before command-buffer execution and endResidency() when they no longer need to remain GPU-accessible.

That turns residncy into another lifetime decision.

A level-specific heap does not necessarily need the same residency policy as a permanent UI or material-resource pool.

Understand the Cost of Heap-Level Residency

There is an important detail when combining heaps and residency sets.

Apple states that adding a resource allocation originating from an MTLHeap to a residency set effectively makes the entire heap resident.

That affects how you should group resources.

Imagine a huge heap containing both frequently used textures and rarely visited level assets. If one small resource forces residency of the entire heap, your grouping may become less memory-efficient than expected.

Smaller logical heaps can sometimes produce better residency granularity.

But again, too many heaps add allocator complexity.

Think in terms of residency domains.

Resources that usually become active and inactive together are natural candidates for the same heap. Those with very different activity patterns may deserve separation.

This is similar to designing streaming zones in an open world: group what tends to live together.

Synchronize Aliasing Explicitly

Heap reuse is powerful because multiple resource objects can occupy the same underlying memory at different times.

The danger is obvious: the GPU is asynchronous.

The CPU can move far ahead and schedule new work while earlier commands are still consuming a resource.

Apple warns that once an automatic heap resource becomes aliasable, the renderer must make sure previous work is finished before another alias accesses the same backing memory. Metal fences and events are tools for protecting those transitions.

Residency sets do not solve this automatically either. Apple notes that residency sets do not perform hazard tracking, so applications remain responsible for resource hazards where applicable.

A frame graph can help because dependencies are already explicit.

If Pass B reads Texture A, the graph knows Texture A cannot release its region until Pass B’s dependency requirements are satisfied.

Correct syncronization should be part of allocation planning, not a patch added after flickering appears.

Monitor Both Used Space and Largest Free Block

Heap statistics can reveal problems before allocations fail.

usedSize tells you how much heap memory current resources occupy. maxAvailableSize(alignment:) tells you the largest allocation that can fit for a given alignment and is specifically documented as a way to measure fragmentation.

These numbers tell different stories.

Suppose a 400 MB heap reports 280 MB used. You might assume there is 120 MB available.

But if the largest usable region is only 35 MB, a new 64 MB target cannot fit despite the apparent free capacity.

Record these values during demanding scenes.

A useful debug overlay can report total size, used space, peak use, and maximum free region for key heaps.

Watching those values across gameplay exposes whether fragmentation accumulates slowly during long sessions.

That kind of measurment is especially valuable in live-service games where rendering features change over years.

Keep Memoryless Resources Outside the Heap Plan

A sophisticated heap allocator should still know when not to allocate memory.

On Apple GPUs, memoryless textures store temporary render-pass data in tile memory. Apple recommends them for temporary resources such as depth or multisample attachments when their contents do not need to survive outside the render pass.

These resources can reduce ordinary system-memory usage substantially.

So the frame graph can classify temporary images into at least two categories.

Some live only inside one render pass and should be memoryless where supported. Others survive across passes and are better candidates for heap aliasing.

This classification prevents the heap from carrying allocations Metal can eliminate more efficiently.

A memory-efficient renderer is not one with the cleverest heap allocator. It is one that avoids unnecessary backing memory in the first place.

Make Memory Pressure Part of Quality Scaling

Apple emphasizes that iOS can terminate an app when its memory footprint exceeds a device-specific limit and recommends profiling on all supported hardware.

That means heap budgets should not be identical across every iPhone generation.

A renderer can adapt memory-heavy features on constrained devices.

Lower shadow-map resolution. Reduce temporary post-processing buffers. Stream smaller textures. Shorten cache lifetimes.

Apple also recommends compressed textures, smaller assets, reduced special-effect buffer sizes, and avoiding unused resources when memory pressure matters.

Heap architecture makes these decisions easier because resource categories already have measurable budgets.

If the transient rendering heap regularly peaks too close to its safe limit on an older device, the game can lower specific visual features before stability suffers.

Memory quality scaling can be just as valuable as GPU frame-time scaling.

Designing Metal Resource Heaps becomes most effective when heap allocation is connected to resource lifetimes, residency, synchronization, and device memory budgets.

The goal is not simply creating fewer Metal objects-it is reducing how much backing memory must exist simultaneously.

Profile your frame graph, identify non-overlapping GPU resources, and measure fragmentation over long sessions. Those steps reveal where smarter reuse can deliver meaningful savings without reducing visible quality.