[client] egl: use buffer age extension to render only damaged parts

We avoid rendering any area that has not changed since the buffer was used
and also not covered by an overlay.
This commit is contained in:
Quantum 2021-08-02 21:37:58 -04:00 committed by Geoffrey McRae
parent f9977332a6
commit 87aac8cf03
3 changed files with 144 additions and 22 deletions

View File

@ -40,7 +40,7 @@ bool util_guestCurToLocal(struct DoublePoint *local);
void util_localCurToGuest(struct DoublePoint *guest); void util_localCurToGuest(struct DoublePoint *guest);
void util_rotatePoint(struct DoublePoint *point); void util_rotatePoint(struct DoublePoint *point);
bool util_hasGLExt(const char * exts, const char * ext); bool util_hasGLExt(const char * exts, const char * ext);
int util_mergeOverlappingRects(FrameDamageRect * out, const FrameDamageRect * rects, int count); int util_mergeOverlappingRects(FrameDamageRect * rects, int count);
static inline double util_clamp(double x, double min, double max) static inline double util_clamp(double x, double min, double max)
{ {

View File

@ -47,9 +47,13 @@
#include "desktop.h" #include "desktop.h"
#include "cursor.h" #include "cursor.h"
#include "splash.h" #include "splash.h"
#include "util.h"
#define SPLASH_FADE_TIME 1000000 #define SPLASH_FADE_TIME 1000000
#define DESKTOP_DAMAGE_COUNT 2 #define MAX_BUFFER_AGE 3
#define DESKTOP_DAMAGE_COUNT 4
#define MAX_ACCUMULATED_DAMAGE ((KVMFR_MAX_DAMAGE_RECTS + MAX_OVERLAY_RECTS + 2) * MAX_BUFFER_AGE)
#define IDX_AGO(counter, i, total) ((counter) + (total) - i) % (total)
struct Options struct Options
{ {
@ -107,6 +111,11 @@ struct Inst
unsigned int desktopDamageIdx; unsigned int desktopDamageIdx;
LG_Lock desktopDamageLock; LG_Lock desktopDamageLock;
bool hasBufferAge;
struct Rect overlayHistory[DESKTOP_DAMAGE_COUNT][MAX_OVERLAY_RECTS + 1];
int overlayHistoryCount[DESKTOP_DAMAGE_COUNT];
unsigned int overlayHistoryIdx;
RingBuffer importTimings; RingBuffer importTimings;
GraphHandle importGraph; GraphHandle importGraph;
}; };
@ -725,6 +734,8 @@ static bool egl_render_startup(void * opaque, bool useDMA)
return false; return false;
} }
this->hasBufferAge = util_hasGLExt(client_exts, "EGL_EXT_buffer_age");
if (g_egl_dynProcs.glEGLImageTargetTexture2DOES) if (g_egl_dynProcs.glEGLImageTargetTexture2DOES)
{ {
if (util_hasGLExt(client_exts, "EGL_EXT_image_dma_buf_import")) if (util_hasGLExt(client_exts, "EGL_EXT_image_dma_buf_import"))
@ -771,7 +782,7 @@ static bool egl_render_startup(void * opaque, bool useDMA)
eglSwapInterval(this->display, this->opt.vsync ? 1 : 0); eglSwapInterval(this->display, this->opt.vsync ? 1 : 0);
if (!egl_desktop_init(&this->desktop, this->display, useDMA, 1)) if (!egl_desktop_init(&this->desktop, this->display, useDMA, MAX_ACCUMULATED_DAMAGE))
{ {
DEBUG_ERROR("Failed to initialize the desktop"); DEBUG_ERROR("Failed to initialize the desktop");
return false; return false;
@ -811,29 +822,132 @@ static bool egl_needs_render(void * opaque)
return !this->waitDone; return !this->waitDone;
} }
inline static EGLint egl_buffer_age(struct Inst * this)
{
if (!this->hasBufferAge)
return 0;
EGLint result;
eglQuerySurface(this->display, this->surface, EGL_BUFFER_AGE_EXT, &result);
return result;
}
inline static void renderLetterBox(struct Inst * this)
{
bool hLB = this->destRect.x > 0;
bool vLB = this->destRect.y > 0;
if (hLB || vLB)
{
glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
glEnable(GL_SCISSOR_TEST);
if (hLB)
{
glScissor(0.0f, 0.0f, this->destRect.x, this->height);
glClear(GL_COLOR_BUFFER_BIT);
float x2 = this->destRect.x + this->destRect.w;
glScissor(x2, 0.0f, this->width - x2, this->height);
glClear(GL_COLOR_BUFFER_BIT);
}
if (vLB)
{
glScissor(0.0f, 0.0f, this->width, this->destRect.y);
glClear(GL_COLOR_BUFFER_BIT);
float y2 = this->destRect.y + this->destRect.h;
glScissor(0.0f, y2, this->width, this->height - y2);
glClear(GL_COLOR_BUFFER_BIT);
}
glDisable(GL_SCISSOR_TEST);
}
}
static bool egl_render(void * opaque, LG_RendererRotate rotate, const bool newFrame, static bool egl_render(void * opaque, LG_RendererRotate rotate, const bool newFrame,
const bool invalidateWindow) const bool invalidateWindow)
{ {
struct Inst * this = (struct Inst *)opaque; struct Inst * this = (struct Inst *)opaque;
EGLint bufferAge = egl_buffer_age(this);
glClearColor(0.0f, 0.0f, 0.0f, 1.0f); bool renderAll = invalidateWindow || !this->start || this->hadOverlay ||
glClear(GL_COLOR_BUFFER_BIT); bufferAge <= 0 || bufferAge > MAX_BUFFER_AGE;
bool hasOverlay = false; bool hasOverlay = false;
struct CursorState cursorState = { .visible = false }; struct CursorState cursorState = { .visible = false };
struct DesktopDamage * desktopDamage; struct DesktopDamage * desktopDamage;
char accumulated_[
sizeof(struct DamageRects) +
MAX_ACCUMULATED_DAMAGE * sizeof(struct FrameDamageRect)
];
struct DamageRects * accumulated = (struct DamageRects *) accumulated_;
accumulated->count = 0;
INTERLOCKED_SECTION(this->desktopDamageLock, { INTERLOCKED_SECTION(this->desktopDamageLock, {
if (!renderAll)
{
for (int i = 0; i < bufferAge; ++i)
{
struct DesktopDamage * damage = this->desktopDamage +
IDX_AGO(this->desktopDamageIdx, i, DESKTOP_DAMAGE_COUNT);
if (damage->count < 0)
{
renderAll = true;
break;
}
memcpy(accumulated->rects + accumulated->count, damage->rects,
damage->count * sizeof(struct FrameDamageRect));
accumulated->count += damage->count;
}
}
desktopDamage = this->desktopDamage + this->desktopDamageIdx; desktopDamage = this->desktopDamage + this->desktopDamageIdx;
this->desktopDamageIdx = (this->desktopDamageIdx + 1) % DESKTOP_DAMAGE_COUNT; this->desktopDamageIdx = (this->desktopDamageIdx + 1) % DESKTOP_DAMAGE_COUNT;
this->desktopDamage[this->desktopDamageIdx].count = 0;
}); });
if (!renderAll)
{
double matrix[6];
egl_screenToDesktopMatrix(matrix, this->format.width, this->format.height,
this->translateX, this->translateY, this->scaleX, this->scaleY, rotate,
this->width, this->height);
for (int i = 0; i < bufferAge; ++i)
{
int idx = IDX_AGO(this->overlayHistoryIdx, i, DESKTOP_DAMAGE_COUNT);
int count = this->overlayHistoryCount[idx];
struct Rect * damage = this->overlayHistory[idx];
if (count < 0)
{
renderAll = true;
break;
}
for (int j = 0; j < count; ++j)
accumulated->count += egl_screenToDesktop(
accumulated->rects + accumulated->count, matrix, damage + j,
this->format.width, this->format.height
);
}
++this->overlayHistoryIdx;
accumulated->count = util_mergeOverlappingRects(accumulated->rects, accumulated->count);
}
if (renderAll)
renderLetterBox(this);
if (this->start) if (this->start)
{ {
if (egl_desktop_render(this->desktop, if (egl_desktop_render(this->desktop,
this->translateX, this->translateY, this->translateX, this->translateY,
this->scaleX , this->scaleY , this->scaleX , this->scaleY ,
this->scaleType , rotate, NULL)) this->scaleType , rotate, renderAll ? NULL : accumulated))
{ {
if (!this->waitFadeTime) if (!this->waitFadeTime)
{ {
@ -848,7 +962,7 @@ static bool egl_render(void * opaque, LG_RendererRotate rotate, const bool newFr
this->width, this->height); this->width, this->height);
} }
else else
desktopDamage->count = -1; hasOverlay = true;
} }
if (!this->waitDone) if (!this->waitDone)
@ -901,14 +1015,24 @@ static bool egl_render(void * opaque, LG_RendererRotate rotate, const bool newFr
damage[i].y = this->height - damage[i].y - damage[i].h; damage[i].y = this->height - damage[i].y - damage[i].h;
} }
if (damageIdx >= 0 && cursorState.visible)
damage[damageIdx++] = cursorState.rect;
int overlayHistoryIdx = this->overlayHistoryIdx % DESKTOP_DAMAGE_COUNT;
if (hasOverlay)
this->overlayHistoryCount[overlayHistoryIdx] = -1;
else
{
if (damageIdx > 0)
memcpy(this->overlayHistory[overlayHistoryIdx], damage, damageIdx * sizeof(struct Rect));
this->overlayHistoryCount[overlayHistoryIdx] = damageIdx;
}
if (!hasOverlay && !this->hadOverlay) if (!hasOverlay && !this->hadOverlay)
{ {
if (this->cursorLast.visible) if (this->cursorLast.visible)
damage[damageIdx++] = this->cursorLast.rect; damage[damageIdx++] = this->cursorLast.rect;
if (cursorState.visible)
damage[damageIdx++] = cursorState.rect;
if (desktopDamage->count == -1) if (desktopDamage->count == -1)
// -1 damage count means invalidating entire window. // -1 damage count means invalidating entire window.
damageIdx = 0; damageIdx = 0;
@ -928,7 +1052,6 @@ static bool egl_render(void * opaque, LG_RendererRotate rotate, const bool newFr
this->hadOverlay = hasOverlay; this->hadOverlay = hasOverlay;
this->cursorLast = cursorState; this->cursorLast = cursorState;
desktopDamage->count = 0;
app_eglSwapBuffers(this->display, this->surface, damage, damageIdx); app_eglSwapBuffers(this->display, this->surface, damage, damageIdx);
return true; return true;

View File

@ -225,13 +225,12 @@ static bool rectIntersects(const FrameDamageRect * r1, const FrameDamageRect * r
r2->y + r1->height > r2->y; r2->y + r1->height > r2->y;
} }
int util_mergeOverlappingRects(FrameDamageRect * out, const FrameDamageRect * rects, int count) int util_mergeOverlappingRects(FrameDamageRect * rects, int count)
{ {
bool removed[count]; bool removed[count];
bool changed; bool changed;
memset(removed, 0, sizeof(removed)); memset(removed, 0, sizeof(removed));
memcpy(out, rects, count * sizeof(FrameDamageRect));
do do
{ {
@ -239,14 +238,14 @@ int util_mergeOverlappingRects(FrameDamageRect * out, const FrameDamageRect * re
for (int i = 0; i < count; ++i) for (int i = 0; i < count; ++i)
if (!removed[i]) if (!removed[i])
for (int j = i + 1; j < count; ++j) for (int j = i + 1; j < count; ++j)
if (!removed[j] && rectIntersects(out + i, out + j)) if (!removed[j] && rectIntersects(rects + i, rects + j))
{ {
uint32_t x2 = max(out[i].x + out[i].width, out[j].x + out[j].width); uint32_t x2 = max(rects[i].x + rects[i].width, rects[j].x + rects[j].width);
uint32_t y2 = max(out[i].y + out[i].height, out[j].y + out[j].height); uint32_t y2 = max(rects[i].y + rects[i].height, rects[j].y + rects[j].height);
out[i].x = min(out[i].x, out[j].x); rects[i].x = min(rects[i].x, rects[j].x);
out[i].y = min(out[i].y, out[j].y); rects[i].y = min(rects[i].y, rects[j].y);
out[i].width = x2 - out[i].x; rects[i].width = x2 - rects[i].x;
out[i].height = y2 - out[i].y; rects[i].height = y2 - rects[i].y;
removed[j] = true; removed[j] = true;
changed = true; changed = true;
} }
@ -256,7 +255,7 @@ int util_mergeOverlappingRects(FrameDamageRect * out, const FrameDamageRect * re
int o = 0; int o = 0;
for (int i = 0; i < count; ++i) for (int i = 0; i < count; ++i)
if (!removed[i]) if (!removed[i])
out[o++] = out[i]; rects[o++] = rects[i];
return o; return o;
} }