Metal是苹果WWDC 2014推出的一套图形API,它可以更大限度地调用GPU的性能,降低CPU的占用率从而提高效率。
Metal的相关类
MTKView
MTKView继承自UIView,是用于创建、配置、展示Metal对象的特殊控件。先上一段苹果官方的介绍。
The MTKView class provides a default implementation of a Metal-aware view that you can use to render graphics using Metal and display them onscreen. When asked, the view provides a MTLRenderPassDescriptor object that points at a texture for you to render new contents into. Optionally, an MTKView can create depth and stencil textures for you and any intermediate textures needed for antialiasing. The view uses a CAMetalLayer to manage the Metal drawable objects.
The view requires a MTLDevice object to manage the Metal objects it creates for you. You must set the device property and, optionally, modify the view’s drawable properties before drawing.
<来自苹果官方文档>
可以知道,MTKView提供了MTLRenderPassDescriptor
对象供纹理绘制渲染,同时MTKView也可以实现深度和模版纹理供多种纹理混合,与GLKView类似,MTKView的绘制实际由CAMetalLayer
完成。另外,MTKView需要MTLDevice
对象来管理创建的Metal对象,在绘制前,必须创建一个MTLDevice
对象。
MTLDevice
MTLDevice
是一个protocol,是开发者使用GPU进行图形绘制或并发运算时的“界面”,或者我可以理解为GPU本身。
The MTLDevice protocol defines the interface to a GPU. You can query a MTLDevice for the unique capabilities it offers your Metal app, and use the MTLDevice to issue all of your Metal commands. Don't implement this protocol yourself; instead, request a GPU from the system at runtime using MTLCreateSystemDefaultDevice in iOS or tvOS, and in macOS, get a list of available MTLDevice objects using MTLCopyAllDevicesWithObserver(handler:). See Getting the Default GPU for a full discussion on choosing the right GPU(s).
MTLDevice objects are your go-to object to do anything in Metal, so all of the Metal objects your app interacts with come from the MTLDevice instances you acquire at runtime. MTLDevice-created objects are expensive but persistent; many of them are designed to be initialized once and reused through the lifetime of your app. However, these objects are specific to the MTLDevice that issued them. If you use multiple MTLDevice instances or want to switch from one MTLDevice to another, you need to create a separate set of objects for each MTLDevice.
<来自苹果官方文档>
MTLDevice协议并不需要我们去实现,我们可以用MTLCreateSystemDefaultDevice
得到一个MTLDevice对象,一般来说一个MTLDevice对象在一个app中最好是反复利用的,因为创建一个MTLDevice对象是开销很大的。
MTLCommandBuffer
MTLCommandBuffer
是一个承载CPU客户端需要交给GPU进行渲染命令的容器,和MTLDevice
一样,MTLCommandBuffer
也是一个protocol,同样不需要开发者实现,我们可以通过MTLCommandQueue
对象的commandBuffer
方法得到一个MTLCommandBuffer对象。
而创建一个命令缓冲区,我们需要一个编码对象来对这个这个命令进行填充,MTLCommandBuffer
有相关方法可以获得一个编码对象,而编码对象有4种类型:MTLRenderCommandEncoder
、MTLComputeCommandEncoder
、MTLBlitCommandEncoder
、MTLParallelRenderCommandEncoder
,编码填充完成后,MTLCommandBuffer
对象执行commit
方法即可执行渲染
MTLCommandQueue
MTLCommandQueue
是一个用于管理MTLCommandBuffer
对象的队列,根据苹果官方的建议,我们的渲染命令通过最好通过该队列创建并管理。
MTLCommandQueue
同样也是一个不需要开发着实现的协议,我们可以通过MTLDevice
,通过调用newCommandQueue
方法得到一个MTLCommandQueue对象,也可以调用newCommandQueueWithMaxCommandBufferCount:
方法得到一个有最大命令数量限制的队列。
根据苹果官方文档介绍得知MTLCommandQueue是线程安全的。
苹果关于Metal的3条建议
- Metal相关实现放到单独的文件中进行,与业务代码或其他代码进行分离
- 实现MTKViewDelegate方法
- 通过MTLCommandQueue管理commandBuffer
demo练习
先用现学知识实现一个Metal界面
ViewController.m中
- (void)viewDidLoad {
[super viewDidLoad];
//1. 创建MTKView
MTKView *mtkView = [[MTKView alloc] init];
mtkView.frame = CGRectMake(0, 0, self.view.frame.size.width, self.view.frame.size.height);
[self.view addSubview:mtkView];
// 2. 创建MTLDevice
mtkView.device = MTLCreateSystemDefaultDevice();
if (!mtkView.device) {
NSLog(@"not device");
return;
}
// 根据苹果建议,Metal相关方法在LJRender中实现
_render = [[LJRender alloc] initWithMtkView:_mtkView];
if (!_render) {
NSLog(@"no render");
return;
}
// 指定代理
_mtkView.delegate = _render;
}
LJRender.h
#import <Foundation/Foundation.h>
@import MetalKit;
NS_ASSUME_NONNULL_BEGIN
@interface LJRender : NSObject <MTKViewDelegate>
// 初始化方法,mktView由外界提供
- (instancetype)initWithMetalKitView:(MTKView *)mktView;
@end
NS_ASSUME_NONNULL_END
LJRender
#import "LJRender.h"
// 定义颜色结构体
typedef struct {
float red, green, blue, alpha;
} Color;
@interface LJRender ()
@property(nonatomic, strong) id<MTLDevice> device;
@property(nonatomic, strong) id<MTLCommandQueue> commandQueue;
@end
@implementation LJRender
- (instancetype)initWithMetalKitView:(MTKView *)mktView {
if (self = [super init]) {
// 获得MTLDevice(MTLDevice由外部创建)
_device = mktView.device;
// 创建命令队列
_commandQueue = _device.newCommandQueue;
}
return self;
}
// 颜色渐变方法
- (Color)makeFancyColor {
static BOOL growing = YES;
static NSUInteger primaryChannel = 0;
static float colorsChannel[] = {1.0, 0.0, 0.0, 1.0};
const float DynamicColorRate = 0.015;
if (growing) {
NSUInteger dynamicChannelIndex = (primaryChannel+1)%3;
colorsChannel[dynamicChannelIndex] += DynamicColorRate;
if (colorsChannel[dynamicChannelIndex] >= 1.0) {
growing = NO;
primaryChannel = dynamicChannelIndex;
}
} else {
NSUInteger dynamicChannelIndex = (primaryChannel + 2) % 3;
colorsChannel[dynamicChannelIndex] -= DynamicColorRate;
if (colorsChannel[dynamicChannelIndex] <= 0.0) {
growing = YES;
}
}
Color color;
color.red = colorsChannel[0];
color.green = colorsChannel[1];
color.blue = colorsChannel[2];
color.alpha = colorsChannel[3];
return color;
}
#pragma mark - MTKViewDelegate
// MTKViewDelegate方法,但视口大小改变时调用
- (void)mtkView:(MTKView *)view drawableSizeWillChange:(CGSize)size {
}
// 每次需要执行渲染时调用
- (void)drawInMTKView:(MTKView *)view {
// 设置颜色
Color color = [self makeFancyColor];
view.clearColor = MTLClearColorMake(color.red, color.green, color.blue, color.alpha);
// 通过命令队列获得命令缓冲区
id<MTLCommandBuffer> commandBuffer = [_commandQueue commandBuffer];
// 给命令缓冲区命名
commandBuffer.label = @"MyCommand";
// 通过MTKView获取MTLRenderPassDescriptor对象(渲染对象),创建encoder时需要
MTLRenderPassDescriptor *renderPassDescrriptor = view.currentRenderPassDescriptor;
if (renderPassDescrriptor != nil) {
// 通过MTLRenderPassDescriptor获得encoder对象
id<MTLRenderCommandEncoder> renderEncoder = [commandBuffer renderCommandEncoderWithDescriptor:renderPassDescrriptor];
// 命名
renderEncoder.label = @"MyRenderEncoder";
// 填充完毕
[renderEncoder endEncoding];
// 一旦框架缓冲区完成,使用当前可绘制的进度表
[commandBuffer presentDrawable:view.currentDrawable];
}
// 将命令缓冲区推送到GPU
[commandBuffer commit];
}
@end
最终效果如图: