Contents

Creating a custom Metal view

Implement a lightweight view for Metal rendering that’s customized to your app’s needs.

Overview

While MetalKit’s MTKView provides significant functionality, allowing you to quickly get started writing Metal code, sometimes you want more control over how your Metal content is rendered. This sample app demonstrates how to create a simple Metal view derived directly from an NSView or UIView. It uses a CAMetalLayer object to hold the view’s contents.

Configure the sample code project

This sample has targets for iOS, tvOS, and macOS. There are significant differences between apps that use UIKit and AppKit. Because of these differences, this sample creates two different classes. The iOS and tvOS versions of the sample use the AAPLUIView class, and the macOS version uses the AAPLNSView class. Both are derived from a common AAPLView class.

This sample provides a number of options you can enable when building the app, such as whether to animate the view’s contents or handle updates through system events. You control these options by changing the preprocessor definitions in the AAPLConfig.h file.

Configure the view with a Metal layer

For Metal to render to the view, the view needs to be backed by a CAMetalLayer.

All views in UIKit are layer backed. To indicate the type of layer backing, the view implements the layerClass class method. To indicate that your view should be backed by a CAMetalLayer, you need to return the CAMetalLayer class type.

In AppKit, you make the view layer backed by setting the view’s wantsLayer property.

This triggers a call to the view’s makeBackingLayer method, which returns a CAMetalLayer object.

Render to the view

To render to the view, create an MTLRenderPassDescriptor object that targets a texture provided by the layer. The AAPLRenderer class stores the render pass descriptor in the _drawableRenderPassDescriptor instance variable. Most of the properties of this descriptor are set up automatically when you initialize the renderer. The code configures the render pass to clear the contents of the texture, and to store any rendered contents to the texture when the render pass completes.

You also need to set the texture that the render pass renders into. Each time the app renders a frame, the renderer obtains a CAMetalDrawable from the Metal layer. The drawable provides a texture for Core Animation to present onscreen. The renderer updates the render pass descriptor to render to this texture:

The rest of the rendering code is similar to that found in other Metal samples. For an explanation of a typical rendering path, see Drawing a triangle with Metal 4.

Implement a render loop

To animate the view, the sample sets up a display link. The display link calls the view at the specified interval, synchronizing updates to the display’s refresh interval. The view calls the renderer object to render a new frame of animation.

AAPLUIView creates a CADisplayLink in the setupCADisplayLinkForScreen method. Because you need to know which screen the window is on before creating the display link, you call this method when UIKit calls your view’s didMoveToWindow() method. UIKit calls this method the first time the view is added to a window and when the view is moved to another screen. The code below stops the render loop and initializes a new display link.

AAPLNSView uses a CVDisplayLink instead of a CADisplayLink because CADisplayLink is not available on macOS. CVDisplayLink and CADisplayLink API look different, but, in principle, have the same goal, which is to allow callbacks in sync with the display. AAPLNSView creates a CVDisplayLink in the setupCVDisplayLinkForScreen method. The setupCVDisplayLinkForScreen method is called from viewDidMoveToWindow(), which AppKit calls immediately after loading the view. If the view is moved to another screen, AppKit also calls viewDidMoveToWindow, and like the previous code for UIKit, the AppKit view needs to recreate the display link for the new screen.

The macOS version of this code performs a few additional steps. After creating the display link, it sets the callback and a parameter to pass to the callback. If you want rendering to happen on the main thread, it passes a dispatch source object; otherwise, it passes a reference to the view itself. Finally, it tells the display link which display the window is located on, and sets a notification to be called when the window is closed.

See Also

Render workflows