图像渲染: 渲染 Command Encoder

这个章节描述如何创建并使用 MTLRenderCommandEncoderMTLParallelRenderCommandEncoder 对象,他们被用来编码图形渲染指令,然后插入到 command buffer。 MTLRenderCommandEncoder 指令描述图形渲染管线,如图 5-1 所示。

图 5-1 Metal 图形渲染管线

MTLRenderCommandEncoder 对象表示一个单独的图形渲染 command encoder。MTLParallelRenderCommandEncoder 对象使得一个单独的渲染 pass 被分成若干个独立的 MTLRenderCommandEncoder 对象,每一个都可以被分配到不同的线程。这些 command encoders 中的指令随后将串行起来,并以一致的可预测的顺序被执行。详见 Multiple Threads for a Rendering Pass.

创建并使用一个 Command Encoder

使用一下步骤来创建,初始化,使用一个单独的渲染 command encoder:

  1. 创建一个 MTLRenderPassDescriptor 对象来定义一系列的 attachment,这些 attachments 将成为在 command buffer 中渲染指令操作的目标产出物。一个 MTLRenderPassDescriptor 对象通常被创建一次,然后被多次用来创建 MTLRenderCommandEncoder 对象。 详见 Creating a Render Pass Descriptor.
  2. 使用之前准备的 MTLRenderPassDescriptor 对象, 调用 MTLCommandBufferrenderCommandEncoderWithDescriptor: 方法来创建一个 MTLRenderCommandEncoder 对象,详见 Using the Render Pass Descriptor to Create a Render Command Encoder.
  3. 创建一个 MTLRenderPipelineState 对象来定义图像渲染管线的状态(包括着色器、混合、多重采样和可见测试),以便进行一次或者多次绘制命令调用。调用 MTLRenderCommandEncoder 对象的 setRenderPipelineState: 方法,设置 state 对象给一个 encoder,表示使用这个渲染管线 state 对象来进行图元绘制。详见 Creating a Render Pipeline State.
  4. 设置 MTLRenderCommandEncoder 对象需要用到的纹理、缓存、采样器。详见 Specifying Resources for a Render Command Encoder.
  5. 调用 MTLRenderCommandEncoder 的特定方法设置一些额外的固定渲染管线状态,包括深度和模板缓存状态。详见 Fixed-Function State Operations.
  6. 最后,调用 MTLRenderCommandEncoder 对象的方法绘制图元,详见 Drawing Geometric Primitives.

创建一个 MTLRenderPassDescriptor

MTLRenderPassDescriptor 对象代表被编码的渲染指令操作的目标产出物,它是一系列 attachment 对象的集合。它的属性包括一个最多含有4个元素的表示颜色(color)的 attachment 对象数组,一个表示深度缓存(depth)的 attachment 对象,一个表示模板缓存(stencil)的 attachment 对象。 MTLRenderPassDescriptor 的便捷方法 renderPassDescriptor ,用来产生一个 MTLRenderPassDescriptor 对象,这个对象的颜色,深度缓存,模板缓存 attachment 具有默认的状态。 MTLRenderPassDescriptorvisibilityResultBuffer 属性指明一块缓存,在这里 device 可以更新其数据,用于标识采样器是否通过深度和模板测试。详见 Fixed-Function State Operations.

每一个单独的 attachment 对象,包括将被写入数据的纹理,都是由一个 attachment descriptor 来描述的。对于一个 attachment descriptor 来说,和其相关联的纹理的像素格式必须正确选择,以便用来存放颜色,深度或是模板数据。对于一个颜色 attachment descriptor(MTLRenderPassColorAttachmentDescriptor)来说,要使用颜色缓存适配(color-renderable)的像素格式;对于一个深度 attachment descriptor (MTLRenderPassDepthAttachmentDescriptor)来说,要使用深度缓存适配的像素格式,比如 MTLPixelFormatDepth32Float ;对于一个模板 attachment descriptor(MTLRenderPassStencilAttachmentDescriptor)来说,要使用模板缓存适配的像素格式,比如 MTLPixelFormatStencil8

对于一个纹理,其每像素实际使用的设备上的内存数并不总是和代码中的像素格式相同,因为设备会因为对齐或者其他原因添加留白字节,查阅 Metal Feature Set Tables 章节可以知道每种像素格式实际使用了多少内存,以及大小的限制和 attachments 的数量。

loadAction 和 storeActions

loadActionstoreAction 是 attachment descriptor 的两个属性,它们用来设定在每个渲染 pass 的开始和介绍要做什么特定操作(对于 MTLParallelRenderCommandEncoder 对象来说,loadAction 和 storeAction 发生在整体指令的边界,而不是它拥有的每个 MTLRenderCommandEncoder 对象上。详见 Multiple Threads for a Rendering Pass.)

loadAction 有这么几种取值:

  • MTLLoadActionClear,它将为每个像素(由某个 attachment descriptor 设定的)写入相同的值。详见:Specifying the Clear Load Action.
  • MTLLoadActionLoad,它将保留纹理中已经存在的内容。
  • MTLLoadActionDontCare,它将允许在渲染 pass 开始时,纹理中的每个像素取任意值。

如果你的 App 将为给定的一帧画面渲染所有的像素,那么 loadAction 应该使用默认值 MTLLoadActionDontCareMTLLoadActionDontCare 允许 GPU 避免了加载纹理中已经存在的内容,从而保证获得更好的性能。否则,你可以使用 MTLLoadActionClear 来清除之前的内容,或者使用 MTLLoadActionLoad 来保留之前的内容。 MTLLoadActionClear 也可以避免加载已经存在的纹理内容,但它要花费时间用颜色填充缓存。

storeAction 有这么几种取值:

  • MTLStoreActionStore, 它将渲染 pass 最终结果保存到 attachment 中。
  • MTLStoreActionMultisampleResolve, 它将渲染 pass 得到的数据作为多重采样数据,计算得到一个采样值,将采样值存储到由 resolveTexture 属性指定的一块纹理中,而不操作 attachment 。详见Example: Creating a Render Pass Descriptor for Multisampled Rendering.
  • MTLStoreActionDontCare, 它将在渲染 pass 结束后将 attachment 置为 undefined 状态,这可以提高性能因为它避免了保存渲染结果的操作。

对于颜色 attachments, MTLStoreActionStore 是 storeAction 的默认值,因为 App 几乎总是在渲染 pass 结束的时候保存最终的颜色值在 attachment 中。MTLStoreActionDontCare 是深度,模板attachments 的默认值,因为这两种 attachments 不需要再绘制 pass 结束时保存渲染结果。

详述 MTLLoadActionClear

如果一个 attachment descriptor 对象的 loadAction 属性被设置为 MTLLoadActionClear,那么在每个渲染 pass 开始的时候,这个 attachment descriptor 对应的每个像素都被写入一个清除值,这个值根据 attachment 对象的类型而定。

  • 对于 MTLRenderPassColorAttachmentDescriptor 来说, clearColor 包含一个 MTLClearColor 类型的值,它是由四个双精度浮点(double-precision floating-point)类型数组成的,代表 RGBA 颜色分量,用来填充清理颜色 attachment。 MTLClearColorMake 方法用来创建一个相应的MTLClearColor 类型的颜色值,通过设置 red, green, blue,和 alpha 各个颜色分量。默认值为 (0.0, 0.0, 0.0, 1.0),代表不透明的黑色。
  • 对于 MTLRenderPassDepthAttachmentDescriptor 来说, clearDepth 类型的值由一个双精度浮点类型数组成,它的取值范围是 [0.0, 1.0],默认值是1.0,用来填充清除深度 attachment。
  • 对于 MTLRenderPassStencilAttachmentDescriptor 来说, clearStencil 类型的值由一个32位无符号整数(32-bit unsigned integer)组成,默认值是0,用来填充清理模板 attachment。

示例: 通过设置 loadAction 和 storeAction 创建 Render Pass Descriptor

列表 5-1 用颜色和深度 attachments 创建了一个简单的 render pass descriptor。首先,创建了两个纹理对象,一个被赋予了颜色缓存适配(color-renderable)的像素格式,另外一个被赋予深度缓存适配的像素格式。接着通过 MTLRenderPassDescriptor 类的 renderPassDescriptor 快捷方法创建一个默认的 render pass descriptor。 然后 MTLRenderPassDescriptor 对象的颜色和深度 attachments 属性被访问,位于 colorAttachments[0] 的纹理对象和 action 被设定,colorAttachments[0] 表示 MTLRenderPassDescriptor 对象的第一个颜色 attachment (数组中的 index 为0),最后深度 attachment 也被设定。

列表 5-1 通过设置 loadAction 和 storeAction 创建 Render Pass Descriptor

MTLTextureDescriptor *colorTexDesc = [MTLTextureDescriptor
           texture2DDescriptorWithPixelFormat:MTLPixelFormatRGBA8Unorm
           width:IMAGE_WIDTH height:IMAGE_HEIGHT mipmapped:NO];
id <MTLTexture> colorTex = [device newTextureWithDescriptor:colorTexDesc];

MTLTextureDescriptor *depthTexDesc = [MTLTextureDescriptor
           texture2DDescriptorWithPixelFormat:MTLPixelFormatDepth32Float
           width:IMAGE_WIDTH height:IMAGE_HEIGHT mipmapped:NO];
id <MTLTexture> depthTex = [device newTextureWithDescriptor:depthTexDesc];

MTLRenderPassDescriptor *renderPassDesc = [MTLRenderPassDescriptor renderPassDescriptor];
renderPassDesc.colorAttachments[0].texture = colorTex;
renderPassDesc.colorAttachments[0].loadAction = MTLLoadActionClear;
renderPassDesc.colorAttachments[0].storeAction = MTLStoreActionStore;
renderPassDesc.colorAttachments[0].clearColor = MTLClearColorMake(0.0,1.0,0.0,1.0);

renderPassDesc.depthAttachment.texture = depthTex;
renderPassDesc.depthAttachment.loadAction = MTLLoadActionClear;
renderPassDesc.depthAttachment.storeAction = MTLStoreActionStore;
renderPassDesc.depthAttachment.clearDepth = 1.0;

Example: 为多重采样渲染(Multisampled Rendering)创建 Render Pass Descriptor

为了使用 MTLStoreActionMultisampleResolve,你必须把 texture 属性设置为一个多重采样类型的纹理,同时 resolveTexture 属性包含多重采样 resolve 操作的结果 (如果 texture 不支持多重采样,那么 MTLStoreActionMultisampleResolve 行为的结果不确定)。attachment 对象的这三个属性也用于多重采样 resolve 操作: resolveLevel 指定 mipmap 的层级, resolveSlice 指定立方切片,resolveDepthPlane 指定多重采样纹理的深度平面。几乎所有情况下,这三个属性的默认值都是可用的。在列表 5-2中,一个 attachment 被创建,然后它的 loadAction, storeAction, textureresolveTexture 属性被赋值用于支持多重采样 resolve 操作。

列表 5-2 为 Attachment 设置属性来支持多重采样 resolve 操作

MTLTextureDescriptor *colorTexDesc = [MTLTextureDescriptor
           texture2DDescriptorWithPixelFormat:MTLPixelFormatRGBA8Unorm
           width:IMAGE_WIDTH height:IMAGE_HEIGHT mipmapped:NO];
id <MTLTexture> colorTex = [device newTextureWithDescriptor:colorTexDesc];

MTLTextureDescriptor *msaaTexDesc = [MTLTextureDescriptor
           texture2DDescriptorWithPixelFormat:MTLPixelFormatRGBA8Unorm
           width:IMAGE_WIDTH height:IMAGE_HEIGHT mipmapped:NO];
msaaTexDesc.textureType = MTLTextureType2DMultisample;
msaaTexDesc.sampleCount = sampleCount;  //  must be > 1
id <MTLTexture> msaaTex = [device newTextureWithDescriptor:msaaTexDesc];

MTLRenderPassDescriptor *renderPassDesc = [MTLRenderPassDescriptor renderPassDescriptor];
renderPassDesc.colorAttachments[0].texture = msaaTex;
renderPassDesc.colorAttachments[0].resolveTexture = colorTex;
renderPassDesc.colorAttachments[0].loadAction = MTLLoadActionClear;
renderPassDesc.colorAttachments[0].storeAction = MTLStoreActionMultisampleResolve;
renderPassDesc.colorAttachments[0].clearColor = MTLClearColorMake(0.0,1.0,0.0,1.0);

使用 Render Pass Descriptor 来创建 Render Command Encoder

当你创建完 MTLRenderPassDescriptor 对象并设置好它的属性后,使用 MTLCommandBuffer 对象的 renderCommandEncoderWithDescriptor: 方法来创建 MTLRenderCommandEncoder 对象。如列表 5-3 所示。

列表 5-3 使用 Render Pass Descriptor 来创建 Render Command Encoder

id <MTLRenderCommandEncoder> renderCE = [commandBuffer
                    renderCommandEncoderWithDescriptor:renderPassDesc];

使用 Core Animation 显示渲染后的内容

Core Animation 定义了 CAMetalLayer 类,它被设计专门用来控制承载使用 Metal 渲染内容的基于层的视图(layer-backed view)的行为。一个 CAMetalLayer 对象表示了渲染内容的几何区域(position、size)、 视觉属性 (background color, border,和 shadow),还有 Metal 用来呈现内容的颜色 attachment 资源。它还封装了内容呈现的定时器,资源当内容准备好或是在某个特定的时候,内容就可以被显示出来。更多关于 Core Animation, 详见 Core Animation Programming Guide.

Core Animation 还为可视化资源定义了 CAMetalDrawable 协议。 CAMetalDrawable 协议扩展了 MTLDrawable 协议,并且提供适配 MTLTexture 协议的对象,所以实现了 CAMetalDrawable 协议的可视化对象可以作为渲染指令的目标产出物。在 CAMetalLayer 对象中实现渲染,你应该为每个渲染 pass 创建新的 CAMetalDrawable 对象,获取它提供的 MTLTexture 对象,使用这个 MTLTexture 对象来创建颜色 attachment。和颜色 attachments 不同,深度 和 模板 attachment 对象的创建和销毁成本很高。如果你需要使用这两种 attachments 对象,只创建一次,然后在随后的每帧渲染中重用它。

通常,重写 layerClass 方法来指派 CAMetalLayer 作为自定义 UIView 子类的支持图层类型(backing layer type),如列表 5-4 所示。否则,可以创建一个 CAMetalLayer 类型对象,实现它的 init 方法,并且在已存在的视图中包含这个图层。

列表5-4 使用 CAMetalLayer 作为 UIView 子类的支持图层

+ (id) layerClass {
    return [CAMetalLayer class];
}

为了在图层上显示 Metal 渲染的内容,必须从 CAMetalLayer 对象上获取一个可视化资源, 然后通过把它附加到 MTLRenderPassDescriptor 对象上来在这个资源上渲染到纹理。为了实现这个,首先要设置 CAMetalLayer 对象提供的用来描述可绘制资源的属性,然后在每一帧开始渲染的时候,调用 nextDrawable 方法。如果 CAMetalLayer 的相关属性没有设置,nextDrawable 方法将会调用失败。以下的 CAMetalLayer 对象属性用来描述可绘制对象( CAMetalDrawable ):

  • device 属性代表 MTLDevice 对象,资源由它创建。
  • pixelFormat 属性描述纹理对象的像素格式。可用的值有 MTLPixelFormatBGRA8Unorm (默认值) 和 MTLPixelFormatBGRA8Unorm_sRGB.
  • drawableSize 属性描述了纹理的像素尺寸。为了确保 App 在一个精确的尺寸渲染内容(不需要在设备上执行额外的采样阶段),当需要计算图层所需的大小时候,采用目标屏幕的 nativeScalenativeBounds 属性。
  • framebufferOnly 属性指明纹理对象只能被用作 attachment (YES) ,或者它还能被用作纹理采样和像素读写操作 (NO)。如果设置为 YES ,图层对象会优化纹理对象来显示。对于大多数 App,建议设置值为 Yes
  • presentsWithTransaction 属性描述图层的渲染资源的变化,根据标准 Core Animation 变换机制更 (YES) ,或者是相对于普通图层的变化异步地更新 (NO, 默认值)。

如果 nextDrawable 方法调用成功,它返回一个 CAMetalDrawable 对象,这个对象具有以下的只读属性:

  • texture 属性持有一个纹理对象,当创建渲染管线 (MTLRenderPipelineColorAttachmentDescriptor 对象)时,你可以用它作为一个 attachment 对象。
  • layer 属性指向 CAMetalLayer 对象,它和显示相关。

重要:: 系统只有为数不多的可绘制资源(drawable resources),所以在一个长时间的帧绘制将短暂耗尽这些资源,如此会导致 nextDrawable 方法阻断 CPU 线程知道方法完成。为了避免昂贵的 CPU 停顿,调用 CAMetalLayer 对象的 nextDrawable 方法之前,先执行其他不需要可绘制资源的所有每帧操作( all per-frame operations )。

为了在渲染完成后,显示可绘制对象的内容,你必须使用可绘制对象的 present 方法来把它提交到 Core Animation 中。 为了同步显示已完成与渲染相关的 command buffer 的可绘制对象,可以调用 MTLCommandBuffer 对象的 presentDrawable: 或者 presentDrawable:atTime: 方法,这两个方法使用预备好的 handler (详见 Registering Handler Blocks for Command Buffer Execution) 来调用可绘制对象的 present 方法,它覆盖了大多数的场景。 presentDrawable:atTime: 方法在可绘制对象被绘制时提供更多控制。

创建渲染管线(Render Pipeline) State 对象

为了使用 MTLRenderCommandEncoder 对象来编码渲染指令,必须先设置一个 MTLRenderPipelineState 对象来定义每个绘制命令的图形状态。一个渲染管线 state 对象是一个拥有长生命周期的对象,它可以在 render command encoder 对象生效范围外被创建,最后可以被缓存起来,然后被重用于多个 render command encoder 对象。当描述相同的图形状态,重用先前创建的渲染管线 state 对象,这样可以避免高成本的重新评估和转换操作(将特定状态转换成 GPU 指令)。

渲染管线 state 对象是一个不可变对象。要创建一个渲染管线 state 对象,首先创建一个可变的 MTLRenderPipelineDescriptor 对象,它描述了渲染管线 state 的属性。然后你可以使用这个 descriptor 来创建一个 MTLRenderPipelineState 对象。

创建并设置渲染管线 Descriptor 对象

要创建一个渲染管线 state 对象,首先需要创建一个 MTLRenderPipelineDescriptor 对象,它含有相关属性,用来描述在一个渲染 pass 中使用到的图形渲染管线 state。如图 5-2 所示。MTLRenderPipelineDescriptor 对象的 colorAttachments 属性包含了一个 MTLRenderPipelineColorAttachmentDescriptor 对象数组,其中的每个 descriptor 对象表示一个颜色 attachment 状态,这个状态为 attachment 指定了混合操作类型和因子。详见 Configuring Blending in a Render Pipeline Attachment Descriptor。descriptor 对象还设定了像素格式,这个像素格式必须和 MTLRenderPipelineDescriptor 的相应的 attachment 下标的纹理相匹配,否则会产生错误。

图5-2 通过 Descriptor 创建渲染管线 State

MTLRenderPipelineDescriptor 对象设置属性:

  • 设置 depthAttachmentPixelFormat 属性和 MTLRenderPassDescriptor 对象的 depthAttachment 的纹理像素格式一致。
  • 设置 stencilAttachmentPixelFormat 属性和 MTLRenderPassDescriptor 对象的 stencilAttachment 像素格式一致。
  • 通过 vertexFunction 来设置渲染管线 state 中的定点着色器程序, fragmentFunction 来设置渲染管线 state 中的片段着色器程序。如果 fragmentFunction 设置为 nil,将使得像素光栅化不可用(无法将渲染结果写入特定的颜色 attachment),通常只有两种情况会这样设置,它们是只和深度相关的渲染或者从顶点着色器输出数据到特定缓存。
  • 如果顶点着色器程序具有描述每个像素输入属性的参数,则可以设置 vertexDescriptor 属性来描述顶点数据的组织方式。详见 Vertex Descriptor for Data Organization.
  • 通常情况下,一般的渲染任务 rasterizationEnabled 属性都使用默认值 YES 。在某些不常见的情况下,图形管线只使用顶点阶段(比如,收集顶点着色器中转变后的数据),设置属性值为 NO.
  • 如果 attachment 支持多重采样(这以为着, 该 attachment 是一个 MTLTextureType2DMultisample 类型的纹理), 那么每像素多重采样器会被创建。为了确定片段如何结合来提供像素覆盖范围,使用如下的 MTLRenderPipelineDescriptor 属性:
    • sampleCount 属性表示每个像素的采样数。当 MTLRenderCommandEncoder 对象被创建,每个 attachments 的 sampleCount 属性值必须和这个 sampleCount 属性一致。如果 attachment 不支持多重采样, sampleCount 属性值为1,这也是默认值。
    • 如果 alphaToCoverageEnabled 属性设置为 YES,那么 colorAttachments[0] 的 attachment 对象,其 alpha 通道被读取并用来做成一个覆盖遮罩。
    • 如果 alphaToOneEnabled 设置为 YES,那么 colorAttachments[0] 的 attachment 对象,其 alpha 通道将被设置为 1.0,这是最大的有效值(其他的 attachment 对象不受影响)。

通过 Descriptor 创建渲染管线 State

当创建完 MTLRenderPipelineDescriptor 对象并设置好它的各个属性后,就可以用它来创建 MTLRenderPipelineState 对象。因为创建一个渲染管线 state 需要一个耗时的图形状态诊断并且可能伴随特定的着色程序编译,所以可以使用最适合 App 的方式同步或者异步调用类似的工作。

  • 同步创建渲染管线 state 对象,可以调用 MTLDevice 对象的 newRenderPipelineStateWithDescriptor:error: 方法或者 newRenderPipelineStateWithDescriptor:options:reflection:error: 方法。当 Metal 评估 descriptor 的图形状态信息和编译着色器代码来创建管线 state 对象的时候,这些方法会祖师当前线程。
  • 异步创建渲染管线 state 对象,可以调用 MTLDevice 对象的 newRenderPipelineStateWithDescriptor:completionHandler: 方法或者 newRenderPipelineStateWithDescriptor:options:completionHandler: 方法。这些方法立刻返回—Metal 异步评估 descriptor 的图形状态信息和编译着色器代码来创建管线 state 对象,然后调用 completionHandler 来提供新的 MTLRenderPipelineState 对象。

当你创建一个 MTLRenderPipelineState 对象,你也可以选择创建用于描述着色程序和它参数的 reflection 数据。 newRenderPipelineStateWithDescriptor:options:reflection:error:newRenderPipelineStateWithDescriptor:options:completionHandler: 方法提供这个数据。如果不使用 reflection 数据,那么就不要创建它。 详见 Determining Function Details at Runtime.

