Metal框架詳細解析(四十二) —— Metal編程指南之圖形渲染:渲染命令編碼器之Part 2(七)

版本記錄

版本號 時間
V1.0 2018.11.10 星期六

前言

很多做視頻和圖像的,相信對這個框架都不是很陌生,它渲染高級3D圖形,并使用GPU執行數據并行計算。接下來的幾篇我們就詳細的解析這個框架。感興趣的看下面幾篇文章。
1. Metal框架詳細解析(一)—— 基本概覽
2. Metal框架詳細解析(二) —— 器件和命令(一)
3. Metal框架詳細解析(三) —— 渲染簡單的2D三角形(一)
4. Metal框架詳細解析(四) —— 關于GPU Family 4(一)
5. Metal框架詳細解析(五) —— 關于GPU Family 4之關于Imageblocks(二)
6. Metal框架詳細解析(六) —— 關于GPU Family 4之關于Tile Shading(三)
7. Metal框架詳細解析(七) —— 關于GPU Family 4之關于光柵順序組(四)
8. Metal框架詳細解析(八) —— 關于GPU Family 4之關于增強的MSAA和Imageblock采樣覆蓋控制(五)
9. Metal框架詳細解析(九) —— 關于GPU Family 4之關于線程組共享(六)
10. Metal框架詳細解析(十) —— 基本組件(一)
11. Metal框架詳細解析(十一) —— 基本組件之器件選擇 - 圖形渲染的器件選擇(二)
12. Metal框架詳細解析(十二) —— 基本組件之器件選擇 - 計算處理的設備選擇(三)
13. Metal框架詳細解析(十三) —— 計算處理(一)
14. Metal框架詳細解析(十四) —— 計算處理之你好,計算(二)
15. Metal框架詳細解析(十五) —— 計算處理之關于線程和線程組(三)
16. Metal框架詳細解析(十六) —— 計算處理之計算線程組和網格大小(四)
17. Metal框架詳細解析(十七) —— 工具、分析和調試(一)
18. Metal框架詳細解析(十八) —— 工具、分析和調試之Metal GPU Capture(二)
19. Metal框架詳細解析(十九) —— 工具、分析和調試之GPU活動監視器(三)
20. Metal框架詳細解析(二十) —— 工具、分析和調試之關于Metal著色語言文件名擴展名、使用Metal的命令行工具構建庫和標記Metal對象和命令(四)
21. Metal框架詳細解析(二十一) —— 基本課程之基本緩沖區(一)
22. Metal框架詳細解析(二十二) —— 基本課程之基本紋理(二)
23. Metal框架詳細解析(二十三) —— 基本課程之CPU和GPU同步(三)
24. Metal框架詳細解析(二十四) —— 基本課程之參數緩沖 - 基本參數緩沖(四)
25. Metal框架詳細解析(二十五) —— 基本課程之參數緩沖 - 帶有數組和資源堆的參數緩沖區(五)
26. Metal框架詳細解析(二十六) —— 基本課程之參數緩沖 - 具有GPU編碼的參數緩沖區(六)
27. Metal框架詳細解析(二十七) —— 高級技術之圖層選擇的反射(一)
28. Metal框架詳細解析(二十八) —— 高級技術之使用專用函數的LOD(一)
29. Metal框架詳細解析(二十九) —— 高級技術之具有參數緩沖區的動態地形(一)
30. Metal框架詳細解析(三十) —— 延遲照明(一)
31. Metal框架詳細解析(三十一) —— 在視圖中混合Metal和OpenGL渲染(一)
32. Metal框架詳細解析(三十二) —— Metal渲染管道教程(一)
33. Metal框架詳細解析(三十三) —— Metal渲染管道教程(二)
34. Metal框架詳細解析(三十四) —— Hello Metal! 一個簡單的三角形的實現(一)
35. Metal框架詳細解析(三十五) —— Hello Metal! 一個簡單的三角形的實現(二)
36. Metal框架詳細解析(三十六) —— Metal編程指南之概覽(一)
37. Metal框架詳細解析(三十七) —— Metal編程指南之基本Metal概念(二)
38. Metal框架詳細解析(三十八) —— Metal編程指南之命令組織和執行模型(三)
39. Metal框架詳細解析(三十九) —— Metal編程指南之資源對象:緩沖區和紋理(四)
40. Metal框架詳細解析(四十) —— Metal編程指南之函數和庫(五)
41. Metal框架詳細解析(四十一) —— Metal編程指南之圖形渲染:渲染命令編碼器之Part 1(六)

