Sample Code

Argument Buffers with GPU Encoding

Demonstrates how to encode an argument buffer with a compute pass and then access its arguments in a subsequent render pass.

Download

Overview

In the Argument Buffers with Arrays and Resource Heaps sample, you learned how to combine argument buffers with arrays of resources and resource heaps.

In this sample, you’ll learn how to encode resources into argument buffers with a graphics or compute function. In particular, you’ll learn how to write data into an argument buffer from a compute pass and then read that data in a render pass. The sample renders a grid of multiple quad instances with two textures applied to each, where the textures slide from left to right within the quad and move from left to right between quads.

Getting Started

The sample can run only on macOS devices that support Tier 2 argument buffers. Tier 2 devices allow graphics or compute functions to encode data into an argument buffer, whereas Tier 1 devices only allow these functions to read data from an argument buffer. Additionally, Tier 2 devices can access more textures in an instanced draw call than Tier 1 devices. See About Argument Buffers for more information about argument buffer tiers, limits, and capabilities.

This sample checks for Tier 2 argument buffer support when the renderer is initialized.

if(_view.device.argumentBuffersSupport != MTLArgumentBuffersTier2)
{
    NSAssert(0, @"This sample requires a Metal device that supports Tier 2 argument buffers.");
}

Encode Data into Argument Buffers

During initialization, the sample encodes data with the CPU into an argument buffer defined by the SourceTextureArguments structure.

typedef struct SourceTextureArguments {
    array<texture2d<float>, AAPLNumTextures> textures [[ id(AAPLArgumentBufferIDTextures) ]];
} SourceTextureArguments;

This argument buffer is backed by the _sourceTextures buffer and is accessed via the source_textures variable in the updateInstances function. source_textures contains an array of references to textures loaded by the sample’s renderer.

Layout diagram that shows an array of textures encoded into an argument buffer as an array of references to those textures.

After initialization, for each frame, the sample encodes data with the GPU into a separate argument buffer defined by the InstanceArguments structure.

typedef struct InstanceArguments {
    vector_float2    position;
    texture2d<float> left_texture;
    texture2d<float> right_texture;
} InstanceArguments;

This argument buffer is backed by the _instanceParameters buffer and is accessed via the instance_params variable in the updateInstances, vertexShader, and fragmentShader functions. instance_params is an array of structures whose data is populated in a compute pass and then accessed in a render pass via an instanced draw call.

Layout diagram that shows an array of structures as an argument buffer.

Create an Array of Argument Buffer Structures

The sample defines an InstanceArguments structure into which a compute function, updateInstances, encodes a vector and two textures.

typedef struct InstanceArguments {
    vector_float2    position;
    texture2d<float> left_texture;
    texture2d<float> right_texture;
} InstanceArguments;

Previous argument buffer samples used the encodedLength property to directly determine the required size for the MTLBuffer that backs an argument buffer structure. However, this sample needs one instance of this structure for each quad rendered by a subsequent render pass. Therefore, the sample multiplies the value of encodedLength by the total number of instances, which is defined by the value of the AAPLNumInstances constant.

NSUInteger instanceParameterLength = instanceParameterEncoder.encodedLength * AAPLNumInstances;

_instanceParameters = [_device newBufferWithLength:instanceParameterLength options:0];

Encode an Argument Buffer with a Compute Function

For each quad to be rendered, the sample executes the updateInstances compute function to determine the quad’s position and textures. The compute pass executed by the sample iterates through the instance_params array and encodes the correct data for each quad. The sample encodes data into instance_params by setting InstanceArguments values in the array element at the instanceID index value.

device  InstanceArguments & quad_params = instance_params[instanceID];

// Store the position of the quad
quad_params.position = position;

// Select and store the textures to apply to this quad
quad_params.left_texture = source_textures.textures[left_texture_index];
quad_params.right_texture = source_textures.textures[right_texture_index];

Render Instances with an Argument Buffer

The sample issues an instanced draw call to render all the quads while incurring a minimal amount of CPU overhead. Combining this technique with an argument buffer allows the sample to use a unique set of resources for each quad within the same draw call, where each instance draws a single quad.

The sample declares an instanceID variable in both the vertex and fragment function’s signatures. The render pipeline uses instanceID to index into the instance_params array that was previously encoded by the updateInstances compute function.

In the vertex function, instanceID is defined as an argument with the [[instance_id]] attribute qualifier.

vertex RasterizerData
vertexShader(uint                            vertexID        [[ vertex_id ]],
             uint                            instanceID      [[ instance_id ]],
             const device AAPLVertex        *vertices        [[ buffer(AAPLVertexBufferIndexVertices) ]],
             const device InstanceArguments *instance_params [[ buffer(AAPLVertexBufferIndexInstanceParams) ]],
             constant AAPLFrameState        &frame_state     [[ buffer(AAPLVertexBufferIndexFrameState) ]])

The vertex function reads position data from the argument buffer to render the quad in the right place in the drawable.

float2 quad_position = instance_params[instanceID].position;

The vertex function then passes the instanceID variable to the fragment function, via the RasterizerData structure and the [[stage_in]] attribute qualifier. (In the fragment function, instanceID is accessed via the in argument.)

fragment float4
fragmentShader(RasterizerData            in              [[ stage_in ]],
               device InstanceArguments *instance_params [[ buffer(AAPLFragmentBufferIndexInstanceParams) ]],
               constant AAPLFrameState  &frame_state     [[ buffer(AAPLFragmentBufferIndexFrameState) ]])

The fragment function samples from the two textures specified in the argument buffer and then chooses an output sample based on the value of slideFactor.

texture2d<float> left_texture = instance_params[instanceID].left_texture;
texture2d<float> right_texture = instance_params[instanceID].right_texture;

float4 left_sample = left_texture.sample(texture_sampler, in.tex_coord);
float4 right_sample = right_texture.sample(texture_sampler, in.tex_coord);

if(frame_state.slideFactor < in.tex_coord.x)
{
    output_color = left_sample;
}
else
{
    output_color = right_sample;
}

The fragment function outputs the selected sample. The left texture slides in from the left and the right texture slides out to the right. After the right texture has completely slid off the quad, the sample assigns this texture as the left texture in the next compute pass. Thus, each texture moves from left to right across the grid of quads.

Next Steps

In this sample, you learned how to encode resources into argument buffers with a graphics or compute function. In the Dynamic Terrain with Argument Buffers sample, you’ll learn how to combine several argument buffer techniques to render a dynamic terrain in real time.

See Also

Argument Buffers

About Argument Buffers

Improve your app’s performance by grouping your resources into an argument buffer.

Basic Argument Buffers

Demonstrates how to manage groups of resources with an argument buffer.

Argument Buffers with Arrays and Resource Heaps

Demonstrates how to define an argument buffer with arrays and reduce CPU overhead by combining argument buffers with resource heaps.

Dynamic Terrain with Argument Buffers

Demonstrates how to use argument buffers to render a dynamic terrain in real time with a GPU-driven pipeline.

Indexing Argument Buffers

Assign resource indices within an argument buffer.

Tracking the Resource Residency of Argument Buffers

Optimize resource performance within an argument buffer.

MTLArgumentDescriptor

A representation of an argument within an argument buffer.

MTLArgumentEncoder

An object used to encode data into an argument buffer.

Beta Software

This documentation contains preliminary information about an API or technology in development. This information is subject to change, and software implemented according to this documentation should be tested with final operating system software.

Learn more about using Apple's beta software