OpenCL (or Open Computing Language) is a framework that allows you to write code across different connected devices to your computer. Code that you write can execute on CPUs, GPUs, DPSs amongst other pieces of hardware. The framework itself is a standard that puts the focus on running your code across these devices but also emphasises parallel computing.
Today’s post will just be on getting your development environment setup on Debian Wheezy to start writing some code.
Installation
The installation process is pretty straight forward, but there are some choices in libraries. The major vendors (Intel, NVIDIA and AMD) all have development libraries that are installable from Debian’s package repository. There’s plenty of banter on the internet as to who’s is better for what purpose.
First off, we need to install the header files we’ll use to create OpenCL programs.
$ sudo apt-get install opencl-headers
This has now put all of the development headers in place for you to compile some code.
$ ls -al /usr/include/CL
total 1060
drwxr-xr-x 2 root root 4096 Nov 25 22:51 .
drwxr-xr-x 56 root root 4096 Nov 25 22:51 ..
-rw-r--r-- 1 root root 4859 Nov 15 2011 cl_d3d10.h
-rw-r--r-- 1 root root 4853 Apr 18 2012 cl_d3d11.h
-rw-r--r-- 1 root root 5157 Apr 18 2012 cl_dx9_media_sharing.h
-rw-r--r-- 1 root root 9951 Nov 15 2011 cl_ext.h
-rw-r--r-- 1 root root 2630 Nov 17 2011 cl_gl_ext.h
-rw-r--r-- 1 root root 7429 Nov 15 2011 cl_gl.h
-rw-r--r-- 1 root root 62888 Nov 17 2011 cl.h
-rw-r--r-- 1 root root 915453 Feb 4 2012 cl.hpp
-rw-r--r-- 1 root root 38164 Nov 17 2011 cl_platform.h
-rw-r--r-- 1 root root 1754 Nov 15 2011 opencl.h
Secondly, we need to make a choice in what library we’ll use:
The amd-opencl-dev package will install AMD’s implementation, which you can read up on here. NVIDIA’s package is installable through the nvidia-opencl-dev package which you can read up on here. Finally, Intel’s implementation is available through the beignet-dev package and you can read up on their implementation here.
I went with AMD’s.
$ sudo apt-get install amd-opencl-dev
From here, it’s time to write some code. I’ll have some more blog posts on the way which will be walk-throughs for your first applications.
In my previous post, we went through the basics of rasterising polygons on screen by use of horizontal lines. To sum up, we interpolated values along each edge of the polygon, collecting minimum and maximums for each y-axis instance.
Today, we’re going to define a colour value for each point on the polygon and interpolate the colours along each edge. This is the technique employed to draw polygons that are Gouraud shaded.
The Code
The structure of this is very similar to drawing a single colour block polygon. For a solid colour polygon, we interpolated the x values over the length of the y values. We’ll now employ this same interpolation technique over the red, green, blue and alpha channels of each colour defined for each polygon point. Here’s the scanline function.
varscanline_g=function(p1,p2,miny,edges){// if the y values aren't y1 < y2, flip them// this will also flip the colour componentsif(p2.y<p1.y){varp=p1;p1=p2;p2=p;}// initialize our counters for the x-axis, r, g, b and a colour componentsvarx=p1.x;varr=p1.r,g=p1.g,b=p1.b,a=p1.a;// calculate the deltas that we'll use to interpolate along the length of// the y-axis here (y2 - y1)varyLen=p2.y-p1.y;vardx=(p2.x-p1.x)/yLen;vardr=(p2.r-p1.r)/yLen;vardg=(p2.g-p1.g)/yLen;vardb=(p2.b-p1.b)/yLen;varda=(p2.a-p1.a)/yLen;// find our starting array indexvarofs=p1.y-miny;// enumerate each point on the y axisfor(vary=p1.y;y<=p2.y;y++){// test if we have a new minimum, and if so save itif(edges[ofs].min.x>x){edges[ofs].min.x=Math.floor(x);edges[ofs].min.r=Math.floor(r);edges[ofs].min.g=Math.floor(g);edges[ofs].min.b=Math.floor(b);edges[ofs].min.a=Math.floor(a);}// test if we have a new maximum, and if so save itif(edges[ofs].max.x<x){edges[ofs].max.x=Math.floor(x);edges[ofs].max.r=Math.floor(r);edges[ofs].max.g=Math.floor(g);edges[ofs].max.b=Math.floor(b);edges[ofs].max.a=Math.floor(a);}// move our interpolators along their respective pathsx+=dx;r+=dr;g+=dg;b+=db;a+=da;// move to the next array offsetofs++;}};
An immediate code restructure difference here from the first tutorial, is I’m now passing an actual point object through as opposed to each component of each point being a function parameter. This is just to clean up the interface of these functions. We’re creating differentials not only for x now but also the r, g, b and a components. These will form the start and ending colours for each horizontal line that we’ll draw. We still have extra interpolation to do once we’re in the horizontal line draw function as well. Here it is.
varhline_g=function(x1,x2,y,w,c1,c2,buffer){// calculate the starting offset to draw atvarofs=(x1+y*w)*4;// calculate the length of the linevarlineLength=x2-x1;// calculate the deltas for the red, green, blue and alpha channelsvardr=(c2.r-c1.r)/lineLength;vardg=(c2.g-c1.g)/lineLength;vardb=(c2.b-c1.b)/lineLength;varda=(c2.a-c1.a)/lineLength;// initialize our countersvarr=c1.r,g=c1.g,b=c1.b,a=c1.a;// interpolate every position on the x axisfor(varx=x1;x<=x2;x++){// draw this coloured pixelbuffer[ofs]=Math.floor(r);buffer[ofs+1]=Math.floor(g);buffer[ofs+2]=Math.floor(b);buffer[ofs+3]=Math.floor(g);// move the interpolators onr+=dr;g+=dg;b+=db;a+=da;// move to the next pixrlofs+=4;}};
Again, more interpolation of colour components. This is what will give us a smooth shading effect over the polygon. Finally, the actual polygon function is a piece of cake. It just gets a little more complex as we have to send in colours for each point defined.
varpolygon_g=function(p1,p2,p3,p4,w,buffer){// work out the minimum and maximum y values for the polygonvarminy=p1.y,maxy=p1.y;if(p2.y>maxy)maxy=p2.y;if(p2.y<miny)miny=p2.y;if(p3.y>maxy)maxy=p3.y;if(p3.y<miny)miny=p3.y;if(p4.y>maxy)maxy=p4.y;if(p4.y<miny)miny=p4.y;varh=maxy-miny;varedges=newArray();// create the edge storage so we can keep track of minimum x, maximum x// and corresponding r, g, b, a componentsfor(vari=0;i<=h;i++){edges.push({min:{x:1000000,r:0,g:0,b:0,a:0},max:{x:-1000000,r:0,g:0,b:0,a:0}});}// perform the line scans on each polygon egdescanline_g(p1,p2,miny,edges);scanline_g(p2,p3,miny,edges);scanline_g(p3,p4,miny,edges);scanline_g(p4,p1,miny,edges);// enumerate over all of the edge itemsfor(vari=0;i<edges.length;i++){// get the start and finish colourc1={r:edges[i].min.r,g:edges[i].min.g,b:edges[i].min.b,a:edges[i].min.a};c2={r:edges[i].max.r,g:edges[i].max.g,b:edges[i].max.b,a:edges[i].max.a};// draw the linehline_g(edges[i].min.x,edges[i].max.x,i+miny,w,c1,c2,buffer);}};
Aside from the interface changing (just to clean it up a bit) and managing r, g, b and a components - this hasn’t really changed from the block colour version. If you setup this polygon draw in a render loop, you should end up with something like this:
In a previous post I laid down some foundation code to get access to the pixel buffer when in context of a HTML canvas. Good for those who have experience writing graphics code directly against the video buffer - it almost feels like you’re writing to 0xA000 :-)<
Today’s post will focus on drawing polygons to the screen using scan lines.
Scan lines
The whole idea here is that a polygon can be represented on screen as a series of horizontal lines. Take the following picture for example. You can see the red and blue horizontal lines making up the filling of the polygon.
So, to define this all we do is take note of the minimum and maximum x values for every y-axis instance that there is a line on. We run through the array of values drawing horizontal lines at each instance, and then we have a polygon on screen - pretty easy.
Code
First of all, we’ll define our drawing primitive for a horizontal line.
varhline_c=function(x1,x2,y,w,r,g,b,a,buffer){// calculate the offset into the buffervarofs=(x1+y*w)*4;// draw all of the pixelsfor(varx=x1;x<=x2;x++){buffer[ofs]=r;buffer[ofs+1]=g;buffer[ofs+2]=b;buffer[ofs+3]=a;// move onto the next pixelofs+=4;}};
We pass in the two x (x1 and x2) values for the line to go between, the y value for the line to sit on. To help with the offset calculation we also pass in the width w to correctly calculate the pitch. Finally the colour components and buffer to draw to are passed in. Setting this code up in a run loop, you end up with something like this:
Yep, there’s lots of horizontal lines. Referring to our horizontal line diagram above, we still need a way to walk the edges of the polygon so that we can get the minimum and maximum x values to start drawing. Because our basic unit is the pixel (considering we’re rasterising to a pixelated display), we can easily calculate the gradient of the line that we need by:
(change in x) / (change in y)
For a line given by (x1, y1) - (x2, y2), this translates into:
(x2 - x1) / (y2 - y1)
Taken out of context of maths, this just says to us: we want to walk from x1 to x2 using (y2 - y1) steps.
varscanline_c=function(x1,y1,x2,y2,miny,edges){// flip the values if need beif(y1>y2){vary=y1;y1=y2;y2=y;varx=x1;x1=x2;x2=x;}// start at the startvarx=x1;// change in x over change in y will give us the gradientvardx=(x2-x1)/(y2-y1);// the offset the start writing at (into the array)varofs=y1-miny;// cover all y co-ordinates in the linefor(vary=y1;y<=y2;y++){// check if we've gone over/under the max/minif(edges[ofs].minx>x)edges[ofs].minx=x;if(edges[ofs].maxx<x)edges[ofs].maxx=x;// move along the gradientx+=dx;// move along the bufferofs++;}};
From the code above, we treat x1, y1 as the starting point and x2, y2 as the ending point. Our for-loop is biased in the positive direction, so it’s important for us to flip the values if they come in inverted. The edges array that’s passed in is prepared by the caller of this function. It’s initialized with very unreasonable minimum and maximum values. We than run over all 4 polygon edges
At the end of this process, edges is full of minimum/maximum values ready for drawing. Here’s the code for the polygon.
varpolygon_c=function(x1,y1,x2,y2,x3,y3,x4,y4,w,r,g,b,a,buffer){// work out the minimum and maximum y valuesvarminy=y1,maxy=y1;if(y2>maxy)maxy=y2;if(y2<miny)miny=y2;if(y3>maxy)maxy=y3;if(y3<miny)miny=y3;if(y4>maxy)maxy=y4;if(y4<miny)miny=y4;// the height will determine the size of our edges arrayvarh=maxy-miny;varedges=newArray();// build the array with unreasonable limitsfor(vari=0;i<=h;i++){edges.push({minx:1000000,maxx:-1000000});}// process each line in the polygonscanline_c(x1,y1,x2,y2,miny,edges);scanline_c(x2,y2,x3,y3,miny,edges);scanline_c(x3,y3,x4,y4,miny,edges);scanline_c(x4,y4,x1,y1,miny,edges);// draw each horizontal linefor(vari=0;i<edges.length;i++){hline_c(Math.floor(edges[i].minx),Math.floor(edges[i].maxx),Math.floor(i+miny),w,r,g,b,a,buffer);}};
This really is just putting all the pieces together. The building of the edges array is important - as is using the y co-ordinate (adjusted back to zero by proxy of the minimum y value) as an array index. Once you’ve got this setup in a random position & colour loop, you’ll end up with something like this:
During the test phases of getting your software setup, you’ll find it useful to completely toast what ever data you’ve already indexed to start fresh. This is as simple as issuing a delete query with open criteria *.*. The full query should translate to
Gaining pixel-level access using the HTML canvas opens up some possibilities for some frame-buffer style rasterisation. Today’s post will focus on the code required to get you access to this array. Here’s the code on how to get started:
// create the canvas objectvarcanvas=document.createElement("canvas");// maximise the canvas to stretch over the windowcanvas.width=window.innerWidth;canvas.height=window.innerHeight;// get the 2d drawing context for varcxt=canvas.getContext("2d");// get the image datavarimageData=cxt.createImageData(width,height);// save off the dimensions for later usevarwidth=canvas.width;varheight=canvas.height;// get the canvas on the pagedocument.body.appendChild(canvas);
First of all, we programmatically create our canvas object using document.createElement. Using the inner dimensions of the window, we can then set the canvas’ size. Of course this can be custom set to the dimensions you require - I just like to take over the whole window! Using the canvas object, we then pull out the drawing context with getContext. The next part, using createImageData we then get a reference to the frame-buffer to draw to. This gives us read/write access to the canvas through an array. Finally, we’ll take note of the width and height (this will come in handy later) and then pop the canvas onto the page.
Frame-buffer structure
So, I say “frame-buffer” - but it’s just an array. It’s quite nicely laid out such that pixels start at every 4 elements within the array. The first element being the red component, second is green, third is blue and the fourth is the alpha. Calculating an offset into the array is a piece of cake. For example, take the following piece of code which will allow you to set a single pixel on the frame-buffer.
varsetPixel=function(x,y,r,g,b,a,buffer){// calculate the start of the pixelvaroffset=((y*width)+x)*4;// set the componentsbuffer[offset]=r;buffer[offset]=g;buffer[offset]=b;buffer[offset]=a;};
The main part to focus on here is the calculation of the offset. Above, I said it was important to take note of the dimensions - we’re only using the width here. This is pretty straight forward calculation of an offset within a linear data segment with Cartesian co-ordinates.
Flip out!
Now that we’ve drawn all of the data to the image buffer (frame-buffer), we need a way to get it back onto our canvas. This is simply done using putImageData.