前言
文章翻译是的apple的Metal开发文档demo:Customizing Render Pass Setup
,这是做离屏渲染的基础,项目下载链接
概览
渲染过程是绘制到一组纹理中的一系列渲染命令。此示例执行一对呈现过程来呈现视图的内容。对于第一个过程,示例将创建自定义渲染过程以将图像渲染到纹理中。此过程是一个屏幕外渲染过程,因为示例渲染为普通纹理,而不是显示系统创建的纹理。第二个渲染过程使用由MTKView对象提供的渲染过程描述符来渲染和显示最终图像。该示例使用来自屏幕外渲染过程的纹理作为第二个渲染过程中的绘图命令的源数据。
对于更大或更复杂的渲染器,屏幕外渲染过程是基本的构建块。例如,许多照明和阴影算法需要一个屏幕外渲染过程来渲染阴影信息,而第二个过程来计算最终场景照明。当对不需要在屏幕上显示的数据执行批处理时,屏幕外渲染过程也很有用。
为屏幕外渲染过程创建纹理
MTKView对象会自动创建要渲染到的可绘制纹理。在屏幕外渲染过程中,示例还需要一个要渲染到的纹理。要创建该纹理,它首先创建MTLTextureDescriptor对象并配置其属性。
MTLTextureDescriptor *texDescriptor = [MTLTextureDescriptor new];
texDescriptor.textureType = MTLTextureType2D;
texDescriptor.width = 512;
texDescriptor.height = 512;
texDescriptor.pixelFormat = MTLPixelFormatRGBA8Unorm;
texDescriptor.usage = MTLTextureUsageRenderTarget |
MTLTextureUsageShaderRead;
示例配置usage属性,以准确地说明它打算如何使用新纹理。它需要在屏幕外渲染过程中将数据渲染到纹理中,并在第二个过程中读取数据。示例通过设置MTLTextureUsageRenderTarget和MTLTextureUsageShaderRead标志指定此用法。
精确地设置使用标志可以提高性能,因为Metal只用为指定的用途配置纹理的基础数据。
创建渲染管道
渲染管道指定如何执行绘图命令,包括要执行的顶点和片段函数,以及它所作用的任何渲染目标的像素格式。稍后,当示例创建自定义渲染过程时,它必须使用相同的像素格式。
此示例为每个渲染过程创建一个渲染管道,对屏幕外渲染管道使用以下代码:
pipelineStateDescriptor.label = @"Offscreen Render Pipeline";
pipelineStateDescriptor.sampleCount = 1;
pipelineStateDescriptor.vertexFunction = [defaultLibrary newFunctionWithName:@"simpleVertexShader"];
pipelineStateDescriptor.fragmentFunction = [defaultLibrary newFunctionWithName:@"simpleFragmentShader"];
pipelineStateDescriptor.colorAttachments[0].pixelFormat = _renderTargetTexture.pixelFormat;
_renderToTextureRenderPipeline = [_device newRenderPipelineStateWithDescriptor:pipelineStateDescriptor error:&error];
为可绘制渲染过程创建管道的代码与使用渲染管道渲染基本体中的代码类似。为了确保两种像素格式匹配,示例将描述符的像素格式设置为视图的colorpixelsformat。类似地,在创建屏幕外渲染管道时,示例将描述符的像素格式设置为屏幕外纹理的格式。
设置屏幕外的 Render Pass Descriptor
若要渲染到屏幕外纹理,示例将配置新的渲染过程描述符。它创建MTLRenderPassDescriptor对象并配置其属性。此示例渲染为单色纹理,因此它设置colorAttachment[0]。纹理指向屏幕外纹理:
关键代码:
_renderToTextureRenderPassDescriptor.colorAttachments[0].texture = _renderTargetTexture;
示例还必须为此呈现目标配置加载操作和存储操作。
_renderToTextureRenderPassDescriptor.colorAttachments[0].loadAction = MTLLoadActionClear;
_renderToTextureRenderPassDescriptor.colorAttachments[0].clearColor = MTLClearColorMake(1, 1, 1, 1);
_renderToTextureRenderPassDescriptor.colorAttachments[0].storeAction = MTLStoreActionStore;
加载操作在GPU执行任何绘图命令之前,确定渲染过程开始时纹理的初始内容。类似地,存储操作在渲染过程完成后运行,并确定GPU是否将最终图像写入纹理。该示例将配置一个加载操作以擦除渲染目标的内容,并配置一个存储操作以将渲染数据存储回纹理。它需要执行后者,因为第二个渲染过程中的绘图命令将对该纹理数据进行采样。
Metal使用加载和存储操作来优化GPU管理纹理数据的方式。大型纹理会消耗大量内存,而处理这些纹理会消耗大量内存带宽。正确设置渲染目标操作可以减少GPU用于访问纹理的内存带宽,提高性能和电池寿命。请参阅设置加载和存储操作以获取指导。
此示例中没有使用渲染过程描述符的其他属性来进一步修改渲染过程。有关自定义渲染过程描述符的其他方法的信息,请参见MTLRenderPassDescriptor。
渲染到屏幕外纹理
该示例拥有对两个渲染过程进行编码所需的所有内容。在查看示例如何对渲染过程进行编码之前,了解Metal如何在GPU上调度命令是很重要的。
当应用程序将命令缓冲区提交到命令队列时,默认情况下,Metal必须像按顺序执行命令一样执行。为了提高性能和更好地利用GPU,Metal可以同时运行命令,只要这样做不会产生与顺序执行不一致的结果。为了实现这一点,当一个过程写入一个资源,而随后的一个过程从中读取时(如本示例中所示),Metal会检测到依赖关系,并自动延迟后一个过程的执行,直到第一个过程完成。因此,与同步CPU和GPU工作(CPU和GPU需要显式同步)不同,该示例不需要做任何特殊的事情。它只是按顺序对两个过程进行编码,而Metal则确保它们按这个顺序运行。
示例将两个渲染过程编码到一个命令缓冲区中,从屏幕外的渲染过程开始。它使用以前创建的屏幕外渲染过程描述符创建渲染命令编码器。
id<MTLRenderCommandEncoder> renderEncoder =
[commandBuffer renderCommandEncoderWithDescriptor:_renderToTextureRenderPassDescriptor];
renderEncoder.label = @"Offscreen Render Pass";
[renderEncoder setRenderPipelineState:_renderToTextureRenderPipeline];
渲染过程中的其他所有内容都类似于使用渲染管道来渲染基本体。它配置管道和任何必要的参数,然后对绘图命令进行编码。对命令进行编码后,它调用endEncoding来完成编码过程。
必须将多个过程按顺序编码到命令缓冲区中,因此示例必须在开始下一个渲染过程之前完成对第一个渲染过程的编码。
渲染到可绘制纹理
第二个渲染过程需要渲染最终图像。drawable渲染管道的片段着色器从纹理采样数据,并将该采样作为最终颜色返回:
// Fragment shader that samples a texture and outputs the sampled color.
fragment float4 textureFragmentShader(TexturePipelineRasterizerData in [[stage_in]],
texture2d<float> texture [[texture(AAPLTextureInputIndexColor)]])
{
sampler simpleSampler;
// Sample data from the texture.
float4 colorSample = texture.sample(simpleSampler, in.texcoord);
// Return the color sample as the final color.
return colorSample;
}
该代码使用视图的渲染过程描述符创建第二个渲染过程,并对绘图命令进行编码以渲染带纹理的四边形。它指定屏幕外纹理作为命令的纹理参数。
id<MTLRenderCommandEncoder> renderEncoder =
[commandBuffer renderCommandEncoderWithDescriptor:drawableRenderPassDescriptor];
renderEncoder.label = @"Drawable Render Pass";
[renderEncoder setRenderPipelineState:_drawableRenderPipeline];
[renderEncoder setVertexBytes:&quadVertices
length:sizeof(quadVertices)
atIndex:AAPLVertexInputIndexVertices];
[renderEncoder setVertexBytes:&_aspectRatio
length:sizeof(_aspectRatio)
atIndex:AAPLVertexInputIndexAspectRatio];
// Set the offscreen texture as the source texture.
[renderEncoder setFragmentTexture:_renderTargetTexture atIndex:AAPLTextureInputIndexColor];
当示例提交命令缓冲区时,Metal按顺序执行两个渲染过程。在这种情况下,Metal检测到第一个渲染过程写入到屏幕外纹理,而第二个渲染过程从中读取。当Metal检测到这种依赖性时,它会阻止后续的pass执行,直到GPU完成执行第一个pass为止。
总结
离屏渲染是做各种滤镜的基础,利用它可以创建一系列的filters来处理图像。
网友评论