当你创建完 MTLRenderPipelineState 对象,调用 MTLRenderCommandEncoder 对象的 setRenderPipelineState: 方法来设置 command encoder 对象的 state 属性。

列表 5-5 示例了如何创建一个叫做 pipeline 渲染管线 state。

列表 5-5 创建渲染管线 state

MTLRenderPipelineDescriptor *renderPipelineDesc =
                             [[MTLRenderPipelineDescriptor alloc] init];
renderPipelineDesc.vertexFunction = vertFunc;
renderPipelineDesc.fragmentFunction = fragFunc;
renderPipelineDesc.colorAttachments[0].pixelFormat = MTLPixelFormatRGBA8Unorm;

// Create MTLRenderPipelineState from MTLRenderPipelineDescriptor
NSError *errors = nil;
id <MTLRenderPipelineState> pipeline = [device
         newRenderPipelineStateWithDescriptor:renderPipelineDesc error:&errors];
assert(pipeline && !errors);

// Set the pipeline state for MTLRenderCommandEncoder
[renderCE setRenderPipelineState:pipeline];

变量 vertFuncfragFunc 是着色程序,它们被赋值给一个叫做 renderPipelineDesc 的渲染管线的 state descriptor 对象的属性。调用 MTLDevice 对象的 newRenderPipelineStateWithDescriptor:error: 方法,同步使用刚创建的管线 state descriptor 来创建渲染管线 state 对象。然后调用 MTLRenderCommandEncoder 对象的 setRenderPipelineState: 方法,指定刚刚创建的 MTLRenderPipelineState 对象被 command encoder 对象用于渲染。

注意: 因为 MTLRenderPipelineState 对象的创建开销很大,如果你使用的图形状态相同,就尽量重用它。

在渲染管线 Attachment Descriptor 中配置混合(Blending)

混合是使用高度可配置的混色操作,将片段着色器的输出(作为源)和 attachment 中的像素值(作为目标)进行计算。混合操作决定如何将源和目标按混合因子进行组合。

为了配置颜色 attachment 的混合操作,需要设置如下的 MTLRenderPipelineColorAttachmentDescriptor 对象相关属性:

  • 为了允许混合操作,设置 blendingEnabled 属性值为 YES。默认是 NO,混合操作不生效。
  • writeMask 属性设置哪个颜色通道参与混合,默认值是 MTLColorWriteMaskAll ,表示所有的颜色通道都参与混合。
  • rgbBlendOperationalphaBlendOperation 属性分别设置片段数据的 RGB 和 Alpha 如何混合,默认值都是 MTLBlendOperation
  • sourceRGBBlendFactor, sourceAlphaBlendFactor, destinationRGBBlendFactor, 和 destinationAlphaBlendFactor 这些属性用于设置源和目标的混合因子。

理解混合因子和混合操作

上述四个混合因子和如下的混合颜色常量值相关: MTLBlendFactorBlendColor, MTLBlendFactorOneMinusBlendColor,MTLBlendFactorBlendAlpha,和 MTLBlendFactorOneMinusBlendAlpha. 使用这些混合因子,调用 MTLRenderCommandEncoder 对象的 setBlendColorRed:green:blue:alpha: 方法来设定颜色常量和 alpha 常量。详见 Fixed-Function State Operations.

有一些混合操作是这样实施的,使用一个源MTLBlendFactor 对象(简称 SBF)和源像素值相乘,使用一个目标混合因子(简称DBF)和目标像素值相乘,最后根据由 MTLBlendOperation 值指定的混合算法去计算最后的色值。 (如果 MTLBlendOperation 的值是 MTLBlendOperationMin 或是 MTLBlendOperationMax, SBF 和 DBF 混合因子被忽略)。举例来说,如果 rgbBlendOperationalphaBlendOperation 的值都是 MTLBlendOperationAdd ,那么 RGB 和 Alpha 的色值计算公式如下:

  • RGB = (Source.rgb sourceRGBBlendFactor) + (Dest.rgb destinationRGBBlendFactor)
  • Alpha = (Source.a sourceAlphaBlendFactor) + (Dest.a destinationAlphaBlendFactor)

默认的混合行为是将源完全覆盖目标,这时会将 sourceRGBBlendFactorsourceAlphaBlendFactor 设置为 MTLBlendFactorOne,并且将 destinationRGBBlendFactordestinationAlphaBlendFactor 设置为 MTLBlendFactorZero。色值计算公式如下:

  • RGB = (Source.rgb 1.0) + (Dest.rgb 0.0)
  • A = (Source.a 1.0) + (Dest.a 0.0)

另外一个常用的混合操作,源的 alpha 值决定目标颜色被保留多少,色值的计算公式如下:

  • RGB = (Source.rgb 1.0) + (Dest.rgb (1 - Source.a))
  • A = (Source.a 1.0) + (Dest.a (1 - Source.a))

使用自定义混合设置

列表 5-6 展示如何设定一个混合,使用 MTLBlendOperationAdd 混合操作,SBF 设置为 MTLBlendFactorOne,DBF 设置为 MTLBlendFactorOneMinusSourceAlphacolorAttachments[0] 是一个 MTLRenderPipelineColorAttachmentDescriptor 对象,混合的设置过程就是为该对象设置属性。

列表 5-6 自定义混合设置

MTLRenderPipelineDescriptor *renderPipelineDesc = 
                             [[MTLRenderPipelineDescriptor alloc] init];
renderPipelineDesc.colorAttachments[0].blendingEnabled = YES; 
renderPipelineDesc.colorAttachments[0].rgbBlendOperation = MTLBlendOperationAdd;
renderPipelineDesc.colorAttachments[0].alphaBlendOperation = MTLBlendOperationAdd;
renderPipelineDesc.colorAttachments[0].sourceRGBBlendFactor = MTLBlendFactorOne;
renderPipelineDesc.colorAttachments[0].sourceAlphaBlendFactor = MTLBlendFactorOne;
renderPipelineDesc.colorAttachments[0].destinationRGBBlendFactor = 
       MTLBlendFactorOneMinusSourceAlpha;
renderPipelineDesc.colorAttachments[0].destinationAlphaBlendFactor = 
       MTLBlendFactorOneMinusSourceAlpha;

NSError *errors = nil;
id <MTLRenderPipelineState> pipeline = [device 
         newRenderPipelineStateWithDescriptor:renderPipelineDesc error:&errors];

为渲染 Command Encoder 设置绘制资源

这个章节中讨论的 MTLRenderCommandEncoder 方法,为顶点和片段着色器程序(着色程序由 MTLRenderPipelineState 对象的 vertexFunctionfragmentFunction 属性指定)的参数设置绘制资源。这些方法设定一个着色器资源(缓存,纹理,采样器)到渲染 command encoder 对应的参数列表下标位置 (atIndex) ,如图 5-3 所示。

图 5-3 渲染 Command Encoder 的参数列表

下面这些 setVertex* 方法为相应的顶点着色器程序参数设置一个或是多个资源。

  • setVertexBuffer:offset:atIndex:
  • setVertexBuffers:offsets:withRange:
  • setVertexTexture:atIndex:
  • setVertexTextures:withRange:
  • setVertexSamplerState:atIndex:
  • setVertexSamplerState:lodMinClamp:lodMaxClamp:atIndex:
  • setVertexSamplerStates:withRange:
  • setVertexSamplerStates:lodMinClamps:lodMaxClamps:withRange:

