by date
Reinventing the wheel - Creating a 2D API
Reinventing the wheel - Creating a 2D API
| Name: | Ben Versaw | |
|---|---|---|
| Date Posted: | Jun 11, 2008 | |
| Rating: | Not Rated | |
| Public: | YES | |
| Comments: | YES | |
| RSS Feed: | or Subscribe with . | |
| Profile Page: | View profile page for Ben Versaw |
Blog post
Disclaimer long technical post follows - related images are included but aren't really interesting
In an effort to expand my programming knowledge I recently undertook an interesting project - code a 2D graphics API from scratch in C++.
A few days later I have emerged from my cave with a functional 2D API I have affectionately named "KABLAM". So how does this work you may ask?
Well the first thing is that the screen is really just an array of pixels with color information. That was easy enough to recreate using a simple:
The API then resizes the array to a size of [desiredScreenWidth * desiredScreenHeight] so a 4x5 screen would be 20 units long and look like the following:

However, the screen is multidimensional as shown:

A valid move would have been to make the mPixels array a multidimensional array as well - but the entire purpose of the exercise was to learn how the underlaying features of programming I often take for granted works, and a multidimensional array is just an array with additional code to make it easier on the coder.
So I broke out the pencil and paper and scribbled out the scenario trying to figure out how to map the x and y points of the screen to an array.
I quickly managed to boil it down to a simple formula:
This made it easy to be able to do things such as "draw(Vector2D(x,y),Color(255,0,0)" as drawing one pixel to the array just involves using the formula above to find the ID and setting the cell to the desired color.
But what about drawing objects? We definitely don't want the end user to have to manually draw every pixel themselves!
For example we want to draw 2x2 red square that is located at (-1,1) as shown:

After quite a few pages of trying out the steps on paper I finally reached the following steps when dealing with objects.
Get the start position: This is the upper left most pixel of the object as represented by the position of the object.
Loop through the pixels
Advance to the next column: The reason this happens immediately is because if a point is skipped and we advance to the next column at the end of the loop it would require redundant pieces of code.
Check if the point is on screen: If the point is not on the screen we cannot put it into the array. Points off the screen always cause the calculated ID to be invalid - either because they are negative or because they are simply wrong. Even if the point did not produce an invalid id we don't want to waste any more time on a point that will never be seen.
Checking if a point is on screen is as simple as making sure both the x and y components are greater than zero and less then width and height (respectively).
Draw the pixel to the array: The last part of the process is calculating the ID of the pixel in the array and then drawing it to the array. Noticed that the code belows calls into "blendColor". This is to allow the API to support alpha blending and blend the object's color with anything currently in the spot based on alpha. The formula used and more about alpha blending is easily found on wikipedia.
Code to better demonstrate the above steps:
Troubleshooting / Design Process
For the majority of the development cycle (less than a week) the code I was writing had no visible effect meaning that I had to test everything on paper first and then code it. Even then I sometimes found that the results were not as expected when the API was finally rendering to the screen.
I found that drawing out the arrays, variables, and then manually going through the code - performing the action the code did by hand on paper - helped a ton. When dealing with things as small as a pixel its often difficult to analyze the problem by just seeing the end result.
But wait none of that code or theory actually draws anything to the screen!
Yes, you caught me. However, using the windows function "setPixel" and simply looping through the array allows you to easily draw everything in the surface. In fact, its a function the API directly supports.

