004 - How to Animate Raw Pixels on the Screen
Table of Contents
In the previous post, I showed you how to allocate a back buffer containing a memory block that represents a pixel array.
In this post, I'm going to demonstrate how to animate that back buffer and set some pixels on the screen.
Improving Build script
Before starting, I changed the build script flags to add the full path to the Emacs compiler error. So, I can jump into the line of code that cause the error.
To do that, add into build.bat
the flag -FC
.
cl -nologo -FC -Zi ..\src\win32_myproject.cpp %LINKER_FLAGS%
The Back Buffer Struct
Let's define a struct that holds some values related to the back buffer:
struct Win32_Back_Buffer { int width; int height; int pitch; void* data; BITMAPINFO bitmap_info; };
As you can see, the struct represents our memory with some properties like the memory itself, the pitch and the size of bitmap. Finally, the bitmap information, which is a struct for configuring the renderer.
The Render Gradient
The next step is to create a function that interates through X and Y axes and fill each 4 bytes per pixel, which is equivalent to the AA RR GG BB channels.
The functions calls render_gradient
and the code should be:
static void render_gradient(Win32_Back_Buffer *back_buffer, int x_offset, int y_offset) { unsigned char *row = (unsigned char *) back_buffer->data; for (int y = 0; y < back_buffer->height; ++y) { unsigned int *pixel = (unsigned int *) row; for (int x = 0; x < back_buffer->width; ++x) { unsigned char xx = (x + x_offset); unsigned char yy = (y + y_offset); *pixel = (xx << 8) | yy; pixel++; } row += back_buffer->pitch; } }
Note that we cast a memory block to an unsigned char (u8) pointer to start at the beginning of the array. Next, we iterate on the X axis, and set ARGB color based on the channel color. Each channel has a capacity of 0-255 bits.
So, to fill a single pixel, we must shift left by 16 bits for the red color, 8 bits for the green color, 0 shift for blue color and use the OR operator to merge them.
0xFF112233 A R G B
To move to the next line of matrix, we use the pitch
which represents how many bytes per row our buffer have.
Windows uses Little Endian Architecture. For this reason, in registers, it fills BB GG RR AA, while in memory, it fills AA RR GG BB
Typedefs
I don't remember if I already mentioned this before, but the way that I learn to program in C is to simplify some things like types using typedef
.
The typedef is useful to naming another types and fix the size based on archicture of system. As you can see in the next block, it's much more easier to type and read the code:
#include <stdint.h> typedef uint8_t u8; typedef uint32_t u32; // ... another types static void render_gradient(Win32_Back_Buffer *back_buffer, int x_offset, int y_offset) { u8 *row = (u8 *) back_buffer->data; for (int y = 0; y < back_buffer->height; ++y) { u32 *pixel = (u32 *) row; for (int x = 0; x < back_buffer->width; ++x) { u8 xx = (x + x_offset); u8 yy = (y + y_offset); *pixel = (xx << 8) | yy; pixel++; } row += back_buffer->pitch; } }
Animating the Back Buffer
Refactor the code to create the back buffer and pass the parameters that it need.
BITMAPINFOHEADER *bmi_header = &back_buffer->bitmap_info.bmiHeader; bmi_header->biSize = sizeof(BITMAPINFOHEADER); bmi_header->biWidth = width; bmi_header->biHeight = -height; // another properties ...
Now, let's call the render_gradient
function inside the main loop, and after processing message queue.
For X offset and Y offset parameters, I use temporary int value to animate the gradient.
Last but not least, I call update_window
with the current window and swap the back buffer with the newly filled pixel array.
Win32_Back_Buffer back_buffer = {}; create_back_buffer(&back_buffer, WIDTH, HEIGHT); HDC context = GetDC(window); int x_offset = 0; int y_offset = 0; while(!should_quit) { process_message_queue(); x_offset++; y_offset++; render_gradient(&back_buffer, x_offset, y_offset); update_window(&back_buffer, context, WIDTH, HEIGHT); }
Conclusion
Extract the data and bitmap_info variables to the struct called Win32_Back_Buffer
. Also, add the width, height and pitch (width * bytes per pixel).
We can unify the BITMAPINFOHEADER property using a pointer.
Now, update the update_window
and create_back_buffer
to set these values.
Go back to the main-loop and between process queue message and update_window
, add a new function called render_gradient
to test our renderer with some piece of pixels according the x and y values.
Iterate the whole x-axis and y-axis to fill the pixels color with ARGB channels.
Next, add the x_ofsset and y_offset to increase for each frame and move the gradient to right and down as result.
Review
Extract the data and bitmap_info variables to the struct called Win32_Back_Buffer
. Also, add the width, height and pitch (width * bytes per pixel).
We can unify the BITMAPINFOHEADER property using a pointer.
// before BITMAPINFOHEADER bmi_header = {}; bmi_header.biSize = sizeof(BITMAPINFOHEADER); bmi_header.biWidth = width; bmi_header.biHeight = -height; // top-down back_buffer.bitmap_info.bmiHeader = bmi_header; // ... // after BITMAPINFOHEADER *bmi_header = &back_buffer->bitmap_info.bmiHeader; bmi_header->biSize = sizeof(BITMAPINFOHEADER); bmi_header->biWidth = width; bmi_header->biHeight = -height; // top-down // ...
Now, update the update_window
and create_back_buffer
to set these values.
Go back to the main-loop and between process queue message and update_window
, add a new function called render_gradient
to test our renderer with some piece of pixels according the x and y values.
Iterate the whole x-axis and y-axis to fill the pixel color with ARGB channels.
Next, add the x_ofsset and y_offset to increase for each frame and move the gradient to right and down as result.
My Quick Access
- Create a struct of Win32_Back_Buffer;
- Create a function render gradient to fill pixels based on X and Y offset;
- Also, add the Win32_Back_Buffer as parameters;
- Set the properties of bitmapinfo into back buffer;
- Call render_gradient, update_window after process message queue and update the offset values to move the gradient at screen;