Metal 初体验和绘制三角形

2020-08-22  本文已影响0人  东旭39

metal是苹果自己推出的渲染库。相对OpenGL ES可以提高对GPU的利用。Metal 应用 和GPU的关系

  1. App是客户端。
  2. GPU是服务端
  3. App发送command(命令)到GPU
  4. 处理完命令,GPU会通知App可以继续发送命令

To send commands to a GPU, you add them to a command buffer using a command encoder object. You add the command buffer to a command queue and then commit the command buffer when you're ready for Metal to execute the command buffer's commands. The order that you place commands in command buffers, enqueue and commit command buffers, is important because it effects the perceived order in which Metal promises to execute your commands.

这段话可以贯穿metal的使用过程。
大致流程 commandQueue- commandBuffer ->>commendEncoder-> commandBuffer commit

命令队列

_commandQueue = [_device newCommandQueue];

命令队列管理多个命令缓冲区


27dc137c-d852-461e-aff2-e93ab71f44e9.png

管线对象

管线对象告诉metal如何处理这些命令。这里需要用到metal的着色器语言

  1. 写Metal shader functions 处理数据
  2. 创建包含着色器的管道对象
  3. 启用管线
  4. 画图形或计算等
    mteal不会立即执行计算,需要用commandEnconder将命令插入命令缓冲区,commit后发送动GPU才会处理命令

向GPU发送命令

  1. 创建命令缓冲区
  2. 将命令存入命令缓冲区
  3. 提交命令缓冲区到GPU

案例1.
背景色渐变,创建一个Renderer类用于管理渲染。

#import "Renderer.h"

typedef struct {
    float red, green , blue, alpha;
}Color;

@implementation Renderer
{
    id<MTLDevice> _device;
    id<MTLCommandQueue> _commandQueue;
}


-(id)initWithMetalKitView:(MTKView *)mtkView{
    
    self = [super init];
    if (self) {
        _device = mtkView.device;
        
        _commandQueue = [_device newCommandQueue];
    }
    return self;
}

//颜色
-(Color)makeFancyColor{
    static BOOL growing = YES;
    static NSUInteger primaryChannel = 0;
    
    static float colorChannels[] = {1.0,0.0,0.0,1.0};
    const float DynamicColorRate = 0.015;
    if (growing) {
        NSUInteger dynamicChannelIndex = (primaryChannel +1)%3;
        
        colorChannels[dynamicChannelIndex] += DynamicColorRate;
        
        if (  colorChannels[dynamicChannelIndex]>=1) {
            growing = NO;
            
            primaryChannel = dynamicChannelIndex;
        }
    }else{
        NSUInteger dynamicChannelIndex = (primaryChannel +2)%3;
               
       colorChannels[dynamicChannelIndex] -= DynamicColorRate;
       
        if (  colorChannels[dynamicChannelIndex]<= 0.0) {
           growing = YES;
           
          
       }
    }
    
    Color color;
    
    color.red = colorChannels[0];
    color.green = colorChannels[1];
    color.blue = colorChannels[2];
    color.alpha = colorChannels[3];
    
    return color;
}

//画view的内容,这个代理方法会按帧率执行
-(void)drawInMTKView:(MTKView *)view{
    //获取颜色
    Color color = [self makeFancyColor];
    //设置背景色
    view.clearColor = MTLClearColorMake(color.red, color.green, color.blue, color.alpha);
    
    //创建一个命令缓冲区
    id<MTLCommandBuffer> commandBuffer = [_commandQueue commandBuffer];
    commandBuffer.label = @"mycommand";
    
    //渲染过程,用于保存渲染过程的结果
    MTLRenderPassDescriptor *renderPassDescriptor = view.currentRenderPassDescriptor;
    
    if (renderPassDescriptor != nil) {
        id<MTLRenderCommandEncoder> renderEncoder = [commandBuffer renderCommandEncoderWithDescriptor:renderPassDescriptor];
        renderEncoder.label = @"myrenderEncoder";
        
        //结束编码
        [renderEncoder endEncoding];
        
        //注册一个可绘制图像
        [commandBuffer presentDrawable:view.currentDrawable];
    }
    //提交命令到GPU
    [commandBuffer commit];
}

//视口发生变化会被调用
- (void)mtkView:(nonnull MTKView *)view drawableSizeWillChange:(CGSize)size {
    
}
@end

案例2.三角形

三角形在学习OpenGL ES第一个入门案例,在metal中如何画出一个三角行


Simulator Screen Shot - iPhone 11 - 2020-08-20 at 23.00.37.png

1.创建一个ShaderType.h的头文件,改文件定义了顶点结构体、顶点输入的索引。

#ifndef ShaderTypes_h
#define ShaderTypes_h
//该头文件可以在oc和meal文件中使用
//缓冲区索引
typedef enum VertexInputIndex{
    VertexInputIndexVertices = 0,   //顶点索引,顶点函数的第二个入参
    VertexInputIndexViewportSize =1,  //视口大小索引,顶点函数的第三个入参
}VertexInputIndex;