下面这些 setFragment* 方法为相应的片段着色器程序参数设置一个或是多个资源。

  • setFragmentBuffer:offset:atIndex:
  • setFragmentBuffers:offsets:withRange:
  • setFragmentTexture:atIndex:
  • setFragmentTextures:withRange:
  • setFragmentSamplerState:atIndex:
  • setFragmentSamplerState:lodMinClamp:lodMaxClamp:atIndex:
  • setFragmentSamplerStates:withRange:
  • setFragmentSamplerStates:lodMinClamps:lodMaxClamps:withRange:

在缓存参数列表中最多有31个入口,在纹理参数列表最多有31个入口,在采样器状态列表里最多有16个入口。

在 Metal 着色语言源代码中用于定位资源的属性修饰符必须和 Metal 框架代码中相应方法设置的参数列表下标相吻合。如列表 5-7 所示,两个缓存 (posBuftexCoordBuf)分别对应下标0和1,那么在顶点着色器程序中也要这样设置。

列表 5-7 Metal框架: 为顶点着色器程序指定资源

[renderEnc setVertexBuffer:posBuf offset:0 atIndex:0];
[renderEnc setVertexBuffer:texCoordBuf offset:0 atIndex:1];

如列表 5-8 所示,顶点着色器程序的方法签名有对应的用 “index” 修饰的参数说明,表示使用 buffer(0)buffer(1)

列表 5-8 Metal 着色语言:顶点着色器程序的参数匹配框架参数列表下标。

vertex VertexOutput metal_vert(float4 *posData [[ buffer(0) ]],
                               float2 *texCoordData [[ buffer(1) ]])

类似的,如列表 5-9 所示,一个缓冲,一个纹理,一个着色器 (依次为 fragmentColorBuf, shadeTex,和 sampler), 都使用下标0,它们是为一个片段着色器程序准备的。

Listing 5-9 Metal框架: 为片段着色器程序指定资源

[renderEnc setFragmentBuffer:fragmentColorBuf offset:0 atIndex:0];
[renderEnc setFragmentTexture:shadeTex atIndex:0];
[renderEnc setFragmentSamplerState:sampler atIndex:0];

如列表 5-10 所示,方法签名有对应的用 “index” 修饰的参数声明,表示使用 buffer(0), texture(0),和 sampler(0)

Listing 5-10 Metal 着色语言: 片段着色器程序的参数匹配框架参数列表下标。

fragment float4 metal_frag(VertexOutput in [[stage_in]],
                           float4 *fragColorData [[ buffer(0) ]],
                           texture2d<float> shadeTexValues [[ texture(0) ]],
                           sampler samplerValues [[ sampler(0) ]] )

用于数据组织的顶点 Descriptor

在 Metal 框架代码中,可以为每个管线 state 准备一个 MTLVertexDescriptor 对象,它用来描述顶点着色器程序使用的输入数据如何组织,以及资源在着色器程序代码(使用 Metal 着色语言编写,运行在 GPU 上)和框架代码之间的映射信息。

在 Metal 着色器语言代码中,每个顶点的输入(如标量或矢量的整数或浮点数)可以被组织在一个结构体中,它可以通过一个声明为 [[ stage_in ]] 属性修饰符的参数传入,如列表 5-11 所示示例顶点着色器程序 vertexMath 中的 VertexInput 结构体。每个顶点的输入结构体中每一项,都有 [[ attribute(index) ]] 修饰符,它指定顶点属性参数表中的索引。

列表5-11 Metal 着色语言:顶点着色器程序的输入

struct VertexInput {
    float2    position [[ attribute(0) ]];
    float4    color    [[ attribute(1) ]];
    float2    uv1      [[ attribute(2) ]];
    float2    uv2      [[ attribute(3) ]];
};

struct VertexOutput {
    float4 pos [[ position ]];
    float4 color;
};

vertex VertexOutput vertexMath(VertexInput in [[ stage_in ]])
{
  VertexOutput out;
  out.pos = float4(in.position.x, in.position.y, 0.0, 1.0);

  float sum1 = in.uv1.x + in.uv2.x;
  float sum2 = in.uv1.y + in.uv2.y;
  out.color = in.color + float4(sum1, sum2, 0.0f, 0.0f);
  return out;
}

使用 [[ stage_in ]] 修饰符来指定顶点着色器程序的输入,创建并设置好一个 MTLVertexDescriptor 对象,然后把它设置为 MTLRenderPipelineState 对象的 vertexDescriptor 属性值。MTLVertexDescriptor 有两个属性: attributeslayouts

MTLVertexDescriptorattributes 属性是一个 MTLVertexAttributeDescriptorArray 对象,它定义了一个缓冲中每个顶点的各属性如何组织并且映射到一个顶点着色程序的入参中。 attributes 属性支持访问在同一块缓存中交叉存放的多重顶点数据信息(比如顶点坐标、表面法向量、纹理坐标)。在着色程序和框架程序中,这些交替存放的数据信息的顺序可以不同。在 attributes 数组中的每个 descriptor 对象有如下属性,它们用于为顶点着色程序提供定位和加载入参数据:

  • bufferIndex 属性是一个缓存参数列表的下标,它标示哪个 MTLBuffer 对象将被访问。缓存参数列表详见 Specifying Resources for a Render Command Encoder.
  • format 属性标识在 Metal 框架代码中数据是如何被解释的。如果数据类型不完全符合,它将被转换或是延展。比如,如果着色语言中类型是 half4 ,框架代码中 formatMTLVertexFormatFloat2,那么当数据被顶点着色程序用作入参时,它将从 float 转换成 half 并且从2维向量变成4维向量(后面多出来的两个分量将被赋予0.0和1.0)。
  • offset 属性指定从一个顶点缓存哪个位置获取数据。

图 5-4 举例说明了 Metal 框架代码中的 MTLVertexAttributeDescriptorArray 数组,它描述了缓存中顶点数据如何交替,如何为 列表 5-11 中着色语言代码中顶点着色程序 vertexMath 提供入参。

图 5-4 纹理结构和顶点属性(Vertex Attribute) Descriptors

列表 5-12 展示了 Metal 框架代码如何实现图 5-4 描述的缓存。

列表 5-12 Metal 框架: 使用顶点 Descriptor 访问交错数据

id <MTLFunction> vertexFunc = [library newFunctionWithName:@"vertexMath"];            
MTLRenderPipelineDescriptor* pipelineDesc =      
                             [[MTLRenderPipelineDescriptor alloc] init];
MTLVertexDescriptor* vertexDesc = [[MTLVertexDescriptor alloc] init];

vertexDesc.attributes[0].format = MTLVertexFormatFloat2;
vertexDesc.attributes[0].bufferIndex = 0;
vertexDesc.attributes[0].offset = 0;
vertexDesc.attributes[1].format = MTLVertexFormatFloat4;
vertexDesc.attributes[1].bufferIndex = 0;
vertexDesc.attributes[1].offset = 2 * sizeof(float);  // 8 bytes
vertexDesc.attributes[2].format = MTLVertexFormatFloat2;
vertexDesc.attributes[2].bufferIndex = 0;
vertexDesc.attributes[2].offset = 8 * sizeof(float);  // 32 bytes
vertexDesc.attributes[3].format = MTLVertexFormatFloat2;
vertexDesc.attributes[3].bufferIndex = 0;
vertexDesc.attributes[3].offset = 6 * sizeof(float);  // 24 bytes
vertexDesc.layouts[0].stride = 10 * sizeof(float);    // 40 bytes
vertexDesc.layouts[0].stepFunction = MTLVertexStepFunctionPerVertex;

pipelineDesc.vertexDescriptor = vertexDesc;
pipelineDesc.vertexFunction = vertFunc;

