[SDL3.x]Multiple renderers slow down

I’m currently trying to render in multiple windows in parallel. What I notice is that the loop gets slower the higher I set the #define winCount.

This surprises me, as I was once advised here in the forum that the hardware renderer depends on the fps.

Therefore, I suspect that my approach in my program is wrong when rendering in multiple windows.

#include <SDL3/SDL.h>

#define winCount   4

int main(int argc, char *argv[])
{
  SDL_FRect rDest = {150, 150, 256, 256};

  SDL_Init(SDL_INIT_VIDEO);

  SDL_Window *win[winCount];
  SDL_Renderer *renderer[winCount];

  for (int i=0; i < winCount; i++) {
    win[i] = SDL_CreateWindow("Hello World", 640, 480, SDL_WINDOW_RESIZABLE ) ;
    renderer[i] = SDL_CreateRenderer(win[i], NULL);
  }

  SDL_bool quit = SDL_FALSE;
  while (!quit)
  {
    SDL_Event event;
    while (SDL_PollEvent(&event))
    {
      switch (event.type)
      {
        case SDL_EVENT_WINDOW_CLOSE_REQUESTED:
             quit = SDL_TRUE;
             break;
      }
    }

    rDest.x += 0.1;
    if (rDest.x > 300) {
      rDest.x = 0.0;
    }

    for (int i=0; i < winCount; i++) {
      SDL_SetRenderDrawColorFloat(renderer[i], 0.5, 1.0, 1.0, SDL_ALPHA_OPAQUE);
      SDL_RenderClear(renderer[i]);

      SDL_SetRenderDrawColorFloat(renderer[i], 1.0, 0.5, 0.5, SDL_ALPHA_OPAQUE);
      SDL_RenderFillRect(renderer[i], &rDest);

      SDL_RenderPresent(renderer[i]);
    }
  }

  for (int i=0; i < winCount; i++) {
    SDL_DestroyRenderer(renderer[i]);
    SDL_DestroyWindow(win[i]);
  }
  SDL_Quit();
  return 0;
}

Doing more is slower than doing less.

You might be thinking of vsync which limits the fps to the monitor’s refresh rate. It’s turned off by default.

If you enable vsync you’ll notice that it runs much slower than before.

I’m not sure how to handle it properly with multiple windows. Normally you would call SDL_SetRenderVSync(renderer, 1); to enable vsync but if you do that for all renderers it would wait each time you call SDL_RenderPresent which is not what you want. Maybe only enable vsync for the last renderer is the way to go…

Agreed.

Your example is requesting the GPU to redraw the screen for each window for every frame. This is a problem because communication with the GPU is itself a big bottleneck. The best way to optimize for multiple windows is to be aware of the contents of the window. If a window’s state is static then it is best to avoid redrawing it’s contents for as long as possible. Best case scenario is that you are aware of where the user is currently focused (by where the mouse is or because you know where the game/programs key content is) and let the other windows idle until they are needed.

If it is a window full of GUI elements that the user isn’t currently interacting with, then just let it be until the mouse or keyboard interaction activates it. Those elements do not need to be redrawn each frame. This allows you to focus the CPU on the areas where the FPS matters more.

If a side-window has a small animation on display, then run that window at the FPS of that animation.

1 Like

I understand that running an animation in multiple windows is a very expensive thing.
Either you ask for changes or you limit yourself to a single window app.

Ignoring vsync…

I don’t know if that is true. There might be a small overhead for each window but it’s mostly a matter of how much “work” you do. If having more windows means you do more work then of course it will run slower, but if you do the same amount of work (e.g. having two windows running one animation each, instead of one window running both animations, assuming the animations take up the same size in both cases and that the total window area is about the same) then it will probably be similar.

Normally you would want to write the code in such a way that the animation speed is not affected by the rendering speed. In games it’s common to calculate the delta time (i.e. time since last update) and use that to decide how much to move things. If the delta time becomes too big it can lead to problems for things like collision detection so to avoid that you might want to put an upper limit on the delta time (and accept that it will run slower in that case) or you might want to use a fixed value for the delta time which means you might sometimes have to run the update code more than once for each graphics update. You could for example run the game logic at 60 fps while the graphics might only be able to keep up with 30 fps. With interpolation/extrapolation it might also be possible to run the graphics updates at a faster pace than the logic updates.

In case you want to look it up:

  • Using a varying value for the delta time is known as “variable time step”.
  • Using a fixed value for the delta time is know as “fixed time step”.
Example of your original program using a variable time step

Note how the animation runs at the same speed no matter how many windows you have.

#include <SDL3/SDL.h>

#define winCount   1