It may not be pretty - but it shows that it works.
What's the point?
To learn. Using SDL, GDI+, or DirectX would have certainly been easier but I feel like I learned a ton about the underlaying features of the three APIs by trying to develop my own.
Was the way I went about doing this the correct way? Most likely not. I'm a self trained programmer with absolutely zero formal training and although I tried to research how things worked for this project I probably got a lot of it wrong. But, hey it works and I learned a lot.
What's next?
Reading images from files and converting them into color arrays / drawing them to the screen. Next, would be figuring out how to do a double buffer effect as obviously looping through an array and changing pixels one at a time takes time that creates a noticeable effect.
Also I'm working on making the API convert its screen into a texture that could be applied to a 3D object.
Lastly, I would love to attempt to make the API work on a platform other than windows - maybe some day soon I will own a different operating system to experiment on.
Wait - is that Torque code style?
Although my code doesn't run using Torque, I have been inspired by the neatness of GarageGames code and have been attempting to "professionalize" my code. Using their guidelines in my own code has added a lot to its readability.
In an effort to expand my programming knowledge I recently undertook an interesting project - code a 2D graphics API from scratch in C++.
A few days later I have emerged from my cave with a functional 2D API I have affectionately named "KABLAM". So how does this work you may ask?
Well the first thing is that the screen is really just an array of pixels with color information. That was easy enough to recreate using a simple:
Color *mPixels;
The API then resizes the array to a size of [desiredScreenWidth * desiredScreenHeight] so a 4x5 screen would be 20 units long and look like the following:

However, the screen is multidimensional as shown:

A valid move would have been to make the mPixels array a multidimensional array as well - but the entire purpose of the exercise was to learn how the underlaying features of programming I often take for granted works, and a multidimensional array is just an array with additional code to make it easier on the coder.
So I broke out the pencil and paper and scribbled out the scenario trying to figure out how to map the x and y points of the screen to an array.
I quickly managed to boil it down to a simple formula:
ArrayID = y*screenWidth + x;
This made it easy to be able to do things such as "draw(Vector2D(x,y),Color(255,0,0)" as drawing one pixel to the array just involves using the formula above to find the ID and setting the cell to the desired color.
But what about drawing objects? We definitely don't want the end user to have to manually draw every pixel themselves!
For example we want to draw 2x2 red square that is located at (-1,1) as shown:

After quite a few pages of trying out the steps on paper I finally reached the following steps when dealing with objects.
Get the start position: This is the upper left most pixel of the object as represented by the position of the object.
Loop through the pixels
Advance to the next column: The reason this happens immediately is because if a point is skipped and we advance to the next column at the end of the loop it would require redundant pieces of code.
Check if the point is on screen: If the point is not on the screen we cannot put it into the array. Points off the screen always cause the calculated ID to be invalid - either because they are negative or because they are simply wrong. Even if the point did not produce an invalid id we don't want to waste any more time on a point that will never be seen.
Checking if a point is on screen is as simple as making sure both the x and y components are greater than zero and less then width and height (respectively).
Draw the pixel to the array: The last part of the process is calculating the ID of the pixel in the array and then drawing it to the array. Noticed that the code belows calls into "blendColor". This is to allow the API to support alpha blending and blend the object's color with anything currently in the spot based on alpha. The formula used and more about alpha blending is easily found on wikipedia.
Code to better demonstrate the above steps:
//-----------------------------------------------------------
// Function Name: DrawSurface::drawToSurface
// Summary: Draws the pixels in the array to the position given
// Returns: Nothing
//-----------------------------------------------------------
void DrawSurface::drawToSurface(Color pixels[], Rect2D bounds)
{
if(!mCreated)
return; // If the surface isn't created yet we cannot draw to it
// Get the start position of the pixels
Vector2D startPosition = Vector2D(bounds.x, bounds.y);
// Loop through the data to be drawn
KBI col = -1;
KBI row = 0;
for ( KBI n = 0 ; n < (bounds.w * bounds.h) ; n++)
{
// Advance to the next column
col += 1;
// See if this causes the image to go to its next row
if(col > bounds.w - 1)
{
col = 0;
row += 1;
}
// Check if the point is on the screen
KBI x = startPosition.x + col;
KBI y = startPosition.y + row;
bool check = isPointOnscreen(Vector2D(x,y));
if(!check)
continue; // The point isn't on screen so skip it
// Calculate the id of the point: ID = y(width) + x
KBI id = y*mWidth + x;
// Draw the pixel
mPixels[id] = blendColor(mPixels[id], pixels[n]);
}
mInvalidated = true;
}
Troubleshooting / Design Process
For the majority of the development cycle (less than a week) the code I was writing had no visible effect meaning that I had to test everything on paper first and then code it. Even then I sometimes found that the results were not as expected when the API was finally rendering to the screen.
I found that drawing out the arrays, variables, and then manually going through the code - performing the action the code did by hand on paper - helped a ton. When dealing with things as small as a pixel its often difficult to analyze the problem by just seeing the end result.
But wait none of that code or theory actually draws anything to the screen!
Yes, you caught me. However, using the windows function "setPixel" and simply looping through the array allows you to easily draw everything in the surface. In fact, its a function the API directly supports.

