Metal -- 如何處理大批量頂點數(shù)據(jù)以及加載圖片

一、首先我們要知道如何劃分批量數(shù)據(jù)范圍以及如何處理小批量的頂點數(shù)據(jù)
  • 蘋果的官方文檔中對數(shù)據(jù)有如下說明,當(dāng)數(shù)據(jù)量小于4KB的時候,我們可以稱為小批量數(shù)據(jù),當(dāng)頂點的數(shù)據(jù)量大于4KB時,數(shù)據(jù)量就很大了,我們可以叫大批量數(shù)據(jù)。主要區(qū)別在于蘋果對這兩種的數(shù)據(jù)量的處理方式不同。
  • 當(dāng)我們的頂點數(shù)據(jù)小于4KB時候,我們可以直接將頂點數(shù)據(jù)放在數(shù)組中,并使用- (void)setVertexBytes:(const void *)bytes length:(NSUInteger)length atIndex:(NSUInteger)index方法將數(shù)據(jù)傳遞到頂點著色器函數(shù)。
  • 當(dāng)頂點數(shù)據(jù)量大于4KB時候,我們可以使用一個叫MTLBuffer的對象,它能夠?qū)?shù)據(jù)存儲到頂點緩存區(qū)中,便于GPU對這些數(shù)據(jù)進(jìn)行快速的訪問處理。
二、如何使用Metal渲染一張圖片githubDemo地址
  • 思路:
    1.Render類設(shè)置頂點相關(guān)操作、設(shè)置渲染管道相關(guān)操作、加載紋理、- (void)drawInMTKView:(nonnull MTKView *)view代理方法進(jìn)行渲染;
    2.BaseShaderTypes.h文件來橋接OC和Metal方法;
    3.BaseShaders.metal設(shè)置Metal的頂點著色器函數(shù)和片元著色器函數(shù);
    4.BaseImage類通過加載一個簡單的TGA文件初始化這個圖像.只支持32bit的TGA文件;
  • BaseShaderTypes.h橋接文件的具體實現(xiàn),設(shè)置的結(jié)構(gòu)體以及枚舉即支持OC又支持Metal的調(diào)用,方法如下:

typedef enum BaseVertexInputIndex {
    
    //頂點
    BaseVertexInputIndexVertices   =0,
    
    //視圖大小
    BaseVertexInputIndexViewportSize=1,
    
}BaseVertexInputIndex;
//紋理索引
typedef enum BaseTextureIndex
{
    BaseTextureIndexBaseColor = 0
}BaseTextureIndex;

//結(jié)構(gòu)體
typedef struct {
    //像素空間位置
    vector_float2 position;
    
    //2D紋理
    vector_float2 textureCoordinate;
}BaseVertex;
  • Render類的實現(xiàn)邏輯

一、設(shè)置頂點相關(guān)操作:
1.根據(jù)頂點/紋理坐標(biāo)建立一個MTLBuffer;

static const BaseVertex quadVertices[] = {
        //像素坐標(biāo),紋理坐標(biāo)
        { {  250,  -250 },  { 1.f, 0.f } },
        { { -250,  -250 },  { 0.f, 0.f } },
        { { -250,   250 },  { 0.f, 1.f } },
        
        { {  250,  -250 },  { 1.f, 0.f } },
        { { -250,   250 },  { 0.f, 1.f } },
        { {  250,   250 },  { 1.f, 1.f } },
        
    };

2.創(chuàng)建我們的頂點緩沖區(qū),并用我們的Qualsits數(shù)組初始化它;

    _vertices = [_device newBufferWithBytes:quadVertices
                                     length:sizeof(quadVertices)
                                    options:MTLResourceStorageModeShared];

3.通過將字節(jié)長度除以每個頂點的大小來計算頂點的數(shù)目;

    _numVertices = sizeof(quadVertices) / sizeof(BaseVertex);

二、設(shè)置渲染管道相關(guān)操作:
1.創(chuàng)建我們的渲染通道;

