How to use setFragmentBuffer(:::) function

Hello Caroline, today I read Apple’s document and would u please tell me how to use setFragmentBuffer(:::slight_smile: function

setFragmentBuffer is equivalent to setVertexBuffer, but used with the fragment function instead of the vertex function.

For the vertex function, we used setVertexBytes wherever we could, in order not to have to create a vertex buffer. When the data was too big for setVertexBytes, then we used setVertexBuffer and sent a buffer to the GPU.

Similarly, until chapter 14, we were able to use setFragmentBytes to send data to the fragment function. But in chapter 14, the number of lights became too many to send using setFragmentBytes, so we switched the data to be held in a buffer and used setFragmentBuffer.

Hi Caroline, you saved my day, week and month with your tutorials on Metal. Thanks.

I use setFragmentBuffer to send an array of textures and uvMatrices to the fragment shader. It works, but I doesn’t work with several submeshes.

Each submesh could have several textures/samples/uvMatrix, so when I draw I iterate through the submeshes then for each submesh I iterate through the submesh’s textures.

for(i = 0; i < submesh.textures.count; i++)
{
texture = submesh.textures[i];
setTexture:texture.image
setSamplerState:texture.sampler

simd_float3x3 *uvMatrixAddress = [argumentEncoder constantDataAtIndex:MMArgumentBufferIDuvMatrices+totTexUsedForMesh];
*uvMatrixAddress = texture.uvMatrix;
}

Then I draw the submesh

[cmdEncoder setFragmentBuffer:argumentBuffer offset:0 atIndex:0];

[cmdEncoder drawIndexedPrimitives:submesh.primitiveType indexCount:submesh.indexCount indexType:submesh.indexType indexBuffer:submesh.indexBuffer.buffer indexBufferOffset:submesh.indexBuffer.offset];

Well, it works just with one mesh. When I draw more than one mesh, all the meshes get the same texture/sampler/uvMatrix as the last mesh.

I surely miss something but I can’t guess what. May you please cast a light to this topic? Thanks.

I’m glad the book is helping :slight_smile:, and welcome to the Metal community!

It’s difficult to tell what’s going wrong with just snippets of code. The problem could be anywhere. (And my Obj-C is very rusty!)

I do wonder why you have an assignment to uvMatrixAddress twice. Doesn’t the second one overwrite the first one?

Also, to format code, you surround it with back ticks.

For a section of code it’s three back ticks eg:

// This section is surrounded by ```
[cmdEncoder setFragmentBuffer:argumentBuffer offset:0 atIndex:0];

For a short piece of code like these words, surround it with one backtick.

Thanks Caroline. I write the code in a clear way.

#define SBFragmentBufferIndexArguments 0

typedef enum SBArgumentBufferID
{
    SBArgumentBufferIDTextures  = 0,
    SBArgumentBufferIDSamplers   = 100,
    SBArgumentBufferIDuvMatrices = 200
} SBArgumentBufferID;


struct FragmentShaderArguments {
    array<texture2d<float>, 8> textures  [[ id(SBArgumentBufferIDTextures)  ]];
    array<sampler, 8> uvSampler  [[ id(SBArgumentBufferIDSamplers)  ]];
    array<simd_float3x3, 8> uvMatrix[[ id(SBArgumentBufferIDuvMatrices)  ]];
};


fragment half4 DefaultFragmentShader(VertexOut vertexIn [[ stage_in ]],
                                device FragmentShaderArguments &args [[buffer(SBFragmentBufferIndexArguments)]])
{  
    float4 textureColor = vertexIn.meshColor;

    for(uint32_t i = 0; i < 8; i++)
    {
        if(is_null_texture(args.textures[i])) break;
        
        sampler              uvSampler = args.uvSampler[i];
        simd_float3x3   uvMatrix = args.uvMatrix[i];
        float2                 uvTex = (uvMatrix * float3(vertexIn.uvTex.xy, 1)).xy;
        float4                 textureValue = args.textures[i].sample(uvSampler, uvTex);

        textureColor.rgb = textureColor.rgb * (1 - textureValue.a) + textureValue.rgb * textureValue.a;
    }

   ...
}

At launch, I set up the argumentBuffer

_mArgumentEncoder = [pipelineStateDescriptor.fragmentFunction newArgumentEncoderWithBufferIndex:SBFragmentBufferIndexArguments];

    NSUInteger argumentBufferLength = _mArgumentEncoder.encodedLength;
    _mFragmentShaderArgumentBuffer = [device newBufferWithLength:argumentBufferLength options:0];
    _mFragmentShaderArgumentBuffer.label = @"Argument Buffer Fragment Shader";
    [_mArgumentEncoder setArgumentBuffer:_mFragmentShaderArgumentBuffer offset:0];

Then at each render pass, I draw the submeshes

for(submesh in submeshes)
{
     totTexUsed = 0;

     // for now I set nil here, so the frag shader knows how many textures to draw
     // On the next version I will add a texturesCount to let the shader knows
     [mArgumentEncoder setTexture:nil atIndex:SBArgumentBufferIDTextures + totTexUsed];

     for(i = 0; i < submesh.textures.count; i++)
     {
          texture = submesh.textures[i];

          [mArgumentEncoder setSamplerState:texture.sampler atIndex:SBArgumentBufferIDSamplers+totTexUsed];

          [cmdEncoder useResource:texture.image usage:MTLResourceUsageRead stages:MTLRenderStageFragment];
           [mArgumentEncoder setTexture:texture.image atIndex:SBArgumentBufferIDTextures + totTexUsed];

          simd_float3x3 *uvMatrixAddress = [mArgumentEncoder constantDataAtIndex: SBArgumentBufferIDuvMatrices + totTexUsed];
          *uvMatrixAddress = texture.uvMatrix;

          totTexUsed++;
     }

     // Then I draw the submesh

     [cmdEncoder setFragmentBuffer:mFragmentShaderArgumentBuffer offset:0 atIndex:SBFragmentBufferIndexArguments];

     [cmdEncoder drawIndexedPrimitives:submesh.primitiveType indexCount:submesh.indexCount indexType:submesh.indexType indexBuffer:submesh.indexBuffer.buffer indexBufferOffset:submesh.indexBuffer.offset];
}

If I have one only submesh, everything works well, but when the submeshes are 2 or more, all the submeshes get the same group of textures as the last submesh. It seems to me that the setFragmentBuffer can be set just once at each render pass, not just before each drawIndexedPrimitives command. I probably miss something here.

I do wonder why you have an assignment to uvMatrixAddress twice. Doesn’t the second one overwrite the first one?

The first line gets the pointer to the constant data.

simd_float3x3 *uvMatrixAddress = [mArgumentEncoder constantDataAtIndex: SBArgumentBufferIDuvMatrices + totTexUsed]; 

The second line assigns the texture.uvMatrix to that pointer; I presume.

*uvMatrixAddress = texture.uvMatrix;

I would be glad to get your suggestion. Thanks.

I can’t see anything obviously wrong.

Have you inspected with the GPU frame capture?

Check that the list of commands is correct, and that the buffers and textures contain what you think they should contain.

If you haven’t seen it, Chapter 26’s challenge code is an example of rendering multiple submeshes. However, it doesn’t have texture arrays as yours appears to - it’s difficult to tell what’s happening without all your code.

If you run the challenge code and capture the GPU frame, you’ll see the textures as indirect resources. Have you checked that your textures are on the GPU correctly as indirect resources?

Screenshot 2022-09-21 at 4.34.58 pm

Caroline,
I modified the Apple sample code “BasicArgumentBuffers” and I got the same behaviour with the fragmentArgumentBuffer.

As I understand, I should use a different fragmentArgumentBuffer per submesh. In fact, if I set the textures for the submesh A (in the fragmentArgumentBuffer A), then I drawPrimitive A, then I modify the textures for the submesh B (in the same fragmentArgumentBuffer A) then I drawPrimitive B, both the submeshes A and B get the B textures, that is the last fragmentArgumentBuffer settings I set. So, I thought, I should create a new fragmentArgumentBuffer and setArgumentBuffer for each submesh.

On the other hands, I couldn’t suppose that anytime I call drawPrimitive, the current fragmentArgumentBuffer values get copied and stored in the GPU once again.

Am I right or wrong?

I’m not in front of my computer for a couple of days :confused:.

Did you check the sample code from Chapter 26 that uses multiple submeshes?

@lorenzo_dev - You shouldn’t need different buffers per submesh. The Chapter 26 sample doesn’t use different buffers.

I wonder if I am understanding what you are trying to do. It seems very complicated. Do you really need 8 sampler states for each submesh?

It appears that for each submesh you have 8 textures, and when you render each fragment, you iterate through all the textures adding the texture to the color of the fragment? Is that your intent?

Also, what device are you targeting? There are limits with argument buffers on some hardware. https://developer.apple.com/metal/Metal-Feature-Set-Tables.pdf. This doesn’t seem to show the limits any more, just the availability.

I’m coding on a
MacBook Pro 16" late 2019, 2.3 GHz 8-Core Intel Core i9
macOS 12.1
GPU AMD Radeon Pro 5500M
MTLGPUFamilyMac1 , MTLGPUFamilyMac2, MTLGPUFamilyCommon1, MTLGPUFamilyCommon2, MTLGPUFamilyCommon3.

I created 4 samplers only, then I reuse those on several MTLBuffer(s).
Yes, actually I’m using a different argument buffer per submesh, and I guess if there is a limit, I could hit that limit soon. I would rather use one only one, as you suggest. I would like to study your Chapter 26th but today your web site refused the purchase of your “Metal by Tutorial”. Never happened before with my credit card. I contacted the support and I’m waiting for their reply.

I checked the Apple Metal Feature pdf and I read

Maximum number of entries in the buffer argument table, per graphics or kernel function = 31

but I can’t understand what “entries” exactly means. I have the following settings. Should I consider each “id” as one entry? Or the <array 8> means 8 entries? Has my struct Light 15 entries? If so, should I add it to he total count? Actually it seems I have about 50 entries (>31) and it works. So I should better understand what this number means.

#define SBFragmentBufferIndexArguments  0
#define SBFragmentBufferIndexLights     1

typedef enum SBArgumentBufferID
{
    SBArgumentBufferIDTexturesCount = 0,
    SBArgumentBufferIDTextures  = 1,
    SBArgumentBufferIDSamplers   = 9,
    SBArgumentBufferIDuvMatrices = 17,
    SBArgumentBufferIDBlendMode = 26,
    SBArgumentBufferIDBlendOpacity = 34,
    SBArgumentBufferIDMaterial = 42,
    
    SBArgumentBufferIDLightsCount = 0, 
    SBArgumentBufferIDLights = 1
} SBArgumentBufferID;

// Can SBArgumentBufferIDLightsCount be zero, or should it follow the previous index 42, so should it be 43?

typedef struct
{
    simd_float4     color;
    float           ambient;
    float           diffuse;
    float           specular;
    float           shininess;
    float           emission;
} Material;

struct FragmentShaderArguments {
    int8_t                  texCount    [[ id(SBArgumentBufferIDTexturesCount) ]];
    array<texture2d<float>, 8> textures [[ id(SBArgumentBufferIDTextures) ]];
    array<sampler, 8>       uvSampler   [[ id(SBArgumentBufferIDSamplers) ]];
    array<simd_float3x3, 8> uvMatrix    [[ id(SBArgumentBufferIDuvMatrices) ]];
    array<int8_t, 8>        blendMode   [[ id(SBArgumentBufferIDBlendMode) ]];
    array<float, 8>         blendOpacity [[ id(SBArgumentBufferIDBlendOpacity) ]];
    Material                material    [[ id(SBArgumentBufferIDMaterial) ]];
};

// Maybe here above I should rather use only one array of 8 "Texture" structs each one including all the other entries as uvSampler, uvMatrix…

typedef struct
{
    bool        on;
    simd_float3 position;
    simd_float3 target;
    simd_float4 direction;
    simd_float4 color;
    int8_t      kind;
    float       spotAngle;
    float       focus;
    
    float  brightness;
    float  ambientIntensity;
    float  diffuseIntensity;
    float  specularIntensity;
    
    float  decayConstant;
    float  decayLinear;
    float  decayQuadratic;
}
Light;

struct FragmentShaderLights {
    int8_t                  count       [[ id(SBArgumentBufferIDLightsCount) ]];
    array<Light, 8>         lights      [[ id(SBArgumentBufferIDLights) ]];
};


fragment half4 DefaultFragmentShader(VertexOut vertexIn [[ stage_in ]],
                                device FragmentShaderArguments &args  [[buffer(SBFragmentBufferIndexArguments)]],
                                device FragmentShaderLights    &lamps [[buffer(SBFragmentBufferIndexLights)]])
{

}

Thanks

Entries in the buffer argument table means the buffer index that you set when transferring to the GPU.

For example, if I set ParamsBuffer = 32 and then do this:

computeEncoder.setBuffer(paramsBuffer, offset: 0, index: ParamsBuffer.index)

I’ll get a compile error ‘buffer’ attribute parameter is out of bounds: must be between 0 and 30.

It doesn’t mean the number of buffers held overall - you can have as many as your memory will hold of those.

Your indices greater than 31 are ids rather than entries into the buffer argument table. The buffer argument table entry containing your ids is SBFragmentBufferIndexArguments, which is index 0.

Have you looked at the Apple samples and watched the videos on this topic? Apple Metal documentation has vastly improved over the last couple of years.

This one in particular addresses argument buffers with arrays: Apple Developer Documentation

There’s a whole section in the documentation on buffers and argument buffers with several sample projects:

https://developer.apple.com/documentation/metal/buffers

The Metal by Tutorials book code isn’t as complex as this, and I don’t know that it will help you, except with the basics. The code that goes with the book is freely available here: GitHub - raywenderlich/met-materials: The projects and the materials that accompany the Metal by Tutorials book.

Hi Caroline, I’m studying on your book. I will better reply later, when ready.

1 Like