- iOS 12.0+
- tvOS 12.0+
- macOS 10.14+
- Xcode 11.0+
This sample demonstrates:
Using events instead of fences to manage resource dependencies and work synchronization
Creating heaps for static and dynamic textures
Using aliasing to reduce the amount of memory used for temporary resources
Using events 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 Gaussian blur filter. For more information, including implementation details about heaps for static and dynamic textures, see Image Filter Graph with Heaps and Fences.
The Xcode project contains schemes for running the sample on macOS, iOS, or tvOS. The default scheme is macOS, which runs the sample as is on your Mac.
Compare Events with Fences
MTLFence API allows you to specify synchronization points in your app that wait for a workload to complete execution, provided that execution begins before a fence is encountered. However, this synchronization mechanism means that your app can’t wait for a workload to complete execution if the execution begins after a fence is encountered. A fence can wait for workloads that have already begun, but it can’t wait for future workloads.
Fences work well in an image filter graph because each filter in the graph is applied sequentially. You can use a fence to wait for one filter to complete execution before you begin executing another.
In contrast, although the
MTLEvent API also allows you to specify similar synchronization points in your app, it allows for more flexibility than the
MTLFence API. Unlike fences, events can wait for workloads that have already begun, as well as future workloads. Additionally, events are specified outside command encoder boundaries, not between the encoded commands of a command encoder. Because the event synchronization mechanism is implemented in the command buffer scheduler, events block workloads at the command buffer level within the GPU. Therefore, command buffers on one queue can execute while a command buffer on another queue is blocked by an event.
Events also work well in an image filter graph because they provide the equivalent functionality of fences. However, events are easier to specify and track because their synchronization mechanism is managed with a discrete signal value that increases monotonically. Using this signal value, events insert a strict execution order between command encoder boundaries in the GPU.
Implement an Event Wrapper for Synchronization Routines
The sample wraps the
MTLEvent API in the
AAPLEvent protocol accessed through an
AAPLSingle object. This convenience wrapper encapsulates the main synchronization mechanism, and primarily manages the discrete signal value through the
The sample calls the
wait: method to wait for a workload to complete execution.
The sample calls the
signal: method to signal that a workload has completed execution. (This method increments the value of
Manage Dependencies Between Filters
The sample uses
_event to control access to dynamic textures allocated from
_scratch and prevent GPU race conditions in the filter graph. The event ensures that operations on dynamic textures are completed before the filter graph begins subsequent operations that depend on the result of previous operations.
At the start of the filter graph, the sample calls the
wait: method to ensure that the previous frame has completed execution.
The first filter, implemented by the sample in
AAPLDownsample, creates a dynamic texture,
out, from the heap and allocates enough space for mipmaps.
Next, the downsample filter blits a source texture,
out and generates the mipmaps. The sample then calls the
end method to finalize the blit pass.
Finally, the downsample filter calls the
signal: method to indicate that its operations are complete.
The second filter, implemented by the sample in
AAPLGaussian, calls the
wait: method immediately before 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 event, 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.
After blurring each mipmap level, the sample calls the
end method to indicate that the compute operations for the given mipmap level are complete.
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 also calls the
signal: method to indicate that the blur operations for the given mipmap level 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.
Manage Dependencies Between Frames
The sample calls the
wait: method to wait for the filter graph to complete execution before rendering the filtered image to a drawable.
The sample then renders the filtered image and schedules a drawable presentation.
Finally, the sample calls the
signal: method to indicate that the frame has been completed and rendered.