本章描述了如何创建和使用 MTLRenderCommandEncoder 和 MTLParallelRenderCommandEncoder ,使用它们将graphic render command encode到一个command buffer中。 MTLRenderCommandEncoder describe一个graphic render pipeline,如下图:
MTLRenderCommandEncoder 提供一个render command encoder。MTLParallelRenderCommandEncoder 将一个render pass分成若干个独立的 MTLRenderCommandEncoder,每个都可以被分配到不同的thread。然后不同render command encoder中的command 会被以一致的、可预测的顺序连接在一起并执行。
下面是创建、初始化、使用一个render command encoder的步骤
一个 MTLRenderPassDescriptor 通过一组 attachment 设置 encoded render command 的 rt。render pass descriptor可以包含一组最多4个 color attachment,一个depth attachment,一个 stencil attachment。 renderPassDescriptor 是一个 convenience method 用于创建一个 MTLRenderPassDescriptor object,将 color、depth、stencil attachment设置为默认的attachment state。visibilityResultBuffer 属性指定一个buffer用于device 存放 any samples 通过 depth/stencil test。
每个 attachment,包含即将写入信息的 texture,由一个 attachment descriptor 表现。attachment descriptor 对应的 texture 的 pixel format 必须适合存储 color、depth、stencil。MTLRenderPassColorAttachmentDescriptor 使用一个 color-renderable pixel format。MTLRenderPassDepthAttachmentDescriptor 使用一个 depth-renderable pixel format,比如 MTLPixelFormatDepth32Float。 MTLRenderPassStencilAttachmentDescriptor 使用一个 stencil-renderable pixel format,比如 MTLPixelFormatStencil8
texture中每个像素在device上的内存,并非always match metal framework code 中 texture pixel format对应的尺寸,因为device会添加一些 padding for alignment 或者其它用处。 Metal Feature Set Tables 中介绍了每个pixel format实际使用的内存,以及attachment的数量和尺寸限制
Load and Store Actionsattachment 的 loadAction 和 storeAction 属性指定 render pass 起始处和结尾处的 action(MTLParallelRenderCommandEncoder 的话,load and store action 发生在整个命令的边界处,而非每个 MTLRenderCommandEncoder object)
loadAction 值包括
如果在某一帧将会在attachment上写入所有像素,则使用默认load action MTLLoadActionDontCare。MTLLoadActionDontCare 避免了GPU去加载 texture中的现有内容,确保最佳性能。其它情况,可以使用 MTLLoadActionClear 来将attachment之前的内容全部晴空,或者使用 MTLLoadActionLoad 将它们保留下来。 MTLLoadActionClear 也避免了加载texture的现有内容,但是会产生使用纯色填充的消耗。
storeAction 值包括
color attachment中,MTLStoreActionStore为默认操作,因为应用程序大多会在render pass结束的时候讲attachment 的最终颜色保存下来。depth/stencil attachment, MTLStoreActionDontCare 为默认值,因为这些attachment在render pass结束的时候,基本上不需要保存内容
Specifying the Clear Load Action如果attachment descriptor的 loadAction 被设置成了 MTLLoadActionClear,则在render pass开始的时候讲一个clear值写入attachment的所有像素。clear值依赖attachment的类型
下面代码创建了一个 render pass descriptor,包含color和depth attachment。首先,创建2个texture,一个使用color-renderable pixel format,另外一个使用depth pixel format。然后使用 MTLRenderPassDescriptor 的 renderPassDescriptor convenience method 创建一个默认的render pass descriptor。然后通过 MTLRenderPassDescriptor 的属性可以访问 color 和 depth attachment。colorattachment[0]包含第一个color attachment对应的texture 和 action,depthAttachment 包含 depth attachment对应的texture 和 action
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: Creating a Render Pass Descriptor for Multisampled Rendering
如果要使用 MTLStoreActionMultisampleResolve ,必须将 texture 设置为一个multisample-type texture,resolveTexture 将包含 multisample resolve后的结果。(如果texture不支持multisample,则multisample resolve action后的结果为未定义)。resolveLevel, resolveSlice, and resolveDepthPlane 将被用于 multisample resolve 操作来指定 multisample texture 的 mipmap level、cube slice、depth plane。大多数情况下,这三个属性将使用默认值。下面代码讲创建一个attachment,然后它的loadAction、storeAction、texture、resolveTexture 属性豆浆支持multisample 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 descritptor并设置好其属性之后,使用MTLCommandBuffer 的 renderCommandEncoderWithDescriptor 来创建 render command encoder。参见下面代码
id <.MTLRenderCommandEncoder> renderCE = [commandBuffer renderCommandEncoderWithDescriptor:renderPassDesc];
Core Animation 定义了 CAMetalLayer Class,用于layer-backed view,content由metal渲染。 CAMetalLayer 展现geometry of content(位置和尺寸)、visual attribute(background color、border、shadow)以及展现 content 的 color attachment resource used by metal。还封装了timeing of content presentation,所以内容将在特定的时间被立即显示。
Core Animation 还定义了 CAMetalDrawable protocol 用于显示。 CAMetalDrawable 继承自 MTLDrawable ,并包含一个 MTLTexture protocol 的 object,所以可以被用作 render command 的destination。 为了渲染到一个 CAMetalLayer object,必须为每个render pass获取一个新的 CAMetalDrawable ,获取到它提供的 MTLTexture,使用这个texture 创建color attachment。和color attachment不同,创建和小会一个depth/stencil attachment 都非常耗。如果需要depth/stencil attachment,创建它们一次,然后在每帧中重用它们。
如下面代码所示,你可以通过 layerclass 方法来设置 CAMetalLayer 作为 custom UIView subclass 的backing layer type。否则,可以使用init方法创建一个 CAMetalLayer ,并将其放在一个已经存在的view中
+ (id) layerClass { return [CAMetalLayer class]; }
为了显示layer中metal渲染的content,必须从 CAMetalLayer 获取一个 displayable resource(一个 CAMetalDrawable object),然后把它attch到 MTLRenderPassDescriptor上,然后渲染到其texture上。想要完成上述操作,需要首先设置 CAMetalLayer 的属性,获取它提供的 drawable resource,然后在开始新的一帧渲染的时候,调用它的 nextDrawable 方法。如果 CAMetalLayer 的属性没有设置,则 nextDrawable 方法会返回fails。 CAMetalLayer 的下列属性描述 drawable object
如果执行 nextDrawable 方法成功,则会返回一个包含下列只读属性的 CAMetalDrawable
注意:由于 drawable resource非常有限,所以长时间的帧渲染可能会导致 nextDrawable 方法block CPU thread直到这个方法结束。所以为了避免昂贵的 CPU stall,先执行当前帧所有不需要 drawable resource 的操作,然后再调用 CAMetalLayer object的 nextDrawable 方法。
在渲染结束后,为了显示drawable 的content,你必须通过drawable object 的 present 方法,将其submit到Core Animation。为了同步drawable的现实和command buffer的渲染完成,可以通过 MTLCommandBuffer 的 presentDrawable 和 presentDrawable:atTime: convenience method。这些method使用scheduled handler去调用drawable 的 present 方法,覆盖了大部分 scenarios。presentDrawable:atTime: 方法提供对何时显示drawable的进一步控制。
为了使用 MTLRenderCommandEncoder 去 encode render command,你必须首先指定一个 MTLRenderPipelineState object来指定 dc中的graphics state。render pipeline state是long-lived persistent object,可以created outside render command encoder,cached,然后在多个render command encoders中重复使用。当要使用一个相同的graphics state,重复使用一个之前创建的pipeline state object可以避免昂贵的操作去re-evaluate and translate the specified state to GPU commands
render pipeline state是一个immutable object。为了创建render pipeline state,必须先创建和配置一个mutable MTLRenderPipelineDescriptor object描述 render pipeline state的属性。然后使用该descriptor去创建一个 MTLRenderPipelineState object
为了创建一个 render pipeline state,首先需要创建一个 MTLRenderPipelineDescriptor object,包含描述render pass中用到的 graphics render pipeline state所需要的属性,详见下图。MTLRenderPipelineDescriptor 的 colorAttachments 属性 包含一个 MTLRenderPipelineColorAttachmentDescriptor 数组,每个都代表一个 color attachment state指定该attachment 的 blend operation/blend factor。attachment descriptor 还对应 attachment 的pixel format,必须与render pipeline descriptor 对应 index 的 attachment 的 texture 的pixel format对应,否则就会出现error
配置 color attachment 的时候,需要设置 MTLRenderPipelineDescriptor的如下参数
创建了 render pipeline descriptor 并设置了它的属性后,使用它去创建 MTLRenderPipelineState object。由于创建一个 render pipeline state会导致昂贵的graphics state 的 evaluation 并可能产生指定 graphics shader 的编译。建议使用block 或者一个异步方法来执行这种工作来获取最好的性能
创建 MTLRenderPipelineState 的时候可以选择同时创建 reflection data 展示 pipeline shader function 的detail 和argument。对应的方法是 MTLDevice 的 newRenderPipelineStateWithDescriptor:options:reflection:error: 和 newRenderPipelineStateWithDescriptor:options:completionHandler: 。如果不用的话,尽量不要生成 reflection data。
创建了 MTLRenderPipelineState 后,调用 MTLRenderCommandEncoder 的 setRenderPipelineState: 方法去对 command encoder 设置 render pipelinbe state 用于渲染。
下面代码创建了一个 render pipeline state object 叫做 pipeline
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];
vertFunc 和 fragFunc 是 shader functions,作为 render pipeline state descriptor renderPipelineDesc 的属性。调用 MTLDevice 的 newRenderPipelineStateWithDescriptor:error: 方法同步的使用pipeline state descriptor 去创建 render pipeline state object。调用 MTLRenderCommandEncoder 的 setRenderPipelineState: 方法来指定 MTLRenderPipelineState object 用于 render command encoder。
注意:MTLRenderPipeline object 创建起来很昂贵,所以当你需要用相同的graphics state 的时候重用它。
blend 使用一个高度配置化的 blend operation来混合 fragment function 的输出(source)和attachment 上的pixel value(destination)。blend operation 决定source 和 destination 如何根据blend factor 混合
通过设置 MTLRenderPipelineColorAttachmentDescriptor 的属性,来配置 color attachment 的 blend
设置 blendingEnabled 属性为 YES 来开启 blend。默认为关闭
writeMask 属性指定哪些color channel 被 blend。默认值 MTLColorWriteMaskAll 允许所有 color channel被 blend
rgbBlendOperation and alphaBlendOperation 分别用 MTLBlendOperation 值设置 fragment data 中 RGB 和 A 的blend operation,默认情况下两者均为 MTLBlendOperationAdd
sourceRGBBlendFactor, sourceAlphaBlendFactor, destinationRGBBlendFactor, and destinationAlphaBlendFactor 设置 source 和 destination 的 blend factor
Understanding Blending Factors and Operations有四个blend factor 是blend color 常数值:MTLBlendFactorBlendColor, MTLBlendFactorOneMinusBlendColor, MTLBlendFactorBlendAlpha, and MTLBlendFactorOneMinusBlendAlpha。调用 MTLRenderCommandEncoder 的 setBlendColorRed:green:blue:alpha: 方法来指定这些 blend factor 将要用到的 color 和 alpha 值。
一些 blend operation 将 fragment value 乘以 source MTLBlendFactor(缩写为 SBF),将 destination value 乘以 destination blend factor(DBF),然后使用 MTLBlendOperation 值指示的算术组合来计算。(如果 blend operation 是 MTLBlendOperationMin or MTLBlendOperationMax,则 SBF 和 DBF blend factor 将被忽略)。比如 rgbBlendOperation 和 alphaBlendOperation 都为 MTLBlendOperationAdd 的时候执行下列操作
默认的 blend 操作为 source 完全覆盖 destination。这个操作相当于设置 sourceRGBBlendFactor and sourceAlphaBlendFactor 为 MTLBlendFactorOne,destinationRGBBlendFactor and destinationAlphaBlendFactor 设置为 MTLBlendFactorZero。用公式来描述是:
还有一个常见的 blend operation,用source alpha 来决定保留多少 destination color,用公式来描述是:
下列代码描述了一个自定义的blend 配置,使用 MTLBlendOperationAdd,source blend factor 为 MTLBlendFactorOne , destination blend factor 为 MTLBlendFactorOneMinusSourceAlpha 。colorAttachments[0]是使用了上述 blend 配置的 MTLRenderPipelineColorAttachmentDescriptor object
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];
本节中介绍的 MTLRenderCommandEncoder 方法用于指定vs和ps中使用的 argument。vs和ps由 MTLRenderPipelineState 的 vertexFunction and fragmentFunction 属性指定。如下图所示,这些方法将 shader resource (buffer、texture、samplers)对应 render command encoder 的 argument table index(atIndex)
下列的 setVertex* 函数将一个或者多个 resource 对应到 vs function 对应的argument
下列的 setFragment* 函数将一个或者多个 resource 对应到 ps function 对应的argument
buffer argument table 的上限是31,texture argument table 的上限是31,sampler state argument table 的上限是16
metal shading language source code 中用于指定 resource location 的 attribute qualifier 必须和metal framework method 中的 argument table indices对应。下面代码为 vs 定义了2个buffer(posBuf和texCoordBuffer),index 为 0 和 1。
[renderEnc setVertexBuffer:posBuf offset:0 atIndex:0]; [renderEnc setVertexBuffer:texCoordBuf offset:0 atIndex:1];
下面代码的function signature 使用 attribute qualifiers buffer(0)和buffer(1)对应arguments
vertex VertexOutput metal_vert(float4 *posData [[ buffer(0) ]], float2 *texCoordData [[ buffer(1) ]])
类似的,下面代码为 ps 创建了一个buffer、texture、sampler(fragmentColorBuf、shadeTex、sampler),index 为 0
[renderEnc setFragmentBuffer:fragmentColorBuf offset:0 atIndex:0]; [renderEnc setFragmentTexture:shadeTex atIndex:0]; [renderEnc setFragmentSamplerState:sampler atIndex:0];
下面代码的function signature 使用 attribute qualifiers buffer(0)、texture(0)和sampler(0)对应arguments
fragment float4 metal_frag(VertexOutput in [[stage_in]], float4 *fragColorData [[ buffer(0) ]], texture2d<.float> shadeTexValues [[ texture(0) ]], sampler samplerValues [[ sampler(0) ]] )
metal framework code 中,可以只有一个 MTLVertexDescriptor 用于所有的 pipeline state,用于描述穿入 vertex shader function 的数据,以及 shading language 和 frameworke code 之间共享的 resource location information
在 metal shading language code 中,per- vertex input(比如 int/float-point 类型的 scalar、vector)可以被组织放入一个结构体,这样可以通过一个拥有 stage_in attribute qualifier 的 argument 声明,比如下面例子中 vertexMath 函数 的 VertexInput 结构体。该结构体的每个值都有一个 qualifier attribute(index),用于对应 vertex attribute argument table 中的 index
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 qualifier 的 shader function input,需要定义一个 MTLVertexDescriptor object,然后将其设置为 MTLRenderPipelineState 的 vertexDescriptor 属性。 MTLVertexDescriptor 有2个属性: attribute 和 layouts
MTLVertexDescriptor 的 attribute 属性是一个 MTLVertexAttributeDescriptorArray object 定义了vertex attribute 的每个属性是如何在buffer中组织,并映射到 vertex function argument 中。attribute 属性支持将多个 attribute(比如 vertex coordinate、surface normal、texture coordinate)放在同一个buffer中。shading language 中的顺序不一定需要按照 framework code 的buffer中的顺序。每个 vertex attribute descirptor都具备以下属性,用于提供vs function information 去 定位 和 load argument data。
下面展示了 Metal framework code 中的 MTLVertexAttributeDescriptorArray 实现了一个 buffer 对应 shading language vs vertexMath 的输入
下面代码为对应上述图片的 metal framework code
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 对应 shader function 中的 VertexInput 结构体。attributes[1].bufferIndex = 0 表示使用 argument table 中 index 0 的buffer(这个例子中,每个 MTLVertexAttributeDescriptor 都有一个相同的 bufferIndex,所以全部都对应相同的 vertex buffer,也就是argument table中index 0的buffer。)offset 指定数据的偏移,所以attributes[1].offset = 2 * sizeof(float) 的位置是buffer偏移8 bytes后的数据。format 对应 shader function 中的 data type,所以 attributes[1].format = MTLVertexFormatFloat4 指定使用 4 float-point value
MTLVertexDescriptor 的 layouts 属性 为 MTLVertexBufferLayoutDescriptorArray。layouts 中的每个 MTLVertexBufferLayoutDescriptor 表明在Metal渲染primitive 的时候 vertex attribute data是如何从argument table中对应的MTLBuffer中获取。 MTLVertexBufferLayoutDescriptor 的 stepFunction 属性决定是针对每个顶点获取数据,还是一定数量instance,还是只活去一次。如果 stepFunction 被设置为以一定数量instance为单位活去数据,则 MTLVertexBufferLayoutDescriptor 的 stepRate 属性决定多少数量的instance 为单位。 stride 属性决定了两个 vertex的数据间距,以bytes为单位
下图定义了 MTLVertexBufferLayoutDescriptor 对应上面的代码,layouts[0] 定义了如何从 buffer argument table 中 index 0 的buffer 中获取数据。layouts[0].stride 表示 2个vertex 的数据间距为 40 bytes。layouts[0].stepFunction 为 MTLVertexStepFunctionPerVertex,表示在渲染的时候,数据以vertex为单位获取数据,如果 stepFunction 为 MTLVertexStepFunctionPerInstance,则 stepRate 属性用于决定多少个 instance 获取一次数据。比如 stepRate 为1,data按照每个instance 获取数据,如果 stepRate 为2,则2个instance获取一次数据。
使用 MTLRenderCommandEncode 的下列方法来设置 fixed-function graphics state value
使用 MTLRenderCommandEncoder 的下面方法去 encode fixed-function state change command
metal 定义了它的 Normalized Device Coordinate(NDC)系统为一个2*2*1的cube,中心点为(0,0,0.5)。左下角的x和y为-1,右上角的x和y为+1
viewport指定了NDC到window coordinates的变换矩阵。Metal viewport 是一个 3D transformation,由 MTLRenderCommandEncoder 的 setViewport: 方法定义。window coordinate 的原始点为左下角
在metal中,pixel 中心点会进行(0.5,0.5)的偏移。比如原始点像素的中心点为(0.5,0.5),其右侧相邻像素的中心点为(1.5,0.5)。这个也适用于texture
depth/stencil operation 是 fragment operation,以下面方式指定
如果开启 depth test,render pipeline state 必须包含一个 depth attachment 去支持 depth value 的写入。为了支持 stencil test,render pipeline state 必须包含一个 stencil attachment
如果想要改变depth/stencil state,可以重用 state descriptor object,修改它的值以创建更多的 state object
注意:在shader中采样一个 depth-format texture的时候,implement shader 中的 sample operation不使用 MTLSamplerState
MTLDepthStencilDescriptor 的如下属性设置 depth/stencil state
没有必要显式的关闭stencil state。metal决定是否开启stencil test是基于stencil descriptor 是否被配置了一个合法的stencil operation
下面代码展示了创建和使用一个 MTLDepthStencilDescriptor 并创建 MTLDepthStencilState object。然后被用于 render command encoder。在这个例子中,front-facing primitive 的stencil state是通过 depth/stencil state descriptor 的 frontFaceStencil property 设置。front-facing primitive 的stencil test 被显式关闭了
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 的下列属性定义了 stencil test
当 pipeline state 和 fixed-function state 都配置好后,可以调用 MTLRenderCommandEncoder 方法去渲染 geometric primitive。这些方法 reference resource(比如包含 vertex coordinate、texture coordinate、surface normal以及其他数据的 buffer)去执行之前定义在 MTLRenderCommandEncoder 中包含 shader function 和其他 state 的 pipeline
上述的所有渲染方法的第一个输入值,都通过一个 MTLPrimitiveType 值决定primitive type。其他输入值决定用哪些顶点来组成 primitive。所有这些方法,输入值 instanceStart 决定第一个渲染的 instance,instanceCount 决定渲染多少个 instance。
正如上述讨论的, setTriangleFillMode: 决定三角形是按照填充的方式渲染还是只渲染 wireframe, setCullMode: and setFrontFacingWinding: 决定渲染时如何进行 GPU cull triangle。
当渲染一个point primitive 的时候,vs的shader language coder必须包含 point_size attribute,否则point size 为未定义
当按照 flat shading 的方式渲染三角形的时候,第一个vertex (也被称为 provoking vertex)的属性将被用于整个三角形。vs的shader language code也必须包含 flat interpolation qualifier
更多 metal shading language attribute and qualifier,参考Metal Shading Language Guide.
结束一个render pass,需要调用 render command encoder 的 endEncoding 函数。当结束了之前 command encoder 后,可以创建一个新的任意类型的command encoder 去将更多的 command encode 到 command buffer 中
根据下列步骤合代码,描述一个渲染三角形的基本步骤
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];
上述代码中 ,MTLFunction 表示名为 hello_vertex 的shader function。MTLRenderCommandEncoder 的 setVertexBuffer:offset:atIndex: 函数用于指定 vertex resource(这个例子中是2个buffer),也作为argument传入 hello_vertex。setVertexBuffer:offset:atIndex: 的 atIndex 对应 vs source code 中的 attribute buffer(atIndex),下面为对应的shader code
vertex VertexOutput hello_vertex( const global float4 *pos_data [[ buffer(0) ]], const global float4 *color_data [[ buffer(1) ]]) { ... }
在一些情况下,app的性能可能会受限于一个 render pass 在单一CPU encode command 的 workload。如果拆成多个render pass encoded在多个 cpu thread可能也会对性能产生影响,因为每个 render pass 都需要设置 load/store action来保留 render target content
Instead,使用 MTLParallelRenderCommandEncoder 对应多个 subordinate MTLRenderCommandEncoder object,共享相同的 command buffer 和 render pass descriptor。parallel render command encoder 确保 attachment load and store action 之发生在整个render pass 的开始和结束处,并非每个 subordinate render command encoder 的开始和结束处。通过这个架构,可以将每个 MTLRenderCommandEncoder object 并行的放在自己的先生,得到一个安全高效的方案
使用 MTLCommandBuffer 的 parallelRenderCommandEncoderWithDescriptor: 方法来创建 parallel render command encoder。在每个cpu thread 调用MTLParallelRenderCommandEncoder 的 renderCommandEncoder 方法一次,去创建 subordinate render command encoder。同一个parallel render command encoder 创建的所有的 subordinate render command encoder 将 command encode 到同一个 command buffer 中。command encode到command buffer 的顺序,按照 render command encoder创建的顺序。调用 MTLRenderCommandEncoder 的 endEncoding 方法来结束 指定 render command encoder 的encoding。当parallel render command encoder对应的所有render command encoder 都encoding后,调用 MTLParallelRenderCommandEncoder 来结束 render pass
下面代码表示用 MTLParallelRenderCommandEncoder 创建3个 MTLRenderCommandEncoder object:rCE1,rCE2,rCE3
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 encoder 调用 endEncoding 的顺序与 command encoded和appended 到 MTLCommandBuffer 的顺序无关。MTLParallelRenderCommandEncoder中,MTLCommandBuffer 中的命令顺序只与 subordinate render command encoder 创建的顺序有关,如下图
本节教程就到此结束,希望大家继续阅读我之后的教程。
谢谢大家,再见!
原创技术文章,撰写不易,转载请注明出处:电子设备中的画家|王烁 于 2022 年 4 月 25 日发表,原文链接(http://geekfaner.com/shineengine/blog50_MetalProgrammingGuide_5.html)