1
votes

I have a per-instance uniform buffer where each element in the buffer is 64-bytes but I only use the first 16-bytes (float3) of each element in the vertex shader. I set the stride up to describe this. The problem is it does not stride over the other 48 bytes unless I add padding to the struct in the shader so that it is also 64 bytes.

// Particle Instance Position
vertexDescriptor.attributes[2].format = .Float3   // 16 bytes with padding.     
vertexDescriptor.attributes[2].offset = 0
vertexDescriptor.attributes[2].bufferIndex = 2
vertexDescriptor.layouts[2].stride = strideof(Particle)
vertexDescriptor.layouts[2].stepFunction = .PerInstance

...

commandEncoder.setVertexBuffer(instanceUniformBuffer, offset:0, atIndex:2)

App side particle struct:

struct Particle {

    var position = float3()
    var prevPos = float3()
    var attractPoint = float3()

    var ref: DataRef!
    var state = State.Free

    enum State: Int {
        case Free = 0
        case Active
    }
}

And here's the corresponding Metal struct, i.e. Particle.position corresponds to InstanceUniforms.instanceTranslate in the shader. I expect the stride setup above to mean that this gets loaded with the Particle.position for each instance and the other 48 bytes of each Particle in the buffer is skipped over.

struct InstanceUniforms
{
    float3 instanceTranslate [[ attribute(2) ]];
};

Some sanity checks, everything makes sense:

sizeof(float3) 16
alignof(float3) 16
sizeof(Particle) 57
alignof(Particle) 16
strideof(Particle) 64

But it doesn't work unless I pad the shader struct out to 64 bytes:

struct InstanceUniforms
{
    float3 instanceTranslate [[ attribute(2) ]];
    float4 pad[3];
};

Otherwise for the second instance the shader actually sets instanceTranslate to the Particle.prevPos of the first element in the buffer, like it only strides over the size of the InstanceUniforms struct, regardless of what the stride is set to in the vertex descriptor.

I'm sure I must be doing something wrong here, it seems like you shouldn't need to pad your shader structs.

1

1 Answers

2
votes

So it seems like this is Just The Way It Is™ with Metal buffers: you have to pad the shader struct to match the stride of the app struct. I wouldn't think it would be that difficult for the Metal shader runtime to load stride bytes from the buffer and cast the first sizeof(MyShaderStruct) of them. Or autopad the struct when the pipeline state is attached. But these conveniences probably have efficiency implications.

Here's what my Swift / Shader structs look like now for reference:

Metal:

struct Particle
{
   float3 position;
   float3 prevPos;
   float4 color;
   float4 lineColor;
   float  scale;
   char   pad[60];
};

Swift:

struct Particle {
    var position = float3()
    var prevPos = float3()
    var color = float4()
    var lineColor = float4()
    var scale:Float = 0
    var gravity = float3()

    var data: DataRef!
    weak var physics: Physics? = nil
    var state = State.Free

    enum State: Int {
        case Free = 0
        case Active
    }
}

sizeof(Particle) 113
alignof(Particle) 16
strideof(Particle) 128