How to have two identical windows in SDL2 ?

In my app I draw things and put sprites on a renderer, and I need to have two windows that show the exact same thing, from this renderer. Is there a way I can do that ?

I tried things such as reading pixels from a renderer to put them in another texture tied to the other renderer for the other window, and the function SDL_CreateWindowFrom(), which caused a crash (BadWindow error)

In the example below, I have two different ways to do this, though they are similar.
On my computer doing so takes a pretty hefty toll. Even with such small window sizes, copying the screen from one window immediately drops my frame rate under 400 FPS.

I would recommend wrapping the method that you chose in an if statement that only lets it update the second window at 60 fps or less so that you get some CPU time back. That or use VSYNC to give the CPU a break.

#include <SDL3/SDL.h>

int main()
{
	SDL_Init(SDL_INIT_VIDEO);
	SDL_Window * win1 = SDL_CreateWindow("Title", 400, 400, 0);
	SDL_Window * win2 = SDL_CreateWindow("Title", 400, 400, 0);

	// Parent Screen
	SDL_Renderer * screenP = SDL_CreateRenderer(win1, 0);

	// Child Screen
	// remove the comment character for one of the following depending on the method you chose to run below.
	// SDL_Renderer * screenC = SDL_CreateRenderer(win2, 0);
	// SDL_Surface * screenC = SDL_GetWindowSurface(win2);


	SDL_FRect pos = {0, 0, 40, 40};
	SDL_Rect windowSize = {0, 0, 400, 400};
	

	bool run = true;
	size_t count = 0;
	size_t startTick = SDL_GetTicks();
	while(run)
	{
		SDL_Event ev;
		while(SDL_PollEvent(&ev))
		{
			switch(ev.type)
			{
				case SDL_EVENT_MOUSE_MOTION:
					pos.x = ev.motion.x;
					pos.y = ev.motion.y;
					break;
				case SDL_EVENT_WINDOW_CLOSE_REQUESTED:
				case SDL_EVENT_QUIT:
					run = false;
					break;
			}
		}

		SDL_SetRenderDrawColor(screenP, 235, 255, 255, 255);
		SDL_RenderClear(screenP);
		SDL_SetRenderDrawColor(screenP, 235, 255, 25, 255);
		SDL_RenderFillRect(screenP, &pos);

		/*  // Texture option
		SDL_Surface * tempData = SDL_RenderReadPixels(screenP, &windowSize);
		SDL_Texture * tempTexture = SDL_CreateTextureFromSurface(screenC, tempData);
		SDL_RenderTexture(screenC, tempTexture, NULL, NULL);
		SDL_DestroySurface(tempData);
		SDL_DestroyTexture(tempTexture);

		SDL_RenderPresent(screenC);
		*/


		/*  // Surface Blit option
		SDL_Surface * tempData = SDL_RenderReadPixels(screenP, &windowSize);
		SDL_BlitSurface(tempData, NULL, screenC, NULL);
		SDL_UpdateWindowSurface(win2);
		SDL_DestroySurface(tempData);
		*/

		SDL_RenderPresent(screenP);
		count ++;
	}

	SDL_Log("FPS: %d", (1000 * count)/(SDL_GetTicks() - startTick));
	SDL_DestroyWindow(win1);
	SDL_DestroyWindow(win2);
	SDL_Quit();
}

On my computer the Texture Option is about 60 FPS faster, but the other might win out on a different device.

You might get different results using a streaming texture, but I suspect it would run at about the same speed.

The culprit for the longer run-time is the call to SDL_RenderReadPixels().
Requesting data from the GPU to RAM is a slow process in comparison to other common functions.
You’ll find it is faster just to duplicate your draw calls of the two renderers instead of trying to duplicate the screen of one window to the other. This version runs at just over 4000 fps on the same computer as above:

#include <SDL3/SDL.h>

SDL_Renderer * screenP = NULL;
SDL_Renderer * screenC = NULL;

void duplo_clear()
{
	SDL_SetRenderDrawColor(screenP, 235, 255, 255, 255);
	SDL_RenderClear(screenP);
	SDL_SetRenderDrawColor(screenC, 235, 255, 255, 255);
	SDL_RenderClear(screenC);
}
void duplo_fillRect(SDL_FRect * rectangle)
{
	SDL_SetRenderDrawColor(screenP, 235, 255, 25, 255);
	SDL_RenderFillRect(screenP, rectangle);
	SDL_SetRenderDrawColor(screenC, 235, 255, 25, 255);
	SDL_RenderFillRect(screenC, rectangle);
}

int main()
{
	SDL_Init(SDL_INIT_VIDEO);
	SDL_Window * win1 = SDL_CreateWindow("Parent", 400, 400, 0);
	SDL_Window * win2 = SDL_CreateWindow("Child", 400, 400, 0);

	screenP = SDL_CreateRenderer(win1, 0);
	screenC = SDL_CreateRenderer(win2, 0);

	SDL_FRect pos = {0, 0, 40, 40};

	bool run = true;
	size_t count = 0;
	size_t startTick = SDL_GetTicks();
	while(run)
	{
		SDL_Event ev;
		while(SDL_PollEvent(&ev))
		{
			switch(ev.type)
			{
				case SDL_EVENT_MOUSE_MOTION:
					pos.x = ev.motion.x;
					pos.y = ev.motion.y;
					break;
				case SDL_EVENT_WINDOW_CLOSE_REQUESTED:
				case SDL_EVENT_QUIT:
					run = false;
					break;
			}
		}
		duplo_clear();
		duplo_fillRect(&pos);

		SDL_RenderPresent(screenP);
		SDL_RenderPresent(screenC);
		count ++;
	}
	SDL_Log("FPS: %d", (1000 * count)/(SDL_GetTicks() - startTick));
	SDL_DestroyWindow(win1);
	SDL_DestroyWindow(win2);
	SDL_Quit();
}

(I should note that the window size is the same as the previous example to make the results comparable. Size of the window does affect the FPS).

The most obvious way to accomplish this would be to simply run the rendering code twice. If you put your rendering code in function(s) then you could simply pass the renderer as an argument.

drawGraphics(rendererForWindow1);
drawGraphics(rendererForWindow2);
2 Likes

I’ve read all of these answers and I’ll make tests to see what suits me the best. I might take the classic path of RenderReadPixels because I load textures, but like, A LOT, and as long as the frame rate is above 30 FPS then it is good because said app is an Undertale fangame.
Or I might just change everything and switch to SDL3 when it’s available for Termux (I’m on 2.30.4) to write my versions of the RenderCopy and all.
Thanks for your time.