Strange artefacts when painting a new screen color every 500ms

I am learning programming by making a Tetris game, my example code below references that but really my problems is so much more trivial than Tetris, as a test I am painting random colors on a 640x480 buffer, and already I am screwing up, how people make Call of Duty I just can’t imagine…

I can’t describe what the problem is so I took a video and posted it here: https://drive.google.com/open?id=13LbsmVtk1zBervDJRy8ofu__CC41iFwi

It is quite hard to see, but I see a horizontal zig-zag line flash up about 5 times during this video of my “game”. I have no idea why it’s happening, any keywords I can educate myself with would be amazing.

My code is here,

/* To compile:
 * First compile SDL from hg checkout using "mkdir build; cd build; cmake .. -DCMAKE_BUILD_TYPE=Debug" 
 * cc -g -I./SDL/include tetris.c ./SDL/build/libSDL2d.a /usr/lib/x86_64-linux-gnu/libsndio.so -ldl -lm -lpthread -o tetris
 */
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>

#include <SDL.h>

#define LOGICAL_WIDTH  640
#define LOGICAL_HEIGHT 480

// The screen is divided into blocks of this many square pixels.
// BLOCK_WIDTH_HEIGHT must be a factor of LOGICAL_WIDTH and LOGICAL_HEIGHT.
// Valid widths for 640x480: 1,2,4,5,8,10,16,20,32,40,80,160
#define BLOCK_WIDTH_HEIGHT  80
#define BLOCK_NUM_PIXELS  (BLOCK_WIDTH_HEIGHT * BLOCK_WIDTH_HEIGHT)
#define NUM_BLOCKS_ACROSS (LOGICAL_WIDTH / BLOCK_WIDTH_HEIGHT)
#define NUM_BLOCKS_DOWN (LOGICAL_HEIGHT / BLOCK_WIDTH_HEIGHT)
#define NUM_BLOCKS   (NUM_BLOCKS_ACROSS * NUM_BLOCKS_DOWN)

// Taken from stb.h
static uint32_t tetris__random_seed = 0;
uint32_t tetris_srandLCG()
{
    tetris__random_seed = tetris__random_seed * 2147001325 + 715136305; // BCPL generator
    // shuffle non-random bits to the middle, and xor to decorrelate with seed
    return 0x31415926 ^ ((tetris__random_seed >> 16) + (tetris__random_seed << 16));
}
#define tetris_rng   tetris_srandLCG

// Where the frames are drawn.
static uint32_t *frame_buffer_start;
static uint32_t frame_buffer_n_pixels;
static uint32_t frame_buffer_pitch;

static void
tetris_render_solid(uint32_t color)
{
    uint32_t *pixel = frame_buffer_start;
    uint32_t pixels_left_to_paint = frame_buffer_n_pixels;
    while (pixels_left_to_paint--) {
	*pixel++ = color;
    }
}

static void
tetris_render_random_colored_pixels()
{
    uint32_t *pixel = frame_buffer_start;
    uint32_t pixels_left_to_paint = frame_buffer_n_pixels;
    while (pixels_left_to_paint--) {
	*pixel++ = tetris_rng();
    }
}


static void
tetris_render_block_solid_color_at (uint32_t block_color, uint32_t block_n)
{
    uint32_t block_row_start_pixel =
        BLOCK_WIDTH_HEIGHT * block_n + ((block_n / NUM_BLOCKS_ACROSS) * (NUM_BLOCKS_ACROSS * BLOCK_NUM_PIXELS));

    for (int i = 0; i < BLOCK_WIDTH_HEIGHT; i++) {
	uint32_t *pixels = frame_buffer_start + block_row_start_pixel;
	// Render a row
	for (int j = 0; j < BLOCK_WIDTH_HEIGHT; j++)
            *pixels++ = block_color;

	block_row_start_pixel += LOGICAL_WIDTH;
    }
}


// #include "test_block.data"

void
tetris_render_test_blocks()
{
    for (int block_num = 0; block_num < NUM_BLOCKS; block_num++)
	tetris_render_block_solid_color_at (0xFF0000FF, block_num);
}

