Fix T98462: Save Screenshot (glReadPixels) fails under Wayland

Use an off-screen buffer for the screen-shot operator.

Reading from the front-buffer immediately after calling swap-buffers
failed for GHOST/Wayland in some cases.
While EGL can request to preserve the front-buffer while drawing,
this isn't always supported. So workaround the problem by avoiding
use of the front-buffer entirely.
This commit is contained in:
Campbell Barton 2022-08-17 12:53:35 +10:00
parent 54827bb7cd
commit 48da8c4040
Notes: blender-bot 2023-02-14 10:35:28 +01:00
Referenced by issue #98462, Save Screenshot (glReadPixels) fails under Wayland
4 changed files with 54 additions and 2 deletions

View File

@ -54,13 +54,12 @@ static int screenshot_data_create(bContext *C, wmOperator *op, ScrArea *area)
{
int dumprect_size[2];
wmWindowManager *wm = CTX_wm_manager(C);
wmWindow *win = CTX_wm_window(C);
/* do redraw so we don't show popups/menus */
WM_redraw_windows(C);
uint *dumprect = WM_window_pixels_read(wm, win, dumprect_size);
uint *dumprect = WM_window_pixels_read_offscreen(C, win, dumprect_size);
if (dumprect) {
ScreenshotData *scd = MEM_callocN(sizeof(ScreenshotData), "screenshot");

View File

@ -134,7 +134,24 @@ void WM_window_pixel_sample_read(const wmWindowManager *wm,
const int pos[2],
float r_col[3]);
/**
* Read pixels from the front-buffer (fast).
*
* \note Internally this depends on the front-buffer state,
* for a slower but more reliable method of reading pixels, use #WM_window_pixels_read_offscreen.
* Fast pixel access may be preferred for file-save thumbnails.
*
* \warning Drawing (swap-buffers) immediately before calling this function causes
* the front-buffer state to be invalid under some EGL configurations.
*/
uint *WM_window_pixels_read(struct wmWindowManager *wm, struct wmWindow *win, int r_size[2]);
/**
* Draw the window & read pixels from an off-screen buffer (slower than #WM_window_pixels_read).
*
* \note This is needed because the state of the front-buffer may be damaged
* (see in-line code comments for details).
*/
uint *WM_window_pixels_read_offscreen(struct bContext *C, struct wmWindow *win, int r_size[2]);
/**
* Support for native pixel size

View File

@ -1191,6 +1191,39 @@ static void wm_draw_surface(bContext *C, wmSurface *surface)
wm_surface_clear_drawable();
}
uint *WM_window_pixels_read_offscreen(bContext *C, wmWindow *win, int r_size[2])
{
/* NOTE(@campbellbarton): There is a problem reading the windows front-buffer after redrawing
* the window in some cases (typically to clear UI elements such as menus or search popup).
* With EGL `eglSurfaceAttrib(..)` may support setting the `EGL_SWAP_BEHAVIOR` attribute to
* `EGL_BUFFER_PRESERVED` however not all implementations support this.
* Requesting the ability with `EGL_SWAP_BEHAVIOR_PRESERVED_BIT` can even cause the EGL context
* not to initialize at all.
* Confusingly there are some cases where this *does* work, depending on the state of the window
* and prior calls to swap-buffers, however ensuring the state exactly as needed to satisfy a
* particular GPU back-end is fragile, see T98462.
*
* So provide an alternative to #WM_window_pixels_read that avoids using the front-buffer. */
/* Draw into an off-screen buffer and read it's contents. */
r_size[0] = WM_window_pixels_x(win);
r_size[1] = WM_window_pixels_y(win);
GPUOffScreen *offscreen = GPU_offscreen_create(r_size[0], r_size[1], false, GPU_RGBA8, NULL);
if (UNLIKELY(!offscreen)) {
return NULL;
}
const uint rect_len = r_size[0] * r_size[1];
uint *rect = MEM_mallocN(sizeof(*rect) * rect_len, __func__);
GPU_offscreen_bind(offscreen, false);
wm_draw_window_onscreen(C, win, -1);
GPU_offscreen_unbind(offscreen, false);
GPU_offscreen_read_pixels(offscreen, GPU_DATA_UBYTE, rect);
GPU_offscreen_free(offscreen);
return rect;
}
/** \} */
/* -------------------------------------------------------------------- */

View File

@ -1951,6 +1951,9 @@ void WM_window_pixel_sample_read(const wmWindowManager *wm,
uint *WM_window_pixels_read(wmWindowManager *wm, wmWindow *win, int r_size[2])
{
/* WARNING: Reading from the front-buffer immediately after drawing may fail,
* for a slower but more reliable version of this function #WM_window_pixels_read_offscreen
* should be preferred. See it's comments for details on why it's needed, see also T98462. */
bool setup_context = wm->windrawable != win;
if (setup_context) {