Search code examples
c++copenclglm-math

include c++ libraries into openCL kernel?


Is it possible to utilize C++ style libraries for use in an openCL kernel?

I'm trying to implement a kernel that performs the tasks seen in the following code. There are two things that could make this really difficult: 1. The fact that I'm using the GLM math library, and 2. That I'm using structs (land_map_t).

For example, if I wanted to use a kernel to loop through a large 3-dimensional array, is it possible to include the GLM math library inside of the kernel and utilize its functionalities such as glm::simplex? I've heard that modern C++ functionalities such as classes aren't compatible with kernels.

And if that's not possible, how would one pass a struct to the kernel? should I define the same struct in both the kernel and my implementation? All the struct contains is a 3-dimensional array, so I could easily just turn it into a default C++ type if it was necessary.


land_map_t * Chunk::terrain_gen(glm::ivec3 pos)
{

    float frequency = 500;
    float noise_1;

    land_map_t* landmap = new land_map_t;

    for (int x = 0; x < chunkSize + 2; x++) {
        for (int y = 0; y < chunkSize + 2; y++) {
            for (int z = 0; z < chunkSize + 2; z++) {

                noise_1 = (glm::simplex(
                    glm::vec2(glm::ivec2(x, z) + glm::ivec2(pos.x, pos.z)) / frequency));

                landmap->i[x][y][z] = BLOCK::AIR;
                if (pow(noise_1, 2) * 40.0 + 6.0 > (y + pos.y))
                {
                    landmap->i[x][y][z] = BLOCK::DIRT;
                }
            }
        }
    }

    return landmap;
}



