Alright, so I managed to (sort of) reproduce this weird behavior with a self-contained SDL program. The full source code is given below. It’s using Linux syscalls for memory estimation so it’s not compatible with other platforms, but that’s for the sake of the example.
What this code is doing:
- first, fill an array of
SDL_Texture*
with rectangles, and display (or not) a “loading” screen
- second, just display one green rectangle repeatedly for a certain time
While doing the first part (“loading”), you can choose to either:
- just create the rectangles, do nothing else
- display a (red) rectangle to indicate that loading is ongoing
- display the rectangle AND wait for 1s between each rectangle creation
By tracking the resident memory usage, I can compare the three options and I get the following curves:
Weirdly, I find the exact opposite behavior of my original bug (= adding a delay makes the memory usage way higher after the loading period). It seems that not displaying anything while loading is better for memory usage afterwards.
Note that the curves do not “drop” exactly when loading is done, as loading stops at 200 iterations. So I’m really suspecting some cache operations going on there.
The full source code is below, you can compile it with:
$ gcc sdl_mem.c `pkg-config --cflags --libs glib-2.0 sdl2`
Run it with argument 0
(or no argument) to do nothing, with 1
to add the red "loading screen"and with
2` to add delay.
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <glib.h>
#include <SDL.h>
enum Strategy { NOTHING = 0, LOADING_IMAGE = 1, DELAY = 2 };
void init(SDL_Window** window, SDL_Renderer** renderer, GArray** images);
void finish(SDL_Window* window, SDL_Renderer* renderer, GArray* images);
void append_rectangle (SDL_Renderer* renderer, GArray* images, Uint8 r, Uint8 g, Uint8 b);
void render_start (SDL_Renderer* renderer);
void render_end (SDL_Renderer* renderer);
void display (SDL_Renderer* renderer, SDL_Texture* texture);
int exit_requested();
size_t mem_usage();
int main (int argc, char** argv)
{
enum Strategy strategy = 0;
int nb_images = 200;
if (argc > 1)
strategy = atoi(argv[1]);
if (argc > 2)
nb_images = atoi(argv[2]);
SDL_Window* window = NULL;
SDL_Renderer* renderer = NULL;
GArray* images = NULL;
init (&window, &renderer, &images);
append_rectangle (renderer, images, 255, 0, 0);
append_rectangle (renderer, images, 0, 255, 0);
Uint32 latest = SDL_GetTicks();
int iteration = 0;
while (images->len < nb_images)
{
if (exit_requested())
break;
printf("Loading %i %li\n", iteration, mem_usage());
iteration ++;
append_rectangle (renderer, images, 0, 0, 255);
if (strategy != NOTHING)
{
if (strategy == DELAY)
SDL_Delay(1000);
Uint32 ticks = SDL_GetTicks();
if (ticks - latest > 15)
{
render_start(renderer);
display(renderer, g_array_index (images, SDL_Texture*, 0));
render_end(renderer);
latest = ticks;
}
}
}
while (iteration < 5 * nb_images)
{
iteration ++;
printf("Running %i %li\n", iteration, mem_usage());
if (exit_requested())
break;
Uint32 ticks = SDL_GetTicks();
if (ticks - latest < 30)
SDL_Delay(30 - (ticks - latest));
render_start(renderer);
display(renderer, g_array_index (images, SDL_Texture*, 1));
render_end(renderer);
latest = ticks;
}
finish (window, renderer, images);
return EXIT_SUCCESS;
}
/******************************************************************************
* Functions
*****************************************************************************/
void init (SDL_Window** window, SDL_Renderer** renderer, GArray** images)
{
SDL_Init(SDL_INIT_VIDEO);
*window = SDL_CreateWindow ("SDL Memory test", SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED,
800, 600, SDL_WINDOW_RESIZABLE);
*renderer = SDL_CreateRenderer (*window, -1, 0);
*images = g_array_new (FALSE, FALSE, sizeof(SDL_Texture*));
}
void finish (SDL_Window* window, SDL_Renderer* renderer, GArray* images)
{
int i = 0;
for (; i < images->len; i ++)
SDL_DestroyTexture(g_array_index (images, SDL_Texture*, i));
g_array_free(images, TRUE);
SDL_DestroyRenderer(renderer);
SDL_DestroyWindow(window);
SDL_Quit();
}
void render_start (SDL_Renderer* renderer)
{
SDL_SetRenderDrawColor (renderer, 0, 0, 0, 255);
SDL_RenderClear (renderer);
}
void display (SDL_Renderer* renderer, SDL_Texture* texture)
{
SDL_Rect source;
source.x = 0;
source.y = 0;
source.w = 600;
source.h = 400;
SDL_Rect target;
target.x = 0;
target.y = 0;
target.w = 600;
target.h = 400;
SDL_RenderCopy(renderer, texture, &source, &target);
}
void render_end (SDL_Renderer* renderer)
{
SDL_RenderPresent (renderer);
}
int exit_requested()
{
SDL_Event ev;
int broken = FALSE;
while (SDL_PollEvent(&ev))
if (ev.type == SDL_QUIT || (ev.type == SDL_KEYDOWN && ev.key.keysym.sym == SDLK_ESCAPE))
return TRUE;
return FALSE;
}
void append_rectangle (SDL_Renderer* renderer, GArray* images, Uint8 r, Uint8 g, Uint8 b)
{
Uint32 rmask, gmask, bmask, amask;
#if SDL_BYTEORDER == SDL_BIG_ENDIAN
rmask = 0xff000000;
gmask = 0x00ff0000;
bmask = 0x0000ff00;
amask = 0x000000ff;
#else
rmask = 0x000000ff;
gmask = 0x0000ff00;
bmask = 0x00ff0000;
amask = 0xff000000;
#endif
SDL_Surface* surface = SDL_CreateRGBSurface (0, 600, 400, 32, rmask, gmask, bmask, amask);
SDL_FillRect(surface, NULL, SDL_MapRGBA(surface->format, r, g, b, 255));
SDL_Texture* texture = SDL_CreateTextureFromSurface (renderer, surface);
g_array_append_val (images, texture);
SDL_FreeSurface (surface);
}
size_t mem_usage()
{
FILE* file = fopen("/proc/self/stat", "r");
size_t charlen = 256;
char pid[charlen], comm[charlen], state[charlen], ppid[charlen], pgrp[charlen], session[charlen], tty_nr[charlen],
tpgid[charlen], flags[charlen], minflt[charlen], cminflt[charlen], majflt[charlen], cmajflt[charlen],
utime[charlen], stime[charlen], cutime[charlen], cstime[charlen], priority[charlen], nice[charlen],
O[charlen], itrealvalue[charlen], starttime[charlen], vsize[charlen];
long rss;
fscanf(file, "%s %s %s %s %s %s %s %s %s %s %s %s %s %s %s %s %s %s %s %s %s %s %s %li",
pid, comm, state, ppid, pgrp, session, tty_nr, tpgid,
flags, minflt, cminflt, majflt, cmajflt,
utime, stime, cutime, cstime, priority, nice, O,
itrealvalue, starttime, vsize, &rss);
fclose(file);
return rss * sysconf(_SC_PAGE_SIZE) / (1024 * 1024);
}