- iOS 11.3+
- tvOS 10.0+
- macOS 10.15+
- Xcode 11.0+
This sample demonstrates:
Creating heaps for static and dynamic textures
Using aliasing to reduce the amount of memory used for temporary resources
Using fences to manage dependencies between encoders that produce and consume dynamic textures
This implementation minimizes memory usage in an orderly fashion for a filter graph with a downsample and a Gaussian blur filter.
The Xcode project contains schemes for running the sample on macOS, iOS, or tvOS. Metal is not supported in the iOS or tvOS Simulator, so the iOS and tvOS schemes require a physical device to run the sample. The default scheme is macOS, which runs the sample as is on your Mac.
Optimize Resource Allocation and Performance
Storing textures in a heap gives the sample more control over how resource memory is allocated and accessed. It’s also much faster to allocate resources from a heap than from a device. When resources are allocated from a device, Metal creates and tracks additional state to ensure that the resource memory is allocated, synchronized, and made available throughout the lifetime of any command buffer that needs the given resource. It does so even if the resource itself is destroyed before the command buffer begins execution.
Although Metal also carries out this process for heaps, it doesn’t do so for resources within the heap. Instead, the app must perform explicit fine-grained synchronization when it creates objects from the heap and reuses memory. However, the overall cost of allocating resources from a heap is much lower than that of allocating resources from a device, particularly in the middle of a frame.
Create a Heap for Static Textures
The sample loads image files into an array called
_image. Instead of using
_image directly, the sample uses
_image, from which it allocates static textures. The sample creates a heap large enough to store all the static textures by aggregating their sizes. For each texture in
_image, the sample calls the
heap method to calculate the size and alignment values required to allocate sufficient memory backing for each texture.
For each texture in
_image, the sample allocates a new texture,
heap, from the heap.
The sample blits the contents of
heap, and then replaces
Create a Heap for Dynamic Textures
The sample uses a separate heap,
_scratch, from which it allocates dynamic textures with a temporary lifetime. These textures have the same properties of the static texture being filtered in a given frame.
The sample uses
_scratch to quickly allocate temporary textures for the downsample and Gaussian blur filters. Thus, the required size and alignment values for
_scratch are equal to the sum of the same required values for each filter.
Any textures allocated from
_scratch can also be deallocated, which allows the sample to reuse that same memory backing to allocate another texture. This memory reuse is known as aliasing.
Manage Dependencies Between Filters
The sample uses
_fence to control access to dynamic textures allocated from
_scratch and prevent GPU race conditions in the filter graph. This fence ensures that operations on dynamic textures are completed before the filter graph begins subsequent operations that depend on the results of previous operations.
The first filter, implemented by the sample in
AAPLDownsample, creates a dynamic texture,
out, from the heap and allocates enough space for mipmaps.
The downsample filter then blits a source texture,
out and generates the mipmaps.
Finally, the downsample filter calls the
end methods to indicate that its operations are complete.
The second filter, implemented by the sample in
wait immediately after creating a compute command encoder. This forces the Gaussian blur filter to wait for the downsample filter to complete its work before beginning its own work. A waiting period is necessary because the Gaussian blur filter depends on dynamic texture data generated by the downsample filter. Without the fence, the GPU could execute both filters in parallel, and thus read uninitialized dynamic texture data allocated from the heap.
Reuse Memory and Manage Dependencies Within a Filter
The Gaussian blur filter performs a horizontal blur and a vertical blur for each mipmap level of the dynamic texture produced by the downsample filter. For each mipmap level, the sample allocates a temporary texture,
intermediary, from the dynamic textures heap.
This texture is temporary because it’s used only as an output destination from the horizontal blur and as an input source to the vertical blur. After the sample executes these blurs, the final texture data is stored in
out (which is a texture view of
in). Therefore, the texture data contained in
intermediary is unused after each mipmap level iteration.
Instead of allocating new memory for each mipmap level, the sample reuses the existing memory allocated for
intermediary. After each mipmap level iteration, the sample calls the
make method to indicate that this memory can be reused by subsequent allocations from the same dynamic textures heap.
This memory reuse creates dynamic texture dependencies between mipmap levels. Therefore, after blurring each mipmap level, the sample calls the
end methods to indicate that the blur operations are complete.
Because the sample already calls the
wait method to wait for the downsample filter to complete its work, the sample leverages this same call to wait for any previous mipmap levels to complete their work before beginning a new mipmap level iteration.