//從項目中加載.metal文件,創(chuàng)建一個library
       id<MTLLibrary>defalutLibrary = [_device newDefaultLibrary];
       //從庫中加載頂點函數(shù)
       id<MTLFunction>vertexFunction = [defalutLibrary newFunctionWithName:@"vertexShader"];
       //從庫中加載片元函數(shù)
       id<MTLFunction> fragmentFunction = [defalutLibrary newFunctionWithName:@"fragmentShader"];

2.配置用于創(chuàng)建管道狀態(tài)的管道;

MTLRenderPipelineDescriptor *pipelineStateDescriptor = [[MTLRenderPipelineDescriptor alloc] init];
       //管道名稱
       pipelineStateDescriptor.label = @"Texturing Pipeline";
       //可編程函數(shù),用于處理渲染過程中的各個頂點
       pipelineStateDescriptor.vertexFunction = vertexFunction;
       //可編程函數(shù),用于處理渲染過程總的各個片段/片元
       pipelineStateDescriptor.fragmentFunction = fragmentFunction;
       //設(shè)置管道中存儲顏色數(shù)據(jù)的組件格式
       pipelineStateDescriptor.colorAttachments[0].pixelFormat = baseMTKView.colorPixelFormat;

3.同步創(chuàng)建并返回渲染管線對象;

 NSError *error = NULL;
    _pipeLineState = [_device newRenderPipelineStateWithDescriptor:pipelineStateDescriptor error:&error];
       //判斷是否創(chuàng)建成功
    if (!_pipeLineState)
       {
           NSLog(@"Failed to created pipeline state, error %@", error);
       }

4.使用_device創(chuàng)建commandQueue;

_commandQueue = [_device newCommandQueue];

三、加載紋理TGA文件
1.獲取tag的路徑

 NSURL *imageFileLocation = [[NSBundle mainBundle] URLForResource:@"Image"withExtension:@"tga"];
    //將tag文件->CCImage對象
    BaseImage *image = [[BaseImage alloc]initWithTGAFileAtLocation:imageFileLocation];
    //判斷圖片是否轉(zhuǎn)換成功
    if(!image)
    {
        NSLog(@"Failed to create the image from:%@",imageFileLocation.absoluteString);
        
    }

2.創(chuàng)建紋理描述對象

    MTLTextureDescriptor *textureDescriptor = [[MTLTextureDescriptor alloc]init];
    //表示每個像素有藍(lán)色,綠色,紅色和alpha通道.其中每個通道都是8位無符號歸一化的值.(即0映射成0,255映射成1);
    textureDescriptor.pixelFormat = MTLPixelFormatBGRA8Unorm;
    //設(shè)置紋理的像素尺寸
    textureDescriptor.width = image.width;
    textureDescriptor.height = image.height;
    //使用描述符從設(shè)備中創(chuàng)建紋理
    _texture = [_device newTextureWithDescriptor:textureDescriptor];
    //計算圖像每行的字節(jié)數(shù)
    NSUInteger bytesPerRow = 4 * image.width;

3.創(chuàng)建MTLRegion 結(jié)構(gòu)體

/*
     typedef struct
     {
     MTLOrigin origin; //開始位置x,y,z
     MTLSize   size; //尺寸width,height,depth
     } MTLRegion;
     */
    //MLRegion結(jié)構(gòu)用于標(biāo)識紋理的特定區(qū)域。 demo使用圖像數(shù)據(jù)填充整個紋理;因此,覆蓋整個紋理的像素區(qū)域等于紋理的尺寸。
    MTLRegion region = {
        {0,0,0},
        {image.width,image.height,1}
    };

4.復(fù)制圖片數(shù)據(jù)到texture

    [_texture replaceRegion:region mipmapLevel:0 withBytes:image.data.bytes bytesPerRow:bytesPerRow];

四、MTKViewDelegate代理方法實現(xiàn)
1.為當(dāng)前渲染的每個渲染傳遞創(chuàng)建一個新的命令緩沖區(qū);

 id<MTLCommandBuffer> commandBuffer = [_commandQueue commandBuffer];
    //指定緩存區(qū)名稱
    commandBuffer.label = @"MyCommand";

