Guides and Sample Code

Developer

Metal Best Practices Guide

On This Page

Drawables

Best Practice: Hold a drawable as briefly as possible.

Most Metal apps implement a layer-backed view defined by a CAMetalLayer object. This layer vends an efficient displayable resource conforming to the CAMetalDrawable protocol, commonly referred to as a drawable. A drawable provides a MTLTexture object that is typically used as a displayable render target attached to a MTLRenderPassDescriptor object, with the goal of being presented on the screen.

A drawable’s presentation is registered by calling a command buffer’s presentDrawable: method before calling its commit method. However, the drawable itself is actually presented only after the command buffer has completed execution and the drawable has been rendered or written to.

A drawable tracks whether it has outstanding render or write requests on it and will not present until those requests have been completed. A command buffer registers its drawable requests only when it is scheduled for execution. Registering a drawable presentation after the command buffer is scheduled guarantees that all command buffer work will be completed before the drawable is actually presented. Do not wait for the command buffer to complete its GPU work before registering a drawable presentation; this will cause a considerable CPU stall.

Hold a Drawable as Briefly as Possible

Drawables are expensive system resources created and maintained by the Core Animation framework. They exist within a limited and reusable resource pool and may or may not be available when requested by your app. If there is no drawable available at the time of your request, the calling thread is blocked until a new drawable becomes available (which is usually at the next display refresh interval).

To hold a drawable as briefly as possible, follow these two steps:

  1. Always acquire a drawable as late as possible; preferably, immediately before encoding an on-screen render pass. A frame’s CPU work may include dynamic data updates and off-screen render passes that you can perform before acquiring a drawable.

  2. Always release a drawable as soon as possible; preferably, immediately after finalizing a frame’s CPU work. It is highly advisable to contain your rendering loop within an autorelease pool block to avoid possible deadlock situations with multiple drawables.

Figure 6-1 shows the lifetime of a drawable in relation to other CPU work.

Figure 6-1The lifetime of a drawable image: ../Art/DisplayManagement_DrawableLifetime_2x.png

Use a MetalKit View to Interact with Drawables

Using an MTKView object is the preferred way to interact with drawables. An MTKView object is backed by a CAMetalLayer object and provides the currentDrawable property to acquire the drawable for the current frame. The current frame renders into this drawable and the presentDrawable: method schedules the actual presentation to occur at the next display refresh interval. The currentDrawable property is automatically updated at the end of every frame.

An MTKView object also provides the currentRenderPassDescriptor convenience property that references the current drawable’s texture; use this property to create a render command encoder that renders into the current drawable. A call to the currentRenderPassDescriptor property implicitly acquires the drawable for the current frame, which is then stored in the currentDrawable property.

Listing 6-1 shows how to use a drawable with a MetalKit view.

Listing 6-1Using drawables with a MetalKit view
  1. - (void)render:(MTKView *)view {
  2. // Update your dynamic data
  3. [self update];
  4. // Create a new command buffer
  5. id <MTLCommandBuffer> commandBuffer = [_commandQueue commandBuffer];
  6. // BEGIN encoding any off-screen render passes
  7. /* ... */
  8. // END encoding any off-screen render passes
  9. // BEGIN encoding your on-screen render pass
  10. // Acquire a render pass descriptor generated from the drawable's texture
  11. // 'currentRenderPassDescriptor' implicitly acquires the drawable
  12. MTLRenderPassDescriptor* renderPassDescriptor = view.currentRenderPassDescriptor;
  13. // If there's a valid render pass descriptor, use it to render into the current drawable
  14. if(renderPassDescriptor != nil) {
  15. id<MTLRenderCommandEncoder> renderCommandEncoder = [commandBuffer renderCommandEncoderWithDescriptor:renderPassDescriptor];
  16. /* Set render state and resources */
  17. /* Issue draw calls */
  18. [renderCommandEncoder endEncoding];
  19. // END encoding your on-screen render pass
  20. // Register the drawable presentation
  21. [commandBuffer presentDrawable:view.currentDrawable];
  22. }
  23. /* Register optional callbacks */
  24. // Finalize the CPU work and commit the command buffer to the GPU
  25. [commandBuffer commit];
  26. }
  27. - (void)drawInMTKView:(MTKView *)view {
  28. @autoreleasepool {
  29. [self render:view];
  30. }
  31. }