MTLVertexDescriptor 对象中 attributes 数组的每一个 MTLVertexAttributeDescriptor 对象相当于着色程序中 VertexInput 中的被索引的结构体成员。attributes[1].bufferIndex = 0 表示使用的缓存在参数列表中 index 为0。 (在这个例子中,每个 MTLVertexAttributeDescriptor 有相同的 bufferIndex,所以每一个都指的是参数列表中 index 为0的顶点缓存。 offset 属性指定从一个顶点缓存哪个位置获取数据,比如 attributes[1].offset = 2 * sizeof(float) 表示从缓存起点后8个字节获取数据。format 的值选择符合着色程序中数据类型的,比如 attributes[1].format = MTLVertexFormatFloat4 表示使用浮点类型的值。

MTLVertexDescriptor 对象的 layouts 属性是一个 MTLVertexBufferLayoutDescriptorArray 数组。对于 layouts 中的每一个 MTLVertexBufferLayoutDescriptor 对象的属性,描述了在绘制图元的时候如何从对应的 MTLBuffer 对象中获取顶点和其属性数据。 (更多关于绘制图元,详见 Drawing Geometric Primitives.) MTLVertexBufferLayoutDescriptor 对象的 stepFunction 属性决定了是为每个顶点获取属性,还是一些图元,还是只获取一次。 如果 stepFunction 被设置为为图元获取属性数据,那么 MTLVertexBufferLayoutDescriptor 对象的 stepRate 属性决定有多少图元, stride 属性决定相邻两个顶点数据间的间隔(单位是字节)。

图 5-5 描述了 列表 5-12 中代码设置的 MTLVertexBufferLayoutDescriptorlayouts[0] 表示如何从对应下标为0的缓存参数列表中获取顶点数据。 layouts[0].stride 表示相邻两个顶点数据相隔40字节layouts[0].stepFunction 被设置为 MTLVertexStepFunctionPerVertex,表示每个顶点绘制的时候都要获取顶点属性数据。 如果 stepFunction 被设置为 MTLVertexStepFunctionPerInstance, 那么 stepRate 属性决定获取顶点属性数据的频率。比如,如果 stepRate 是1,每个图元都获取数据;如果 stepRate 是2,每两个图元获取一次数据,等等。

Figure 5-5 Buffer Organization with Vertex Buffer Layout Descriptors

执行 Command Encoder 对象的固定渲染管线操作

使用如下 MTLRenderCommandEncoder 对象的方法来设置固定图形渲染管线 state:

  • setViewport: 在屏幕坐标系下设置一个区域,该区域是虚拟 3D 场景的投影。视口是 3D,所以它含有深度值。详见 Working with Viewport and Pixel Coordinate Systems.
  • setTriangleFillMode: 用于决定如何填充三角形或是三角条带图元,MTLTriangleFillModeLines 表示仅描绘线条,MTLTriangleFillModeFill 表示填充三角形。默认值是 MTLTriangleFillModeFill.
  • setCullMode:setFrontFacingWinding: 配合使用,决定是否以及如何应用剔除(Cull)。 你可以使用剔除来隐藏清除某些几何模型的表面,比如用填充三角形渲染的可定向(orientable)球体 (表面可定向意味着它的图元是始终顺时针或者逆时针绘制的)。
    • setFrontFacingWinding: 指定图元的正面绘制时顺时针方向处理 (MTLWindingClockwise) 还是逆时针方向处理 (MTLWindingCounterClockwise) ,默认值是 MTLWindingClockwise
    • setCullMode: 设置剔除模式,MTLCullModeNone 表示不剔除,MTLCullModeFront 表示正面剔除,MTLCullModeBack 表示背面剔除。

使用如下的 MTLRenderCommandEncoder 对象的方法来编码固定渲染状态变更指令:

  • setScissorRect: 设置一个 2D 裁剪矩形,位于该矩形外侧的片段将被丢弃。

  • setDepthStencilState: 设置深度和模板缓存状态,详见 Depth and Stencil States.

  • setStencilReferenceValue: 设置模板缓存参考值。

  • setDepthBias:slopeScale:clamp: 设置一个偏移量,它用于比较阴影位图和深度值(由片段着色器输出)。

  • setVisibilityResultMode:offset: 设置如果一个采样器通过深度和模板测试,是否要被监视。当设置为 MTLVisibilityResultModeBoolean 时,如果任何采样器通过深度和模板测试,一个非零值被写由 MTLRenderPassDescriptor 对象的 visibilityResultBuffer 属性所指定的缓存中。详见 Creating a Render Pass Descriptor.

    你可以使用这个 mode 执行遮挡测试。如果绘制的是一个包围盒,并且没有采样器通过测试,那么可以推断包围盒内的物体不可见,所以这些物体是无需绘制的。

  • setBlendColorRed:green:blue:alpha: 设置常量混合色值和 alpha 值。详见 Configuring Blending in a Render Pipeline Attachment Descriptor.

视口和像素坐标系

Metal 定义了一个规范化的设备坐标系 (NDC) ,它是一个 2x2x1 的立方体,它的中心坐标是 (0, 0, 0.5)。左边界 x 的值为-1,下边界 y 的值为-1,右边界 x 的值为1,上边界 y 的值为1。

视口定义了 NDC 到窗口坐标系的转换。Metal 的视口是一个由 MTLRenderCommandEncoder 对象的 setViewport: 方法指定的 3D 向量。窗口坐标系的原点在窗口的左上角。

在 Metal 中,像素的中心被偏移了 (0.5, 0.5)。比如,在原点的像素其中心位于 (0.5, 0.5);这个像素点右边的那个像素,其中心位于 (1.5, 0.5)。对于纹理也是这样的。

执行深度和模板操作

深度和模板操作都是片段操作,它们的设置方法如下:

  1. 设置一个 MTLDepthStencilDescriptor 对象,它包含深度/模板状态信息。创建一个 MTLDepthStencilDescriptor 对象需要一个或者两个 MTLStencilDescriptor 对象来对应正面图元和背面图元。
  2. 调用 MTLDevice 对象的 newDepthStencilStateWithDescriptor: 方法创建一个 MTLDepthStencilState 对象,使用步骤1创建好的 MTLDepthStencilDescriptor 对象。
  3. 为了设置深度/模板状态,调用 MTLRenderCommandEncoder 对象的 setDepthStencilState: 方法,使用步骤2创建的 MTLDepthStencilState 对象。
  4. 如果模板测试可用,调用 MTLRenderCommandEncoder 对象的 setStencilReferenceValue: 方法设置模板测试参考值。

如果深度测试可用,必须有一个深度 attachment 对象用于写入深度值。为了应用模板测试,渲染管线 state 必须包含一个模板 attachment。如何配置 attachments,详见 Creating and Configuring a Render Pipeline Descriptor.

如果需要周期性地改变深度/模板状态,可用重用 state descriptor 对象。重用 state descriptor 对象创建其他 state 对象时,如果需要,可用改变 descriptor 对象相关的属性值。

注意: 如果在一个着色器程序中从深度格式的纹理做采样,那么在着色器中实现采样操作不要使用 MTLSamplerState

使用如下的 MTLDepthStencilDescriptor 对象的属性来设置深度和模板状态:

  • 为了允许把深度值写入深度 attachment,设置 depthWriteEnabledz 值为 YES
  • depthCompareFunction 指定深度测试如何进行。如果一个片段的深度值没有通过深度测试,该片段将被丢弃。举例来说, MTLCompareFunctionLess 函数常用来设置该属性值,如果当前进行测试的片段的深度值相比之前写入深度缓存片段的深度值大,那么这个片段是远离观察者的,它将不能通过深度测试而被认为被遮挡不可见。
  • frontFaceStencilbackFaceStencil 属性分别设置图元正面/背面测试模板用的 MTLStencilDescriptor 对象。如果图元的正面和背面的模板测试状态一样,那么可以设置同一个 MTLStencilDescriptor 对象给 frontFaceStencilbackFaceStencil 属性。如果要显示地设置正面或者背面模板测试不可用,那么设置相应的属性为 nil,它也是默认值。

显示地禁用模板状态不是必要的。Metal 基于模板 descriptor 是否由一个有效的模板操作配置来决定是否允许模板测试。

列表 5-13 展示了如何创建并使用 MTLDepthStencilDescriptor 对象来配置 MTLDepthStencilState对象,然后用于渲染 command encoder。在这个例子中,图元的正面模板测试状态由深度/模板状态 descriptor 的 frontFaceStencil 属性决定,图元的背面模板测试被显示关闭。

列表 5-13 创建并使用深度/模板 Descriptor

MTLDepthStencilDescriptor *dsDesc = [[MTLDepthStencilDescriptor alloc] init];
if (dsDesc == nil)
     exit(1);   //  if the descriptor could not be allocated
dsDesc.depthCompareFunction = MTLCompareFunctionLess;
dsDesc.depthWriteEnabled = YES;

dsDesc.frontFaceStencil.stencilCompareFunction = MTLCompareFunctionEqual;
dsDesc.frontFaceStencil.stencilFailureOperation = MTLStencilOperationKeep;
dsDesc.frontFaceStencil.depthFailureOperation = MTLStencilOperationIncrementClamp;
dsDesc.frontFaceStencil.depthStencilPassOperation =
                          MTLStencilOperationIncrementClamp;
dsDesc.frontFaceStencil.readMask = 0x1;
dsDesc.frontFaceStencil.writeMask = 0x1;
dsDesc.backFaceStencil = nil;
id <MTLDepthStencilState> dsState = [device
                          newDepthStencilStateWithDescriptor:dsDesc];

[renderEnc setDepthStencilState:dsState];
[renderEnc setStencilReferenceValue:0xFF];

MTLStencilDescriptor 的如下属性定义模板测试:

  • readMask 是一个与操作位掩码,它即用于模板参考值,也用于模板存储值。模板测试是一个比较操作,比较参考值的与运算结果和存储值的与运算结果。
  • writeMask 是一个位掩码,用来计算那些模板值在模板操作中写入模板 attachment。
  • stencilCompareFunction 定义片段的模板测试如何执行。在列表 5-13, 模板比较函数是 MTLCompareFunctionEqual,那么当一个片段位运算后的参考值和位运算后存储值相同时,模板测试通过。
  • stencilFailureOperation, depthFailureOperation,和 depthStencilPassOperation 设置三种不同测试结果下,存储在模板 attachment 中的模板值如何处理:如果模板测试失败;如果模板测试通过,但是深度测试失败;如果模板测试和深度测试都通过。在前面的例子中,如果模板测试失败,模板值是不变的 (MTLStencilOperationKeep) ,但如果模板测试通过,模板值是增加的,除非模板值已经是最大值了 (MTLStencilOperationIncrementClamp).

绘制几何图元

当你已经创建了渲染管线 state 和固定管线 state,你可以调用如下的 MTLRenderCommandEncoder 方法来绘制几何图元。这些绘制方法引用资源(比如包含顶点坐标,纹理坐标,表面法线或其他数据的纹理),并且使用着色器程序以及之前通过 MTLRenderCommandEncoder 对象创建的其他 state,来执行管线。

  • drawPrimitives:vertexStart:vertexCount:instanceCount: 将渲染由 instanceCount 指定数量的图元实例,将使用连续存放的数组元素中的顶点数据, vertexStart 指示数组中的一个下标位置,这里是第一个顶点数据开始的地方, vertexStart + vertexCount - 1 指示的下标是顶点数据结束的位置。
  • drawPrimitives:vertexStart:vertexCount: 和前一个方法相同,只是 instanceCount 被指定为1。
  • drawIndexedPrimitives:indexCount:indexType:indexBuffer:indexBufferOffset:instanceCount: 将渲染由 instanceCount 指定数量的图元实例。将使用 indexBuffer 参数指定的 MTLBuffer 对象中的索引列表,参数 indexCount 决定了索引的数量。 indexBufferOffset 参数指定了在 indexBuffer 中偏移的字节数,偏移后就是索引列表开始的地方。 indexBufferOffset 参数是索引长度的乘积,索引的类型由 indexType 决定。
  • drawIndexedPrimitives:indexCount:indexType:indexBuffer:indexBufferOffset: 和前一个方法相同,只是 instanceCount 被指定为1。

上面列出的每一个图元渲染方法,第一个输入参数决定了图元的类型(一个 MTLPrimitiveType 类型的值)。其他的参数决定哪些顶点数据用于组装图元。这几个方法中, instanceStart 参数决定绘制的第一个图元实例。 instanceCount 参数决定绘制多少个图元实例。

正如之前所讨论的, setTriangleFillMode: 方法决定了三角形渲染的时候是填充的还是线框的。 setCullMode:setFrontFacingWinding: 设置决定渲染时 GPU 是否剔除三角形。详见 Fixed-Function State Operations).

