Handmade Penguin Chapter 3: Allocating a Backbuffer

This chapter covers roughly the content in the Allocating a Backbuffer part of the Handmade Hero course, under the Linux operating system.

<-- Chapter 2 | Back to Index | Chapter 4 -->

Quitting our Game

If you've followed Chapter 2, you'll see our code already lets our window quit. However, we've done it slightly differently: instead of having a global Running variable, we're having our HandleEvent() function return true if we want to quit.

We're not going to change this now, but if you wanted to, it would be pretty easy. We would have to:

  1. Make HandleEvent() not return anything (i.e. return void).
  2. Get rid of the ShouldQuit variable and every line where it's used.
  3. Add the Running variable, like in the stream, and set it to false in our SDL_QUIT handler.
  4. Change our for(;;) loop to be a while(Running) loop.
  5. And get rid of the if statement around our call to HandleEvent()
That's it!

Getting the size of our window

This is really simple. Just like Window has GetClientRect, SDL has SDL_GetWindowSize(). You need to pass SDL_GetWindowSize() your SDL_Window pointer, and a pointers to integers for width and height. So:

int Width, Height;
SDL_GetWindowSize(Window, &Width, &Height;);
and now you'll find Width and Height have the width and height respectively of our window.

Creating Textures

The way (well, one of the ways) to render something to the screen in SDL is to put your pixel data in a texture, and then stretch that texture across the screen, Textures in SDL are represented by a pointer to an SDL_Texture. Since SDL_Textures have a size, we'll need to recreate it every time the size of our window changes. To do that, we'll create a SDLResizeTexture(SDL_Renderer *Renderer, int Width, int Height) function, and call it whenever our window's size changes. The SDL_WINDOWEVENT_RESIZE event only happens when the user manually resizes our window, so we'll change our SDL_WINDOWEVENT_RESIZE handler to handle the SDL_WINDOWEVENT_SIZE_CHANGED event, which happens any time the size of our window changes. We'll then make that handler just call our SDLResizeTexture() function. (We'll need to use the SDL_GetWindowFromID and SDL_GetRenderer functions again to get the pointer to our SDL_Renderer.)

We create an SDL_Texture with the unsurpisingly named SDL_CreateTexture() function:

SDL_Texture* SDL_CreateTexture(SDL_Renderer* renderer,
                               Uint32        format,
                               int           access, 
                               int           w,
                               int           h)
We can think of this as being the SDL equivalent of Windows' CreateDIBSection. Let's go through its parameters one by one: Putting that together, we get:
SDL_Texture *Texture = SDL_CreateTexture(Renderer,
                                         SDL_PIXELFORMAT_ARGB8888,
                                         SDL_TEXTUREACCESS_STREAMING,
                                         Width,
                                         Height);

We also need to allocate some memory for our actual pixels. (We can actually use the memory in our Texture directly if we want — look up SDL_LockTexture — but it's a bit nastier to use and can be slower.) We'll use the malloc() function to get that memory. malloc() is a very simple function, its only argument is the number of bytes or size of the block of memory we want. Before we can use malloc() though, we need to include the stdlib.h header file.

    void *Pixels = malloc(Width * Height * 4);
The return value has type void * which basically means it's a pointer without a specific type. We can cast it to whatever type we want.

Because we're going to be putting this in our ResizeTexture function (Win32ResizeDIBSection is a terrible name), we need to check if we've already got a Texture handle. If so, we want to delete it with the SDL_DestroyTexture() function before we create a new one.

if (Texture)
{
    SDL_DestroyTexture(Texture);
}

Similarly, we want to free the memory we use when we resize. There's a free() function which just returns an allocated block of memory to the system. It just accepts the pointer we got from malloc(). So we can just:

if (Pixels)
{
    free(Pixels);
}

Note: There's actually a realloc() function which lets you resize a block of memory. It even keeps the old data intact (which free() and malloc() doesn't), though that sometimes makes it slower. Feel free to look it up, though, it's an interesting function.

Finally, we're going to want to know the width of our texture later, so we can calculate the pitch we'll need, so let's save a copy of that in a global variable for now.

TextureWidth = Width;

Updating our Texture and Putting it on the Screen

Now we need to actually get our pixels into that texture. This is a job for SDL_UpdateTexture(), Let's look at the definition of SDL_UpdateTexture()::

int SDL_UpdateTexture(SDL_Texture*    texture,
                      const SDL_Rect* rect,
                      const void*     pixels,
                      int             pitch)
Looks complicated, doesn't it? Really, though, it's not that bad: If an error occurs when trying to update the texture, SDL_UpdateTexture() returns an error code (which is always a negative number). If it works, it returns 0.
if (SDL_UpdateTexture(Texture,
                      0,
                      Pixels,
                      TextureWidth * 4))
{
    // TODO: Do something about this error!
}

We now just need to actually put the texture on the screen. We do that with SDL_RenderCopy() function:

int SDL_RenderCopy(SDL_Renderer*   renderer,
                   SDL_Texture*    texture,
                   const SDL_Rect* srcrect,
                   const SDL_Rect* dstrect)
For the most part, this is a pretty self-explanatory function: it takes the texture pointed to by texture and stretches the part of it inside the rectangle srcrect, across the part of the screen inside the rectangle dstrect.

The useful thing here is that if we pass null pointers for the rectangles, it will stretch the whole texture over the whole screen, which is what we want.

SDL_RenderCopy(Renderer,
               Texture,
               0,
               0);
Now all we need to do is remember to SDL_RenderPresent(), and we've drawn the contents of our Pixels memory to the screen. Let's call our SDLUpdateWindow() function in our SDL_WINDOWEVENT_EXPOSED handler.

End of Lesson!

Well done! We've now set up our backbuffer. Let's have a look at our beautiful window:
Our handsome window, sporting its stylish new backbuffer.
It's black because most memory defaults to all 0s when we allocate it, and all 0s forms the colour black. It's not guaranteed that we'll get all 0s: if you resize the window a lot, you might see some random colours start to appear. That's the random garbage that happens to be in memory!

Now take a break, and we'll see you tommorrow for Chapter 4!

If you've bought Handmade Hero, the source for the Linux version can be downloaded here.


<-- Chapter 2 | Back to Index | Chapter 4 -->