Specifying Resources for a Render Command Encoder - 為渲染命令編碼器指定資源

本節中討論的MTLRenderCommandEncoder方法指定用作頂點和片段著色器函數的參數的資源,這些函數由MTLRenderPipelineState對象中的vertexFunctionfragmentFunction屬性指定。 這些方法將著色器資源(緩沖區,紋理和采樣器)分配給渲染命令編碼器中對應的參數表索引(atIndex),如圖5-3所示。

Figure 5-3 Argument Tables for the Render Command Encoder

以下setVertex *方法將一個或多個資源分配給頂點著色器函數的相應參數。

這些setFragment *方法類似地將一個或多個資源分配給片段著色器函數的相應參數。

緩沖區參數表中最多有31個條目,紋理參數表中有31個條目,采樣器狀態參數表中有16個條目。

在Metal著色語言源代碼中指定資源位置的屬性限定符必須與Metal框架方法中的參數表索引匹配。 在Listing 5-7中,為頂點著色器定義了兩個分別為索引0和1的緩沖區(posBuftexCoordBuf

Listing 5-7  Metal Framework: Specifying Resources for a Vertex Function

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

Listing 5-8中,函數簽名具有與屬性限定符buffer(0)buffer(1)相對應的參數。

Listing 5-8  Metal Shading Language: Vertex Function Arguments Match the Framework Argument Table Indices

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

類似地,在Listing 5-9中,為片段著色器定義了一個緩沖區,一個紋理和一個采樣器(分別為fragmentColorBufshadeTexsampler),它們都具有索引0。

Listing 5-9  Metal Framework: Specifying Resources for a Fragment Function

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

Listing 5-10中,函數簽名分別具有屬性限定符buffer(0)texture(0)sampler(0)的對應參數。

Listing 5-10  Metal Shading Language: Fragment Function Arguments Match the Framework Argument Table Indices

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

1. Vertex Descriptor for Data Organization - 數據組織的頂點描述符

在Metal框架代碼中,每個管道狀態可以有一個MTLVertexDescriptor,用于描述輸入到頂點著色器函數的數據的組織,并在著色語言和框架代碼之間共享資源位置信息。

在Metal著色語言代碼中,每個頂點輸入(例如標量或整數或浮點值向量)可以組織在一個結構中,該結構可以在一個使用[[stage_in]]屬性限定符聲明的參數中傳遞 ,如Listing 5-11中示例頂點函數vertexMathVertexInput結構中所示。 每頂點輸入結構的每個字段都有[[attribute(index)]]限定符,它指定頂點屬性參數表中的索引。

Listing 5-11  Metal Shading Language: Vertex Function Inputs with Attribute Indices

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對象,然后將其設置為MTLRenderPipelineStatevertexDescriptor屬性。 MTLVertexDescriptor有兩個屬性:attributeslayouts

MTLVertexDescriptorattributes屬性是一個MTLVertexAttributeDescriptorArray對象,它定義每個頂點屬性在映射到頂點函數參數的緩沖區中的組織方式。 attributes屬性可以支持訪問在同一緩沖區中交錯的多個屬性(例如頂點坐標,曲面法線和紋理坐標)。著色語言代碼中成員的順序不必保留在框架代碼的緩沖區中。數組中的每個頂點屬性描述符都具有以下屬性,這些屬性提供頂點著色器函數信息以定位和加載參數數據:

  • bufferIndex,它是緩沖區參數表的索引,用于指定訪問哪個MTLBuffer。在 Specifying Resources for a Render Command Encoder中討論了緩沖區參數表。
  • format,指定如何在框架代碼中解釋數據。如果數據類型不是精確類型匹配,則可以轉換或擴展它。例如,如果著色語言類型為half4且框架格式為MTLVertexFormatFloat2,那么當數據用作頂點函數的參數時,它可以從float轉換為一半并從兩個元素擴展為四個元素(使用0.0,最后兩個元素中的1.0)。
  • offset,指定從頂點的開頭可以找到數據的位置。

圖5-4說明了Metal框架代碼中的MTLVertexAttributeDescriptorArray,它實現了一個交錯緩沖區,該緩沖區對應于Listing 5-11中著色語言代碼中頂點函數vertexMath的輸入。

Figure 5-4 Buffer Organization with Vertex Attribute Descriptors

Listing 5-12顯示了與圖5-4中所示的交錯緩沖區相對應的Metal框架代碼。

Listing 5-12  Metal Framework: Using a Vertex Descriptor to Access Interleaved Data

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指定在參數表中使用索引0處的緩沖區。 (在此示例中,每個MTLVertexAttributeDescriptor具有相同的bufferIndex,因此每個引用參數表中索引0處的相同頂點緩沖區。)。offset指定頂點內數據的位置,因此attributes[1].offset = 2 * sizeof(float)定位從緩沖區起始處開始的相應數據的8個字節。選擇format值以匹配著色器函數中的數據類型,因此attributes[1].format = MTLVertexFormatFloat4指定使用四個浮點值。

MTLVertexDescriptorlayouts屬性是MTLVertexBufferLayoutDescriptorArray。對于layouts中的每個MTLVertexBufferLayoutDescriptor,屬性指定在Metal繪制基元時如何從參數表中的相應MTLBuffer獲取頂點和屬性數據。 (有關繪制圖元的更多信息,請參見Drawing Geometric Primitives。)。MTLVertexBufferLayoutDescriptorstepFunction屬性確定是為每個頂點,某些實例獲取屬性數據,還是僅獲取一次。如果將stepFunction設置為獲取某些實例的屬性數據,則MTLVertexBufferLayoutDescriptorstepRate屬性將確定實例數。 stride屬性指定兩個頂點的數據之間的距離(以字節為單位)。

圖5-5描述了MTLVertexBufferLayoutDescriptor,它對應于 Listing 5-12中的代碼。 layouts [0]指定如何從緩沖區參數表中的相應索引0獲取頂點數據。 layouts [0] .stride指定兩個頂點的數據之間的距離為40個字節。 layouts [0] .stepFunctionMTLVertexStepFunctionPerVertex的值指定在繪制時為每個頂點提取屬性數據。如果stepFunction的值為MTLVertexStepFunctionPerInstance,則stepRate屬性確定獲取屬性數據的頻率。例如,如果stepRate為1,則為每個實例提取數據;如果stepRate為2,則每兩個實例,依此類推。

Figure 5-5 Buffer Organization with Vertex Buffer Layout Descriptors

Performing Fixed-Function Render Command Encoder Operations - 執行固定功能渲染命令編碼器操作

使用這些MTLRenderCommandEncoder方法設置固定功能圖形狀態值:

使用以下MTLRenderCommandEncoder方法對固定函數狀態更改命令進行編碼:

您可以使用此模式執行遮擋測試。如果繪制邊界框并且沒有樣本通過,則可以得出結論,該邊界框內的任何對象都被遮擋,因此不需要渲染。

1. Working with Viewport and Pixel Coordinate Systems - 使用Viewport和像素坐標系

Metal將其標準化設備坐標(Normalized Device Coordinate - NDC)系統定義為2x2x1立方體,其中心位于(0,0,0.5)。 NDC系統的x和y的左側和底部分別指定為-1。 NDC系統的x和y的右側和頂部分別指定為+1。

視口指定從NDC到窗口坐標的轉換。 Metal視口是由MTLRenderCommandEncodersetViewport:方法指定的3D轉換。 窗口坐標的原點位于左上角。

在Metal中,像素中心偏移(0.5,0.5)。 例如,原點處的像素的中心位于(0.5,0.5); 右邊相鄰像素的中心是(1.5,0.5)。 紋理也是如此。

2. Performing Depth and Stencil Operations - 執行深度和模板操作

深度和模板操作是您指定的片段操作,如下所示:

如果啟用了深度測試,則渲染管道狀態必須包含深度附件以支持寫入深度值。要執行模板測試,渲染管道狀態必須包含模板附件。要配置附件,請參閱Creating and Configuring a Render Pipeline Descriptor

如果要定期更改深度/模板狀態,則可能需要重用狀態描述符對象,根據需要修改其屬性值以創建更多狀態對象。

注意:要從著色器函數中的深度格式紋理進行采樣,請在著色器中實施采樣操作,而不使用MTLSamplerState

使用MTLDepthStencilDescriptor對象的屬性,如下所示設置深度和模板狀態:

  • 要將深度值寫入深度附件,請將depthWriteEnabled設置為YES
  • depthCompareFunction指定深度測試的執行方式。如果片段的深度值未通過深度測試,則丟棄片段。例如,常用的MTLCompareFunctionLess函數導致比(先前寫入的)像素深度值更遠離觀察者的片段值無法進行深度測試;也就是說,片段被較早的深度值視為被遮擋。
  • frontFaceStencilbackFaceStencil屬性均為前向和后向基元指定單獨的MTLStencilDescriptor對象。要對前置和后置基元使用相同的模板狀態,可以為frontFaceStencilbackFaceStencil屬性分配相同的MTLStencilDescriptor。要顯式禁用一個或兩個面的模板測試,請將相應的屬性設置為nil(默認值)。

不必顯式禁用模板狀態。 Metal根據是否為有效的模板操作配置模板描述符來確定是否啟用模板測試。

Listing 5-13顯示了創建和使用MTLDepthStencilDescriptor對象以創建MTLDepthStencilState對象的示例,該對象隨后與渲染命令編碼器一起使用。 在此示例中,從深度/模板狀態描述符的frontFaceStencil屬性訪問前置基元的模板狀態。 對于背面基元,顯式禁用模板測試

Listing 5-13  Creating and Using a Depth/Stencil 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是一個位掩碼;GPU使用模板參考值和存儲的模板值計算此掩碼的按位AND。模板測試是在得到的掩碼參考值和掩碼存儲值之間的比較。
  • writeMask是一個位掩碼,用于限制模板操作將哪些模板值寫入模板附件。
  • stencilCompareFunction指定如何對片段執行模板測試。在Listing 5-13中,模板比較函數是MTLCompareFunctionEqual,因此如果掩碼的參考值等于已經存儲在片段位置的掩碼模板值,則模板測試通過。
  • stencilFailureOperation, depthFailureOperation
    depthStencilPassOperation指定如何對模板附件中存儲的模板值進行三種不同的測試結果:如果模板測試失敗,如果模板測試通過,如果深度測試失敗,或者如果模板和深度測試分別成功 。在前面的示例中,如果模板測試失敗,則模板值不變(MTLStencilOperationKeep),但如果模板測試通過則模板值會增加,除非模板值已經是最大可能值(MTLStencilOperationIncrementClamp)。

Drawing Geometric Primitives - 繪制幾何圖元

建立管道狀態和固定功能狀態后,可以調用以下MTLRenderCommandEncoder方法來繪制幾何圖元。這些繪制方法引用資源(例如包含頂點坐標,紋理坐標,曲面法線和其他數據的緩沖區)來執行具有著色器函數的管道以及先前使用 MTLRenderCommandEncoder建立的其他狀態。

對于上面列出的每個基本渲染方法,第一個輸入值使用MTLPrimitiveType值之一確定基元類型。其他輸入值確定用于組合基元的頂點。對于所有這些方法,instanceStart輸入值確定要繪制的第一個實例,而instanceCount輸入值確定要繪制的實例數。

如前所述,setTriangleFillMode:確定三角形是渲染為填充還是線框,setCullMode:setFrontFacingWinding:設置確定GPU在渲染過程中是否剔除三角形。有關更多信息,請參閱Fixed-Function State Operations)。

