iOS 上的 Metal 纹理和着色器
如何在 iOS 上开始使用 Metal。
引言
这篇文章是我的 OpenGL 文章 的续篇,展示了如何使用 Apple 的新 Metal-API,该 API 从头开始开发,旨在通过更好的效率、性能和可维护性来取代 OpenGL。重要的是 Metal 仅在较新的硬件上受支持,具体来说:Apple 的 A7 及更高版本的芯片,并运行 iOS 8。因此,有些人可能需要更新,也许还需要在硬件方面进行更新。不同的按钮可以访问实现不同着色器技术的例程。
背景
在处理 OpenGL 的一些任务时,我想了解一下 Metal。因此,这是我关于 OpenGL 的 结构相同的文章 的续篇,这使得比较成为可能,因为我使用了相同的任务级别和设计。
使用代码
Metal-API 的使用非常简单,但可以分为三个部分。一方面,您初始化 Metal 设备,着色器代码,另一方面,您运行您的绘图代码。我的理解是,“设备”是 GPU 和显示器,都是硬件。而着色器是在 GPU 上运行的软件。
硬件
硬件(A7 或更高版本)和软件(iOS 8 或更高版本)检查在 Model 中进行
_device = MTLCreateSystemDefaultDevice();
if( _device == nil )
{
AppLog(@"ERROR: No Metal device");
return false;
}
以及在 View 中。请记住,它仅在层支持 Metal 时才有效。否则它为 nil。
_metalLayer = (CAMetalLayer*) self.layer; AppLog(@"Layer pixel format: %d",(int)_metalLayer.pixelFormat); if( _metalLayer == nil ) { AppLog(@"ERROR: No Metal layer. iOS 8 and A7 or higher needed"); return false; }
我之前也有一些代码来检查和诊断这种情况的发生。
软件
我将 Metal 着色语言浓缩到这个链接 Apple 文档,但强调与 OpenGL 的相似之处在于,您需要一个顶点函数和一个片段函数。您还拥有特殊关键字,这些关键字用一对双引号标记。这些关键字是与 objective-C 语言的接口。您可以在缓冲区(如结构)和纹理中传输数据。最有趣的是,这些内存是由 Metal-API 分配的,因此 Metal-API 可以直接访问它。
在顶点函数中计算各个顶点(4D 点)的坐标,然后将结果输入到片段函数中,该函数为每个像素调用并插值颜色。因此,从顶点返回一个结构体以增强片段的输入很有意思。
vertex TheVertex vertexTexture( constant float4 *position [[ buffer(0) ]], uint vid [[ vertex_id ]]) { TheVertex data; data.color = half4(0,1,0,1);//possibly set color return data; } fragment half4 fragmentBuffer( TheVertex input [[ stage_in ]],
给我看看像素
要看到一些像素,我们需要使用我们的数据调用软件。为此,我们需要设置一些缓冲区或纹理,并最终让 Metal 完成工作。但这有很多步骤。有趣的是,您一次准备所有固定的数据,并且只重建需要的内容。
创建一个纹理并用漂亮的图像位填充它
//create Texturedescriptor (blueprint for texture) MTLTextureDescriptor *mtlTextDesc = [MTLTextureDescriptor texture2DDescriptorWithPixelFormat:MTLPixelFormatRGBA8Unorm width:_width height:_height mipmapped:NO]; //create texture with "blueprint" _texture = [device newTextureWithDescriptor:mtlTextDesc]; MTLRegion region = MTLRegionMake2D(0, 0, _width, _height); [_texture replaceRegion:region mipmapLevel:0 withBytes:imageBits bytesPerRow:4*_width];
通过指向着色器程序来创建渲染管线。Metal 的一个优点是,程序已经在构建时编译好了,所以只需要加载它们
// get the vertex function from the library vertexProgram = [library newFunctionWithName:progVertex]; // create a pipeline description MTLRenderPipelineDescriptor *desc = [MTLRenderPipelineDescriptor new]; desc.vertexFunction = vertexProgram; desc.fragmentFunction = fragmentProgram; desc.depthAttachmentPixelFormat = MTLPixelFormatInvalid; //no depth texture set desc.stencilAttachmentPixelFormat = MTLPixelFormatInvalid; desc.colorAttachments[0].pixelFormat = metalLayer.pixelFormat;// framebuffer pixel format must match with metal layer desc.sampleCount = 1; NSError *error = nil; idpipelineState = [device newRenderPipelineStateWithDescriptor:desc error:&error]; if (error != nil) { NSLog(@"Error creating RenderPipelineState: %@. Vertex or fragment bogous?", error); } NSAssert(pipelineState != nil, @"ERROR: We need a renderpipelinestate"); return pipelineState;
最后阶段是创建渲染通道并将其放入命令缓冲区。这是最后的准备阶段,非常复杂。有趣的是,一个或多个渲染通道可以一次处理。命令缓冲区通过这行代码获得最终的“运行”
// Put command buffer now into the queue [commandBuffer commit];
关注点
Metal 是一个强大的 API,但在测试它时,我必须承认,我的需求已经通过 OpenGL 得到了满足。但我真的钦佩 Apple 人们的伟大工作,这使得编译器会警告大多数错误,并且通过抛出缺少什么的异常来调试代码很有趣。
我进行了其他测试,其中 Metal 速度更快,并且节能约 5%。也许绘制一些像素并不会对优化发出强烈需求。
另一个值得关注的点是,Metal 正在寻址像素,因此 Retina 分辨率需要更多的像素处理,而高分辨率需要更多的功率和时间。为此,我花了一些时间来制作一个有用的日志。Retina 例程需要标准分辨率的 4 倍。这并不奇怪,因为像素是原来的 4 倍。
现在谁对这种 Metal 感兴趣,应该深入研究 Apple 文档。视频提供了很好的概述,示例代码非常有趣。
此外,项目 MetalCamera 和 Metal by Example 都是非常出色的软件。
历史
- 初始版本