“This is the 22nd day of my participation in the Gwen Challenge in November. Check out the details: The Last Gwen Challenge in 2021.”

An overview of the

Render passes are a series of render commands used to draw a set of textures. This example performs a pair of render passes to render the contents of the view. For the first channel, the example creates a custom render that renders the image to a texture. This channel is an off-screen render channel because the sample is rendered as a normal texture rather than a texture created by the display subsystem. The second render channel uses the render channel descriptor provided by the MTKView object to render and display the final image. This example uses the texture of the off-screen render channel as the source data for the second render channel draw command.

Off-screen render channels are the basic building blocks for larger or more complex renderers. For example, many lighting and shadow algorithms require an off-screen render channel to render shadow information, and a second channel to calculate the final scene lighting. Off-screen render channels are also useful for batch processing of data that does not need to be displayed on screen.

Create an off-screen channel texture

The MTKView object automatically creates a drawable texture for rendering. This example also requires a texture rendered in an off-screen render channel. To create the texture, first create an MTLTextureDescriptor object and configure its properties.

MTLTextureDescriptor *texDescriptor = [MTLTextureDescriptor new];

texDescriptor.textureType = MTLTextureType2D;

texDescriptor.width = 512;

texDescriptor.height = 512;

texDescriptor.pixelFormat = MTLPixelFormatRGBA8Unorm;

texDescriptor.usage = MTLTextureUsageRenderTarget |

                      MTLTextureUsageShaderRead;

Copy the code

This example configures the ‘Usage’ attribute to specify exactly how it intends to use the new texture. It needs to render data to the texture in the off-screen channel and read data from the texture in the second channel. This example specifies this usage by setting the renderTarget and shaderRead flags.

Setting the use flag precisely improves performance because Metal can configure the underlying data for the texture only for the specified purpose.

Create render pipeline

The render pipeline specifies how to execute the drawing command, including the vertex and fragment functions to execute, and the pixel format of any render target it acts on. Later, when the example creates a custom render channel, it must use the same pixel format.

This example creates a render pipeline for each render channel, using the following code to create an off-screen render pipeline:

pipelineStateDescriptor.label = @"Offscreen Render Pipeline";
pipelineStateDescriptor.sampleCount = 1;
pipelineStateDescriptor.vertexFunction =  [defaultLibrary newFunctionWithName:@"simpleVertexShader"];
pipelineStateDescriptor.fragmentFunction =  [defaultLibrary newFunctionWithName:@"simpleFragmentShader"];
pipelineStateDescriptor.colorAttachments[0].pixelFormat = _renderTargetTexture.pixelFormat;
_renderToTextureRenderPipeline = [_device newRenderPipelineStateWithDescriptor:pipelineStateDescriptor error:&error];
Copy the code

The code to create a pipeline for rendering channels is similar to the code in Metal Framework’s Rendering Pipeline Renderers. To ensure that the two pixel formats match, the example sets the pixel format of the descriptor to the view’s colorPixelFormat. Similarly, when creating the off-screen render pipeline, the example sets the pixel format of the descriptor to that of the off-screen texture.

Configure the off-screen render channel descriptor

To render to an off-screen texture, the example configures a new render channel descriptor. Create an MTLRenderPassDescriptor object and configure its properties. Since this example is rendered to a monochrome texture, set colorAttachment[0]. Texture to point to an off-screen texture:

_renderToTextureRenderPassDescriptor.colorAttachments[0].texture = _renderTargetTexture;
Copy the code

This example must also configure the load action and the store action for the render target.

_*renderToTextureRenderPassDescriptor.colorAttachments[0].loadAction = MTLLoadActionClear;

_renderToTextureRenderPassDescriptor.colorAttachments[0].clearColor = MTLClearColorMake(1, 1, 1, 1);

_renderToTextureRenderPassDescriptor.colorAttachments[0].storeAction = MTLStoreActionStore;

Copy the code

The load action determines the initial content of the texture at the beginning of the render channel before the GPU executes any drawing commands. Similarly, the storage operation runs after the render channel is complete and determines whether the GPU writes the final image back to the texture. The example configures a load operation to erase the contents of the render target and a store operation to store the render data back into the texture. The store operation must be performed because the drawing command in the second render channel samples the data.