渲染點基元時,頂點函數的著色器語言代碼必須提供[[point_size]]屬性,或者點大小未定義。

渲染具有平面著色的三角形圖元時,第一個頂點(也稱為激發頂點)的屬性用于整個三角形。頂點函數的著色器語言代碼必須提供[[flat]]插值限定符。

有關所有Metal著色語言屬性和限定符的詳細信息,請參見Metal Shading Language Guide


Ending a Rendering Pass - 結束渲染過程

要終止渲染過程,請在渲染命令編碼器上調用endEncoding。 在結束上一個命令編碼器之后,您可以創建任何類型的新命令編碼器,以將其他命令編碼到命令緩沖區中。


Code Example: Drawing a Triangle - 代碼示例:繪制三角形

Listing 5-14中所示的以下步驟描述了渲染三角形的基本過程。

在此示例中,僅設置和使用第一個顏色附件。 (假設變量currentTexture包含用于顏色附件的MTLTexture。)然后MTLRenderPassDescriptor用于創建新的MTLRenderCommandEncoder

setVertexBuffer:offset:atIndex:方法的atIndex輸入值對應于頂點函數的源代碼中的屬性緩沖區(atIndex)

Listing 5-14  Metal Code for Drawing a Triangle

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];

Listing 5-14中,MTLFunction對象表示名為hello_vertex的著色器函數。MTLRenderCommandEncoder的方法setVertexBuffer:offset:atIndex:用于指定作為參數傳遞給 hello_vertex的頂點資源(在本例中為兩個緩沖區對象)。setVertexBuffer:offset:atIndex:方法的atIndex輸入值對應頂點函數源代碼中的屬性 buffer(atIndex),如Listing 5-15所示。

