Notes about using Core Animation for Macos applications.
For graphics, Apple describes Core Animation as the level just below AppKit. Therefore anything we would like to achieve with AppKit has to be provided via Core Animation already.
Core Animation's main purpose is to composite layers of bitmap content onto the display. Layers are arranged in trees and their properties are animated by Core Animation.
A layer is made of properties:
- bounds rectangle
- position
- opacity
- transform
- contentsScale, the scale for retina displays
Core Animation's default is to animate these properties as they are changed. An animation is said to represent an Action.
QuartzCore framework
A NSView asks for Core Animation to be activated by saying it wants a layer:
setWantsLayer: { return YES; }
NSView can either be:
- layer-backed, where layer creation and updates are automatic
- layer-hosting, where it's the application that takes those responsibilities.
A layer-backed view gets a default CALayer layer, which can be changed by overriding makeBackingLayer
.
Why would one create a layer-hosting view? When you want to chose your own layer class and you would like to configure a hierachy of layers associated with a single view.
To create set a NSView as layer-hosting, one simply calls setLayer
on it. From that point on the view has to update its layer's properties correctly when something change.
Some types of layers:
CALayer
: default layer typeCAMetalLayer
: when you want to draw using the Metal rendering pipelineCAOpenGLLayer
: when you want to emit OpenGL commands
@note: is CALayer accelerated already? In which case for a bitmap based app, CALayer is the minimal API to use.
By default the NSView gets associated as a delegate of the layer, and its drawRect
procedure will be called to draw into a bitmap. This is the recommended method for layer-backed views with dynamic rendering. For retina support, the layer's image will automatically be created at the right size.
For performing native OpenGL or Metal rendering, since Macos 10.8 one can ask the updateLayer
procedure to get called by overriding:
wantsUpdateLayer: { return YES; }
updateLayer // this is where the rendering now takes place
So when is redrawing taking place? This is controlled by what layerContentsRedrawPolicy
returns.
During resize, the default policy (NSViewLayerContentsRedrawDuringViewResize) is to call the drawRect
procedure continuously. Alternatively, one could set the recommened NSViewLayerContentsRedrawOnSetNeedsDisplay
which requires the view to decide when to redraw itself, in absence of that it will automatically stretch the content as it gets resized. This would be the best method if the UI was entirely constructed of composited CALayers, because the Core Animation system would then be able to redraw a close-enough approximation of the desired view by moving the layers with respect to one another.
The CALayer uses a CAMediaTiming & CADisplayLink to synchronize rendering with physical display frames, and provide time information to the drawing procedure, such as the presentation time of the previous frame and estimate the approximate presentation time for the next frame.
CADisplayLink is the class that synchronizes display and rendering:
@url: https://developer.apple.com/documentation/quartzcore/cadisplaylink
if your application cannot provide frames in the time provided, you may want to choose a slower frame rate. An application with a slower but consistent frame rate appears smoother to the user than an application that skips frames.
Actual frame rates are always a factor of the maximum refresh rate of the device.