Running Simulations on the Graphics Processing Unit (GPU)

CS 493/693 Lecture, Dr. Lawlor

This is all well and good for drawing pictures, but there are lots of other problems out there that don't involve pictures in any way.  Or do they?  All computation is just data manipulation, and we can write *anything* in a pixel shader--floats are floats, after all!

Deep down, the GPU supports a fairly small number of primitives:
The only annoying part is that though a pixel program can read from any location on any texture it likes, it can only write to its own pixel.  And no, you can't bind the same texture for both reads and writes (at least not reliably).  Hey, that's because RAR is not a dependency, but WAR/RAW/WAW is!

I wrote a little wrapper for GLSL.   The main class is "gpu_array", which is a 2D grid of pixels.  All the OpenGL side stuff is hidden inside gpu_env--just make one, and pass it to each gpu_array.  Creating gpu_arrays is expensive (texture and framebuffer allocation is ridiculously slow), but you can operate on or swap the arrays cheaply.

Here are some examples:
#include <iostream>
#include "ogl/gpgpu.h"
#include "ogl/glsl.cpp"
#include "ogl/glew.c" /* include entire body here, for easy linking */

int main() {
gpu_env e; /* make the environment (OpenGL window &c) */
const int n=16;
gpu_array A(e,"A",n,1,0,GL_LUMINANCE32F_ARB);
GPU_RUN(A,
gl_FragColor=vec4(location.x);
)
float out[n];
A.read(out,n,1);
for (int i=0;i<n;i++)
std::cout<<"out["<<i<<"]="<<out[i]<<" = "<<out[i]*n<<"/"<<n<<"\n";
return 0;
}

(Try this in NetRun now!)

Here we have two textures, and B reads from A.
/* 
Tiny demo GLSL program, used for GPGPU computing.
Dr. Orion Lawlor, olawlor@acm.org, 2009-04-17 (Public Domain)
*/
#include "ogl/glew.c"
#include "ogl/gpgpu.h"
#include "ogl/glsl.cpp"
#include <iostream>
#include <math.h>

int main() {
int w=4, h=1;
const int n=w*h;

// prepare CPU-side arrays
float *a=new float[n], *b=new float[n];
for (int i=0;i<n;i++) {
a[i]=rand()%4;
}

// prepare GPU-side arrays (and copy data over)
gpu_env e;
gpu_array A(e,"A",w,h,a,GL_LUMINANCE32F_ARB);
gpu_array B(e,"B",w,h,b,GL_LUMINANCE32F_ARB);

// compute stuff on the GPU
GPU_RUN(B,
gl_FragColor=texture2D(A,location) + 1.0;
)

// copy back to the CPU
B.read(b,w,h);
for (int i=0;i<n;i++) {
std::cout<<"a["<<i<<"]= "<<a[i]<<" and b["<<i<<"] = "<<b[i]<<"\n";
}

return 0;
}

(Try this in NetRun now!)

The "gpucloth" example program is a complete example of this method, including shaders for the position update, velocity update (including net force calculation), and rendering.