The purpose of this example is to understand the process of using shaders to draw triangles in Metal

The overall effect is shown below

The overall flow chart is as follows

The viewDidLoad function is basically unchanged compared to Metal 101: Color rendering loading. The main changes are initWithMetalKitView and drawInMTKView proxy methods

  • InitWithMetalKitView function: Added preparation for metal shader file initialization
  • drawInMTKViewProxy method: Added metal’s triangle drawing process

The preparatory work

Create metal file

  • Command + N –> Metal File Create Metal shader File

  • Defines the vertex shader input and the chip shader input, equivalent to the variable modified by OpenGL ES, i.e., the bridge variable

Typedef struct {// handle the vertex information of the space, equivalent to the gl_Position // float4 modifier in OpenGL ES, Is a 4-dimensional vector float4 clipSpacePosition [[position]]; // OpenGL ES gl_FragColor float4 color; }RasterizerData;Copy the code
  • Define vertex shader functions and slice shader functions
/ / / * vertex vertex shader function: modifier, said is a vertex shader RasterizerData: return value vertexShader: function name, customizable vertexID: metal own feedback id are: 1) tell the location of the storage buffer 2) pass data entry is CJLVertexInputIndexVertices are drawn and viewportSizePointer by CJLRenderer passed * / vertex RasterizerData vertexShader(uint vertexID [[vertex_id]], constant CJLVertex *vertices [[buffer(CJLVertexInputIndexVertices)]], Constant vector_uint2 * viewportSizePointer [[buffer (CJLVertexInputIndexViewportSize)]]) {} / * fragments: RGBA fragmentShader: Function name, customizable RasterizerData: Parameter type (modifiable) in: Parameter parameter (modifiable) [[stage_in]] : Attribute modifier that represents a single chip input (output by a fixed-point function) (unmodifiable), Varying */ FRAGMENT FLOAT4 FRAGMENT SHAder (RasterizerData in [[stage_in]]) {}Copy the code

Create a bridge file between C and OC

The purpose of this header file is for the C code and OC code to be shared with the SHAder and C code to ensure that the Metal Shader cache index matches the collection call set by the Metal API Buffer

  • Defines the cache index value, which represents the entry enumeration value that passes data to the Metal shader. It is equivalent to the vertex coordinate entry defined in the GLSL language in OpenGL ESposition
Typedef enum CJLVertexInputIndex {/ / vertex CJLVertexInputIndexVertices = 0, / / view size CJLVertexInputIndexViewportSize = 1, }CJLVertexInputIndex;Copy the code
  • Defines a structure for graph data, containing vertex and color values, similar to the structure for vertex data in OpenGL ES
Struct vector_float4; // Struct vector_float4; // Struct vector_float4; // Struct vector_float4; // RGBA color vector_float4; }CJLVertex;Copy the code

ViewDidLoad function

The flow of this function is as follows

The drawableSizeWillChange method of the MTKViewDelegate protocol is triggered. I won’t go into much detail here

Render loop class

The rendering loop class CJLRenderer is a service of MTKView that manages the rendering of the view and the callbacks to the view’s agent methods

InitWithMetalKitView function

This part of the code is the preparation work before drawing, the flowchart is as follows

There are three main steps

  • Set up the device
  • Metal shader file loading
  • Setting the command queue

Step 1 and Step 3 were mentioned in the previous example, and will not be explained here, mainly focusing on the metal shader file loading

Metal shader file loading

According to the figure above, it can be divided into the following steps

  • Load the Metal file to get the metal shader from the bundle, and get the function corresponding to the vertex shader and the chip shaderMTLLibraryObject is obtained by the corresponding function name
<MTLLibrary> defaultLibrary = [_device newDefaultLibrary]; //2. Load all (.metal) shader files in the project. Id <MTLFunction> vertexFunction = [defaultLibrary newFunctionWithName:@"vertexShader"]; Id <MTLFunction> fragmentFunction = [defaultLibrary newFunctionWithName:@"fragmentShader"];Copy the code
  • Configure the render pipeline to passMTLRenderPipelineDescriptorInitialize the render pipe and set the pipe name, the function corresponding to the vertex & slice shader, and the component to store the color data
//3. Configure the pipeline to create the pipeline state, The rendering pipeline MTLRenderPipelineDescriptor * pipelineStateDescriptor = [[MTLRenderPipelineDescriptor alloc] init]; / / name of Pipeline pipelineStateDescriptor. Label = @ "Simple Pipeline"; / / programmable function, used to handle each vertex pipelineStateDescriptor. In the process of rendering vertexFunction = vertexFunction; / / programmable function, used to handle each fragment in the process of rendering/fragment pipelineStateDescriptor fragmentFunction = fragmentFunction; / / a set of color data storage component pipelineStateDescriptor colorAttachments [0]. PixelFormat = mtkView. ColorPixelFormat;Copy the code
  • Create a render pipeline state object from the device and the render pipeline, and determine whether it was created successfully
/ / 4. Synchronization to create object and returns the rendering pipeline state _pipelineState = [_device newRenderPipelineStateWithDescriptor: pipelineStateDescriptor error:&error]; // Check whether the pipeline state object is returned if (! _pipelineState) {// NSLog(@"Failed to create Pipeline state, error %@", error); return nil; }Copy the code

DrawInMTKView agent method

