SDL icon indicating copy to clipboard operation
SDL copied to clipboard

[SDL3] SDL_RenderReadPixels (screenshot) not working.

Open LiquidityC opened this issue 2 years ago • 19 comments

Hi. While doing some testing I stumbled onto a problem where I was unable to "render to a surface" to take a screenshot. It would all just en up as a transparent BMP. Here is some sample code to reproduce the problem.

#include <stdio.h>
#include "SDL.h"

int main(int argc, char **argv)
{
	int result = 0;
	SDL_Window *window = NULL;
	SDL_Renderer *renderer = NULL;

	if (SDL_Init(SDL_INIT_VIDEO) != 0) {
		printf("Err: %s\n", SDL_GetError());
		result = -1;
		goto out;
	}

	SDL_CreateWindowAndRenderer("Screenshot test", 400, 300, 0, &window, &renderer);
	if (!window) {
		printf("Err: %s\n", SDL_GetError());
		result = -1;
		goto clean;
	}

	if (!renderer) {
		printf("Err: %s\n", SDL_GetError());
		result = -1;
		goto clean;
	}

	/* Main loop */
	SDL_Event e;
	SDL_bool running = SDL_TRUE;
	while (running) {
		//SDL_bool take_screenshot = SDL_FALSE;
		while (SDL_PollEvent(&e)) {
			switch (e.type) {
				case SDL_EVENT_QUIT:
					running = SDL_FALSE;
					break;
				case SDL_EVENT_KEY_DOWN:
					if (e.key.keysym.sym == SDLK_p)
						//take_screenshot = SDL_TRUE;
					break;
			}
		}

		SDL_SetRenderDrawColor(renderer, 100, 100, 100, 255);
		SDL_RenderClear(renderer);

		SDL_SetRenderDrawColor(renderer, 0, 255, 0, 255);

		/* Draw some shapes */
		SDL_FRect r = { 20, 20, 100, 100 };
		SDL_RenderFillRect(renderer, &r);

		r.x += 40;
		r.y += 40;
		SDL_SetRenderDrawColor(renderer, 0, 0, 255, 255);
		SDL_RenderRect(renderer, &r);

		r.x += 40;
		r.y += 40;
		SDL_SetRenderDrawColor(renderer, 255, 0, 0, 255);
		SDL_RenderRect(renderer, &r);

		puts("Creating screenshot");
		SDL_Rect vp;
		SDL_assert(SDL_GetRenderViewport(renderer, &vp) == 0);
		printf("Driver: %s, Viewport dimension: %dx%d\n", SDL_GetRendererName(renderer), vp.w, vp.h);
		SDL_Surface *sshot = SDL_RenderReadPixels(renderer, NULL);
		SDL_assert(sshot);
		SDL_assert(SDL_SaveBMP(sshot, "screenshot.bmp") == 0);
		SDL_DestroySurface(sshot);

		SDL_RenderPresent(renderer);

		running = SDL_FALSE;
	}

clean:
	SDL_DestroyRenderer(renderer);
	SDL_DestroyWindow(window);
	SDL_Quit();
out:
	return result;
}

Opening the created bmp shows a completely transparent image.

This matching code in SDL2 worked as expected:

if (take_screenshot == SDL_TRUE) {
        puts("Creating screenshot");
        int w, h;
        SDL_GetRendererOutputSize(renderer, &w, &h);
        SDL_Surface *sshot = SDL_CreateRGBSurface(0, w, h, 32, 0x00ff0000, 0x0000ff00, 0x000000ff, 0xff000000);
        SDL_RenderReadPixels(renderer, NULL, SDL_PIXELFORMAT_ARGB8888, sshot->pixels, sshot->pitch);
        SDL_SaveBMP(sshot, "screenshot.bmp");
        SDL_FreeSurface(sshot);
}

On my system I'm running Wayland with Intel graphics.

LiquidityC avatar Apr 24 '23 08:04 LiquidityC

I wonder if we're reading from the window framebuffer, but the actual pixels are on the new logical scaling render target thingy.

icculus avatar Apr 24 '23 13:04 icculus

Oh, maybe not, it's working on X11 for me. Can you stick this in your example program right before /* Main loop */ ...

    SDL_RendererInfo info;
    SDL_GetRendererInfo(renderer, &info);
    printf("renderer: %s\n", info.name);

...and tell me what it reports? Here, it says:

renderer: opengl

icculus avatar Apr 24 '23 13:04 icculus

I added this:

SDL_Rect vp;
SDL_RendererInfo info;
SDL_GetRendererInfo(renderer, &info); info:
SDL_assert(SDL_GetRenderViewport(renderer, &vp) == 0);
printf("Driver: %s, Viewport dimension: %dx%d\n", info.name, vp.w, vp.h);

Which gave this: Driver: opengl, Viewport dimension: 600x450

So. Same driver (surprised me :smile:). But I think the fact that the dimensions don't align with the ones used at window creation (400x300) might be a hint here. My screen is one of those HiRes ones.

