..
#gamedev

005 - Stack Memory, Global Variables and Graphics Review

Let's talk about some changes that I want to improve in the code.

Let's begin with structs and functions parameters.

Wrap Properties

In C, we can't return multiple values. So, I need to create a struct to wrap them, like this:

struct Win32_Window_Rect {
        int width;
        int height;
};

Now, when I need to know the size of window, during the program, I can call another function that returns these values for me. Let's see:

inline internal Win32_Window_Rect
win32_get_client_rect(HWND window)
{
        Win32_Window_Rect result = {};

        RECT rect;
        GetClientRect(window, &rect);
        int width  = rect.right  - rect.left;
        int height = rect.bottom - rect.top;

        result.width = width;
        result.height = height;

        return result;
}

The intersting thing is, when I want the size of window (WIDTH and HEIGHT), instead to pass the struct itself as arguments, it's recommended to pass the raw value (int, int), like this:

internal void
update_window(Win32_Back_Buffer *back_buffer, HDC context, int dest_width, int dest_height)
{
        // ... code
}

// NOT update_window(Win32_Back_Buffer *back_buffer, HDC context, Win32_Window_Rect rect)

With this approach we can ensure that any function that call update_window doesn't need to wrap the values into Win32_Window_Rect before pass it. In the other words, if I already have these values, just past to it.

// I already have WINDOW_WIDTH and WINDOW_HEIGHT
update_window(&back_buffer, hdc, WINDOW_WIDTH, WINDOW_HEIGHT);

// OR, I need to get it.
Win32_Window_Rect client_rect = win32_get_client_rect(window);
update_window(&back_buffer, hdc, client_rect.width, client_rect.height);

It's more arbitrary parameters that is better for caller to decide how to use the function (get the value from win32_get_client_rect OR pass the values already got it.

Another reason for this is because the values (width and height) can be treat separately. It's different of Back_Buffer that is a struct containing a lot of properties that works together.

Global variables

Let's talk about global variables.

At this moment, our game runs with only one back buffer.

So, it's completely acceptable to have a global variable here because we've been controlling the memory allocation (and free it) the right way. Let's see an example:

internal void
create_back_buffer(Win32_Back_Buffer *back_buffer, int width, int height)
{
        if (back_buffer->data) {
                if (!VirtualFree(back_buffer->data, 0, MEM_RELEASE)) {
                        // TODO: Log Error GetLastError()
                        print_error("Failed to release memory address!");
                }
        }

        // code ...
        back_buffer->data = VirtualAlloc(NULL, buffer_size, MEM_COMMIT|MEM_RESERVE, PAGE_READWRITE);
        if (!back_buffer->data) {
                // TODO: Log Error GetLastError()
                print_error("Failed to allocate memory address!");
        }
        // code..
}

As we can see, the back buffer always be initialized once, like a Singleton (I couldn't find a better word to describe this behavior).

By the way, if I want to use the back buffer during the window procedure callback, it will be necessary to have global back buffer because it is a callback from Windows call. So, let's define the back buffer globally. Look at the below code:

internal Win32_Back_Buffer the_back_buffer;

Stack Memory Size

During the tests I notice that the memory address on the Stack, at the first variable, was allocated at some specific high number address.

After allocate another variable the stack address goes down.

Which means that as much variables as we allocate, the stack becomes closer to the address zero.

With the first variable's number address, we can compute the size of stack memory and find out when the stack overflow should occurs.

Let's see the example:

0x00000000001ffca0 = 2096288 bytes = 2MB

Try to allocate on the stack a huge memory using arrays, like this code and see the results of Stack Overflow:

u8 test_memory_huge[2 * 1024 * 1024] = {};

We can also increase the stack memory address through compiler flag /F<num_of_bytes>.