MetalKit is a forthcoming framework on iOS 9 and OS X El Capitan that greatly eases certain tasks such as presenting Metal content in a
NSView, texture loading, and working with model data.
This post is an overview of the features offered by MetalKit. Many of our articles so far have focused on details that are not expressly related to Metal, but are instead required to give Metal something to draw on the screen: texture loading, 3D model loading, and setting up the interface between Metal and UIKit.
MetalKit seeks to make these tasks easier by providing classes that perform common operations. In this article, we’ll look briefly at these capabilities.
Interfacing with UIKit and AppKit
MetalKit provides the cross-platform
MTKView class, which subclasses
UIView on iOS and
NSView on OS X. This class does more than simply encapsulate a
CAMetalLayer; it also manages the render target attachments associated with the framebuffer and handles the draw loop. Let’s look at it more detail.
MTKView implements the usual initializers:
initWithCoder:. On iOS, you can optionally set the
device property to an
MTLDevice of your choosing, but this isn’t necessary, as it is set to
MTLSystemCreateDefaultDevice() by default. On OS X, setting this property is mandatory, as it is
nil by default.
MTKView manages your render target attachments, you must configure a few properties on it to ensure that the formats of these attachments match the needs of your application.
The most important of these properties is
colorPixelFormat. For applications that render to a
CAMetalLayer, this value is almost always
MTLPixelFormatBGRA8Unorm, which is the usual format of renderable textures vended by CAMetalDrawable.
clearStencil properties passes through these values to the view’s render pass descriptor, which determines how the various attachments are cleared at the beginning of the frame.
The Draw Loop
To perform drawing, you have the option of subclassing
MTKView and overriding
drawRect:, or providing a delegate conforming to
MTKViewDelegate, wherein you can implement
drawInView: to receive regular callbacks.
You can control the rate at which you receive draw callbacks by configuring the view’s
preferredFramesPerSecond value. The view maintains an internal timer that fires at a rate that corresponds as closely as possible to this value.
drawInView: implementation, you can access the view’s
currentRenderPassDescriptor to get a render pass configured with the current drawable’s texture set as its primary color attachment. With this render pass, you can construct a render command encoder and perform your draw calls.
MetalKit also provides a class for loading texture data:
MTKTextureLoader. You may recall the work involved in loading image data into a texture, especially compressed image data. MetalKit’s texture utility class makes this process substantially simpler.
You initialize a texture loader by passing it a device. You can then load a texture from disk synchronously with the
textureWithContentsOfURL:options:error: method, or asynchronously with
textureWithContentsOfURL:options:completionHandler:. The latter method takes a block that is called with the texture when it is fully loaded. There are similar methods for synchronously and asynchronously loading textures from
NSData. Metal inspects image data to determine its type, whether PNG, JPEG, or a KTX or PVR compressed image container.
Calling any of these methods with a key of
MTKTextureLoaderOptionAllocateMipmaps and a value of
@(YES) in the options dictionary will produce a mipmapped texture.
Managing Model Data
A full discussion of MetalKit’s support for model data handling necessarily entails discussing Model I/O, since the two are designed to work together. A companion to this post will go into greater detail on Model I/O, but let’s take a high-level look at MetalKit’s facilities for working with meshes.
Meshes and Submeshes
Both Model I/O and MetalKit have a hierarchical representation for model data: meshes contain vertex data and submeshes, which submeshes contain contain an index buffer and a material reference. This means that all of the data for a model can be contained in a single contiguous buffer, with each submesh corresponding to a contiguous set of indices into this vertex data. A submesh also has properties signifying its geometry type (point, line, triangle, etc). MetalKit’s classes for meshes and submeshes are
MTKSubmesh, respectively. The
MTKMeshBuffer class abstracts over the Metal buffers that contain the mesh data.
To render a mesh with Metal, we might set its vertex buffers in the argument table, then iterate over its submeshes, using a draw method like
drawIndexedPrimitives:indexCount:indexType:indexBuffer:indexBufferOffset: and providing the geometry type, index count, index type, and index buffer contained by the submesh. In order to draw a submesh properly, we would also need to take into account the properties specified by its associated material, which might entail writing various shaders and ancillary resources like BRDF lookup tables.
The future is now! With Metal coming to OS X with El Capitan, Metal is more important than ever, and you can experiment with MetalKit using prerelease versions of Xcode, OS X, and iOS. Refer to the complete MetalKit reference for more details. We’ll be covering MetalKit here as the public release of the framework approaches.