Listing 5-15  Corresponding Shader Function Declaration

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

Encoding a Single Rendering Pass Using Multiple Threads - 使用多個線程編碼單個渲染通道

在某些情況下,單個渲染過程的編碼命令的單CPU工作負載可能會限制應用程序的性能。但是,嘗試通過將工作負載分成多個CPU線程上編碼的多個渲染通道來繞過此瓶頸也會對性能產生負面影響,因為每個渲染過程都需要其自己的中間附件存儲和加載操作來保留渲染目標內容。

而是使用MTLParallelRenderCommandEncoder對象,該對象管理共享相同命令緩沖區和渲染傳遞描述符的多個從屬MTLRenderCommandEncoder對象。并行渲染命令編碼器確保附件加載和存儲操作僅在整個渲染過程的開始和結束時發生,而不是在每個從屬渲染命令編碼器的命令集的開始和結束時發生。使用此體系結構,您可以以安全且高性能的方式并行地將每個MTLRenderCommandEncoder對象分配給其自己的線程。

要創建并行渲染命令編碼器,請使用MTLCommandBuffer對象的parallelRenderCommandEncoderWithDescriptor:方法。要創建從屬渲染命令編碼器,請為要執行命令編碼的每個CPU線程調用一次MTLParallelRenderCommandEncoder對象的renderCommandEncoder方法。從同一并行渲染命令編碼器創建的所有從屬命令編碼器將命令編碼到同一命令緩沖區。命令按照創建渲染命令編碼器的順序編碼到命令緩沖區。要結束特定渲染命令編碼器的編碼,請調用MTLRenderCommandEncoderendEncoding方法。在并行渲染命令編碼器創建的所有渲染命令編碼器上結束編碼后,調用MTLParallelRenderCommandEncoderendEncoding方法以結束渲染過程。

Listing 5-16顯示MTLParallelRenderCommandEncoder創建三個MTLRenderCommandEncoder對象:rCE1,rCE2和rCE3

Listing 5-16  A Parallel Rendering Encoder with Three Render Command Encoders

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];

命令編碼器調用endEncoding的順序與命令編碼和附加到MTLCommandBuffer的順序無關。 對于MTLParallelRenderCommandEncoderMTLCommandBuffer始終按照創建從屬渲染命令編碼器的順序包含命令,如圖5-6所示。

Figure 5-6 Ordering of Render Command Encoders in a Parallel Rendering Pass

后記

本篇主要講述了圖形渲染:渲染命令編碼器,感興趣的給個贊或者關注~~~

?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容