LiquidityC avatar Apr 24 '23 14:04 LiquidityC

Disabling screen scaling in Gnome3 did not help. But I have 3 monitors connected to this computer. All with different resolutions.

LiquidityC avatar Apr 24 '23 14:04 LiquidityC

Can you force the SDL3 version to use XWayland instead?

SDL_VIDEO_DRIVER=x11 ./mytestprogram

Just to rule out if it's some weird interaction in our Wayland backend.

icculus avatar Apr 24 '23 15:04 icculus

That makes it work

LiquidityC avatar Apr 24 '23 16:04 LiquidityC

Opening the created bmp shows a completely transparent image.

Do you know if all components of the pixels in the image (r, g, b, a) are 0, or just the alpha component is 0 and the rest is what it should be?

slime73 avatar Apr 24 '23 16:04 slime73

Opening the image in gimp and removing the alpha channel creates a completely white image.

LiquidityC avatar Apr 24 '23 17:04 LiquidityC

CC'ing @flibitijibibo in case he has any immediate insight into why Wayland would have issues here where XWayland does not.

icculus avatar Apr 25 '23 02:04 icculus

I could see the backbuffer format being different maybe, but I dunno how that'd affect glReadPixels... apitraces of Xwayland and Wayland could help but now that I think about it I dunno if apitrace supports that (I know RenderDoc still does not).

One other thing to try is SDL_VIDEO_X11_FORCE_EGL=1, that could possibly have some effects?

flibitijibibo avatar Apr 25 '23 02:04 flibitijibibo

I'm not good with opengl but if there is anything more I can do to help with debugging since I'm the one experiencing the issue just tell me.

I know my way around a compiler and a debugger (gdb) :wink:

LiquidityC avatar Apr 25 '23 13:04 LiquidityC

On Fedora 38 with Nvidia 530 drivers, the repro case works fine, even with scaled displays.

I'm wondering if MESA might need the EGL_BUFFER_PRESERVED and/or EGL_SWAP_BEHAVIOR_PRESERVED_BIT attributes set, as otherwise the EGL spec states that the color buffer contents can be left in an undefined state after a call to eglSwapBuffers(). This might be implicitly set on X clients, but not native Wayland clients, which would explain the discrepancy.

Trying to set these attributes on Nvidia results in an unsupported attribute error, so I can't test this myself.

Kontrabant avatar Apr 28 '23 15:04 Kontrabant

In the sample code the screenshot is taken before the present, which is correct, and the backbuffer contents are well defined.

slouken avatar Apr 28 '23 15:04 slouken

So. Same driver (surprised me smile). But I think the fact that the dimensions don't align with the ones used at window creation (400x300) might be a hint here. My screen is one of those HiRes ones.

Out of curiosity, if you try the SDL2 variant with the SDL_WINDOW_ALLOW_HIGHDPI flag set, does it still work?

Kontrabant avatar Apr 28 '23 17:04 Kontrabant

SDL 2.0 is now in maintenance mode, and all inactive issues are being closed. If this issue is impacting you, please feel free to reopen it with additional information.

slouken avatar Nov 08 '23 04:11 slouken

there is something similar in SDL2, when in fullscreen mode, the SDL_RenderReadPixels looks like is not working.

I am using a routine to do a screen fadeout of what has been renderered onto the screen. it works when windowed, but not when is in fullscreen. The only difference is the create window with flag fullscreen (or desktop) basically.

Not sure if it is related to some windows directx driver or a bug in SDL_RenderReadPixels .


Furthermore, if using in combination with SDL_RenderSetLogicalSize let say with a value of (320,200), and using a screen resolution that is not an exact multiple of the logical size, like e.g. (800x600). What is read from the screen is not aligned, it is like it is reading from a bigger screen, in fact, is behaving like having a 960x600 window. This in windowed mode, didn't bother neither to try on fullscreen|desktop mode.

I was going to open a ticket to report these 2 issues related to SDL_RenderReadPixels, let me know if i should do, as anyway it might later on affect SDL3 if it is like an edge case that has been missed.

Raffaello avatar Nov 22 '23 16:11 Raffaello

Go ahead and open new issues for what you're seeing, as I'm not sure it's related and it looks like the original author never responded here.

slouken avatar Nov 28 '23 19:11 slouken

I'm wondering if MESA might need the EGL_BUFFER_PRESERVED and/or EGL_SWAP_BEHAVIOR_PRESERVED_BIT attributes set, as otherwise

So I forgot about this bug due to work and just recently got back to attempting to build what I was originally building. Setting EGL_SWAP_BEHAVIOR_PRESERVED_BIT in SDL_egl.c fixes this issue for me.

@slouken I updated the first comment in this issue making the code work with SDL on master. Do you prefer a new issue or can we re-open this one?

LiquidityC avatar Jun 19 '24 08:06 LiquidityC

I reopened it, thanks!

slouken avatar Jun 19 '24 14:06 slouken

No longer able to reproduce this issue without pulling any new SDL3 source.

LiquidityC avatar Aug 08 '24 11:08 LiquidityC