int
main(int argc, char *argv[])
{
    SDL_Window *window;
    SDL_Renderer *renderer;
    SDL_Texture *texture; // The texture on the GPU.
    SDL_Event event;
    int quit = 0;

    // Note: most SDL calls must happen on the same thread as this call.
    if (SDL_Init(SDL_INIT_VIDEO) != 0) {
        fprintf (stderr, "failed to initialize SDL: [SDL error: %s]", SDL_GetError());
        exit (1);
    }

    // Setup frame buffer memory.
    // Width * Height * (r, g, b, a)
    frame_buffer_pitch = LOGICAL_WIDTH * sizeof(uint32_t);
    uint32_t frame_buffer_size_in_bytes = frame_buffer_pitch * LOGICAL_HEIGHT;

    frame_buffer_start = malloc(frame_buffer_size_in_bytes);
    if (!frame_buffer_start) {
        fprintf (stderr, "failed to create frame buffer\n");
        exit (1);
    }
    frame_buffer_n_pixels = frame_buffer_size_in_bytes / sizeof(uint32_t);

    printf("blocks across: %d\n", NUM_BLOCKS_ACROSS);
    printf("blocks down: %d\n", NUM_BLOCKS_DOWN);

    window = SDL_CreateWindow ("Tetris!",
			       SDL_WINDOWPOS_UNDEFINED,
			       SDL_WINDOWPOS_UNDEFINED,
			       0, 0,
			       SDL_WINDOW_FULLSCREEN_DESKTOP);

    if (!window) {
	fprintf (stderr, "failed to create a game window: [SDL error: %s]\n", SDL_GetError());
	exit (1);
    }

    renderer = SDL_CreateRenderer (window, -1, 0);
    if (!renderer) {
	fprintf (stderr, "failed to create a game renderer: [SDL error: %s]\n", SDL_GetError());
	exit (1);
    }


    // Create a texture on the GPU.
    texture = SDL_CreateTexture (renderer,
				 SDL_PIXELFORMAT_ARGB8888,
				 SDL_TEXTUREACCESS_STREAMING,
				 LOGICAL_WIDTH, LOGICAL_HEIGHT);

    // Make the scaled rendering look smoother.
    SDL_SetHint (SDL_HINT_RENDER_SCALE_QUALITY, "linear");
    SDL_RenderSetLogicalSize (renderer, LOGICAL_WIDTH, LOGICAL_HEIGHT);

    tetris_render_solid (0xFF000000);

    while (!quit) {
	fprintf (stderr,".");
	while (SDL_PollEvent (&event)) {
	    switch (event.type) {
	    case SDL_QUIT:
		quit = 1;
		break;
	    case SDL_KEYDOWN:
		if (event.key.keysym.sym == SDLK_q) {
		    quit = 1;
		}
		break;
	    }
	}

        tetris_render_solid (tetris_rng());

	SDL_UpdateTexture (texture, NULL, frame_buffer_start, frame_buffer_pitch);

	SDL_RenderClear (renderer);
	SDL_RenderCopy (renderer, texture, NULL, NULL);
	SDL_RenderPresent (renderer);

	SDL_Delay (500);
    }

    SDL_DestroyWindow (window);
    SDL_Quit ();
    return 0;
}

You need to enable vertical sync when you create the renderer. Change the line where you create your renderer to
renderer = SDL_CreateRenderer(window, -1, SDL_RENDERER_PRESENTVSYNC);

What is happening is that you are changing the contents of the screen while it’s in the middle of updating (which isn’t instantaneous), so the monitor shows part of one frame and part of the next. Enabling vsync will make SDL_RenderPresent() wait until the screen is finished being updated before changing it.

You can see this happening in pretty much any game that lets you turn off vsync.

Thank you, that has fixed the issue. I have some follow-up questions. In learning what that flag does, I found https://www.khronos.org/registry/OpenGL/extensions/MESA/GLX_MESA_swap_control.txt

This says that GL will swap the colour buffers at most once every video frame on the monitor. Four questions,

  1. I only draw to one colour buffer in my application, does GL create more than one for some reason?
  2. Why the specification at most, does it mean that there can be cases where the “colour buffers” will not be updated, and I lose an update to my framebuffer just because the GL driver decided not to update my buffers?
  3. Why is PRESENTVSYNC not a default on SDL? In what cases would you want buffer updates to happen un-synchronised to the monitor frame rate? Is there a performance hit for this? (I suspect yes!)
  4. I feel horrendously under-educated to be using SDL, are there resources I can read that cover some of these basics?
  1. Yes, it’s double buffered. A front buffer, for what is being shown on the screen, and a back buffer, which is where the actual rendering is done. When you call SDL_RenderPresent(), somewhere in the stack the driver “page flips” the buffers, designating the back buffer as the front buffer and vice versa, causing the GPU to start sending what used to be the back buffer to the monitor. The tearing you saw is what happens when this swap/page flip happens while the monitor is in the middle of displaying a frame.

  2. Yes. While it’s really saying that when enabled you won’t be able to swap buffers more than once per monitor frame, there’s also the implication that if your application waits too long it’ll miss the vertical sync update window and have to wait for the next monitor frame update window. It won’t page flip mid-frame.

  3. Nearly every PC game that has adjustable graphics settings or even just resolution settings lets you turn off vsync. There’s no performance hit per se, but waiting for vsync introduces a very slight lag. Think about it: your frame is finished and you’ve called SDL_RenderPresent(), and now your game is just sitting there, waiting for it to be shown on the screen. This can be noticeable in twitch games especially, because player input is essentially lagging a frame behind.
    People also sometimes turn it off if their system is too slow to keep 60 FPS. If your system can only do 47 FPS, with vsync off you’re actually getting 47 FPS (even if only parts of those frames get displayed). With vsync on, it’s ping-ponging between 60 and 30 FPS because the game is missing the vsync update window and has to wait for the next one, which can be stuttery and jarring.

  4. Making a game from scratch can be a hard way to learn programming. I know from experience that it’s easy to get overwhelmed. You have to learn not only programming and the language you’re learning it in, data structures, and a whole bunch of game dev stuff.

1 Like