Solution

  • You cannot include C++ libraries in OpenCL C. OpenCL is C99, not C++. There are no classes and only 1D arrays in OpenCL. Within a kernel there is also no dynamic memory allocation possible with the new operator.

    The best solution is to split the class components up into arrays and within each array use linear indexing to get from (x, y, z)=(n%(Lx*Ly)%Lx, n%(Lx*Ly)/Lx, n/(Lx*Ly)) in the rectangular box of the size (Lx,Ly,Lz) to the linear index n=x+(y+z*Ly)*Lx; and back.

    Your code in OpenCL could look like this:

    kernel void terrain_gen(global uchar* landmap_flags, global float3* pos)
        const uint n = get_global_id(0);
        const uint x = n%((chunkSize+2)*(chunkSize+2))%(chunkSize+2);
        const uint y = n%((chunkSize+2)*(chunkSize+2))/(chunkSize+2);
        const uint z = n/((chunkSize+2)*(chunkSize+2))
    
        // paste the SimplexNoise struct definition here
        SimplexNoise simplexnoise;
        simplexnoise.initialize();
    
        const float frequency = 500;
        const float noise_1 = (simplexnoise.noise(x,z)+simplexnoise.noise(pos[n].x, pos[n].z))/ frequency;
        landmap_flags[n] = (noise_1*noise_1*40.0f+6.0f>(y+pos[n].y)) ? BLOCK_DIRT : BLOCK_AIR;
    }
    
    

    Regarding GLM, you have to port over the required functions into OpenCL C. For simplex noise, you can use something like this:

    struct SimplexNoise { // simplex noise in 2D, sources: https://gist.github.com/Ellpeck/3df75965a542e2163d1ae9cf3e4777bb, https://github.com/stegu/perlin-noise/tree/master/src
        const float3 grad3[12] = {
            (float3)( 1, 1, 0), (float3)(-1, 1, 0), (float3)( 1,-1, 0), (float3)(-1,-1, 0),
            (float3)( 1, 0, 1), (float3)(-1, 0, 1), (float3)( 1, 0,-1), (float3)(-1, 0,-1),
            (float3)( 0, 1, 1), (float3)( 0,-1, 1), (float3)( 0, 1,-1), (float3)( 0,-1,-1)
        };
        const uchar p[256] = {
            151,160,137, 91, 90, 15,131, 13,201, 95, 96, 53,194,233,  7,225,140, 36,103, 30, 69,142,  8, 99, 37,240, 21, 10, 23,190,  6,148,
            247,120,234, 75,  0, 26,197, 62, 94,252,219,203,117, 35, 11, 32, 57,177, 33, 88,237,149, 56, 87,174, 20,125,136,171,168, 68,175,
             74,165, 71,134,139, 48, 27,166, 77,146,158,231, 83,111,229,122, 60,211,133,230,220,105, 92, 41, 55, 46,245, 40,244,102,143, 54,
             65, 25, 63,161,  1,216, 80, 73,209, 76,132,187,208, 89, 18,169,200,196,135,130,116,188,159, 86,164,100,109,198,173,186,  3, 64,
             52,217,226,250,124,123,  5,202, 38,147,118,126,255, 82, 85,212,207,206, 59,227, 47, 16, 58, 17,182,189, 28, 42,223,183,170,213,
            119,248,152,  2, 44,154,163, 70,221,153,101,155,167, 43,172,  9,129, 22, 39,253, 19, 98,108,110,79,113,224,232,178,185, 112,104,
            218,246, 97,228,251, 34,242,193,238,210,144, 12,191,179,162,241, 81, 51,145,235,249, 14,239,107, 49,192,214, 31,181,199,106,157,
            184, 84,204,176,115,121, 50, 45,127,  4,150,254,138,236,205, 93,222,114, 67, 29, 24, 72,243,141,128,195, 78, 66,215, 61,156,180
        };
        const float F2=0.5f*(sqrt(3.0f)-1.0f), G2=(3.0f-sqrt(3.0f))/6.0f; // skewing and unskewing factors for 2, 3, and 4 dimensions
        const float F3=1.0f/3.0f, G3=1.0f/6.0f;
        const float F4=(sqrt(5.0f)-1.0f)*0.25f, G4=(5.0f-sqrt(5.0f))*0.05f;
        uchar perm[512]; // to remove the need for index wrapping, double the permutation table length
        uchar perm12[512];
        //int floor(const float x) const { return (int)x-(x<=0.0f); }
        float dot(const float3 g, const float x, const float y) const { return g.x*x+g.y*y; }
        void initialize() {
            for(int i=0; i<512; i++) {
                perm[i] = p[i&255];
                perm12[i] = (uchar)(perm[i]%12);
            }
        }
        float noise(float x, float y) const { // 2D simplex noise
            float n0, n1, n2; // noise contributions from the three corners, skew the input space to determine simplex cell
            float s = (x+y)*F2; // hairy factor for 2D
            int i=floor(x+s), j=floor(y+s);
            float t = (i+j)*G2;
            float X0=i-t, Y0=j-t; // unskew the cell origin back to (x,y) space
            float x0=x-X0, y0=y-Y0; // the x,y distances from the cell origin
            // for the 2D case, the simplex shape is an equilateral triangle, determine simplex
            int i1, j1; // offsets for second (middle) corner of simplex in (i,j) coords
            if(x0>y0) { i1=1; j1=0; } // lower triangle, XY order: (0,0)->(1,0)->(1,1)
            else /**/ { i1=0; j1=1; } // upper triangle, YX order: (0,0)->(0,1)->(1,1)
            float x1=x0-  i1+     G2, y1=y0-  j1+     G2; // offsets for middle corner in (x,y) unskewed coords
            float x2=x0-1.0f+2.0f*G2, y2=y0-1.0f+2.0f*G2; // offsets for last corner in (x,y) unskewed coords
            int ii=i&255, jj=j&255; // work out the hashed gradient indices of the three simplex corners
            int gi0 = perm12[ii   +perm[jj   ]];
            int gi1 = perm12[ii+i1+perm[jj+j1]];
            int gi2 = perm12[ii+ 1+perm[jj+ 1]];
            float t0 = 0.5f-x0*x0-y0*y0; // calculate the contribution from the three corners
            if(t0<0) n0 = 0.0f; else { t0 *= t0; n0 = t0*t0*dot(grad3[gi0], x0, y0); } // (x,y) of grad3 used for 2D gradient
            float t1 = 0.5f-x1*x1-y1*y1;
            if(t1<0) n1 = 0.0f; else { t1 *= t1; n1 = t1*t1*dot(grad3[gi1], x1, y1); }
            float t2 = 0.5f-x2*x2-y2*y2;
            if(t2<0) n2 = 0.0f; else { t2 *= t2; n2 = t2*t2*dot(grad3[gi2], x2, y2); }
            return 70.0f*(n0+n1+n2); // add contributions from each corner to get the final noise value, result is scaled to stay inside [-1,1]
        }
    };