Metal学习笔记(一)-- 初步探究

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种类型:MTLRenderCommandEncoderMTLComputeCommandEncoderMTLBlitCommandEncoderMTLParallelRenderCommandEncoder,编码填充完成后,MTLCommandBuffer对象执行commit方法即可执行渲染

MTLCommandQueue

MTLCommandQueue是一个用于管理MTLCommandBuffer对象的队列,根据苹果官方的建议,我们的渲染命令通过最好通过该队列创建并管理。
MTLCommandQueue同样也是一个不需要开发着实现的协议,我们可以通过MTLDevice,通过调用newCommandQueue方法得到一个MTLCommandQueue对象,也可以调用newCommandQueueWithMaxCommandBufferCount:方法得到一个有最大命令数量限制的队列。
根据苹果官方文档介绍得知MTLCommandQueue是线程安全的。

关系图.png

苹果关于Metal的3条建议

  1. Metal相关实现放到单独的文件中进行,与业务代码或其他代码进行分离
  2. 实现MTKViewDelegate方法
  3. 通过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

最终效果如图:

helloMetal.gif
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。