Metal
Metal 是一个和 OpenGL ES 类似的面向底层的图形编程接口,通过使用相关的 api 可以直接操作 GPU ,最早在 2014 年的 WWDC 的时候发布,并于2019年发布了 Metal 2。
Metal框架支持GPU硬件加速、高级3D图形渲染以及大数据并行运算。且提供了先进而精简的API来确保框架的细粒度(fine-grain),并且在组织架构、程序处理、图形呈现、运算指令以及指令相关数据资源的管理上都支持底层控制。其核心目的是尽可能的减少CPU开销,而将运行时产生的大部分负载交由GPU承担
Metal的特点和主要功能
- 主要功能 3D图形渲染和并行计算
- 特点 消除“隐藏”的性能瓶颈,如隐式状态验证。你可以在多线程异步控制GPU,有效用于平行创建和提交命令缓冲区 描述了缓冲和纹理对象代表了GPU的内存分配。纹理对象有特定的像素格式,并可用于纹理图像或附件对象 使用相同的数据结构和资源(如缓冲区、纹理和命令队列),用于图形和计算操作。此外,金属着色语言支持图形和计算功能。Metal使得资源能够和runtime接口、图形着色器、并计算函数之间共享 Metal 着色器可以和你的app代码一样在运行时加载,编译,这样的好处时能够更好的生成代码,以及编译调试 Metal 不能再后台执行命令代码,否则系统崩溃
示例
1.我们可以在Main.storyboard中指定view的类型然后指定view的类型.
_view = (MTKView *)self.view;
2.我们也可以初始化MTKView的对象,然后addSubView到某父试图上
_view = [[MTKView alloc] initWithFrame:self.view.bounds];
[self.view addSubview:_view];
//设置Device
_view.device = MTLCreateSystemDefaultDevice();
设置渲染对象
_renderer = [[MyRender alloc] initWithMetalKitView:_view];
if(!_renderer)
{
NSLog(@"Renderer failed initialization");
return;
}
[_renderer mtkView:_view drawableSizeWillChange:_view.drawableSize];
_view.delegate = _renderer;
typedef struct
{
float4 clipSpacePosition [[position]];
float4 color;
} RasterizerData;
vertex RasterizerData
vertexShader(uint vertexID [[vertex_id]],
constant CCVertex *vertices [[buffer(CCVertexInputIndexVertices)]],
constant vector_uint2 *viewportSizePointer [[buffer(CCVertexInputIndexViewportSize)]])
{
out.clipSpacePosition = vertices[vertexID].position;
out.color = vertices[vertexID].color;
return out;
}
fragment float4 fragmentShader(RasterizerData in [[stage_in]])
{
//返回输入的片元颜色
return in.color;
}
{
//我们用来渲染的设备(又名GPU)
id<MTLDevice> _device;
// 我们的渲染管道有顶点着色器和片元着色器 它们存储在.metal shader 文件中
id<MTLRenderPipelineState> _pipelineState;
//命令队列,从命令缓存区获取
id<MTLCommandQueue> _commandQueue;
//当前视图大小,这样我们才可以在渲染通道使用这个视图
vector_uint2 _viewportSize;
}
//初始化MTKView
- (nonnull instancetype)initWithMetalKitView:(nonnull MTKView *)mtkView
{
self = [super init];
if(self)
{
NSError *error = NULL;
//1.获取GPU 设备
_device = mtkView.device;
//2.在项目中加载所有的(.metal)着色器文件
// 从bundle中获取.metal文件
id<MTLLibrary> defaultLibrary = [_device newDefaultLibrary];
//从库中加载顶点函数
id<MTLFunction> vertexFunction = [defaultLibrary newFunctionWithName:@"vertexShader"];
//从库中加载片元函数
id<MTLFunction> fragmentFunction = [defaultLibrary newFunctionWithName:@"fragmentShader"];
//3.配置用于创建管道状态的管道
MTLRenderPipelineDescriptor *pipelineStateDescriptor = [[MTLRenderPipelineDescriptor alloc] init];
//管道名称
pipelineStateDescriptor.label = @"Simple Pipeline";
//可编程函数,用于处理渲染过程中的各个顶点
pipelineStateDescriptor.vertexFunction = vertexFunction;
//可编程函数,用于处理渲染过程中各个片段/片元
pipelineStateDescriptor.fragmentFunction = fragmentFunction;
//一组存储颜色数据的组件
pipelineStateDescriptor.colorAttachments[0].pixelFormat = mtkView.colorPixelFormat;
//4.同步创建并返回渲染管线状态对象
_pipelineState = [_device newRenderPipelineStateWithDescriptor:pipelineStateDescriptor error:&error];
//判断是否返回了管线状态对象
if (!_pipelineState)
{
//如果我们没有正确设置管道描述符,则管道状态创建可能失败
NSLog(@"Failed to created pipeline state, error %@", error);
return nil;
}
//5.创建命令队列
_commandQueue = [_device newCommandQueue];
}
return self;
}
代理方法
//每当视图改变方向或调整大小时调用
- (void)mtkView:(nonnull MTKView *)view drawableSizeWillChange:(CGSize)size
{
// 保存可绘制的大小,因为当我们绘制时,我们将把这些值传递给顶点着色器
_viewportSize.x = size.width;
_viewportSize.y = size.height;
}
//每当视图需要渲染帧时调用
- (void)drawInMTKView:(nonnull MTKView *)view
{
//1. 顶点数据/颜色数据
static const CCVertex triangleVertices[] =
{
//顶点, RGBA 颜色值
{ { 0.5, -0.25, 0.0, 1.0 }, { 1, 0, 0, 1 } },
{ { -0.5, -0.25, 0.0, 1.0 }, { 0, 1, 0, 1 } },
{ { -0.0f, 0.25, 0.0, 1.0 }, { 0, 0, 1, 1 } },
};
//2.为当前渲染的每个渲染传递创建一个新的命令缓冲区
id<MTLCommandBuffer> commandBuffer = [_commandQueue commandBuffer];
//指定缓存区名称
commandBuffer.label = @"MyCommand";
//3.
// MTLRenderPassDescriptor:一组渲染目标,用作渲染通道生成的像素的输出目标。
MTLRenderPassDescriptor *renderPassDescriptor = view.currentRenderPassDescriptor;
//判断渲染目标是否为空
if(renderPassDescriptor != nil)
{
//4.创建渲染命令编码器,这样我们才可以渲染到something
id<MTLRenderCommandEncoder> renderEncoder =[commandBuffer renderCommandEncoderWithDescriptor:renderPassDescriptor];
//渲染器名称
renderEncoder.label = @"MyRenderEncoder";
//5.设置我们绘制的可绘制区域
/*
typedef struct {
double originX, originY, width, height, znear, zfar;
} MTLViewport;
*/
//视口指定Metal渲染内容的drawable区域。 视口是具有x和y偏移,宽度和高度以及近和远平面的3D区域
//为管道分配自定义视口需要通过调用setViewport:方法将MTLViewport结构编码为渲染命令编码器。 如果未指定视口,Metal会设置一个默认视口,其大小与用于创建渲染命令编码器的drawable相同。
MTLViewport viewPort = {
0.0,0.0,_viewportSize.x,_viewportSize.y,-1.0,1.0
};
[renderEncoder setViewport:viewPort];
//[renderEncoder setViewport:(MTLViewport){0.0, 0.0, _viewportSize.x, _viewportSize.y, -1.0, 1.0 }];
//6.设置当前渲染管道状态对象
[renderEncoder setRenderPipelineState:_pipelineState];
//7.从应用程序OC 代码 中发送数据给Metal 顶点着色器 函数
//顶点数据+颜色数据
// 1) 指向要传递给着色器的内存的指针
// 2) 我们想要传递的数据的内存大小
// 3)一个整数索引,它对应于我们的“vertexShader”函数中的缓冲区属性限定符的索引。
[renderEncoder setVertexBytes:triangleVertices
length:sizeof(triangleVertices)
atIndex:CCVertexInputIndexVertices];
//viewPortSize 数据
//1) 发送到顶点着色函数中,视图大小
//2) 视图大小内存空间大小
//3) 对应的索引
[renderEncoder setVertexBytes:&_viewportSize
length:sizeof(_viewportSize)
atIndex:CCVertexInputIndexViewportSize];
//8.画出三角形的3个顶点
// @method drawPrimitives:vertexStart:vertexCount:
//@brief 在不使用索引列表的情况下,绘制图元
//@param 绘制图形组装的基元类型
//@param 从哪个位置数据开始绘制,一般为0
//@param 每个图元的顶点个数,绘制的图型顶点数量
/*
MTLPrimitiveTypePoint = 0, 点
MTLPrimitiveTypeLine = 1, 线段
MTLPrimitiveTypeLineStrip = 2, 线环
MTLPrimitiveTypeTriangle = 3, 三角形
MTLPrimitiveTypeTriangleStrip = 4, 三角型扇
*/
[renderEncoder drawPrimitives:MTLPrimitiveTypeTriangle
vertexStart:0
vertexCount:3];
//9.表示已该编码器生成的命令都已完成,并且从NTLCommandBuffer中分离
[renderEncoder endEncoding];
//10.一旦框架缓冲区完成,使用当前可绘制的进度表
[commandBuffer presentDrawable:view.currentDrawable];
}
//11.最后,在这里完成渲染并将命令缓冲区推送到GPU
[commandBuffer commit];
}
相关类
MTLLibrary
id<MTLLibrary> defaultLibrary = [_device newDefaultLibrary];
//从库中加载顶点函数
id<MTLFunction> vertexFunction = [defaultLibrary newFunctionWithName:@"vertexShader"];
//从库中加载片元函数
id<MTLFunction> fragmentFunction = [defaultLibrary newFunctionWithName:@"fragmentShader"];
MTLRenderPipelineDescriptor
MTLRenderPipelineDescriptor *pipelineStateDescriptor = [[MTLRenderPipelineDescriptor alloc] init];
//管道名称
pipelineStateDescriptor.label = @"Simple Pipeline";
//可编程函数,用于处理渲染过程中的各个顶点
pipelineStateDescriptor.vertexFunction = vertexFunction;
//可编程函数,用于处理渲染过程中各个片段/片元
pipelineStateDescriptor.fragmentFunction = fragmentFunction;
//一组存储颜色数据的组件
pipelineStateDescriptor.colorAttachments[0].pixelFormat = mtkView.colorPixelFormat;
MTLRenderPipelineState
id<MTLRenderPipelineState> _pipelineState;
//4.同步创建并返回渲染管线状态对象
_pipelineState = [_device newRenderPipelineStateWithDescriptor:pipelineStateDescriptor error:&error];
//判断是否返回了管线状态对象
if (!_pipelineState)
{
//如果我们没有正确设置管道描述符,则管道状态创建可能失败
NSLog(@"Failed to created pipeline state, error %@", error);
return nil;
}
MTLCommandQueue
id<MTLCommandQueue> _commandQueue;
//5.创建命令队列
_commandQueue = [_device newCommandQueue];
MTLCommandBuffer
//2.为当前渲染的每个渲染传递创建一个新的命令缓冲区
id<MTLCommandBuffer> commandBuffer = [_commandQueue commandBuffer];
//指定缓存区名称
commandBuffer.label = @"MyCommand";
MTLRenderCommandEncoder
// MTLRenderPassDescriptor:一组渲染目标,用作渲染通道生成的像素的输出目标。
MTLRenderPassDescriptor *renderPassDescriptor = view.currentRenderPassDescriptor;
id<MTLRenderCommandEncoder> renderEncoder =[commandBuffer renderCommandEncoderWithDescriptor:renderPassDescriptor];
//渲染器名称
renderEncoder.label = @"MyRenderEncoder";