当渲染 MTLPrimitiveTypePoint 类型的图元时,顶点着色程序必须支持 [[ point_size ]] 属性,否则点的大小是不确定的。

更多关于 Metal 着色语言的属性和修饰符,详见 Metal Shading Language Guide

结束一个渲染 Pass

要结束一个渲染 pass,调用渲染 command encoder 的 endEncoding 方法。当这个方法被调用后,你可以创建一个新的任意类型的 command encoder 来编码新的指令,塞到 command buffer 中。

代码示例:绘制一个三角形

以下的步骤,如 列表 5-14 中实现的,描述了渲染一个三角形的基础过程:

  1. 首先创建一个 MTLCommandQueue 对象,然后用它来创建一个 MTLCommandBuffer 对象。

  2. 创建一个 MTLRenderPassDescriptor 对象,它代表一系列的 attachment,attachment 将作为 command buffer 中被编码的渲染指令的最终产出目标。

    在这个例子中,只有第一个颜色 attachment 被设置和使用(假定变量 currentTexture 包含一个用于颜色 attachment 的 MTLTexture 对象) 。然后 MTLRenderPassDescriptor 被用来创建一个新的 MTLRenderCommandEncoder 对象。

  3. 创建两个 MTLBuffer 对象,分别是 posBufcolBuf,然后调用 newBufferWithBytes:length:options: 方法来拷贝顶点坐标 posData 和顶点颜色数据 colData 到缓存存储空间中。

  4. 两次调用 MTLRenderCommandEncoder 对象的 setVertexBuffer:offset:atIndex: 方法来设置坐标和颜色。

    setVertexBuffer:offset:atIndex: 方法的输入参数 atIndex 对应顶点着色程序源代码中的属性缓存索引(buffer(atIndex))。

  5. 创建一个 MTLRenderPipelineDescriptor 对象,并且为它设置顶点着色程序和片段着色程序:

    • 使用 progSrc 中的源代码创建一个 MTLLibrary 对象,假设 progSrc 是一个字符串,它含有 Metal 着色语言源代码。
    • 然后调用 MTLLibrary 对象的 newFunctionWithName: 方法创建一个 MTLFunction 类型的变量 vertFunc,它代表了着色程序hello_vertex ,创建一个 MTLFunction 类型的变量 fragFunc ,它代表着色程序 hello_fragment
    • 最后,把这两个 MTLFunction 对象 分别设置给 MTLRenderPipelineDescriptor 对象的 vertexFunctionfragmentFunction 属性。
  6. MTLRenderPipelineDescriptor 对象为参数,调用 newRenderPipelineStateWithDescriptor:error: 方法创建一个 MTLRenderPipelineState 对象。然后调用 MTLRenderCommandEncodersetRenderPipelineState: 方法使 encoder 得到这个管线 state,以便绘制时候使用。

  7. 调用 MTLRenderCommandEncoder 对象的 drawPrimitives:vertexStart:vertexCount: 方法,把绘制一个填充的三角形 (类型是 MTLPrimitiveTypeTriangle)的指令推入 command buffer中。

  8. 调用 endEncoding 方法来结束这个渲染 pass 的编码。最后调用 MTLCommandBuffercommit 方法开始在设备上执行渲染指令。