2.currentRenderPassDescriptor描述符包含currentDrawables的紋理、視圖的深度、模板和sample緩沖區(qū)和清晰的值。

 MTLRenderPassDescriptor *renderPassDescriptor = view.currentRenderPassDescriptor;

3.創(chuàng)建渲染命令編碼器,這樣我們才可以渲染到something;

 id<MTLRenderCommandEncoder> renderEncoder =
        [commandBuffer renderCommandEncoderWithDescriptor:renderPassDescriptor];
        //渲染器名稱
        renderEncoder.label = @"MyRenderEncoder";

4.設(shè)置我們繪制的可繪制區(qū)域;

 [renderEncoder setViewport:(MTLViewport){0.0, 0.0, _viewportSize.x, _viewportSize.y, -1.0, 1.0 }];

5.設(shè)置渲染管道;

        [renderEncoder setRenderPipelineState:_pipelineState];

6.加載數(shù)據(jù)(大批量數(shù)據(jù)存儲樣式);

//將數(shù)據(jù)加載到MTLBuffer --> 頂點函數(shù)
        [renderEncoder setVertexBuffer:_vertices
                                offset:0
                               atIndex:CCVertexInputIndexVertices];
        //將數(shù)據(jù)加載到MTLBuffer --> 頂點函數(shù)
        [renderEncoder setVertexBytes:&_viewportSize
                               length:sizeof(_viewportSize)
                              atIndex:CCVertexInputIndexViewportSize];

7.設(shè)置紋理對象;

[renderEncoder setFragmentTexture:_texture atIndex:CCTextureIndexBaseColor];

8.繪制;

        // @method drawPrimitives:vertexStart:vertexCount:
        //@brief 在不使用索引列表的情況下,繪制圖元
        //@param 繪制圖形組裝的基元類型
        //@param 從哪個位置數(shù)據(jù)開始繪制,一般為0
        //@param 每個圖元的頂點個數(shù),繪制的圖型頂點數(shù)量
        /*
         MTLPrimitiveTypePoint = 0, 點
         MTLPrimitiveTypeLine = 1, 線段
         MTLPrimitiveTypeLineStrip = 2, 線環(huán)
         MTLPrimitiveTypeTriangle = 3,  三角形
         MTLPrimitiveTypeTriangleStrip = 4, 三角型扇
         */
        [renderEncoder drawPrimitives:MTLPrimitiveTypeTriangle
                          vertexStart:0
                          vertexCount:_numVertices];

9.表示已該編碼器生成的命令都已完成,并且從MTLCommandBuffer中分離;

        [renderEncoder endEncoding];

10.一旦框架緩沖區(qū)完成,使用當(dāng)前可繪制的進(jìn)度表;

        [commandBuffer presentDrawable:view.currentDrawable];

11.最后,在這里完成渲染并將命令緩沖區(qū)推送到GPU;

    [commandBuffer commit];
  • BaseShaders.metal類的實現(xiàn)
    1.設(shè)置結(jié)構(gòu)體
//結(jié)構(gòu)體
typedef struct
{
    
    float4 clipSpacePosition [[position]];
    float2 textureCoordinate;
    
} RasterizerData;

2.設(shè)置頂點著色函數(shù)

