[client] egl: use eglSwapBuffersWithDamageKHR for cursor-only updates

Instead of damaging the entire surface when rendering a cursor move,
we can use the EGL_KHR_swap_buffers_with_damage extension to only
damage the part of the window covered by the cursor. This should
reduce the cursor movement latency on Wayland.
This commit is contained in:
Quantum 2021-05-11 16:49:39 -04:00 committed by Geoffrey McRae
parent f9a9953071
commit e70cfd84fb
14 changed files with 140 additions and 15 deletions

View File

@ -254,7 +254,7 @@ static EGLNativeWindowType sdlGetEGLNativeWindow(void)
} }
} }
static void sdlEGLSwapBuffers(EGLDisplay display, EGLSurface surface) static void sdlEGLSwapBuffers(EGLDisplay display, EGLSurface surface, const struct Rect * damage, int count)
{ {
eglSwapBuffers(display, surface); eglSwapBuffers(display, surface);
} }

View File

@ -41,6 +41,7 @@ bool waylandEGLInit(int w, int h)
DEBUG_ERROR("Failed to create EGL window"); DEBUG_ERROR("Failed to create EGL window");
return false; return false;
} }
return true; return true;
} }
@ -68,9 +69,51 @@ EGLDisplay waylandGetEGLDisplay(void)
return eglGetDisplay(native); return eglGetDisplay(native);
} }
void waylandEGLSwapBuffers(EGLDisplay display, EGLSurface surface) void waylandEGLSwapBuffers(EGLDisplay display, EGLSurface surface, const struct Rect * damage, int count)
{ {
eglSwapBuffers(display, surface); if (!wlWm.eglSwapWithDamageInit)
{
const char *exts = eglQueryString(display, EGL_EXTENSIONS);
wlWm.eglSwapWithDamageInit = true;
if (wl_proxy_get_version((struct wl_proxy *) wlWm.surface) < 4)
DEBUG_INFO("Swapping buffers with damage: not supported, need wl_compositor v4");
else if (strstr(exts, "EGL_KHR_swap_buffers_with_damage") && g_egl_dynProcs.eglSwapBuffersWithDamageKHR)
{
wlWm.eglSwapWithDamage = g_egl_dynProcs.eglSwapBuffersWithDamageKHR;
DEBUG_INFO("Using EGL_KHR_swap_buffers_with_damage");
}
else if (strstr(exts, "EGL_EXT_swap_buffers_with_damage") && g_egl_dynProcs.eglSwapBuffersWithDamageEXT)
{
wlWm.eglSwapWithDamage = g_egl_dynProcs.eglSwapBuffersWithDamageEXT;
DEBUG_INFO("Using EGL_EXT_swap_buffers_with_damage");
}
else
DEBUG_INFO("Swapping buffers with damage: not supported");
}
if (wlWm.eglSwapWithDamage && count)
{
if (count * 4 > wlWm.eglDamageRectCount)
{
free(wlWm.eglDamageRects);
wlWm.eglDamageRects = malloc(sizeof(EGLint) * count * 4);
if (!wlWm.eglDamageRects)
DEBUG_FATAL("Out of memory");
wlWm.eglDamageRectCount = count * 4;
}
for (int i = 0; i < count; ++i)
{
wlWm.eglDamageRects[i*4+0] = damage[i].x;
wlWm.eglDamageRects[i*4+1] = damage[i].y;
wlWm.eglDamageRects[i*4+2] = damage[i].w;
wlWm.eglDamageRects[i*4+3] = damage[i].h;
}
wlWm.eglSwapWithDamage(display, surface, wlWm.eglDamageRects, count);
}
else
eglSwapBuffers(display, surface);
if (wlWm.needsResize) if (wlWm.needsResize)
{ {
@ -169,6 +212,6 @@ void waylandGLSetSwapInterval(int interval)
void waylandGLSwapBuffers(void) void waylandGLSwapBuffers(void)
{ {
waylandEGLSwapBuffers(wlWm.glDisplay, wlWm.glSurface); waylandEGLSwapBuffers(wlWm.glDisplay, wlWm.glSurface, NULL, 0);
} }
#endif #endif

View File

@ -37,7 +37,9 @@ static void registryGlobalHandler(void * data, struct wl_registry * registry,
else if (!strcmp(interface, wl_shm_interface.name)) else if (!strcmp(interface, wl_shm_interface.name))
wlWm.shm = wl_registry_bind(wlWm.registry, name, &wl_shm_interface, 1); wlWm.shm = wl_registry_bind(wlWm.registry, name, &wl_shm_interface, 1);
else if (!strcmp(interface, wl_compositor_interface.name) && version >= 3) else if (!strcmp(interface, wl_compositor_interface.name) && version >= 3)
wlWm.compositor = wl_registry_bind(wlWm.registry, name, &wl_compositor_interface, 3); wlWm.compositor = wl_registry_bind(wlWm.registry, name,
// we only need v3 to run, but v4 can use eglSwapBuffersWithDamageKHR
&wl_compositor_interface, version > 4 ? 4 : version);
#ifndef ENABLE_LIBDECOR #ifndef ENABLE_LIBDECOR
else if (!strcmp(interface, xdg_wm_base_interface.name)) else if (!strcmp(interface, xdg_wm_base_interface.name))
wlWm.xdgWmBase = wl_registry_bind(wlWm.registry, name, &xdg_wm_base_interface, 1); wlWm.xdgWmBase = wl_registry_bind(wlWm.registry, name, &xdg_wm_base_interface, 1);

View File

@ -29,6 +29,7 @@ Place, Suite 330, Boston, MA 02111-1307 USA
# include <EGL/eglext.h> # include <EGL/eglext.h>
#endif #endif
#include "egl_dynprocs.h"
#include "common/locking.h" #include "common/locking.h"
#include "common/countedbuffer.h" #include "common/countedbuffer.h"
#include "interface/displayserver.h" #include "interface/displayserver.h"
@ -66,6 +67,13 @@ struct SurfaceOutput
struct wl_list link; struct wl_list link;
}; };
enum EGLSwapWithDamageState {
SWAP_WITH_DAMAGE_UNKNOWN,
SWAP_WITH_DAMAGE_UNSUPPORTED,
SWAP_WITH_DAMAGE_KHR,
SWAP_WITH_DAMAGE_EXT,
};
struct WaylandDSState struct WaylandDSState
{ {
bool pointerGrabbed; bool pointerGrabbed;
@ -88,8 +96,12 @@ struct WaylandDSState
bool warpSupport; bool warpSupport;
double cursorX, cursorY; double cursorX, cursorY;
#ifdef ENABLE_EGL #if defined(ENABLE_EGL) || defined(ENABLE_OPENGL)
struct wl_egl_window * eglWindow; struct wl_egl_window * eglWindow;
bool eglSwapWithDamageInit;
eglSwapBuffersWithDamageKHR_t eglSwapWithDamage;
EGLint * eglDamageRects;
int eglDamageRectCount;
#endif #endif
#ifdef ENABLE_OPENGL #ifdef ENABLE_OPENGL
@ -197,7 +209,7 @@ void waylandShowPointer(bool show);
#if defined(ENABLE_EGL) || defined(ENABLE_OPENGL) #if defined(ENABLE_EGL) || defined(ENABLE_OPENGL)
bool waylandEGLInit(int w, int h); bool waylandEGLInit(int w, int h);
EGLDisplay waylandGetEGLDisplay(void); EGLDisplay waylandGetEGLDisplay(void);
void waylandEGLSwapBuffers(EGLDisplay display, EGLSurface surface); void waylandEGLSwapBuffers(EGLDisplay display, EGLSurface surface, const struct Rect * damage, int count);
#endif #endif
#ifdef ENABLE_EGL #ifdef ENABLE_EGL

View File

@ -114,5 +114,3 @@ bool waylandIsValidPointerPos(int x, int y)
{ {
return x >= 0 && x < wlWm.width && y >= 0 && y < wlWm.height; return x >= 0 && x < wlWm.width && y >= 0 && y < wlWm.height;
} }

View File

@ -930,7 +930,7 @@ static EGLNativeWindowType x11GetEGLNativeWindow(void)
return (EGLNativeWindowType)x11.window; return (EGLNativeWindowType)x11.window;
} }
static void x11EGLSwapBuffers(EGLDisplay display, EGLSurface surface) static void x11EGLSwapBuffers(EGLDisplay display, EGLSurface surface, const struct Rect * damage, int count)
{ {
eglSwapBuffers(display, surface); eglSwapBuffers(display, surface);
} }

View File

@ -66,7 +66,7 @@ bool app_getProp(LG_DSProperty prop, void * ret);
#ifdef ENABLE_EGL #ifdef ENABLE_EGL
EGLDisplay app_getEGLDisplay(void); EGLDisplay app_getEGLDisplay(void);
EGLNativeWindowType app_getEGLNativeWindow(void); EGLNativeWindowType app_getEGLNativeWindow(void);
void app_eglSwapBuffers(EGLDisplay display, EGLSurface surface); void app_eglSwapBuffers(EGLDisplay display, EGLSurface surface, const struct Rect * damage, int count);
#endif #endif
#ifdef ENABLE_OPENGL #ifdef ENABLE_OPENGL

View File

@ -17,6 +17,8 @@ this program; if not, write to the Free Software Foundation, Inc., 59 Temple
Place, Suite 330, Boston, MA 02111-1307 USA Place, Suite 330, Boston, MA 02111-1307 USA
*/ */
#ifndef _H_LG_EGL_DYNPROCS_
#define _H_LG_EGL_DYNPROCS_
#ifdef ENABLE_EGL #ifdef ENABLE_EGL
#include <EGL/egl.h> #include <EGL/egl.h>
@ -24,6 +26,8 @@ Place, Suite 330, Boston, MA 02111-1307 USA
typedef EGLDisplay (*eglGetPlatformDisplayEXT_t)(EGLenum platform, typedef EGLDisplay (*eglGetPlatformDisplayEXT_t)(EGLenum platform,
void *native_display, const EGLint *attrib_list); void *native_display, const EGLint *attrib_list);
typedef void (*eglSwapBuffersWithDamageKHR_t)(EGLDisplay dpy,
EGLSurface surface, const EGLint *rects, EGLint n_rects);
typedef void (*glEGLImageTargetTexture2DOES_t)(GLenum target, typedef void (*glEGLImageTargetTexture2DOES_t)(GLenum target,
GLeglImageOES image); GLeglImageOES image);
@ -31,6 +35,8 @@ struct EGLDynProcs
{ {
eglGetPlatformDisplayEXT_t eglGetPlatformDisplay; eglGetPlatformDisplayEXT_t eglGetPlatformDisplay;
eglGetPlatformDisplayEXT_t eglGetPlatformDisplayEXT; eglGetPlatformDisplayEXT_t eglGetPlatformDisplayEXT;
eglSwapBuffersWithDamageKHR_t eglSwapBuffersWithDamageKHR;
eglSwapBuffersWithDamageKHR_t eglSwapBuffersWithDamageEXT;
glEGLImageTargetTexture2DOES_t glEGLImageTargetTexture2DOES; glEGLImageTargetTexture2DOES_t glEGLImageTargetTexture2DOES;
}; };
@ -41,3 +47,5 @@ void egl_dynProcsInit(void);
#else #else
#define egl_dynProcsInit(...) #define egl_dynProcsInit(...)
#endif #endif
#endif // _H_LG_EGL_DYNPROCS_

View File

@ -22,6 +22,7 @@ Place, Suite 330, Boston, MA 02111-1307 USA
#include <stdbool.h> #include <stdbool.h>
#include <EGL/egl.h> #include <EGL/egl.h>
#include "common/types.h"
typedef enum LG_ClipboardData typedef enum LG_ClipboardData
{ {
@ -115,7 +116,7 @@ struct LG_DisplayServerOps
/* EGL support */ /* EGL support */
EGLDisplay (*getEGLDisplay)(void); EGLDisplay (*getEGLDisplay)(void);
EGLNativeWindowType (*getEGLNativeWindow)(void); EGLNativeWindowType (*getEGLNativeWindow)(void);
void (*eglSwapBuffers)(EGLDisplay display, EGLSurface surface); void (*eglSwapBuffers)(EGLDisplay display, EGLSurface surface, const struct Rect * damage, int count);
#endif #endif
#ifdef ENABLE_OPENGL #ifdef ENABLE_OPENGL

View File

@ -215,6 +215,16 @@ void egl_cursor_set_state(EGL_Cursor * cursor, const bool visible, const float x
cursor->y = y; cursor->y = y;
} }
struct CursorState egl_cursor_get_state(EGL_Cursor * cursor, int width, int height) {
return (struct CursorState) {
.visible = cursor->visible,
.rect.x = (cursor->x * width + width) / 2,
.rect.y = (-cursor->y * height + height) / 2 - cursor->h * height,
.rect.w = cursor->w * width + 2,
.rect.h = cursor->h * height + 2,
};
}
void egl_cursor_render(EGL_Cursor * cursor, LG_RendererRotate rotate) void egl_cursor_render(EGL_Cursor * cursor, LG_RendererRotate rotate)
{ {
if (!cursor->visible) if (!cursor->visible)

View File

@ -25,6 +25,11 @@ Place, Suite 330, Boston, MA 02111-1307 USA
typedef struct EGL_Cursor EGL_Cursor; typedef struct EGL_Cursor EGL_Cursor;
struct CursorState {
bool visible;
struct Rect rect;
};
bool egl_cursor_init(EGL_Cursor ** cursor); bool egl_cursor_init(EGL_Cursor ** cursor);
void egl_cursor_free(EGL_Cursor ** cursor); void egl_cursor_free(EGL_Cursor ** cursor);
@ -41,4 +46,6 @@ void egl_cursor_set_size(EGL_Cursor * cursor, const float x, const float y);
void egl_cursor_set_state(EGL_Cursor * cursor, const bool visible, void egl_cursor_set_state(EGL_Cursor * cursor, const bool visible,
const float x, const float y); const float x, const float y);
struct CursorState egl_cursor_get_state(EGL_Cursor * cursor, int width, int height);
void egl_cursor_render(EGL_Cursor * cursor, LG_RendererRotate rotate); void egl_cursor_render(EGL_Cursor * cursor, LG_RendererRotate rotate);

View File

@ -106,6 +106,9 @@ struct Inst
unsigned fontSize; unsigned fontSize;
LG_FontObj helpFontObj; LG_FontObj helpFontObj;
unsigned helpFontSize; unsigned helpFontSize;
bool cursorLastValid;
struct CursorState cursorLast;
}; };
static struct Option egl_options[] = static struct Option egl_options[] =
@ -472,6 +475,8 @@ void egl_on_resize(void * opaque, const int width, const int height, const doubl
egl_calc_mouse_state(this); egl_calc_mouse_state(this);
egl_update_font(this); egl_update_font(this);
egl_update_help_font(this); egl_update_help_font(this);
this->cursorLastValid = false;
} }
bool egl_on_mouse_shape(void * opaque, const LG_RendererCursor cursor, bool egl_on_mouse_shape(void * opaque, const LG_RendererCursor cursor,
@ -545,6 +550,7 @@ bool egl_on_frame(void * opaque, const FrameBuffer * frame, int dmaFd)
} }
this->start = true; this->start = true;
this->cursorLastValid = false;
return true; return true;
} }
@ -581,12 +587,14 @@ void egl_on_alert(void * opaque, const LG_MsgAlert alert, const char * message,
} }
this->showAlert = true; this->showAlert = true;
this->cursorLastValid = false;
} }
void egl_on_help(void * opaque, const char * message) void egl_on_help(void * opaque, const char * message)
{ {
struct Inst * this = (struct Inst *)opaque; struct Inst * this = (struct Inst *)opaque;
egl_help_set_text(this->help, message); egl_help_set_text(this->help, message);
this->cursorLastValid = false;
} }
void egl_on_show_fps(void * opaque, bool showFPS) void egl_on_show_fps(void * opaque, bool showFPS)
@ -767,6 +775,10 @@ bool egl_render(void * opaque, LG_RendererRotate rotate)
glClearColor(0.0f, 0.0f, 0.0f, 1.0f); glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT); glClear(GL_COLOR_BUFFER_BIT);
bool hasLastCursor = this->cursorLastValid;
bool cursorRendered = false;
struct CursorState cursorState;
if (this->start && egl_desktop_render(this->desktop, if (this->start && egl_desktop_render(this->desktop,
this->translateX, this->translateY, this->translateX, this->translateY,
this->scaleX , this->scaleY , this->scaleX , this->scaleY ,
@ -780,6 +792,8 @@ bool egl_render(void * opaque, LG_RendererRotate rotate)
this->waitDone = true; this->waitDone = true;
} }
cursorRendered = true;
cursorState = egl_cursor_get_state(this->cursor, this->width, this->height);
egl_cursor_render(this->cursor, egl_cursor_render(this->cursor,
(this->format.rotate + rotate) % LG_ROTATE_MAX); (this->format.rotate + rotate) % LG_ROTATE_MAX);
} }
@ -819,14 +833,39 @@ bool egl_render(void * opaque, LG_RendererRotate rotate)
close = true; close = true;
if (close) if (close)
{
this->showAlert = false; this->showAlert = false;
this->cursorLastValid = false;
}
else else
egl_alert_render(this->alert, this->screenScaleX, this->screenScaleY); egl_alert_render(this->alert, this->screenScaleX, this->screenScaleY);
} }
struct Rect damage[2];
int damageIdx = 0;
if (this->waitDone)
{
if (cursorRendered && hasLastCursor)
{
if (this->cursorLast.visible)
damage[damageIdx++] = this->cursorLast.rect;
if (cursorState.visible)
damage[damageIdx++] = cursorState.rect;
this->cursorLast = cursorState;
}
else if (cursorRendered)
{
this->cursorLast = cursorState;
this->cursorLastValid = true;
}
}
egl_fps_render(this->fps, this->screenScaleX, this->screenScaleY); egl_fps_render(this->fps, this->screenScaleX, this->screenScaleY);
egl_help_render(this->help, this->screenScaleX, this->screenScaleY); egl_help_render(this->help, this->screenScaleX, this->screenScaleY);
app_eglSwapBuffers(this->display, this->surface); app_eglSwapBuffers(this->display, this->surface, damage, damageIdx);
return true; return true;
} }
@ -834,6 +873,7 @@ void egl_update_fps(void * opaque, const float avgUPS, const float avgFPS)
{ {
struct Inst * this = (struct Inst *)opaque; struct Inst * this = (struct Inst *)opaque;
egl_fps_update(this->fps, avgUPS, avgFPS); egl_fps_update(this->fps, avgUPS, avgFPS);
this->cursorLastValid = false;
} }
struct LG_Renderer LGR_EGL = struct LG_Renderer LGR_EGL =

