How to index a ByteAddressBuffer in HLSL

Hi Unity forums!

I’m trying to access a ByteAddressBuffer in HLSL. The buffer contains vertices. I want to write a function that, given an index, will return the float3 at that index.

My current understanding is that the buffer stores a series of bytes which can be accessed in chunks of 4 bytes (and from testing I believe that accessing a non-aligned address rounds the index down to a multiple of 4).

Here is my function:

float3 load_vertex(int i) {
    int i_location = i * 12;
    float3 ret = float3(0.0f, 0.0f, 0.0f);
    ret.x = asfloat(vertices.Load(i_location    ));
    ret.y = asfloat(vertices.Load(i_location + 4));
    ret.z = asfloat(vertices.Load(i_location + 8));
    return ret;
}

I think that this should work: first the i_location shifts the index by 12 times the index (3 floats = 12 bytes) and then the values are accessed in steps of 4 (1 float = 4 bytes).

However, when I use this function and return the value, only the float3 returned at index 0 is correct. All other float3 values are erroneous.

Just in case I’m doing something mega-stupid, here’s how I’m returning the values:

ByteAddressBuffer vertices;
ByteAddressBuffer indices;
RWStructuredBuffer<float3> result;
int index;

float3 load_vertex(int i) {...}

[numthreads(256,1,1)]
void CSMain (uint3 id : SV_DispatchThreadID)
{
    if (id.x == 0) {
        result[0] = load_vertex(index);
    }
}

If it’s relevant, buffer is being set and the shader dispatched like so:

mesh.vertexBufferTarget |= GraphicsBuffer.Target.Raw;
mesh.indexBufferTarget |= GraphicsBuffer.Target.Raw;
        
GraphicsBuffer vertexBuffer = mesh.GetVertexBuffer(0);
GraphicsBuffer indexBuffer = mesh.GetIndexBuffer();

closestPointShader.SetBuffer(0, "vertices", vertexBuffer);
closestPointShader.SetBuffer(0, "indices", indexBuffer);
closestPointShader.SetBuffer(0, "result", outBuffer);
closestPointShader.SetInt("index", indexValue);

closestPointShader.Dispatch(0, 1, 1, 1);

Vector3[] k = new Vector3[1];
outBuffer.GetData(k);
Debug.Log("On GPU: " + k[0]);
Debug.Log("On CPU: " + mesh.vertices[indexValue]);

Thank you!

So as it turns out, the stride of each element in the vertex buffer is 56 rather than 12. Replacing line 2 of the function with

int i_location = i * 56;

Makes it work perfectly.

If anyone could enlighten me as to what is being stored in the other 44 bytes that would be great because I have no idea.

Vertex buffer can contain a lot more than positions like normals, uvs, etc. Check here : Unity - Scripting API: Mesh.GetVertexAttributes

1 Like

hi, how do you read the buffer of “indices”? use chunks of 4 bytes?
i tried like this but the result not right:

float3 getVertex(uint id){
    uint vId = asuint( _IndexBuffer.Load(id*4));
    vId*=56;//the vertex buffer which unity provides has a stride of 56 bytes rather than 12 bytes
    float3 ret = 0;
    ret.x = asfloat(_VertexBuffer.Load(vId));
    ret.y = asfloat(_VertexBuffer.Load(vId+4));
    ret.z = asfloat(_VertexBuffer.Load(vId+8));
    return ret;
}

[numthreads(512,1,1)]
void CSMain (uint3 id : SV_DispatchThreadID)
{
    if(id.x>_TriCount)return;

    uint pId = id.x*3;

    float4 a = mul(_TargetObjectToWorld, float4(getVertex(pId),1));
    float4 b = mul(_TargetObjectToWorld, float4(getVertex(pId+1),1));
    float4 c = mul(_TargetObjectToWorld, float4(getVertex(pId+2),1));
...

For anyone finding this thread in the fututre and struggling with the indices buffer, I figured it out. Unity by default stores indices as 16 bit ushorts. The trouble is, ByteAddressBuffer only allows you to read data in 32 bit chunks. To get around this, you have to calculate the correct 32bit chunk that a given index lives within, and then take the top or bottom 16 bits depending on if the index is even or odd. Here is a function I wrote to do that:

ByteAddressBuffer indexBuffer;

uint load_index(uint i){
    uint buffer_location = (i/2)*4;
    uint raw = indexBuffer.Load(buffer_location);
    if (i%2 == 0){
        return raw & 0xFFFF;
    }else{
        return (raw & 0xFFFF0000)>>16;
    }
}