GPU.js calculating distance between multi objects xyz?

212 Views Asked by At

I try to write some very fast logic to detect all collisions in game.
So I using GPU.js for this and my code was crashing because I trying to create new array variable inside function?
I need list of all objects from constant (loaded to GPU/CPU memory) and this is fastest than in any call with new context.

import {GPU, KernelFunction, IKernelRunShortcut, IConstantsThis, IKernelFunctionThis, Texture} from 'gpu.js';

interface IConstants extends IConstantsThis {
    elementsSize: number,
    elements: Array<[number, number, number]>,
}

interface IThis extends IKernelFunctionThis {
    constants: IConstants,
}

const gpu = new GPU({mode: 'cpu'});

gpu.addFunction<number[]>(function distance(x1, y1, z1, x2, y2, z2) {
    const dx = x1 - x2;
    const dy = y1 - y2;
    const dz = z1 - z2;
    return Math.sqrt(dx * dx + dy * dy + dz * dz);
}, {
    argumentTypes: {x1: 'Float', y1: 'Float', z1: 'Float', x2: 'Float', y2: 'Float', z2: 'Float'},
    returnType: 'Float',
});


const kernelMap = gpu.createKernel(function kernelFunction(this: IThis, objPosition: [number, number, number]) {
    // @ts-ignore
    const d = distance(
        this.constants.elements[0][0],
        this.constants.elements[0][1],
        this.constants.elements[0][2],
        objPosition[0],
        objPosition[1],
        objPosition[2],
    )
    const distances = []; // can't create here new variable
    for (let i = 0; i < this.constants.elementsSize; i++) {
        distances[i] = d;
    }
    return distances;
}, {
    argumentTypes: {objPosition: 'Array(3)'},
    constants: <IConstants>{
        elementsSize: 2,
        elements: [
            [1, 1, 1],
            [2, 2, 2],
            // ... 100k elements
        ],
    },
    output: [1],
    pipeline: true,
    precision: 'single',
    immutable: true
})

const result = kernelMap([0, 0, 0]);

console.log(JSON.stringify(result, null, 2));

How can I check all objects from constants and get result distance of every object?

1

There are 1 best solutions below

0
On

Try this, I'll explain below

import { GPU } from 'gpu.js';

declare type Vec3 = [number, number, number];
function distance(a: Vec3, b: Vec3){
  let dx = a[0] - b[0];
  let dy = a[1] - b[1];
  let dz = a[2] - b[2];
  return Math.sqrt(dx * dx + dy * dy + dz * dz);
}
let elements: Array<Vec3> = [
  [1, 1, 1],
  [2, 2, 2],
  [3, 3, 3],
  // ... 100k elements
]


const gpu = new GPU({mode: 'cpu'});
gpu.addFunction<Vec3[]>(distance, {
  argumentTypes: {
    a: 'Array(3)',
    b: 'Array(3)',
  },
  returnType: 'Float',
});


const kernelMap = gpu.createKernel(
  function kernelFunction(elems: Vec3[], b: Vec3) {
    return distance(elems[this.thread.x], b);
  }, {
    dynamicArguments: true,
    output: [ elements.length ]
  }
)

const result = kernelMap(elements, [0, 0, 0]);

console.log(JSON.stringify(result, null, 2));


// output:
/*
{
  "0": 1.7320507764816284,
  "1": 3.464101552963257,
  "2": 5.196152210235596
}
*/

First of all, if you aren't using this.thread.x in the GPU.js kernel function, you are certainly doing something wrong. I'll try to explain this as easy as possible.

So the kernel function is like the content of the for-loop. If you setOutput([ <number> ]) (an array with single number as argument) then it's a one dimensional for-loop. Therefore, setOutput([ <number>, <number> ]) is 2d, and setOutput([1,2,3]) is 3d.

Then the kernel function is being executed given with this.thread.x this.thread.y this.thread.z, they're just basically i j k. What makes GPU.js kernel function powerful (with downsides) is that it's executed in parallel with a HUGE amount of 'threads' (compared to cpu).

The downside is, the order of execution is uncertain. So you cannot run the kernel function relying on some output in the same for loop (unless you go for chain execution).

(I was thinking to do a side by side comparison with a for loop but I don't have the patience and I think the code is obvious enough)

By the way, as you can see I moved declaration of distance() to the top level so typescript doesn't complain (and therefore no use of //@ts-ignore)