When view, Render and viewport are initialized, the default frame rate of the MTKView object view is 60, which is the same as the frame rate of the screen refresh, so the drawing proxy method is constantly called with the frame rate of the screen refresh

The overall process of this method is as follows

In contrast to the color rendering case, metal has an additional process for drawing triangles. The process is described below. The drawing of the triangles is done after the commandEncoder object is created, before the commandEncoder is finished, and before the drawing, You need to create the vertex and color data for the triangle before creating the commandBuffer

Static const CJLVertex triangleVertices[] = {// 1\. Const CJLVertex triangleVertices[] = {// 2 \. RGBA color value {{0.5, 0.25, 0.0, 1.0}, {1, 0, 0, 1}}, {{0.5, 0.25, 0.0, 1.0}, {0, 1, 0, 1}}, {{- f 0.0, 0.25, 0.0, 1.0}, {0, 0, 1, 1}},};Copy the code

Triangle drawing is mainly divided into the following steps:

  • Set the area that can be drawn, that is, set viewport, equivalent to OpenGL ESglViewPort
    • The viewport is used to specify the drawable area of Metal’s rendered content. Viewports are 3D regions with X and Y offsets, width and height, and near and far planes

    • Assigning a custom viewport to a pipe requires encoding the MTLViewport structure as a render command encoder by calling the setViewport method. If no viewport is specified, Metal sets a default viewport with the same size as the drawable used to create the render command encoder.

MTLViewport viewPort = {0.0, 0.0, _viewportSize. X, _viewportSize. Y, -1.0, 1.0}; GlViewPort [renderEncoder setViewport:viewPort]; //6. Set the current render pipeline state object [renderEncoder setRenderPipelineState:_pipelineState];Copy the code
  • Pass data from the OC code of the app to the Metal vertex shader/chip shader function, which is equivalent to the glVertexAttribPointer in OpenGL ES. Currently there are two kinds of data to pass
    • Vertex + color data
    • View size flat data
// 1) pointer to memory to pass to the shader // 2) Memory size of the data we want to pass // 3) an integer index, which corresponds to the index of the buffer property qualifier in our "vertexShader" function. Is / / CJLVertexInputIndexVertices vertex data entry that needs to be defined by yourself, In the equivalent of OpenGL ES glGetAttribLocation [renderEncoder setVertexBytes: triangleVertices length: sizeof (triangleVertices) atIndex:CJLVertexInputIndexVertices]; //1) send to vertex shader, view size //2) View size memory size //3) index corresponding to [renderEncoder setVertexBytes:&_viewportSize length:sizeof(_viewportSize) atIndex:CJLVertexInputIndexViewportSize];Copy the code
  • Draw a triangle, equivalent to OpenGL ESglDrawArraysThere are 5 types of primitives in Metal and 9 types of primitives in OpenGL ES. The following table shows the primitives in Metal
Primitive type instructions
MTLPrimitiveTypePoint = 0 point
MTLPrimitiveTypeLine = 1 line
MTLPrimitiveTypeLineStrip = 2 Line ring
MTLPrimitiveTypeTriangle = 3 triangle
MTLPrimitiveTypeTriangleStrip = 4 Triangle fan
/ / @ method drawPrimitives: vertexStart: vertexCount: / / @ brief, in the case of not using the index list, draw the figure yuan / / @ the primitive types of param plot assembly / / @ param Began to draw from which position data, generally 0 / / @ param number of vertices each figure drawing graph vertex number [renderEncoder drawPrimitives: MTLPrimitiveTypeTriangle vertexStart: 0 vertexCount:3];Copy the code

So that’s how you draw a triangle

– Improved metal shader function code

This part uses metal-specific syntax, similar to the GLSL language in OpenGL ES

The relationship of the graphics rendering pipeline in Metal is shown as follows: vertex + color data is passed to the vertex shader, which processes the vertices. In the middle, through the assembly and rasterization of primifiers completed by Metal itself, the processed data is passed to the chip shader for processing

The vertex shader function has two main parts to handle

  • Vertices: Performs a coordinate transformation to write the generated vertex clipping space to the return value.
  • Color: Passes the vertex color value to the return value
vertex RasterizerDatavertexShader(uint vertexID [[vertex_id]], constant CJLVertex *vertices [[buffer(CJLVertexInputIndexVertices)]], Constant vector_uint2 * viewportSizePointer [[buffer (CJLVertexInputIndexViewportSize)]]) {/ / 1, define the out RasterizerData out; // 2. The output position of each vertex shader is in clip space (also known as normalized Device coordinate space,NDC), where (-1,-1) represents the lower left corner of the viewport, and (1,1) represents the upper right corner of the viewport. Out.clipspaceposition = vertices[vertexID].position; // Assign the input color directly to the output color. This value interpolates the other color values of the vertices that form the triangle, thus generating a color value for each fragment in our fragment shader. / / finish! Pass the structure to the next stage in the pipeline: return out; }Copy the code

The chip shader function outputs the color value as is

// When the vertex function is executed three times, once for each vertex of the triangle, the next stage in the pipeline is executed. /* Metal-shader function: /* metal-shader function: /* metal-shader function: Fragment float4 fragmentShader(RasterizerData in [[stage_in]]]){return in. Color; }Copy the code

See github-17_metal_triangle _OC for the complete code

Author: Style_ month links: www.jianshu.com/p/d94dcaa88… The copyright of the short book belongs to the author. Commercial reprint please contact the author to obtain authorization, non-commercial reprint please indicate the source.