SDL
SDL copied to clipboard
[SDL3] SDL_RenderReadPixels (screenshot) not working.
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.
I wonder if we're reading from the window framebuffer, but the actual pixels are on the new logical scaling render target thingy.
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
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.
Disabling screen scaling in Gnome3 did not help. But I have 3 monitors connected to this computer. All with different resolutions.
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.
That makes it work
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?
Opening the image in gimp and removing the alpha channel creates a completely white image.
CC'ing @flibitijibibo in case he has any immediate insight into why Wayland would have issues here where XWayland does not.
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?
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:
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.
In the sample code the screenshot is taken before the present, which is correct, and the backbuffer contents are well defined.
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?
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.
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.
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.
I'm wondering if MESA might need the
EGL_BUFFER_PRESERVEDand/orEGL_SWAP_BEHAVIOR_PRESERVED_BITattributes 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?
I reopened it, thanks!
No longer able to reproduce this issue without pulling any new SDL3 source.