The world of C and C style C++, low level programming, writing everything by hand. Some would argue why even bother, there are already solutions out there for me to use. Don’t re-invent the wheel (even though we’ve been continuously reinventing the wheel since the day it was invented). Use existing solutions. Why write a software renderer, its an obsolete way to render anything. Why use DirectX when you can use Unreal or Unity. Why, why, why…
The short, it makes me happy and makes me feel the kind of fulfillment that I haven’t felt before when programming. The long answer, I don’t want to be the guy that only knows how to use other peoples stuff. I don’t want to #include <bobs_best_parset.h> call a bob_init() and the rest is magic. I want to be able to write the parser myself, I want to have the skill set to solve problems myself. The more I used things like Unity and Unreal, the more I relied on existing solutions, the worse I felt as a programmer. Could I even call myself a programmer anymore? I felt like I couldn’t. And finally, I always like to think, is this going to be the solution in 100-200 years? Is Unity or what ever other tool you suggest I use going to be the solution in the future? The answer is always no. And if its no, then I want to be part of that new solution. I bet you in 200 years, we are not going to have Unity version 1135.30. The software will be dead, and there will be other software to replace it. I want to be part of the group of people who write new and better software, who explore new and better solution, not the group that satisfied with the status quo.
Thus begins my journey as a low level Systems and Engine programmer. This isn’t the first time I’ve programmed in C/C++. I did a ton if it in university, and a little in my free time. But this marks the moment in time where I spent hours every day, practicing and improving and indulging myself in a style of programming that I didn’t even know existed before “Handmade Hero” (I will likely be referencing this series and its community multiple times through this blog series). While following the series, I found that I was just copying exactly what Casey was doing, almost line by line. I didn’t feel like I was learning, but more listening to a lecture where you forget most of it later anyway. So after following the first 50ish episodes of the series, writing everything verbatim, I decided to re-write the entire program myself in my own style, while using the series as a guide.
The first project I decided to work on was to rasterize triangles and I wanted to do it with my own software renderer. No aliasing, anti-aliasing or multi-sampling. Nothing special. The rules I wanted to follow were that of MSDN’s DirectX rasterization rules and I wanted to mimic the results of wikipedia’s rasterization reference to resolve how pixels are determined to be in or out of a triangle.
MSDN: D3D Rasterization Rules
Wiki: Rasterization
So… the first step was to figure out how to draw things using the windows API. The concept itself is pretty simple, although as usual Windows made it hard to figure out and complicate with the number of steps and settings you need to setup right off the bat. Once you are able to create and register a window class, you need to setup a few more things to be able to draw to a back buffer, such as a device context, bitmap info, and some basic information about how large this buffer is going to be. For this, I created a RenderBuffer struct that contained all this information for me, and once I populate the back buffer with data, I can display it using StretchDIBits().
typedef struct RenderBuffer{
void* base; // pointer to the start of my backbuffer
size_t size; // size of backbuffer
s32 width; // width of backbuffer
s32 height; // height of backbuffer
s32 bytes_per_pixel; // 4 bytes per pixel (RGBA)
s32 stride;
BITMAPINFO bitmap_info;
HDC device_context;
} RenderBuffer;
RenderBuffer render_buffer;
// initialize the render buffer and all the data relevant for blitting to the screen
static void init_render_buffer(RenderBuffer* render_buffer, s32 width, s32 height){
render_buffer->width = width;
render_buffer->height = height;
render_buffer->bitmap_info.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
render_buffer->bitmap_info.bmiHeader.biWidth = width;
render_buffer->bitmap_info.bmiHeader.biHeight = -height;
render_buffer->bitmap_info.bmiHeader.biPlanes = 1;
render_buffer->bitmap_info.bmiHeader.biBitCount = 32;
render_buffer->bitmap_info.bmiHeader.biCompression = BI_RGB;
s32 bytes_per_pixel = 4;
render_buffer->bytes_per_pixel = bytes_per_pixel;
render_buffer->stride = width * bytes_per_pixel;
render_buffer->size = width * height * bytes_per_pixel;
render_buffer->base = os_virtual_alloc(render_buffer->size);
render_buffer->device_context = GetDC(window);
}
static void update_window(HWND window, RenderBuffer render_buffer){
StretchDIBits(render_buffer.device_context,
0, 0, render_buffer.width, render_buffer.height,
0, 0, render_buffer.width, render_buffer.height,
render_buffer.base, &render_buffer.bitmap_info, DIB_RGB_COLORS, SRCCOPY))
}
With just this code, we can easily rasterize any rectangle we want with any color, by just iterating over some starting point in the buffer, and advancing by the stride to get to the next row of pixels we want to fill in.
static void draw_rect(RenderBuffer *render_buffer, f32 start_x, f32 start_y, s32 w, s32 h, RGBA color){
// get starting position
u8 *row = (u8 *)render_buffer->base + ((render_buffer->height - start_y - 1) * render_buffer->stride) + (start_x * render_buffer->bytes_per_pixel);
// iterate until height
for(f32 y = start_y; y <= start_y + height; ++y){
u32 *pixel = (u32 *)row;
// iterate until width
for(f32 x = start_x; x <= start_x + width; ++x){
// assign new color to pixel
u32 new_color = (round_f32_s32(color.a * 255.0f) << 24 | round_f32_s32(color.r * 255.0f) << 16 | round_f32_s32(color.g * 255.0f) << 8 | round_f32_s32(color.b * 255.0f) << 0);
*pixel++ = new_color;
}
pixel += render_buffer->stride;
}
}
From here, we can start understanding how to draw all kinds of simple shapes such as a line, circle, ray, triangle, quad (2 triangles), …
So naturally we now want to be able to draw a triangle. And this turns out to be also pretty simple. But first we have to understand some terminology such as flat bottom triangle and flat top triangles. All this means is that every triangle we want to draw, either is flat at the bottom and meets at the top, or flat at the top and meets at the bottom.
From here, we just have to calculate the rise over run of both sides, in case it is not uniform and every time we rasterize a row, we either go up or down by the rise/run for each side to completely draw the entire triangle. The code for this looks something like this (where draw_flatbottom_triangle() would be the same except in the opposite direction):
static void draw_flattop_triangle(RenderBuffer *render_buffer, v2 p0, v2 p1, v2 p2, RGBA color){
f32 left_slope = (p0.x - p2.x) / (p0.y - p2.y);
f32 right_slope = (p1.x - p2.x) / (p1.y - p2.y);
s32 start_y = (s32)round_f32_s32(p2.y);
s32 end_y = (s32)round_f32_s32(p0.y);
for(s32 y=start_y; y < end_y; ++y){
f32 x0 = left_slope * (y + 0.5f - p0.y) + p0.x;
f32 x1 = right_slope * (y + 0.5f - p1.y) + p1.x;
s32 start_x = (s32)ceil(x0 - 0.5f);
s32 end_x = (s32)ceil(x1 - 0.5f);
for(s32 x=start_x; x < end_x; ++x){
draw_pixel(render_buffer, (v2){(f32)x, (f32)y}, color); // a helper function to make it simpler to fill in a single pixel
}
}
}
Finally, we need to be able to rasterize all kinds of triangles, specifically ones that have not flat bottom or flat top. The way to do this is to realize that you can take any single triangle, split it in half and turn it into both a flat bottom and flat top triangle. To do this, you just need to figure out which point is in the middle with respect to height, and use that point as the splitting point to create one flat bottom and one flat top triangle. Once you have that information, you can just call the two functions to render both flat top and flat bottom triangles!
Now that I’m able to rasterize any triangle I want, I can start working towards replicating the example shown in Wikipedia while using the DirectX rasterization rules on how to resolve a) if a pixel is in our out of a triangle, and b) which triangle draws the line if two triangles overlap. After banging my head around this for a long time, it turned out that it all revolved around how you round your values, and a couple of ceils that you might have noticed in the above code to draw a flat top triangle.
So with a little bit of work, I’m able to draw the individual pixels that are identical to the wiki, enlarge them by 50 pixels or so, and pass them in as individual triangles to be rendered. The result is this:
At this point the project is pretty much done. But I did spend some more time playing around with shapes, colors and more and ended up drawing a whole bunch of stuff including some simple linear alpha blending. (line, ray, segment, circle (filled/outlined), rect (filled/outlined), quad (filled/outlined), triangle)