“This is the 7th day of my participation in the Gwen Challenge in November. Check out the details: The Last Gwen Challenge in 2021.”

An overview of the

We can use MetalKit MTKView to quickly write views based on Metal rendering. MTKView encapsulates the operations of Metal, and to some extent facilitates development.

But sometimes we want more flexibility in how Metal content is rendered, and we need to start from scratch with Metal to implement the rendering process.

This example will customize the NSView or UIView to implement Metal rendering, using the CAMetalLayer object to hold the view’s contents.

Use Metal Layer to configure the view

To support Metal rendering to a view, you must specify the view’s layer as CAMetalLayer.

All views in UIKit are rendered by layer. The view implements the layerClass method to specify the type of the layer.


+ (Class) layerClass

{

    return [CAMetalLayer class];

}

Copy the code

In AppKit, you need to set the WantsLayer property of the view to support the view layer.


self.wantsLayer = YES;

Copy the code

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


- (CALayer *)makeBackingLayer

{

    return [CAMetalLayer layer];

}

Copy the code

Render to view

To render to the view, you create an MTLRenderPassDescriptor object. This object targets the texture provided by the layer, sets the background color to clear, and stores the rendered content into the texture when the render pass is complete.


_drawableRenderDescriptor = [MTLRenderPassDescriptor new];

_drawableRenderDescriptor.colorAttachments[0].loadAction = MTLLoadActionClear;

_drawableRenderDescriptor.colorAttachments[0].storeAction = MTLStoreActionStore;

_drawableRenderDescriptor.colorAttachments[0].clearColor = MTLClearColorMake(0, 1, 1, 1);

Copy the code

A texture needs to be created to store the rendered results of the render channel. Each time the application renders a frame, the renderer gets a CAMetalDrawable object from the Metal layer that provides the Core Animation with a texture to render on the screen.

id<CAMetalDrawable> currentDrawable = [metalLayer nextDrawable]; // If the current drawable is nil, skip rendering this frame if(! currentDrawable) { return; } _drawableRenderDescriptor.colorAttachments[0].texture = currentDrawable.texture;Copy the code

Implement a render loop

A timer needs to be set to redraw the view at a specified interval. CADisplayLink is used in iOS to create a timer.

You need to know what screen the window is on before you create the timer, so UIKit calls this method when it calls the didMoveToWindow() method of the view. UIKit calls this method the first time it adds a view to a window and moves the view to another screen. The following code stops the rendering loop and initializes a new loop.


- (void)setupCADisplayLinkForScreen:(UIScreen*)screen

{

    [self stopRenderLoop];

    _displayLink = [screen displayLinkWithTarget:self selector:@selector(render)];

    _displayLink.paused = self.paused;

    _displayLink.preferredFramesPerSecond = 60;

}

Copy the code

CADisplayLink is not available in macOS, you need to use CVDisplayLink to create timers, they look different but in principle have the same goal, which is to allow callbacks to synchronize with displays. Creating timer timing is also the same as iOS, where this method is called the first time a view is added to a window and moved to another screen.

- (BOOL)setupCVDisplayLinkForScreen:(NSScreen*)screen { #if RENDER_ON_MAIN_THREAD // The CVDisplayLink callback, DispatchRenderLoop, never executes // on the main thread. To execute rendering on the main thread, create // a dispatch source using the main queue (the main thread). // DispatchRenderLoop merges this dispatch source in  each call // to execute rendering on the main thread. _displaySource = dispatch_source_create(DISPATCH_SOURCE_TYPE_DATA_ADD, 0, 0, dispatch_get_main_queue()); __weak AAPLView* weakSelf = self; dispatch_source_set_event_handler(_displaySource, ^(){ @autoreleasepool { [weakSelf render]; }}); dispatch_resume(_displaySource); #endif // END RENDER_ON_MAIN_THREAD CVReturn cvReturn; // Create a display link capable of being used with all active displays cvReturn = CVDisplayLinkCreateWithActiveCGDisplays(&_displayLink); if(cvReturn ! = kCVReturnSuccess) { return NO; } #if RENDER_ON_MAIN_THREAD // Set DispatchRenderLoop as the callback function and // supply _displaySource as the argument to the callback. cvReturn = CVDisplayLinkSetOutputCallback(_displayLink, &DispatchRenderLoop, (__bridge void*)_displaySource); #else // IF ! RENDER_ON_MAIN_THREAD // Set DispatchRenderLoop as the callback function and // supply this view as the argument to the callback. cvReturn = CVDisplayLinkSetOutputCallback(_displayLink, &DispatchRenderLoop, (__bridge void*)self); #endif // END ! RENDER_ON_MAIN_THREAD if(cvReturn ! = kCVReturnSuccess) { return NO; } // Associate the display link with the display on which the // view resides CGDirectDisplayID viewDisplayID = (CGDirectDisplayID) [self.window.screen.deviceDescription[@"NSScreenNumber"] unsignedIntegerValue]; cvReturn = CVDisplayLinkSetCurrentCGDisplay(_displayLink, viewDisplayID); if(cvReturn ! = kCVReturnSuccess) { return NO; } CVDisplayLinkStart(_displayLink); NSNotificationCenter* notificationCenter = [NSNotificationCenter defaultCenter]; // Register to be notified when the window closes so that you // can stop the display link [notificationCenter addObserver:self selector:@selector(windowWillClose:) name:NSWindowWillCloseNotification object:self.window]; return YES; }Copy the code

Apply colours to a drawing

The code for rendering needs to be written in this loop, called render, and the specific rendering method is briefly explained in this article.

- (void)render {//Copy the code

conclusion

This article introduces the steps to customize the rendering view based on Metal, including specifying the layer type as CAMetalLayer, creating the MTLRenderPassDescriptor object, and realizing the rendering cycle and rendering.