列表 5-14 绘制三角形的 Metal 代码

id <MTLDevice> device = MTLCreateSystemDefaultDevice();

id <MTLCommandQueue> commandQueue = [device newCommandQueue];
id <MTLCommandBuffer> commandBuffer = [commandQueue commandBuffer];

MTLRenderPassDescriptor *renderPassDesc
                               = [MTLRenderPassDescriptor renderPassDescriptor];
renderPassDesc.colorAttachments[0].texture = currentTexture;
renderPassDesc.colorAttachments[0].loadAction = MTLLoadActionClear;
renderPassDesc.colorAttachments[0].clearColor = MTLClearColorMake(0.0,1.0,1.0,1.0);
id <MTLRenderCommandEncoder> renderEncoder =
           [commandBuffer renderCommandEncoderWithDescriptor:renderPassDesc];

static const float posData[] = {
        0.0f, 0.33f, 0.0f, 1.f,
        -0.33f, -0.33f, 0.0f, 1.f,
        0.33f, -0.33f, 0.0f, 1.f,
};
static const float colData[] = {
        1.f, 0.f, 0.f, 1.f,
        0.f, 1.f, 0.f, 1.f,
        0.f, 0.f, 1.f, 1.f,
};
id <MTLBuffer> posBuf = [device newBufferWithBytes:posData
        length:sizeof(posData) options:nil];
id <MTLBuffer> colBuf = [device newBufferWithBytes:colorData
        length:sizeof(colData) options:nil];
[renderEncoder setVertexBuffer:posBuf offset:0 atIndex:0];
[renderEncoder setVertexBuffer:colBuf offset:0 atIndex:1];

NSError *errors;
id <MTLLibrary> library = [device newLibraryWithSource:progSrc options:nil
                           error:&errors];
id <MTLFunction> vertFunc = [library newFunctionWithName:@"hello_vertex"];
id <MTLFunction> fragFunc = [library newFunctionWithName:@"hello_fragment"];
MTLRenderPipelineDescriptor *renderPipelineDesc
                                   = [[MTLRenderPipelineDescriptor alloc] init];
renderPipelineDesc.vertexFunction = vertFunc;
renderPipelineDesc.fragmentFunction = fragFunc;
renderPipelineDesc.colorAttachments[0].pixelFormat = currentTexture.pixelFormat;
id <MTLRenderPipelineState> pipeline = [device
             newRenderPipelineStateWithDescriptor:renderPipelineDesc error:&errors];
[renderEncoder setRenderPipelineState:pipeline];
[renderEncoder drawPrimitives:MTLPrimitiveTypeTriangle
               vertexStart:0 vertexCount:3];
[renderEncoder endEncoding];
[commandBuffer commit];

在列表 5-14 中,一个 MTLFunction 对象代表一个叫做 hello_vertex 的着色程序。MTLRenderCommandEncoder 对象的 setVertexBuffer:offset:atIndex: 方法被用来设置顶点资源(在这个例子中,是两个缓存对象),这些资源作为输入参数传递到着色程序 hello_vertex 中。setVertexBuffer:offset:atIndex: 方法中的输入参数 atIndex 对应着顶点着色程序源代码中的属性缓存(attribute buffer(atIndex) )如列表 5-15 所示。

列表 5-15 相关联的着色程序定义

vertex VertexOutput hello_vertex(
                    const global float4 *pos_data [[ buffer(0) ]],
                    const global float4 *color_data [[ buffer(1) ]])
{
    ...
}

使用多线程编码一个渲染 Pass

在某些情况下, 使用单 CPU 为一个渲染 pass 编码会影响 App 的性能。 然而,将工作负载划分成多个渲染 pass 在多个 CPU 线程上编码也会影响性能,因为每个渲染 pass 都需要执行对中间 attachment 的存储以及加载操作,才能保存渲染结果内容。

更好的办法是使用 MTLParallelRenderCommandEncoder 对象,它管理多个从属的 MTLRenderCommandEncoder 对象,共享相同的 command buffer 和渲染 pass descriptor。 MTLParallelRenderCommandEncoder 确保 attachment 的加载和存储操作只发生在整个渲染 pass 的开头和结尾,不是每个从属的 MTLRenderCommandEncoder 对象的指令的开头和结尾。由于这个体系结构,每个子 MTLRenderCommandEncoder 对象可以被赋予它自己的线程,如此 Encoder 可以安全高效的并行起来。

为了创建 MTLParallelRenderCommandEncoder 对象,使用 MTLCommandBuffer 对象的 parallelRenderCommandEncoderWithDescriptor: 方法。再通过多次调用这个 MTLParallelRenderCommandEncoder 对象的 renderCommandEncoder 方法来创建多个从属的 MTLRenderCommandEncoder 对象,这些从属的 MTLRenderCommandEncoder 对象都共享相同的 command buffer 和 渲染 pass descriptor。渲染指令被编码进入 command buffer 的顺序和 Encoder 创建的顺序相同。要结束一个特点的从属的 MTLRenderCommandEncoder 对象,调用 MTLRenderCommandEncoder 对象的 endEncoding 方法。如果由 MTLParallelRenderCommandEncoder 创建的所有从属的 MTLRenderCommandEncoder 对象都结束了,调用 MTLParallelRenderCommandEncoder 对象的 endEncoding 方法来结束一个渲染 pass。

列表 5-16 展示 MTLParallelRenderCommandEncoder 创建三个 MTLRenderCommandEncoder 对象,分别是: rCE1, rCE2, and rCE3.

列表 5-16 一个 MTLParallelRenderCommandEncoder 对象和三个 MTLRenderCommandEncoder 对象

MTLRenderPassDescriptor *renderPassDesc 
                     = [MTLRenderPassDescriptor renderPassDescriptor];
renderPassDesc.colorAttachments[0].texture = currentTexture;
renderPassDesc.colorAttachments[0].loadAction = MTLLoadActionClear;
renderPassDesc.colorAttachments[0].clearColor = MTLClearColorMake(0.0,0.0,0.0,1.0);

id <MTLParallelRenderCommandEncoder> parallelRCE = [commandBuffer 
                     parallelRenderCommandEncoderWithDescriptor:renderPassDesc];
id <MTLRenderCommandEncoder> rCE1 = [parallelRCE renderCommandEncoder];
id <MTLRenderCommandEncoder> rCE2 = [parallelRCE renderCommandEncoder];
id <MTLRenderCommandEncoder> rCE3 = [parallelRCE renderCommandEncoder];

//  not shown: rCE1, rCE2, and rCE3 call methods to encode graphics commands
//
//  rCE1 commands are processed first, because it was created first
//  even though rCE2 and rCE3 end earlier than rCE1
[rCE2 endEncoding];
[rCE3 endEncoding];
[rCE1 endEncoding];

//  all MTLRenderCommandEncoders must end before MTLParallelRenderCommandEncoder
[parallelRCE endEncoding];

各个从属的 command encoders 调用 endEncoding 的顺序和 encoder 中被编码的渲染指令被推入 MTLCommandBuffer 中执行的顺序不相干。一个 MTLParallelRenderCommandEncoder 对象包含着很多渲染指令(在其从属的 Encoder 中),这些指令的执行顺序和从属的 Encoder 被创建顺序相同。如图 5-6 所示。

图 5-6 在并行渲染 Pass 中渲染 Command Encoders 的执行顺序

results matching ""

    No results matching ""