View File

@ -448,9 +448,9 @@ EGLNativeWindowType app_getEGLNativeWindow(void)
return g_state.ds->getEGLNativeWindow(); return g_state.ds->getEGLNativeWindow();
} }
void app_eglSwapBuffers(EGLDisplay display, EGLSurface surface) void app_eglSwapBuffers(EGLDisplay display, EGLSurface surface, const struct Rect * damage, int count)
{ {
g_state.ds->eglSwapBuffers(display, surface); g_state.ds->eglSwapBuffers(display, surface, damage, count);
} }
#endif #endif

View File

@ -31,6 +31,10 @@ void egl_dynProcsInit(void)
eglGetProcAddress("eglGetPlatformDisplayEXT"); eglGetProcAddress("eglGetPlatformDisplayEXT");
g_egl_dynProcs.glEGLImageTargetTexture2DOES = (glEGLImageTargetTexture2DOES_t) g_egl_dynProcs.glEGLImageTargetTexture2DOES = (glEGLImageTargetTexture2DOES_t)
eglGetProcAddress("glEGLImageTargetTexture2DOES"); eglGetProcAddress("glEGLImageTargetTexture2DOES");
g_egl_dynProcs.eglSwapBuffersWithDamageKHR = (eglSwapBuffersWithDamageKHR_t)
eglGetProcAddress("eglSwapBuffersWithDamageKHR");
g_egl_dynProcs.eglSwapBuffersWithDamageEXT = (eglSwapBuffersWithDamageKHR_t)
eglGetProcAddress("eglSwapBuffersWithDamageEXT");
}; };
#endif #endif