vertex RasterizerData
vertexShader(uint vertexID [[vertex_id]],
             constant BaseVertex *vertexArray [[buffer(BaseVertexInputIndexVertices)]],
             constant vector_float2 *viewportSizePointer [[buffer(BaseVertexInputIndexViewportSize)]])
{
    /*
     處理頂點數(shù)據(jù):
     1) 執(zhí)行坐標(biāo)系轉(zhuǎn)換,將生成的頂點剪輯空間寫入到返回值中.
     2) 將頂點顏色值傳遞給返回值
     */
    
    //定義out
    RasterizerData out;
    
    //初始化輸出剪輯空間位置
    out.clipSpacePosition = vector_float4(0.0, 0.0, 0.0, 1.0);
    
    // 索引到我們的數(shù)組位置以獲得當(dāng)前頂點
    // 我們的位置是在像素維度中指定的.
    float2 pixelSpacePosition = vertexArray[vertexID].position.xy;
    
    //將vierportSizePointer 從verctor_uint2 轉(zhuǎn)換為vector_float2 類型
    vector_float2 viewportSize = vector_float2(*viewportSizePointer);
    
    //每個頂點著色器的輸出位置在剪輯空間中(也稱為歸一化設(shè)備坐標(biāo)空間,NDC),剪輯空間中的(-1,-1)表示視口的左下角,而(1,1)表示視口的右上角.
    //計算和寫入 XY值到我們的剪輯空間的位置.為了從像素空間中的位置轉(zhuǎn)換到剪輯空間的位置,我們將像素坐標(biāo)除以視口的大小的一半.
    out.clipSpacePosition.xy = pixelSpacePosition / (viewportSize / 2.0);
    
    out.clipSpacePosition.z = 0.0f;
    out.clipSpacePosition.w = 1.0f;
    
    //把我們輸入的顏色直接賦值給輸出顏色. 這個值將于構(gòu)成三角形的頂點的其他顏色值插值,從而為我們片段著色器中的每個片段生成顏色值.
    out.textureCoordinate = vertexArray[vertexID].textureCoordinate;
    
    //完成! 將結(jié)構(gòu)體傳遞到管道中下一個階段:
    return out;
}

3.片元著色器函數(shù):當(dāng)頂點函數(shù)執(zhí)行3次,三角形的每個頂點執(zhí)行一次后,則執(zhí)行管道中的下一個階段.柵格化/光柵化.

// 片元函數(shù)
//[[stage_in]],片元著色函數(shù)使用的單個片元輸入數(shù)據(jù)是由頂點著色函數(shù)輸出.然后經(jīng)過光柵化生成的.單個片元輸入函數(shù)數(shù)據(jù)可以使用"[[stage_in]]"屬性修飾符.
//一個頂點著色函數(shù)可以讀取單個頂點的輸入數(shù)據(jù),這些輸入數(shù)據(jù)存儲于參數(shù)傳遞的緩存中,使用頂點和實例ID在這些緩存中尋址.讀取到單個頂點的數(shù)據(jù).另外,單個頂點輸入數(shù)據(jù)也可以通過使用"[[stage_in]]"屬性修飾符的產(chǎn)生傳遞給頂點著色函數(shù).
//被stage_in 修飾的結(jié)構(gòu)體的成員不能是如下這些.Packed vectors 緊密填充類型向量,matrices 矩陣,structs 結(jié)構(gòu)體,references or pointers to type 某類型的引用或指針. arrays,vectors,matrices 標(biāo)量,向量,矩陣數(shù)組.
fragment float4 fragmentShader(RasterizerData in [[stage_in]],
                               texture2d<half> colorTexture [[texture(BaseTextureIndexBaseColor)]])
{
    constexpr sampler textureSampler(mag_filter::linear,
                                     min_filter::linear);
    
    const half4 colorSampler = colorTexture.sample(textureSampler,in.textureCoordinate);
    
    return float4(colorSampler);
    
    //返回輸入的片元顏色
    //return in.color;
}

  • 三、小批量頂點數(shù)據(jù)存儲樣式BaseMetalDemo地址
    - (void)setVertexBytes:(const void *)bytes length:(NSUInteger)length atIndex:(NSUInteger)index使用該方法來處理低于4KB的數(shù)據(jù)量,
