<p>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.</p>
<p>I am visualizing the complexity of rendering each voxel and that creates this image:</p>
<p><a href="https://i.sstatic.net/GVl6A.png" rel="nofollow noreferrer"><img src="https://i.sstatic.net/GVl6A.png" alt="enter image description here" /></a></p>
<p>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).</p>
<p>The logic for my hash table is encapsulated by these shader functions:</p>
<p>hash_function.glsl</p>
<pre class="lang-cpp prettyprint-override"><code>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 &gt;&gt; lod);
    int hash = HashFunction(coord, local_size);
    vec4 read_hash = hash_tree[hash + offset].value;

    uint safety = 0;
    while(read_hash.a != 0.f &amp;&amp; hash_tree[hash + offset].key.xyz != coord)
    {
        hash = (hash * 7) % local_size;
        read_hash = hash_tree[hash + offset].value;
        if(safety++ &gt; 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 &gt;&gt; lod);
    int hash = HashFunction(coord, local_size);
    float read_hash = hash_tree[hash + offset].value.a;

    uint safety = 0;
    while(read_hash != 0.f &amp;&amp; hash_tree[hash + offset].key.xyz != coord)
    {
        hash = (hash * 7) % local_size;
        read_hash = hash_tree[hash + offset].value.a;
        if(safety++ &gt; 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 &gt;&gt; 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 &gt;&gt; lod;
    vec3 final = (position + vec3(1)) * (lod_resolution / 2.f);
    final.y = lod_resolution - final.y;

    return ivec3(final);
}
</code></pre>
<p>As said before, this logic works. Now observer what happens if I change offset from 0 to <code>offset = 1;</code></p>
<p><a href="https://i.sstatic.net/JbDWn.png" rel="nofollow noreferrer"><img src="https://i.sstatic.net/JbDWn.png" alt="enter image description here" /></a></p>
<p>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).</p>
<p>If I eliminate the modulus operation from the hash function and merely return <code>return abs(coord.x * p3 + coord.y * p2 + coord.z * p1)</code>, I get this:</p>
<p><a href="https://i.sstatic.net/hLjC4.png" rel="nofollow noreferrer"><img src="https://i.sstatic.net/hLjC4.png" alt="enter image description here" /></a></p>
<p>Because now I am using the entire allocated span of the SSBO (128^3). So no collisions happen.</p>
<p>I am puzzled, how can a simple addition royally break shader logic like this?</p>

CC BY-SA 4.0
2021-07-10T21:20:21.633Z
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?
Nathan Reed
CC BY-SA 4.0
2021-07-10T21:37:25.177Z
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?
Makogan
CC BY-SA 4.0
2021-07-10T21:42:52.413Z
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.
Nathan Reed
CC BY-SA 4.0
2021-07-10T21:44:39.827Z
I will look into it and try to enable them, thank you for the tip.
Makogan
<shader><vulkan><compute-shader><voxelization>
Offsetting value in shader causes unexpected/wrong logic
Makogan
# Offsetting value in shader causes unexpected/wrong logic

(Asked by: username_0 on 2021-07-10)

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](https://i.sstatic.net/GVl6A.png)](https://i.sstatic.net/GVl6A.png)

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

```cpp
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](https://i.sstatic.net/JbDWn.png)](https://i.sstatic.net/JbDWn.png)

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](https://i.sstatic.net/hLjC4.png)](https://i.sstatic.net/hLjC4.png)

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?

**Tags:** shader, vulkan, compute-shader, voxelization

> **Comments:**
> **username_1**: 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?  
> **username_0**: 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?  
> **username_1**: 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.  
> **username_0**: I will look into it and try to enable them, thank you for the tip.  

---

## 0 answers

