0
$\begingroup$

Let me explain what I am doing. I am trying to reduce the memory footprint of a voxel based algorithm by storing the voxel data as a hash map in an SSBO, as opposed to a 3D image.

I am visualizing the complexity of rendering each voxel and that creates this image:

enter image description here

The more white a voxel is, the more expensive to compute it is. So most voxels are dark grey and a few are a little more clear. This means my algorithm seems to be working quite well. This represents a reduction from 128^3 to about 25 000 buckets of total memory used (i.e. the ssbo hash table only has 25k allocated indices). And it is rendering at about 40 fps on average (depends on view port location).

The logic for my hash table is encapsulated by these shader functions:

hash_function.glsl

layout(binding = 3) uniform VoxelData
{
    int resolution;
    uint display_width;
    uint display_height;
    float scale;
    vec3 locus;
};

struct Node
{
    ivec4 key;
    vec4 value;
};

layout(scalar, binding = 4) buffer hash_tree_ssbo
{
    int depth;
    int depth_size;
    int size;
    int pad2;
    Node hash_tree[];
};

int HashFunction(ivec3 coord, int m)
{
    const int p1 = 7979;
    const int p2 = 3229;
    const int p3 = 7877;

    return abs(coord.x * p3 + coord.y * p2 + coord.z * p1) % 25000;
}

vec4 ReadValue(ivec3 coord, int lod)
{
    int offset = 0;

    const int local_size = int(size >> lod);
    int hash = HashFunction(coord, local_size);
    vec4 read_hash = hash_tree[hash + offset].value;

    uint safety = 0;
    while(read_hash.a != 0.f && hash_tree[hash + offset].key.xyz != coord)
    {
        hash = (hash * 7) % local_size;
        read_hash = hash_tree[hash + offset].value;
        if(safety++ > 10) return vec4(0);
    }
    
    if(hash_tree[hash + offset].key.xyz == coord)
        return hash_tree[hash + offset].value;

    return vec4(0);
}

void StoreValue(vec4 val, ivec3 coord, int lod)
{
    int offset = 0;

    const int local_size = int(size >> lod);
    int hash = HashFunction(coord, local_size);
    float read_hash = hash_tree[hash + offset].value.a;

    uint safety = 0;
    while(read_hash != 0.f && hash_tree[hash + offset].key.xyz != coord)
    {
        hash = (hash * 7) % local_size;
        read_hash = hash_tree[hash + offset].value.a;
        if(safety++ > 10) return;
    }

    val = vec4(vec3(float(safety + 1)) / 10.f, 1);

    hash_tree[hash + offset].value = val;
    hash_tree[hash + offset].key = ivec4(coord, 0);
}

ivec3 WorldPositionToVoxelCoords(vec3 position, int lod)
{
    const uint lod_resolution = resolution >> lod;
    const vec3 relative_pos = position - locus;
    ivec3 texel = ivec3( (vec3(0.5) + relative_pos / (2.f * scale) ) * lod_resolution );
    texel.y = int(lod_resolution) - texel.y;

    return texel;
}

ivec3 NormalizedPositionToVoxelCoords(vec3 position, int lod)
{
    const uint lod_resolution = resolution >> lod;
    vec3 final = (position + vec3(1)) * (lod_resolution / 2.f);
    final.y = lod_resolution - final.y;

    return ivec3(final);
}

As said before, this logic works. Now observer what happens if I change offset from 0 to offset = 1;

enter image description here

Now almost every voxel that requires multiple hashes disappears. And yet, the logic when offset = 1 and when offset = 0 should be identical, save that the SSBO accesses should be shifted by a little (I am changing BOTH instances of offset = 1, I did not forget to update one but not the other).

If I eliminate the modulus operation from the hash function and merely return return abs(coord.x * p3 + coord.y * p2 + coord.z * p1), I get this:

enter image description here

Because now I am using the entire allocated span of the SSBO (128^3). So no collisions happen.

I am puzzled, how can a simple addition royally break shader logic like this?

$\endgroup$
4
  • $\begingroup$ It shouldn't. Have you looked at the SPIR-V output to rule out a compiler bug (at least at the glsl level)? Have you tried turning on VK's "GPU-assisted validation" layer? $\endgroup$ Jul 10 at 21:20
  • $\begingroup$ It seems that I was running into undefined behaviour. There was a "rogue" function (a function that should have been deleted but wasn't) that was deleting 100% of the SSBO data each frame. This in turn made the variables int depth; int depth_size; int size; all 0, which lead t some funky UB in the GPU side. I have the standard validation layers enabled, but it seems that you are talking about a different layer set? $\endgroup$
    – Makogan
    Jul 10 at 21:37
  • 1
    $\begingroup$ Yeah, GPU-assisted validation is an extra thing that instruments your shaders to detect bad descriptors and stuff as the shader tries to access them. Might not have helped in this case, and costs some perf, but it's a good thing to have available to debug shader issues. $\endgroup$ Jul 10 at 21:42
  • $\begingroup$ I will look into it and try to enable them, thank you for the tip. $\endgroup$
    – Makogan
    Jul 10 at 21:44

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Browse other questions tagged or ask your own question.