Introduction of the Metal
Metal is a low-level graphical programming interface similar to OpenGL ES. It can directly operate gpus by using related apis. It was first released at WWDC in 2014 and released in 2019 with Metal 2. Metal supports GPU hardware acceleration, advanced 3D graphics rendering, and big data parallel computing. It also provides advanced and streamlined apis to ensure fine-grain framework and support low-level control in organizational architecture, program processing, graphical presentation, operation instructions, and management of instruction related data resources. Its core purpose is to reduce CPU overhead as much as possible, while transferring most of the load generated at runtime to the GPU
The characteristics of the Metal
- Low CPU consumption
- More efficient GPU performance, Metal can better use GPU performance
- Improve CPU and GPU concurrency
- Effective resource management
Graphics pipeline
Metal’s graphics pipeline is basically the same as OpenGL
- The CPU passes vertex data to the vertex shader
- The vertex shader assembles the processed vertex primitives
- The rasterization process is carried out
- The rasterized data is passed to the chip shader for processing
- Render to screen display
Relationship between Metal commands and objects
-
Command buffer: The command buffer is created from the command Queue
-
Command encoder: Encodes commands into a Command buffer
-
Commit the command buffer and send it to the GPU
-
The GPU executes the command and renders the result as drawable
Introduction of the Metal API
Apple suggested
-
Apple wants Rendering to be done in a Separate class
-
Respond to View Events: MTKDelegate. Respond to View Events are also handled in a separate class
-
Metal Command Objects: Creates a Command object, that is, the GPU that executes the Command, the MTLCommandQueue object that interacts with the GPU, and the MTCommandBuffer rendering cache
MTKView
MetalKit provides a view class MTKView, similar to GLKView in GLKit, which is a subclass of NSView (macOS view class) or UIView (iOS, tvOS view class). Handles details as metal draws and displays to the screen
MTLDevice
Because the Metal is operating the GPU, so need to get the GPU usage rights, namely the object to the GPU, provides MTLDevice agreement says the GPU in the Metal interface, usually by default in the iOS MTLCreateSystemDefaultDevice () for the GPU. Metal must be a real machine, and the machine must be 6s or higher. If the device does not support Metal, it will return null. If you want to use multiple MTLDevice instances, or switch from one MTLDevice to another, you need to create a separate set of objects for each MTLDevice.
The MTLDevice protocol indicates a GPU that can execute commands and provides the following functions: 1. Create a command queue. 2. Allocate the buffer from memory. 3
MTLCommandQueue
The CommandQueue type is MTLCommandQueue. This Queue is the first object that interacts with the GPU, and the Queue stores the Command MTLCommandBuffer to be rendered.
Queues are fetched through the MTLDevice object, and each commandQueue has a long lifetime, so commandqueues can be reused rather than being created and destroyed frequently.
Before drawing, you need to configure the MTKView, MTLDevice and MTLCommandQueue, and then prepare the data rendered to the screen, that is, prepare the cache data MTLCommandBuffer, such as vertex data.
The simple rendering process is as follows: 1. Create render buffer 2 using MTLCommandBuffer. Next, create render descriptor 3 with MTLRenderPassDescriptor. The rendercommandencoder is then coded using the created render cache and rendering descriptor to create the command editor MTLRenderCommandEncoder 4. Finally, the coding is finished and the rendering command is submitted. After the rendering is completed, the command cache area is submitted to GPU
MTLCommandBuffer
The Command Buffer is mainly used to store the Command encoding, and its life cycle is until the cache is submitted to the GPU for execution. A single Command Buffer can contain different encoding commands, mainly depending on the type and number of encoders used to build it.
The command cache can be created by calling the commandBuffer method of MTLCommandQueue. And the command Buffer object can only be submitted to the MTLCommandQueue object that created it
CommandBuffer is not executed before the command cache is submitted. After the command cache is submitted, the command cache is executed in the order in which it is enqueued. CommandBuffer can be submitted in either of the following ways. Enqueue: Sequential execution. The enqueue method reserves a position in the command queue for the command cache. At this time, the command cache is not submitted. Commit: Execute queue as soon as possible. If there is a commit ahead, you still need to wait in line.
MTLRenderCommandEncoder
MTLRenderCommandEncoder represents the render states and render commands associated with a single render process. It has the following functions: 1. Specifies graphic resources, such as cache and texture objects, that contain vertices, tiles, and texture picture data 2. Specifies an MTLRenderPipelineState object that represents the compiled render state, including compile & link cases for vertex shaders and slice shaders. Specifies fixed functions, including viewport, triangle fill mode, scissor rectangle, depth, template test, and other values 4. Map 3 d yuan Before creating commandEncoder, of which it has to create render descriptor MTLRenderPassDescriptor county, render descriptor by MTKView currentRenderPassDescriptor access
MTLRenderPassDescriptor *renderPassDescriptor = view.currentRenderPassDescriptor;
Copy the code
The command editor is then created using the commandBuffer in conjunction with the render descriptor
id<MTLRenderCommandEncoder> renderEncoder = [commandBuffer renderCommandEncoderWithDescriptor:renderPassDescriptor];
Copy the code
Rendercommandencoder, a tutorial on how to render using the render command encoder: 1. The MTLRenderCommandEncoder object is created by calling the makeRenderCommandEncoder (descriptor 🙂 method of the MTLCommandBuffer object. 2. Call the setRenderPipelineState (_ 🙂 method to specify the MTLRenderPipelineState state, which defines the state of the graphics rendering pipeline, including vertex and fragment functions. 3. Specify the resources used for the input and output of vertex and slice functions, and set the location (index) of each resource in the corresponding argument. The vertex data, such as through commandEncoder call setVertexBytes: length: atIndex: function to the metal file vertex shader and fragment shader function. 4. Specify other fixed functional states, such as commandEncoder calling setViewport: to set the viewport size, etc. 6. Call the endEncoding () method to terminate the render command encoder.
Case analysis
Render loop. H files:
/* Separate your rendering loop: When we are developing Metal programs, it is very useful to divide the rendering loop into classes that we create ourselves. Using a separate class, we can better manage the initialization of Metal and the Metal view delegate. - (void)mtkView:(nonnull mtkView *)view drawableSizeWillChange:(CGSize)size; 1. The view calls this method whenever the window size changes or the layout is rearranged (device orientation changes). 2. View the View can be set according to the View properties. PreferredFramesPerSecond frame rate (designated time to call drawInMTKView method), - (void) drawInMTKView: nonnull MTKView *) View; */ #import <Foundation/ foundation.h > @import MetalKit; @interface CCRenderer : NSObject<MTKViewDelegate> -(id)initWithMetalKitView:(MTKView *)mtkView; @endCopy the code
Render loop.m files:
#import "CCRenderer.h" @implementation CCRenderer { //GPU id<MTLDevice> _device; // Render command queue id<MTLCommandQueue> _commandQueue; } // typedef struct {float red, green, blue, alpha; } Color; // init - (id)initWithMetalKitView:(MTKView *) MTKView {self = [super init]; if(self) { _device = mtkView.device; // The first object that any application needs to interact with the GPU is an object. // You use the MTLCommandQueue to create an object and add it to the MTLCommandBuffer object. Make sure they are sent to the GPU in the correct order. For each frame, a new MTLCommandBuffer object is created and filled with commands executed by the GPU. } return self; MakeFancyColor {//1\. Static BOOL growing = YES; static BOOL growing = YES; //2. Color channel value (0~3) static NSUInteger primaryChannel = 0; Static float colorChannels[] = {1.0, 0.0, 0.0, 1.0}; //4. Const float DynamicColorRate = 0.015; NSUInteger dynamicChannelIndex = (primaryChannel+1)%3; // if(growing) {// dynamicChannelIndex = (primaryChannel+1)%3; 0.015 colorChannels[dynamicChannelIndex] += DynamicColorRate; If (colorChannels[dynamicChannelIndex] >= 1.0) {// Set NO to NO; // Change the channel to a dynamic channel primaryChannel = dynamicChannelIndex; NSUInteger dynamicChannelIndex = (primaryChannel+2)%3; ColorChannels [dynamicChannelIndex] -= dynamicColorChannel; If (colorChannels[dynamicChannelIndex] <= 0.0) {if(colorChannels[dynamicChannelIndex] <= 0.0) { }} // create Color Color Color; // Change the color RGBA value color.red = colorChannels[0]; color.green = colorChannels[1]; color.blue = colorChannels[2]; color.alpha = colorChannels[3]; // return color; } #pragma mark - MTKViewDelegate methods - (void)drawInMTKView (nonnull MTKView *)view {//1. Color = [self makeFancyColor]; //2\. Set the view's clearColor view.clearColor = MTLClearColorMake(color.red, color.green, color.blue, color.alpha); Create a new command buffer for each render pass to the current drawable // Use MTLCommandQueue // create a new commandBuffer id<MTLCommandBuffer> commandBuffer = [_commandQueue for each render pass that is currently rendered commandBuffer]; commandBuffer.label = @"MyCommand"; / / 4. From the view map, for rendering descriptor MTLRenderPassDescriptor * renderPassDescriptor = the currentRenderPassDescriptor; //5. Determine whether the renderPassDescriptor is successfully created; otherwise, skip any rendering. Rendercommandencoder object ID <MTLRenderCommandEncoder> renderEncoder = [commandBuffer renderCommandEncoderWithDescriptor:renderPassDescriptor]; renderEncoder.label = @"MyRenderEncoder"; //7. We could use MTLRenderCommandEncoder to commandencoder objects, but in this demo we are just creating the encoder, we are not asking Metal to execute what we are drawing, which means we are done. RenderEncoder commandencoder [renderEncoder endEncoding]; /* When the encoder finishes, the command cache will receive two commands: 1) present 2) commit Because the GPU does not draw directly to the screen, you do not give instructions. There will not be any content rendering to the screen. * / / / 8. Add a command to display the last clear can map screen [commandBuffer presentDrawable: the currentDrawable]; } //9. Finish rendering here and commit the commandBuffer to the GPU [commandBuffer commit]; } - (void) MTKView :(MTKView *)view drawableSizeWillChange:(CGSize)size {} @endCopy the code
Use of the render loop class:
#import "ViewController.h" #import "CCRenderer.h" @interface ViewController () { MTKView *_view; // CCRenderer *_render; } @end @implementation ViewController - (void)viewDidLoad { [super viewDidLoad]; //1\. Get _view _view = (MTKView *)self.view; / / 2. Set MTLDevice for _view (must) / / a MTLDevice object with a GPU, means that we can call the method usually MTLCreateSystemDefaultDevice () to obtain a single object represents the default GPU. _view.device = MTLCreateSystemDefaultDevice(); //3. Check whether the setting is successful. _view.device) { NSLog(@"Metal is not supported on this device"); return; } //4\. Create CCRenderer // Separate your render loop: _render =[[CCRenderer =[CCRenderer =[CCRenderer =[CCRenderer =[CCRenderer =[CCRenderer =[CCRenderer =[CCRenderer =[CCRenderer =[CCRenderer =[CCRenderer =[CCRenderer =[CCRenderer =[CCRenderer =[CCRenderer =[CCRenderer] alloc]initWithMetalKitView:_view]; //5. Check whether _render is successfully created if (! _render) { NSLog(@"Renderer failed initialization"); return; } //6. Set the delegate of MTKView (CCRender implements the delegate method of MTKView) _view.delegate = _render; / / 7. The view can be set according to the view attributes on frame rate (designated time to call drawInMTKView method calls) - view need renders _view. PreferredFramesPerSecond = 60; } - (void)didReceiveMemoryWarning { [super didReceiveMemoryWarning]; // Dispose of any resources that can be recreated. } @endCopy the code