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:
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
|
@ -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");
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
/** \} */
|
||||
|
||||
/* -------------------------------------------------------------------- */
|
||||
|
|
|
@ -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) {
|
||||
|
|
Loading…
Reference in New Issue