Each game state has a background colour. Three textures cover all seven states: dark navy for title, question, and confirm; dark green for correct and win; dark red for wrong and game over. Each frame, the correct texture is blitted full-screen before anything else is drawn on top.
This page fills in the render_init, render_free, and
draw_background stubs in render.c. Both functions are already
declared in game.h and called from main.c — the Setup page put
those connections in place. The only work here is the implementation.
render_init — loading the backgrounds
IMG_LoadTexture loads a PNG file from disk and uploads it directly
to the GPU as a texture — the same mechanism as SDL_CreateTextureFromSurface
from the previous page, but in one call instead of three.
Replace the stub in render.c:
int render_init(game_t *g)
{
g->bg_studio = IMG_LoadTexture(g->ren, "assets/bg_studio.png"); /* dark navy */
g->bg_correct = IMG_LoadTexture(g->ren, "assets/bg_correct.png"); /* dark green */
g->bg_wrong = IMG_LoadTexture(g->ren, "assets/bg_wrong.png"); /* dark red */
if (!g->bg_studio || !g->bg_correct || !g->bg_wrong) {
SDL_Log("render_init: %s", IMG_GetError());
return (-1); /* failure; main checks != 0 */
}
return (0);
}IMG_LoadTexture returns NULL on failure and sets the SDL2_image error
string, retrieved with IMG_GetError(). Checking all three in one
condition keeps the guard short — any NULL triggers the error path.
render_free — freeing the backgrounds
Replace the stub. The NULL assignment after each destroy is the same
guard from c01 and
font_free: safe against a second call, and leaves the struct in a
known state:
void render_free(game_t *g)
{
if (g->bg_studio) { SDL_DestroyTexture(g->bg_studio); g->bg_studio = NULL; }
if (g->bg_correct) { SDL_DestroyTexture(g->bg_correct); g->bg_correct = NULL; }
if (g->bg_wrong) { SDL_DestroyTexture(g->bg_wrong); g->bg_wrong = NULL; }
}draw_background
draw_background picks the texture that matches g->state and blits
it to cover the full window. It is static — called only from
render_frame inside render.c, so it does not need a prototype in
game.h:
static void draw_background(game_t *g)
{
SDL_Texture *tex;
if (g->state == STATE_CORRECT || g->state == STATE_WIN)
tex = g->bg_correct;
else if (g->state == STATE_WRONG || g->state == STATE_GAMEOVER)
tex = g->bg_wrong;
else
tex = g->bg_studio;
SDL_RenderCopy(g->ren, tex, NULL, NULL); /* NULL src + dst = full-screen blit */
}Passing NULL for both the source and destination rectangles tells
SDL_RenderCopy to use the full texture as source and the full
renderer output as destination.
render_frame with backgrounds
Update the stub in render.c:
void render_frame(game_t *g)
{
SDL_RenderClear(g->ren); /* clear to black */
draw_background(g); /* blit state-appropriate background */
SDL_RenderPresent(g->ren); /* swap to screen */
}All three calls operate on the back buffer — an off-screen surface
that the player never sees directly. SDL_RenderClear fills it with
black. draw_background blits the background texture over the entire
surface, covering the black completely. Only then does SDL_RenderPresent
swap the finished buffer to the screen.
The black never appears; by the time the frame is visible it is already covered. This is the same double-buffering pattern from c04 — build the frame in memory, show it whole.
The clear exists because without it the previous frame's pixels would bleed through anywhere nothing was drawn — harmless here since the background covers everything, but essential once the draw functions on the next page only paint part of the screen.
Why PNG instead of SDL_SetRenderDrawColor
The same result could be approached with SDL_SetRenderDrawColor and
SDL_RenderFillRect — but only partially. Each background has four
coloured zones, so you would need four separate fill calls per frame.
With that approach, the zone boundaries live in the rendering code.
With PNG, they live in the file. Changing a zone colour or boundary
means editing the script that generates the assets, not touching
render.c.
More importantly, IMG_LoadTexture does not care what is in the file.
The call is identical whether the image is a generated solid fill or a
photograph. The backgrounds in the companion repo are generated — but
swapping any one of them for a richer image requires no code change.
Loading a pre-made image rather than drawing geometry at runtime is a technique that predates the PS1 by a decade. The mechanism is the same.
Test the backgrounds
Build and run:
make re
./game questions.txtThe window should now show the dark navy background — STATE_TITLE
maps to bg_studio. If the window is still black, confirm
assets/bg_studio.png exists and that render_init is returning 0.