- (void)drawInMTKView:(nonnull MTKView *)view {
   
    //1.頂點數(shù)據(jù)/顏色數(shù)據(jù)
    static const BaseVertex triangleVertices[] =
    {
        //頂點,    RGBA 顏色值
        { {  0.5, -0.25, 0.0, 1.0 }, { 1, 0, 0, 1 } },
        { { -0.5, -0.25, 0.0, 1.0 }, { 0, 1, 0, 1 } },
        { { -0.0f, 0.25, 0.0, 1.0 }, { 0, 0, 1, 1 } },
    };
    
    //2.當(dāng)前渲染的每一個渲染創(chuàng)建一個新的命令緩存區(qū)
    id<MTLCommandBuffer>commandBuffer = [_commandQueue commandBuffer];
    
    //指定緩存區(qū)名字
    commandBuffer.label = @"MyCommand";
    
    //3.MTLRenderPassDescriptor:一組渲染目標(biāo),用作渲染通道生成的像素的輸出目標(biāo)。
    MTLRenderPassDescriptor *renderPassDescriptor = view.currentRenderPassDescriptor;
    //判斷渲染目標(biāo)是否為空
    if(renderPassDescriptor != nil){
        //4.創(chuàng)建渲染命令編碼器,這樣我們才能渲染事物
        id<MTLRenderCommandEncoder>renderEncoder = [commandBuffer renderCommandEncoderWithDescriptor:renderPassDescriptor];
        //渲染器名稱
        renderEncoder.label = @"MyRenderEncoder";
        
        //5.設(shè)置可繪制的區(qū)域
        /*
        typedef struct {
            double originX, originY, width, height, znear, zfar;
        } MTLViewport;
         */
        //視口指定Metal渲染內(nèi)容的drawable區(qū)域。 視口是具有x和y偏移,寬度和高度以及近和遠(yuǎn)平面的3D區(qū)域
        //為管道分配自定義視口需要通過調(diào)用setViewport:方法將MTLViewport結(jié)構(gòu)編碼為渲染命令編碼器。 如果未指定視口,Metal會設(shè)置一個默認(rèn)視口,其大小與用于創(chuàng)建渲染命令編碼器的drawable相同。
        MTLViewport viewPort = {
            0.0,0.0,_viewportSize.x,_viewportSize.y,-1.0,1.0
        };
        [renderEncoder setViewport:viewPort];
        
        //6.設(shè)置當(dāng)前渲染管道對象
        [renderEncoder setRenderPipelineState:_pipelineState];
        
        
        //7.從應(yīng)用程序OC 代碼 中發(fā)送數(shù)據(jù)給Metal 頂點著色器 函數(shù)
        //頂點數(shù)據(jù)+顏色數(shù)據(jù)
        //   1) 指向要傳遞給著色器的內(nèi)存的指針
        //   2) 我們想要傳遞的數(shù)據(jù)的內(nèi)存大小
        //   3)一個整數(shù)索引,它對應(yīng)于我們的“vertexShader”函數(shù)中的緩沖區(qū)屬性限定符的索引。
        [renderEncoder setVertexBytes:&triangleVertices length:sizeof(triangleVertices) atIndex:BaseVertexInputIndexVertices];
        
        //viewPortSize 數(shù)據(jù)
        //1) 發(fā)送到頂點著色函數(shù)中,視圖大小
        //2) 視圖大小內(nèi)存空間大小
        //3) 對應(yīng)的索引
        [renderEncoder setVertexBytes:&_viewportSize
                               length:sizeof(_viewportSize)
                              atIndex:BaseVertexInputIndexViewportSize];
        //8.畫出三角形的3個頂點
        // @method drawPrimitives:vertexStart:vertexCount:
        //@brief 在不使用索引列表的情況下,繪制圖元
        //@param 繪制圖形組裝的基元類型
        //@param 從哪個位置數(shù)據(jù)開始繪制,一般為0
        //@param 每個圖元的頂點個數(shù),繪制的圖型頂點數(shù)量
        /*
         MTLPrimitiveTypePoint = 0, 點
         MTLPrimitiveTypeLine = 1, 線段
         MTLPrimitiveTypeLineStrip = 2, 線環(huán)
         MTLPrimitiveTypeTriangle = 3,  三角形
         MTLPrimitiveTypeTriangleStrip = 4, 三角型扇
         */
        [renderEncoder drawPrimitives:MTLPrimitiveTypeTriangle vertexStart:0 vertexCount:3];
        
        //9.表示已該編碼器生成的命令都已完成,并且從MTLCommandBuffer中分離
        [renderEncoder endEncoding];
        
        //10.一旦框架緩沖區(qū)完成,使用當(dāng)前可繪制的進(jìn)度表
        [commandBuffer presentDrawable:view.currentDrawable];

        
    }
    //11.最后,在這里完成渲染并將命令緩沖區(qū)推送到GPU
    [commandBuffer commit];
    
}
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

友情鏈接更多精彩內(nèi)容