typedef struct {
    vector_float4 position; //顶点坐标
    vector_float4 color; //颜色
}Vertex;

#endif /* ShaderTypes_h */

着色器代码

着色器代码中,依然有顶点函数、片元函数(OpenGL ES中为顶点着色器、片元着色器)。

#include <metal_stdlib>
using namespace metal;

#import "ShaderTypes.h"

//作为顶点函数的输入,片元函数的输入的结构体
typedef struct {
    float4 clipSpacePosition [[position]]; //position声明为顶点,与OpenGL 中的gl_Position类似
    float4 color;
}RasterizerData;

//顶点函数
//vertex声明为顶点函数
//RasterizerData 返回类型
//vertexShader 函数名
vertex RasterizerData vertexShader(uint vertexID [[vertex_id]], //顶点索引,三角形有是三个顶点,那就是0,1,2
                                     //顶点数组,通过   [renderEncoder setVertexBytes:triangle length:sizeof(triangle) atIndex:VertexInputIndexVertices];传入缓存中的位置,通过buffer读取位置
                                   constant Vertex *vertices[[buffer(VertexInputIndexVertices)]],
                                 //视口大小,通过 [renderEncoder setVertexBytes:&_viewportSize length:sizeof(triangle) atIndex:VertexInputIndexViewportSize];传入缓存中的位置,通过buffer读取位置
                                   constant vector_uint2 *viewportSizePointer [[buffer(VertexInputIndexViewportSize)]]
                                   //视口大小
                                   ){
    RasterizerData out;
    out.clipSpacePosition = vertices[vertexID].position;
    out.color =  vertices[vertexID].color;
    return out;
}
//片元函数,
//fragment 声明为片元函数
// float4 返回类型
// fragmentShader 函数名称
//参数 in 是 顶点函数返回RasterizerData,经过图元装配,光栅化,后到片元函数,作为其入参
fragment float4 fragmentShader(RasterizerData in [[stage_in]]){
    return  in.color;
}

创建管线对象

//加载metal文件
id<MTLLibrary> defaultLibrary = [_device newDefaultLibrary];      
 //顶点函数
id<MTLFunction> vertexFunction = [defaultLibrary newFunctionWithName:@"vertexShader"];
//片元函数
id<MTLFunction> fragmentFunction = [defaultLibrary newFunctionWithName:@"fragmentShader"];
        
//渲染管线描述符,是传递给_renderPipelineState的一个参数
MTLRenderPipelineDescriptor *pipelineDescriptor = [[MTLRenderPipelineDescriptor alloc] init];
pipelineDescriptor.label = @"pipeline";
pipelineDescriptor.vertexFunction = vertexFunction;
pipelineDescriptor.fragmentFunction = fragmentFunction;
pipelineDescriptor.colorAttachments[0].pixelFormat = mtkView.colorPixelFormat;
 //创建渲染管线对象
_renderPipelineState = [_device newRenderPipelineStateWithDescriptor:pipelineDescriptor error:NULL];

绘制view的内容

//绘制view的内容
-(void)drawInMTKView:(MTKView *)view{
    
    //顶点坐标和颜色的结构体数组
    static const Vertex triangle[] ={
        { {  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 } },
    };
    
    id<MTLCommandBuffer> commandBuffer = [_commandQueue commandBuffer];
    commandBuffer.label = @"command";
    //渲染过程,用于保存渲染过程的结果
    MTLRenderPassDescriptor *renderPassDescriptor = view.currentRenderPassDescriptor;
    if (renderPassDescriptor != nil) {
        id<MTLRenderCommandEncoder> renderEncoder = [commandBuffer renderCommandEncoderWithDescriptor:renderPassDescriptor];
        renderEncoder.label = @"renderEncoder";
        
        MTLViewport viewPort = {
            0.0,0.0,_viewportSize.x,_viewportSize.y, -1.0,1.0
        };
        [renderEncoder setViewport:viewPort];
        //设置当前的渲染管线状态
        [renderEncoder setRenderPipelineState:_renderPipelineState];
        
        //顶点函数中,顶点数据传入缓存中的位置,通过metal中的buffer读取
        [renderEncoder setVertexBytes:triangle length:sizeof(triangle) atIndex:VertexInputIndexVertices];
        //顶点函数中,视口大小传入缓冲中的位置,通过metal中的buffer读取
        [renderEncoder setVertexBytes:&_viewportSize length:sizeof(triangle) atIndex:VertexInputIndexViewportSize];
        
        //画图元
        [renderEncoder drawPrimitives:MTLPrimitiveTypeTriangle vertexStart:0 vertexCount:3];
        [renderEncoder endEncoding];
        //准备绘制
        [commandBuffer presentDrawable:view.currentDrawable];
    }
    
    //提交命令到GPU,开始绘制
    [commandBuffer commit];
}

地址

上一篇下一篇

猜你喜欢

热点阅读