It may not be pretty - but it shows that it works.
What's the point?
To learn. Using SDL, GDI+, or DirectX would have certainly been easier but I feel like I learned a ton about the underlaying features of the three APIs by trying to develop my own.
Was the way I went about doing this the correct way? Most likely not. I'm a self trained programmer with absolutely zero formal training and although I tried to research how things worked for this project I probably got a lot of it wrong. But, hey it works and I learned a lot.
What's next?
Reading images from files and converting them into color arrays / drawing them to the screen. Next, would be figuring out how to do a double buffer effect as obviously looping through an array and changing pixels one at a time takes time that creates a noticeable effect.
Also I'm working on making the API convert its screen into a texture that could be applied to a 3D object.
Lastly, I would love to attempt to make the API work on a platform other than windows - maybe some day soon I will own a different operating system to experiment on.
Wait - is that Torque code style?
Although my code doesn't run using Torque, I have been inspired by the neatness of GarageGames code and have been attempting to "professionalize" my code. Using their guidelines in my own code has added a lot to its readability.
Recent Blog Posts
| List: | 08/20/08 - Going Mobile: Part 2 08/07/08 - Going Mobile 06/11/08 - Reinventing the wheel - Creating a 2D API 05/23/08 - Rated M - Just to be rated M 11/22/07 - Text Based Cell Phone Games 06/26/07 - Guitarist Joe Needs You! 11/27/05 - Plan for Ben Versaw 07/28/05 - Plan for Ben Versaw |
|---|
Submit your own resources!| Phil Carlisle (Jun 11, 2008 at 06:26 GMT) |
His "zen of optimisation", the black book, or even just reading his old articles. Cant recommend them enough.
Your code really needs some optimisation. If youre going to do pixel work, you HAVE to have it fast.
| Ben Versaw (Jun 11, 2008 at 06:28 GMT) |
I definitely noticed the drag in the code and have been trying to figure out what to do about it. Mainly was a learning project but maybe I'll make optimizing it the next learning project now.
Thanks for the hint.
| Brian Wilson (Jun 11, 2008 at 08:45 GMT) |
| Melv May (Jun 11, 2008 at 11:17 GMT) |
Just wanted to add that more people should take this approach. It's all too easy to use an existing API which is great until something doesn't work and you don't understand the principles.
I did something similar quite a few years ago where I was "manually" creating matrices for transformations to create a simplistic 3D rendering pipeline. I then started doing line-rendering and then eventually doing texturing of triangles. Got into doing perspective correct texturing and things started to get more complex.
It's damn satisfying going back to school on this stuff.
Good work.
Melv.
Edited on Jun 11, 2008 11:29 GMT
| David Montgomery-Blake (Jun 11, 2008 at 13:41 GMT) |
I agree with Melv. Every now and again, I try to go back to basics to make sure that I remember the core concepts behind what is going on. I've been playing a lot with visual representations of algorithms, which (of course) kills their speed but makes me focus on whether or not I *really* understand what is going on in the algorithm. Strangely enough, I've been using Flex and AS3 for it because speed isn't an issue for me. Visualizing concepts is.
| Melv May (Jun 11, 2008 at 19:27 GMT) |
Edit: In-fact I'm sure that's on the GG bookshelf because I remember seeing it last time I was there. I also remember Adam larson looking up some bezier-curve algo for the t2dPath class. Crazy things I remember. I must shake the junk out of my brain sometime.
Melv.
Edited on Jun 11, 2008 19:30 GMT
| David Montgomery-Blake (Jun 11, 2008 at 21:43 GMT) |
You must be a member and be logged in to either append comments or rate this resource.


Not Rated


