Compute shader float arithmetic is really inaccurate

436 views Asked by At

I am currently writing a compute shader that requires a simple float multiplication of a float and a uint3, and I am getting really inaccurate results.

for example, uint3(1, 2, 3) * 0.25
expected result: uint3(0.25, 0.5, 0.75)
actual result: uint3(0.3, 0.5, 0.8)

Any idea how to increase the precision somehow?

The compute shader:

[numthreads(X_THREADS, Y_THREADS, Z_THREADS)]
void CSMain (uint3 id : SV_DispatchThreadID)
{
    // Fill the array
    uint tid = id.x;
    while (tid < octree_length)
    {
        // Normalize tid
        uint depth = OctreeDepth(tid);
        uint mid = tid - depth_offsets[depth];

        // Compute node data
        float node_size = bounds_size / pow(2, depth);
        float3 node_min = MortonToXYZ(mid) * node_size; 

        // Build node
        octree[tid].anchor = node_min;
        octree[tid].size = node_size;

        // Move tid
        tid += total_num_threads;
    }
}

The inaccurate multiplication:

float3 node_min = MortonToXYZ(mid) * node_size; 
  • mid: a Morton code for this thread
  • MortonToXYZ: returns uint3 with elements between [0, infinity], this is a decoded Morton code
  • node_size: a float between [0, infinity]

The code that launches the shader and reads the data back:

ComputeBuffer BuildOctree(Vector3 boundsMin, float boundsSize, int octreeDepth, Vector3Int numThreadGroups) 
{
    // Compute constants
    int totalNumThreads = numThreadGroups.x * numThreadGroups.y * numThreadGroups.z * THREADS_PER_GROUPS;

    // Compute depth offsets and octree length
    int[] depthOffsets = new int[octreeDepth];
    int length = 0;

    for (int i = 0; i < octreeDepth; i++)
    {
        depthOffsets[i] = length;
        length += (int) Mathf.Pow(8, i);
    }

    // Prepare buffers
    ComputeBuffer octree = new ComputeBuffer(length, 4 * sizeof(float), ComputeBufferType.Structured);
    ComputeBuffer depthOffsetsGPU = new ComputeBuffer(octreeDepth, sizeof(int), ComputeBufferType.Structured);

    depthOffsetsGPU.SetData(depthOffsets);
    octree.SetData(new OctreeNode[length]);

    // Load data into shader
    OCTREE_BUILDER.SetBuffer(0, "octree", octree);
    OCTREE_BUILDER.SetBuffer(0, "depth_offsets", depthOffsetsGPU);
    OCTREE_BUILDER.SetInt("total_num_threads", totalNumThreads);
    OCTREE_BUILDER.SetInt("octree_length", length);
    OCTREE_BUILDER.SetFloat("bounds_size", boundsSize);

    // Launch kernal
    OCTREE_BUILDER.Dispatch(0, numThreadGroups.x, numThreadGroups.y, numThreadGroups.z);

    OctreeNode[] output = new OctreeNode[length];
    octree.GetData(output);

    for (int i = 0; i < output.Length; i++)
        Debug.Log("cell[" + i + "]: " + output[i].anchor + ", " + output[i].size);

    // Return octree buffer
    return octree;
}

Note:\

  • I have tried a minimal example of just computing uint3(1, 1, 1) * 0.25
    expected result: uint3(0.25, 0.25, 0.25)
    actual result: uint3(0.3, 0.3, 0.3)\
  • I am using a RTX 2070
1

There are 1 answers

0
niv shalom On BEST ANSWER

As user @bart pointed out, the issue was in the print, unity prints a 2 digit format which rounded my values down, by using ToString("F5") in my print it shows the correct values