Metal uses load and store operations to optimize how the GPU manages texture data. Large textures consume a lot of memory, and processing them consumes a lot of memory bandwidth. Setting up the render target operation correctly can reduce the amount of memory bandwidth a GPU uses to access textures, thereby improving performance and battery life. See Metal Framework setup Loading and Storing operations for more detailed guidance.

The render channel descriptor has additional properties that are not used in this example to further modify the rendering process. For information about other methods of customizing render channel descriptors, see MTLRenderPassDescriptor.

Render to an off-screen texture

This example covers everything you need to encode the two render channels. Before looking at how the example encodes the render channel, it’s important to understand how Metal schedules commands on the GPU. When the App submits the command buffer to the command queue, by default Metal executes the commands in the order in the queue. To improve performance and make better use of the GPU, Metal can run commands concurrently, as long as doing so does not produce results that are inconsistent with sequential execution. To achieve this, when a channel writes to a resource and subsequent channels read from it (as in this example), Metal detects the dependency and automatically delays the execution of the latter channel until the first channel completes. So unlike Metal Framework’s Synchronous CPU and GPU work, which requires explicit CPU and GPU synchronization, this example doesn’t need to do anything special. It simply encodes the two channels in order, and Metal ensures that they run in that order.

The example starts with an off-screen render channel and encodes two render channels into a command buffer. It uses the previously created off-screen render channel descriptor to create a render command encoder.


id<MTLRenderCommandEncoder> renderEncoder =

    [commandBuffer renderCommandEncoderWithDescriptor:_renderToTextureRenderPassDescriptor];

renderEncoder.label = @"Offscreen Render Pass";

[renderEncoder setRenderPipelineState:_renderToTextureRenderPipeline];

Copy the code

Everything else in the render channel is similar to Metal Framework’s Render Pipeline Primitives. It configures the pipe and any necessary parameters, and then encodes the drawing command. After command encoding is complete, endEncoding() is called to complete the encoding process.

[renderEncoder endEncoding];
Copy the code

Multiple channels must be encoded sequentially into the command buffer, so the example must finish coding the first render channel before the next one starts.

Render to a drawable texture

The second render channel needs to render the final image. A fragment shader that can draw a render pipeline samples data from a texture and returns that sample as the final color:


// Fragment shader that samples a texture and outputs the sampled color.

fragment float4 textureFragmentShader(TexturePipelineRasterizerData in      [[stage_in]],

                                      texture2d<float>              texture [[texture(AAPLTextureInputIndexColor)]])

{

    sampler simpleSampler;

    // Sample data from the texture.

    float4 colorSample = texture.sample(simpleSampler, in.texcoord);

    // Return the color sample as the final color.

    return colorSample;

}

Copy the code

This example uses the view’s render channel descriptor to create a second render channel and encodes drawing commands to render the texture quadrilateral. Specifies the off-screen texture as the texture parameter of the command.

id<MTLRenderCommandEncoder> renderEncoder =

    [commandBuffer renderCommandEncoderWithDescriptor:drawableRenderPassDescriptor];

renderEncoder.label = @"Drawable Render Pass";

[renderEncoder setRenderPipelineState:_drawableRenderPipeline];

[renderEncoder setVertexBytes:&quadVertices
                       length:sizeof(quadVertices)
                      atIndex:AAPLVertexInputIndexVertices];
[renderEncoder setVertexBytes:&_aspectRatio
                       length:sizeof(_aspectRatio)
                        atIndex:AAPLVertexInputIndexAspectRatio];



// Set the offscreen texture as the source texture.

[renderEncoder setFragmentTexture:_renderTargetTexture atIndex:AAPLTextureInputIndexColor]

Copy the code

When submitting the command buffer, Metal executes two render channels in turn. In this case, Metal detects that the first render channel writes to the off-screen texture and the second channel reads from the texture. When Metal detects this dependency, it blocks subsequent channel execution until the GPU completes the first channel.

conclusion

This article describes how to render an off-screen texture by creating a custom render channel. This example performs a pair of render passes to render the contents of the view. Describes in detail the steps to create the two render channels and how to set the render order.

Download the sample code for this article