int main(int argc, char *argv[])
{
  SDL_FRect rDest = {150, 150, 256, 256};
  const float rSpeed = 100.0f; // distance per second

  SDL_Init(SDL_INIT_VIDEO);

  SDL_Window *win[winCount];
  SDL_Renderer *renderer[winCount];

  for (int i=0; i < winCount; i++) {
    win[i] = SDL_CreateWindow("Hello World", 640, 480, SDL_WINDOW_RESIZABLE ) ;
    renderer[i] = SDL_CreateRenderer(win[i], NULL);
  }
  
  Uint64 lastUpdate = SDL_GetTicks();

  SDL_bool quit = SDL_FALSE;
  while (!quit)
  {
    SDL_Event event;
    while (SDL_PollEvent(&event))
    {
      switch (event.type)
      {
        case SDL_EVENT_WINDOW_CLOSE_REQUESTED:
             quit = SDL_TRUE;
             break;
      }
    }
    
    Uint64 now = SDL_GetTicks();
    float deltaTime = (now - lastUpdate) / 1000.0f;
    lastUpdate = now;

    rDest.x += rSpeed * deltaTime;
    if (rDest.x > 300) {
      rDest.x = 0.0f;
    }

    for (int i=0; i < winCount; i++) {
      SDL_SetRenderDrawColorFloat(renderer[i], 0.5, 1.0, 1.0, SDL_ALPHA_OPAQUE);
      SDL_RenderClear(renderer[i]);

      SDL_SetRenderDrawColorFloat(renderer[i], 1.0, 0.5, 0.5, SDL_ALPHA_OPAQUE);
      SDL_RenderFillRect(renderer[i], &rDest);

      SDL_RenderPresent(renderer[i]);
    }
  }

  for (int i=0; i < winCount; i++) {
    SDL_DestroyRenderer(renderer[i]);
    SDL_DestroyWindow(win[i]);
  }
  SDL_Quit();
  return 0;
}

In my experience “variable time step” is almost never a good idea, because the delta time you need to use in your calculations is the time to the next display update (vSync) and of course that’s impossible to determine, without a time machine to look into the future!

What people therefore tend to use is the period of the previous frame, which at least is possible to determine, but if the frame rate is variable that’s not the number you need when rendering! Using the wrong delta time value will result in motion artefacts.

But somehow I still don’t understand the whole thing, hence the question in the first post.

With SDL_CreateRenderer, there used to be the following flags.

 SDL_RENDERER_SOFTWARE, SDL_RENDERER_ACCELERATED, DL_RENDERER_PRESENTVSYNC

As far as I know, these have been removed and replaced by SDL_RENDERER_PRESENTVSYNC. When SDL_RENDERER_ACCELERATED still existed, the animation was really fast. I was told that this was removed so that the CPU wouldn’t be put under unnecessary strain. And SDL_RENDERER_PRESENTVSYNC would automatically adjust to the FPS.

SDL_RENDERER_SOFTWARE and SDL_RENDERER_ACCELERATED was opposites.

SDL_RENDERER_ACCELERATED was the default so it wasn’t really necessary.

If you really want to create a software renderer for the window in SDL3 you can still do that by passing SDL_SOFTWARE_RENDERER (or "software") as the second argument to SDL_CreateRenderer.


SDL_RENDERER_PRESENTVSYNC was independent of the other flags. It was used to enable vsync when you created the renderer.

You can still enable vsync when creating the renderer in SDL3 using properties:

SDL_PropertiesID props = SDL_CreateProperties();
SDL_SetProperty(props, SDL_PROP_RENDERER_CREATE_WINDOW_POINTER, window);
SDL_SetNumberProperty(props, SDL_PROP_RENDERER_CREATE_PRESENT_VSYNC_NUMBER, 1);
SDL_Renderer* renderer = SDL_CreateRendererWithProperties(props);
SDL_DestroyProperties(props);

Otherwise you can enable vsync after the renderer has been created using SDL_SetRenderVSync (as shown earlier). Note that this function existed also in SDL2 but was then named SDL_RenderSetVSync.

1 Like

This has been removed.

extern SDL_DECLSPEC SDL_Renderer * SDLCALL SDL_CreateRenderer(SDL_Window *window, const char *name);

Note that I wrote SDL_SOFTWARE_RENDERER (not SDL_RENDERER_SOFTWARE) and second argument (not third argument).

1 Like

Now I see it, this comes in as a string for the name.

/**
 * The name of the software renderer.
 *
 * \since This macro is available since SDL 3.0.0.
 */
#define SDL_SOFTWARE_RENDERER   "software"

But when I look in SDL_render.h, it looks like there is currently only the software flag.

Or am I missing something?

You can get a list of renderer names by running:

int n = SDL_GetNumRenderDrivers();
for (int i = 0; i < n; ++i)
{
	SDL_Log("%s", SDL_GetRenderDriver(i));
}

For me this prints:

INFO: opengl
INFO: opengles2
INFO: vulkan
INFO: software

“opengl” and “opengles2” seems to give similar performance for me. “software” is slower as expected. “vulkan” doesn’t work.

The renderers that are available depends on the platform. A more complete list of renderers is listed here.

2 Likes

“opengl” and “opengles2” seems to give similar performance for me. “software” is slower as expected. “vulkan” doesn’t work.

Do I understand this correctly?
SDL uses a well-known 3D renderer such as OpenGL, Vulkan, etc. in the background for hardware rendering?

vulkan” doesn’t work.

For me this works:

renderer[i] = SDL_CreateRenderer(window[i], "vulkan");

Yes, that’s true.

For me it prints

ERROR: Vulkan: no viable physical devices found

and nothing gets drawn in the window. The computer that I test this on is a 13 year old laptop so maybe that’s why, or maybe there are some vulkan drivers that I need to install, I don’t know (and don’t care as long as the default renderer works).

1 Like

This is probably due to the age of the PC, Vulkan is still relatively young.