From d127944634480b539c3468e440aa75b3dd7b3761 Mon Sep 17 00:00:00 2001 From: Twaik Yont <9674930+twaik@users.noreply.github.com> Date: Wed, 8 Jul 2026 19:23:26 +0300 Subject: [PATCH] feat(present): offload Present copy compositing to the GPU Present clients that can't use the flip fast path (windowed, partial damage) now get composited into the root texture via the renderer's GL context instead of a CPU CopyArea. -disable-gpu-present forces the old path; a lost renderer connection also falls back to CPU. --- app/src/main/cpp/lorie/InitOutput.c | 176 +++++++++++++++++++++-- app/src/main/cpp/lorie/activity.c | 2 + app/src/main/cpp/lorie/buffer.c | 8 +- app/src/main/cpp/lorie/buffer.h | 9 ++ app/src/main/cpp/lorie/cmdentrypoint.c | 10 ++ app/src/main/cpp/lorie/lorie.h | 29 ++++ app/src/main/cpp/lorie/renderer.c | 189 ++++++++++++++++++++++++- app/src/main/cpp/patches/xserver.patch | 135 +++++++++++++++++- 8 files changed, 535 insertions(+), 23 deletions(-) diff --git a/app/src/main/cpp/lorie/InitOutput.c b/app/src/main/cpp/lorie/InitOutput.c index bab97dd3a..8d22666ab 100644 --- a/app/src/main/cpp/lorie/InitOutput.c +++ b/app/src/main/cpp/lorie/InitOutput.c @@ -77,10 +77,13 @@ typedef struct { } root; Bool dri3; + Bool gpuPresentDisabled; uint64_t vblank_interval; struct xorg_list vblank_queue; uint64_t current_msc; + + uint64_t gpuCopySerialCounter; } lorieScreenInfo; ScreenPtr pScreenPtr; @@ -110,12 +113,35 @@ typedef struct { #define LORIE_PIXMAP_PRIV_FROM_PIXMAP(pixmap) (pixmap ? ((LoriePixmapPriv*) exaGetPixmapDriverPrivate(pixmap)) : NULL) #define LORIE_BUFFER_FROM_PIXMAP(pixmap) (pixmap ? ((LoriePixmapPriv*) exaGetPixmapDriverPrivate(pixmap))->buffer : NULL) +static LorieBuffer *lorieEnsureGpuSampleable(PixmapPtr pixmap, int8_t type) { + LoriePixmapPriv *priv = LORIE_PIXMAP_PRIV_FROM_PIXMAP(pixmap); + const LorieBuffer_Desc *desc; + if (!priv || !priv->buffer || priv->mem) + return NULL; + + desc = LorieBuffer_description(priv->buffer); + if (desc->type == LORIEBUFFER_REGULAR) { + LorieBuffer_convert(priv->buffer, type, AHARDWAREBUFFER_FORMAT_R8G8B8X8_UNORM); + if (desc->type != LORIEBUFFER_REGULAR) { + // LorieBuffer_convert does not report status but it does not let the type change in the case of error. + pScreenPtr->ModifyPixmapHeader(pixmap, 0, 0, 0, 0, desc->stride * 4, NULL); + LorieBuffer_lock(priv->buffer, &priv->locked); + } + } + + return desc->type == type ? priv->buffer : NULL; +} + +static Bool lorieServerDebugEnabled = FALSE; + void OsVendorInit(void) { pthread_mutexattr_t mutex_attr; if (lorieScreen.stateFd != -1) // already initialized return; + lorieServerDebugEnabled = getenv("TERMUX_X11_DEBUG") != NULL; + if (-1 == (lorieScreen.stateFd = LorieBuffer_createRegion("xserver", sizeof(*lorieScreen.state)))) { dprintf(2, "FATAL: Failed to allocate server state.\n"); _exit(1); @@ -284,6 +310,7 @@ void ddxUseMsg(void) { ErrorF("-disable-dri3 disabling DRI3 support (to let lavapipe work)\n"); ErrorF("-force-sysvshm force using SysV shm syscalls\n"); ErrorF("-check-drawing run server only able to draw some test image (for testing if rendering root window works or not),\n"); + ErrorF("-disable-gpu-present disable offloading Present copies to the GPU, always use the CPU path\n"); } int ddxProcessArgument(unused int argc, unused char *argv[], unused int i) { @@ -317,6 +344,11 @@ int ddxProcessArgument(unused int argc, unused char *argv[], unused int i) { return 1; } + if (strcmp(argv[i], "-disable-gpu-present") == 0) { + pvfb->gpuPresentDisabled = TRUE; + return 1; + } + return 0; } @@ -472,11 +504,16 @@ static Bool lorieRedraw(__unused ClientPtr pClient, __unused void *closure) { return TRUE; } +static uint64_t gpuCopyAttempts = 0, gpuCopyOffloads = 0; + static CARD32 lorieFramecounter(unused OsTimerPtr timer, unused CARD32 time, unused void *arg) { - if (pvfb->state->renderedFrames) - log(INFO, "%d frames in 5.0 seconds = %.1f FPS", - pvfb->state->renderedFrames, ((float) pvfb->state->renderedFrames) / 5); + if (pvfb->state->renderedFrames || gpuCopyAttempts) + log(INFO, gpuCopyAttempts ? "%d frames in 5.0 seconds = %.1f FPS, %llu/%llu present copies offloaded to GPU" + : "%d frames in 5.0 seconds = %.1f FPS", + pvfb->state->renderedFrames, ((float) pvfb->state->renderedFrames) / 5, + (unsigned long long) gpuCopyOffloads, (unsigned long long) gpuCopyAttempts); pvfb->state->renderedFrames = 0; + gpuCopyAttempts = gpuCopyOffloads = 0; return 5000; } @@ -803,6 +840,111 @@ static void loriePerformVblanks(void) { } } +// Tries to offload a Present "copy" operation (present_execute_copy) to the renderer's GPU +// context instead of doing a CPU CopyArea here. dst is whatever GetWindowPixmap(window) is - root +// for a plain window, or a Composite-redirected window's own backing pixmap. Returns FALSE +// (caller falls back to the regular CPU present_copy_region) whenever either buffer isn't +// GPU-sampleable, or the deferred copy queue is currently full. +Bool lorieTryScheduleGpuCopy(PixmapPtr pixmap, PixmapPtr dst, RegionPtr update, int16_t x_off, int16_t y_off, + uint64_t *out_serial, void **out_dst_buffer) { + LorieBuffer *srcBuffer, *dstBuffer; + LoriePixmapPriv *priv; + const LorieBuffer_Desc *desc, *dstDesc; + LorieGpuCopyEntry *entry; + BoxRec fullBox; + BoxPtr box; + int numRects, i; + uint32_t writeIndex, readIndex; + + if (pvfb->gpuPresentDisabled || pvfb->root.legacyDrawing) { + gpuCopyAttempts++; + return FALSE; + } + + if (!lorieConnectionAlive()) { + // No renderer to drain the queue, so fall back to CPU copy. + gpuCopyAttempts++; + return FALSE; + } + + if (!(srcBuffer = lorieEnsureGpuSampleable(pixmap, LORIEBUFFER_AHARDWAREBUFFER)) || + !(dstBuffer = lorieEnsureGpuSampleable(dst, LORIEBUFFER_AHARDWAREBUFFER))) { + gpuCopyAttempts++; + return FALSE; + } + priv = LORIE_PIXMAP_PRIV_FROM_PIXMAP(pixmap); + desc = LorieBuffer_description(srcBuffer); + dstDesc = LorieBuffer_description(dstBuffer); + + if (update) { + numRects = RegionNumRects(update); + box = RegionRects(update); + } else { + fullBox = (BoxRec) { 0, 0, (short) pixmap->drawable.width, (short) pixmap->drawable.height }; + numRects = 1; + box = &fullBox; + } + + if (numRects <= 0 || numRects > LORIE_GPU_COPY_MAX_RECTS) { + gpuCopyAttempts++; + return FALSE; + } + + writeIndex = pvfb->state->gpuCopyQueue.writeIndex; + readIndex = pvfb->state->gpuCopyQueue.readIndex; + if (writeIndex - readIndex >= LORIE_GPU_COPY_QUEUE_CAPACITY) { + gpuCopyAttempts++; + return FALSE; + } + + // Make sure the renderer has (or will have) this texture. Idempotent if already registered. + lorieRegisterBuffer(srcBuffer); + // Extra reference: keeps the LorieBuffer struct alive on this side until lorieGpuCopyAck() + // releases it, independently from the X pixmap's own lifetime. + LorieBuffer_acquire(srcBuffer); + // Root already has its own lifecycle (recreated on resize, kept alive by pScreenPtr->devPrivate) + // - an extra reference here would outlive a resize and let the renderer keep finding a stale, + // already-destroyed root buffer. Redirected-window destinations have no such guarantee, so they + // still need registering and an extra reference. + Bool dstIsRoot = dst == pScreenPtr->devPrivate; + if (!dstIsRoot) { + lorieRegisterBuffer(dstBuffer); + LorieBuffer_acquire(dstBuffer); + } + *out_dst_buffer = dstIsRoot ? NULL : dstBuffer; + + entry = &pvfb->state->gpuCopyQueue.entries[writeIndex % LORIE_GPU_COPY_QUEUE_CAPACITY]; + entry->serial = ++pvfb->gpuCopySerialCounter; + entry->srcBufferId = desc->id; + entry->dstBufferId = dstDesc->id; + entry->xOff = x_off; + entry->yOff = y_off; + entry->numRects = (uint16_t) numRects; + for (i = 0; i < numRects; i++) + entry->rects[i] = (LorieGpuCopyRect) { box[i].x1, box[i].y1, box[i].x2, box[i].y2 }; + + __sync_synchronize(); // publish entry contents before the renderer can see the new writeIndex + pvfb->state->gpuCopyQueue.writeIndex = writeIndex + 1; + pthread_cond_signal(rendererCond); + + *out_serial = entry->serial; + gpuCopyAttempts++; + gpuCopyOffloads++; + return TRUE; +} + +Bool lorieGpuCopyIsDone(uint64_t serial) { + return pvfb->state->gpuCopyQueue.completedSerial >= serial; +} + +void lorieGpuCopyAck(PixmapPtr pixmap, void *dst_buffer) { + LoriePixmapPriv *priv = LORIE_PIXMAP_PRIV_FROM_PIXMAP(pixmap); + if (priv && priv->buffer) + LorieBuffer_release(priv->buffer); + if (dst_buffer) + LorieBuffer_release((LorieBuffer *) dst_buffer); +} + Bool loriePresentFlip(__unused RRCrtcPtr crtc, __unused uint64_t event_id, __unused uint64_t target_msc, PixmapPtr pixmap, __unused Bool sync_flip) { LoriePixmapPriv* priv = (LoriePixmapPriv*) exaGetPixmapDriverPrivate(pixmap); if (!priv || !priv->buffer || priv->mem || pvfb->root.width != pixmap->drawable.width || pvfb->root.width != pixmap->drawable.height) @@ -813,16 +955,8 @@ Bool loriePresentFlip(__unused RRCrtcPtr crtc, __unused uint64_t event_id, __unu if (desc->type == LORIEBUFFER_FD && priv->imported && !(forceFlip && strcmp(forceFlip, "1") == 0)) return FALSE; // For some reason it does not work fine with turnip. - if (desc->type == LORIEBUFFER_REGULAR) { - // Regular buffers can not be shared to activity, we must explicitly convert LorieBuffer to FD or AHardwareBuffer - int8_t type = pvfb->root.legacyDrawing ? LORIEBUFFER_FD : LORIEBUFFER_AHARDWAREBUFFER; - LorieBuffer_convert(priv->buffer, type, AHARDWAREBUFFER_FORMAT_R8G8B8X8_UNORM); - if (desc->type != LORIEBUFFER_REGULAR) { - // LorieBuffer_convert does not report status but it does not let the type change in the case of error. - pScreenPtr->ModifyPixmapHeader(pixmap, 0, 0, 0, 0, desc->stride * 4, NULL); - LorieBuffer_lock(priv->buffer, &priv->locked); - } - } + // Regular buffers can not be shared to activity, we must explicitly convert LorieBuffer to FD or AHardwareBuffer + lorieEnsureGpuSampleable(pixmap, pvfb->root.legacyDrawing ? LORIEBUFFER_FD : LORIEBUFFER_AHARDWAREBUFFER); if (desc->type != LORIEBUFFER_FD && desc->type != LORIEBUFFER_AHARDWAREBUFFER) return FALSE; @@ -905,9 +1039,17 @@ Bool lorieModifyPixmapHeader(PixmapPtr pPix, __unused int w, __unused int h, __u return FALSE; } +// Whether a CPU access to pPix could race a GPU write from the renderer, and so needs state->lock. +static inline __always_inline Bool lorieNeedsGpuLock(PixmapPtr pPix, LoriePixmapPriv *priv, int index) { + if (pScreenPtr->GetScreenPixmap(pScreenPtr) == pPix) + return index == EXA_PREPARE_DEST || (!pvfb->gpuPresentDisabled && !pvfb->root.legacyDrawing); + return !pvfb->root.legacyDrawing && priv->buffer && + LorieBuffer_description(priv->buffer)->type == LORIEBUFFER_AHARDWAREBUFFER; +} + Bool loriePrepareAccess(PixmapPtr pPix, int index) { LoriePixmapPriv *priv = exaGetPixmapDriverPrivate(pPix); - if (index == EXA_PREPARE_DEST && pScreenPtr->GetScreenPixmap(pScreenPtr) == pPix) + if (lorieNeedsGpuLock(pPix, priv, index)) lorie_mutex_lock(&pvfb->state->lock, &pvfb->state->lockingPid); if (!priv->locked && !priv->mem) { @@ -926,7 +1068,7 @@ Bool loriePrepareAccess(PixmapPtr pPix, int index) { void lorieFinishAccess(PixmapPtr pPix, int index) { LoriePixmapPriv *priv = exaGetPixmapDriverPrivate(pPix); - if (index == EXA_PREPARE_DEST && pScreenPtr->GetScreenPixmap(pScreenPtr) == pPix) + if (lorieNeedsGpuLock(pPix, priv, index)) lorie_mutex_unlock(&pvfb->state->lock, &pvfb->state->lockingPid); if (!priv->wasLocked) { @@ -972,6 +1114,8 @@ static PixmapPtr loriePixmapFromFds(ScreenPtr screen, CARD8 num_fds, const int * if (modifier == DRM_FORMAT_MOD_INVALID || modifier == DRM_FORMAT_MOD_LINEAR || modifier == RAW_MMAPPABLE_FD) { check(!(priv->buffer = LorieBuffer_wrapFileDescriptor(width, strides[0]/4, height, AHARDWAREBUFFER_FORMAT_B8G8R8A8_UNORM, fds[0], offsets[0])), "DRI3: LorieBuffer_wrapAHardwareBuffer failed."); screen->ModifyPixmapHeader(pixmap, width, height, 0, 0, strides[0], NULL); + if (lorieServerDebugEnabled) + log(INFO, "DRI3: imported raw fd, modifier %llu, %ux%u stride %u", (unsigned long long) modifier, width, height, strides[0]); return pixmap; } @@ -997,6 +1141,8 @@ static PixmapPtr loriePixmapFromFds(ScreenPtr screen, CARD8 num_fds, const int * check(!(priv->buffer = LorieBuffer_wrapAHardwareBuffer(buffer)), "DRI3: LorieBuffer_wrapAHardwareBuffer failed."); screen->ModifyPixmapHeader(pixmap, desc.width, desc.height, 0, 0, desc.stride * 4, NULL); + if (lorieServerDebugEnabled) + log(INFO, "DRI3: imported AHardwareBuffer, modifier %llu, %ux%u stride %u", (unsigned long long) modifier, desc.width, desc.height, desc.stride); } return pixmap; diff --git a/app/src/main/cpp/lorie/activity.c b/app/src/main/cpp/lorie/activity.c index 10d0b28df..982d047d0 100644 --- a/app/src/main/cpp/lorie/activity.c +++ b/app/src/main/cpp/lorie/activity.c @@ -23,6 +23,7 @@ #define log(prio, ...) __android_log_print(ANDROID_LOG_ ## prio, "LorieNative", __VA_ARGS__) extern volatile int conn_fd; // The only variable from shared with X server code. +bool lorieDebugEnabled = false; static struct { jclass self; @@ -248,6 +249,7 @@ static jboolean connected(__unused JNIEnv* env,__unused jclass clazz) { static void startLogcat(JNIEnv *env, __unused jobject cls, jint fd) { log(DEBUG, "Starting logcat with output to given fd"); + lorieDebugEnabled = true; switch(fork()) { case -1: diff --git a/app/src/main/cpp/lorie/buffer.c b/app/src/main/cpp/lorie/buffer.c index 6e555a694..9e2eabd56 100644 --- a/app/src/main/cpp/lorie/buffer.c +++ b/app/src/main/cpp/lorie/buffer.c @@ -177,7 +177,7 @@ __LIBC_HIDDEN__ LorieBuffer* LorieBuffer_allocate(int32_t width, int32_t height, return NULL; } else if (type == LORIEBUFFER_AHARDWAREBUFFER) { AHardwareBuffer_Desc desc = { .width = width, .height = height, .format = format, .layers = 1, - .usage = AHARDWAREBUFFER_USAGE_CPU_READ_OFTEN | AHARDWAREBUFFER_USAGE_CPU_WRITE_OFTEN | AHARDWAREBUFFER_USAGE_GPU_SAMPLED_IMAGE }; + .usage = AHARDWAREBUFFER_USAGE_CPU_READ_OFTEN | AHARDWAREBUFFER_USAGE_CPU_WRITE_OFTEN | AHARDWAREBUFFER_USAGE_GPU_SAMPLED_IMAGE | AHARDWAREBUFFER_USAGE_GPU_FRAMEBUFFER }; int err = AHardwareBuffer_allocate(&desc, &ahardwarebuffer); if (err != 0) dprintf(2, "FATAL: failed to allocate AHardwareBuffer (width %d height %d format %d): error %d\n", width, height, format, err); @@ -228,7 +228,7 @@ __LIBC_HIDDEN__ void LorieBuffer_convert(LorieBuffer* buffer, int8_t type, int8_ } else { AHardwareBuffer *b = NULL; AHardwareBuffer_Desc desc = { .width = buffer->desc.width, .height = buffer->desc.height, .format = format, .layers = 1, - .usage = AHARDWAREBUFFER_USAGE_CPU_READ_OFTEN | AHARDWAREBUFFER_USAGE_CPU_WRITE_OFTEN | AHARDWAREBUFFER_USAGE_GPU_SAMPLED_IMAGE }; + .usage = AHARDWAREBUFFER_USAGE_CPU_READ_OFTEN | AHARDWAREBUFFER_USAGE_CPU_WRITE_OFTEN | AHARDWAREBUFFER_USAGE_GPU_SAMPLED_IMAGE | AHARDWAREBUFFER_USAGE_GPU_FRAMEBUFFER }; int err = AHardwareBuffer_allocate(&desc, &b); if (err != 0) return; @@ -425,6 +425,10 @@ __LIBC_HIDDEN__ void LorieBuffer_bindTexture(LorieBuffer *buffer) { glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, buffer->desc.stride, buffer->desc.height, buffer->desc.format == AHARDWAREBUFFER_FORMAT_B8G8R8A8_UNORM ? GL_BGRA_EXT : GL_RGBA, GL_UNSIGNED_BYTE, buffer->desc.data); } +__LIBC_HIDDEN__ unsigned int LorieBuffer_getGLTextureId(LorieBuffer *buffer) { + return buffer ? buffer->id : 0; +} + __LIBC_HIDDEN__ int LorieBuffer_getWidth(LorieBuffer *buffer) { return LorieBuffer_description(buffer)->width; } diff --git a/app/src/main/cpp/lorie/buffer.h b/app/src/main/cpp/lorie/buffer.h index e6947cb2c..751004b13 100644 --- a/app/src/main/cpp/lorie/buffer.h +++ b/app/src/main/cpp/lorie/buffer.h @@ -163,6 +163,15 @@ void LorieBuffer_attachToGL(LorieBuffer* _Nullable buffer); */ void LorieBuffer_bindTexture(LorieBuffer* _Nullable buffer); +/** + * Get the GL texture id the buffer is attached to (see LorieBuffer_attachToGL). + * Returns 0 if the buffer was not attached to GL yet. + * + * @param buffer + * @return + */ +unsigned int LorieBuffer_getGLTextureId(LorieBuffer* _Nullable buffer); + /** * Get width of the buffer. * diff --git a/app/src/main/cpp/lorie/cmdentrypoint.c b/app/src/main/cpp/lorie/cmdentrypoint.c index aa034c676..5716127dd 100644 --- a/app/src/main/cpp/lorie/cmdentrypoint.c +++ b/app/src/main/cpp/lorie/cmdentrypoint.c @@ -223,6 +223,12 @@ static Bool handleClipboardAnnounce(__unused ClientPtr pClient, __unused void *c return TRUE; } +static Bool handleGpuCopyDoneEvent(__unused ClientPtr pClient, __unused void *closure) { + // This must be done only on X server thread (touches present's internal vblank queue). + lorieRecheckGpuCopies(); + return TRUE; +} + static Bool handleClipboardData(__unused ClientPtr pClient, void *closure) { // This must be done only on X server thread. lorieHandleClipboardData(closure); @@ -408,6 +414,10 @@ void handleLorieEvents(int fd, __unused int ready, __unused void *ignored) { lorieSetRendererWakeupCond(wakeupFd); break; } + case EVENT_GPU_COPY_DONE: + QueueWorkProc(handleGpuCopyDoneEvent, NULL, NULL); + lorieWakeServer(); + break; } int n; diff --git a/app/src/main/cpp/lorie/lorie.h b/app/src/main/cpp/lorie/lorie.h index 9a54ad54f..0d0eabbdf 100644 --- a/app/src/main/cpp/lorie/lorie.h +++ b/app/src/main/cpp/lorie/lorie.h @@ -29,12 +29,14 @@ void lorieHandleClipboardAnnounce(void); void lorieHandleClipboardData(const char* data); void lorieSetStylusEnabled(Bool enabled); void lorieWakeServer(void); +void lorieRecheckGpuCopies(void); void lorieChoreographerFrameCallback(__unused long t, AChoreographer* d); void lorieActivityConnected(void); void lorieSendSharedServerState(int memfd); void lorieRegisterBuffer(LorieBuffer* buffer); void lorieUnregisterBuffer(LorieBuffer* buffer); bool lorieConnectionAlive(void); +extern bool lorieDebugEnabled; // Set in activity.c's startLogcat, only called when TERMUX_X11_DEBUG=1. void lorieSetRendererWakeupCond(int fd); int rendererGetWakeupCondFd(void); @@ -109,6 +111,7 @@ typedef enum { EVENT_CLIPBOARD_SEND, EVENT_WINDOW_FOCUS_CHANGED, EVENT_RENDERER_WAKEUP_COND, + EVENT_GPU_COPY_DONE, } eventType; typedef union { @@ -162,6 +165,20 @@ typedef union { } clipboardSend; } lorieEvent; +typedef struct { int16_t x1, y1, x2, y2; } LorieGpuCopyRect; + +#define LORIE_GPU_COPY_MAX_RECTS 16 +#define LORIE_GPU_COPY_QUEUE_CAPACITY 8 + +typedef struct { + uint64_t serial; + uint64_t srcBufferId; + uint64_t dstBufferId; + int16_t xOff, yOff; + uint16_t numRects; + LorieGpuCopyRect rects[LORIE_GPU_COPY_MAX_RECTS]; +} LorieGpuCopyEntry; + struct lorie_shared_server_state { /* * Renderer and X server are separated into 2 different processes. @@ -172,6 +189,18 @@ struct lorie_shared_server_state { pthread_mutex_t lock; // initialized at X server side. pid_t lockingPid; + /* + * Single-producer (X server, present_execute_copy)/single-consumer (renderer) ring buffer + * of deferred GPU copies to be applied to the root window texture before it is drawn to screen. + * X server only ever advances writeIndex, renderer only ever advances readIndex and completedSerial. + */ + struct { + volatile uint32_t writeIndex; + volatile uint32_t readIndex; + volatile uint64_t completedSerial; + LorieGpuCopyEntry entries[LORIE_GPU_COPY_QUEUE_CAPACITY]; + } gpuCopyQueue; + /* ID of root window texture to be drawn. */ uint64_t rootWindowTextureID; diff --git a/app/src/main/cpp/lorie/renderer.c b/app/src/main/cpp/lorie/renderer.c index b326dd4a8..6e160da5a 100644 --- a/app/src/main/cpp/lorie/renderer.c +++ b/app/src/main/cpp/lorie/renderer.c @@ -21,7 +21,9 @@ #include #include #include +#include #include +#include #include "list.h" #include "lorie.h" @@ -145,6 +147,20 @@ static struct { bool cursorChanged; } cursor; +// FBO used to blit deferred Present "copy" entries (see lorieTryScheduleGpuCopy) into the root texture. +static GLuint gpuCopyFbo = 0; + +// The renderer's end of activity.c's socket to the X server; used to notify it immediately when a +// GPU copy batch finishes instead of it waiting for the next vblank-tick poll. +extern volatile int conn_fd; + +static void notifyGpuCopyDone(void) { + if (conn_fd != -1) { + lorieEvent e = { .type = EVENT_GPU_COPY_DONE }; + write(conn_fd, &e, sizeof(e)); + } +} + GLuint g_texture_program = 0, gv_pos = 0, gv_coords = 0; GLuint g_texture_program_bgra = 0, gv_pos_bgra = 0, gv_coords_bgra = 0; @@ -617,6 +633,157 @@ void rendererRefreshContext(void) { static void drawRegion(GLuint id, float x0, float y0, float x1, float y1, float u0, float v0, float u1, float v1, uint8_t flip); static void drawCursor(float displayWidth, float displayHeight, float sourceLeft, float sourceTop); +// Drains the deferred GPU copy queue (filled by present_execute_copy) into the root texture via +// an FBO. Assumes the caller holds state->lock and will flush/fence before unlocking - returns +// the highest drained serial WITHOUT publishing it to completedSerial, since the caller must only +// do that after the fence confirms the GPU actually finished (not just submitted) the draws; +// publishing early would let the client's next write race our still-in-flight read. +// Looks up a registered buffer by id, waiting briefly (bounded) if it hasn't arrived over the +// async registration socket yet instead of busy-spinning the outer loop. +static LorieBuffer *rendererFindBufferWithRetry(uint64_t id) { + LorieBuffer *buf; + int attempt; + + pthread_spin_lock(&bufferLock); + buf = LorieBufferList_findById(&buffers, id); + if (!buf && (buf = LorieBufferList_findById(&addedBuffers, id))) { + LorieBuffer_attachToGL(buf); + LorieBuffer_addToList(buf, &buffers); + } + pthread_spin_unlock(&bufferLock); + + for (attempt = 0; attempt < 20 && !buf; attempt++) { + usleep(5000); + pthread_spin_lock(&bufferLock); + buf = LorieBufferList_findById(&buffers, id); + if (!buf && (buf = LorieBufferList_findById(&addedBuffers, id))) { + LorieBuffer_attachToGL(buf); + LorieBuffer_addToList(buf, &buffers); + } + pthread_spin_unlock(&bufferLock); + } + return buf; +} + +static uint64_t rendererApplyPendingGpuCopiesLocked(void) { + bool fboSetUp = false; + uint64_t lastSerial = 0; + uint64_t boundDstId = 0; + GLint prevViewport[4]; + + if (!state || state->gpuCopyQueue.readIndex == state->gpuCopyQueue.writeIndex) + return 0; + + while (state->gpuCopyQueue.readIndex != state->gpuCopyQueue.writeIndex) { + LorieGpuCopyEntry entry = state->gpuCopyQueue.entries[state->gpuCopyQueue.readIndex % LORIE_GPU_COPY_QUEUE_CAPACITY]; + LorieBuffer *src = rendererFindBufferWithRetry(entry.srcBufferId); + LorieBuffer *dst = rendererFindBufferWithRetry(entry.dstBufferId); + + if (!src) + log("rendererApplyPendingGpuCopies: source buffer %llu not found after waiting, skipping\n", (unsigned long long) entry.srcBufferId); + if (!dst) + log("rendererApplyPendingGpuCopies: destination buffer %llu not found after waiting, skipping\n", (unsigned long long) entry.dstBufferId); + + if (src && dst) { + const LorieBuffer_Desc *srcDesc = LorieBuffer_description(src); + const LorieBuffer_Desc *dstDesc = LorieBuffer_description(dst); + int i; + + if (!fboSetUp) { + glGetIntegerv(GL_VIEWPORT, prevViewport); + if (!gpuCopyFbo) + glGenFramebuffers(1, &gpuCopyFbo); + glBindFramebuffer(GL_FRAMEBUFFER, gpuCopyFbo); + fboSetUp = true; + } + // Different entries can target different pixmaps (root, or a Composite-redirected + // window's own backing pixmap); only rebind the FBO's attachment when it changes. + if (boundDstId != entry.dstBufferId) { + glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, LorieBuffer_getGLTextureId(dst), 0); + glViewport(0, 0, dstDesc->width, dstDesc->height); + boundDstId = entry.dstBufferId; + + // Diagnostic: GLES2 has no glGetTexLevelParameteriv, so ask the AHardwareBuffer + // itself what it was actually allocated as, instead of trusting our own desc. + { + static uint64_t dstSizeLogCount = 0; + if (lorieDebugEnabled && (dstSizeLogCount++ & 15) == 0 && dstDesc->buffer) { + AHardwareBuffer_Desc realDstDesc; + AHardwareBuffer_describe(dstDesc->buffer, &realDstDesc); + loge("gpucopy dst texId=%u real AHB size %ux%u stride=%u vs LorieBuffer desc %dx%d\n", + LorieBuffer_getGLTextureId(dst), realDstDesc.width, realDstDesc.height, + realDstDesc.stride, dstDesc->width, dstDesc->height); + } + } + } + + LorieBuffer_bindTexture(src); + { + static uint64_t srcSizeLogCount = 0; + if (lorieDebugEnabled && (srcSizeLogCount++ & 15) == 0 && srcDesc->buffer) { + AHardwareBuffer_Desc realSrcDesc; + AHardwareBuffer_describe(srcDesc->buffer, &realSrcDesc); + loge("gpucopy src texId=%u real AHB size %ux%u stride=%u vs LorieBuffer desc %dx%d (stride=%d)\n", + LorieBuffer_getGLTextureId(src), realSrcDesc.width, realSrcDesc.height, + realSrcDesc.stride, srcDesc->width, srcDesc->height, srcDesc->stride); + } + } + for (i = 0; i < entry.numRects; i++) { + LorieGpuCopyRect r = entry.rects[i]; + float x0 = 2.f * (float) (r.x1 + entry.xOff) / (float) dstDesc->width - 1.f; + float x1 = 2.f * (float) (r.x2 + entry.xOff) / (float) dstDesc->width - 1.f; + // FBO writes and on-screen draws use opposite y conventions here, unlike x. + float y0 = 1.f - 2.f * (float) (r.y1 + entry.yOff) / (float) dstDesc->height; + float y1 = 1.f - 2.f * (float) (r.y2 + entry.yOff) / (float) dstDesc->height; + // EGLImage-backed textures sample by logical width regardless of row stride; + // only our own CPU-uploaded LORIEBUFFER_FD texture is stride-wide. + float srcUvDivisor = srcDesc->type == LORIEBUFFER_FD ? (float) srcDesc->stride : (float) srcDesc->width; + float u0 = (float) r.x1 / srcUvDivisor; + float u1 = (float) r.x2 / srcUvDivisor; + float v0 = (float) r.y1 / (float) srcDesc->height; + float v1 = (float) r.y2 / (float) srcDesc->height; + // Only swap channels if src/dst storage formats actually differ. + uint8_t needsSwizzle = LorieBuffer_isRgba(src) != LorieBuffer_isRgba(dst); + log("rendererApplyPendingGpuCopies: rect (%d,%d)-(%d,%d) off=(%d,%d) -> ndc=(%.3f,%.3f)-(%.3f,%.3f) uv=(%.3f,%.3f)-(%.3f,%.3f) srcTex=%u dstTex=%u swizzle=%d\n", + r.x1, r.y1, r.x2, r.y2, entry.xOff, entry.yOff, x0, y0, x1, y1, u0, v0, u1, v1, + LorieBuffer_getGLTextureId(src), LorieBuffer_getGLTextureId(dst), needsSwizzle); + drawRegion(0, x0, y0, x1, y1, u0, v0, u1, v1, needsSwizzle); + } + } + + lastSerial = entry.serial; + state->gpuCopyQueue.readIndex++; + } + + if (fboSetUp) { + glBindFramebuffer(GL_FRAMEBUFFER, 0); + glViewport(prevViewport[0], prevViewport[1], prevViewport[2], prevViewport[3]); + } + return lastSerial; +} + +// Standalone entry point used by the renderer thread's main loop. Used when no redraw is going to +// happen on this tick (rare for GPU copies in practice, since scheduling one also marks damage +// non-empty - see lorieTryScheduleGpuCopy), so it has to take the lock and fence/unlock itself. +static void rendererApplyPendingGpuCopies(void) { + uint64_t serial; + if (!state || state->gpuCopyQueue.readIndex == state->gpuCopyQueue.writeIndex) + return; + lorie_mutex_lock(&state->lock, &state->lockingPid); + serial = rendererApplyPendingGpuCopiesLocked(); + if (serial) { + EGLSync fence = eglCreateSyncKHR(egl_display, EGL_SYNC_FENCE_KHR, NULL); + glFlush(); + eglClientWaitSyncKHR(egl_display, fence, 0, EGL_FOREVER); + eglDestroySyncKHR(egl_display, fence); + // Only now that the GPU has actually finished (not just been told to start) is it safe to + // let present_execute_copy release/idle the source pixmap back to the client. + state->gpuCopyQueue.completedSerial = serial; + notifyGpuCopyDone(); + } + lorie_mutex_unlock(&state->lock, &state->lockingPid); +} + void rendererRedrawLocked(bool* waitingForBuffers) { float xfactor = 1.f; LorieBuffer_Desc *desc = NULL; @@ -644,6 +811,9 @@ void rendererRedrawLocked(bool* waitingForBuffers) { log("Buffer %llu is not of expected size, expecting %dx%d or %dx%d, got %dx%d", state->rootWindowTextureID, alignedExpectedW, expectedH, expectedW, expectedH, desc->width, desc->height); + // Otherwise rendererShouldWait sees drawRequested still set and busy-spins retrying this + // same mismatch instead of waiting for the next real trigger (e.g. the pending resize). + state->drawRequested = FALSE; return; } @@ -721,6 +891,8 @@ void rendererRedrawLocked(bool* waitingForBuffers) { // We should signal X server to not use root window while we actively copy it lorie_mutex_lock(&state->lock, &state->lockingPid); + // Share this draw's flush+fence below instead of a separate round trip per frame. + uint64_t gpuCopySerial = rendererApplyPendingGpuCopiesLocked(); state->drawRequested = FALSE; LorieBuffer_bindTexture(buffer); @@ -750,6 +922,10 @@ void rendererRedrawLocked(bool* waitingForBuffers) { // Wait until root window drawing is finished before giving control back to X server eglClientWaitSyncKHR(egl_display, fence, 0, EGL_FOREVER); eglDestroySyncKHR(egl_display, fence); + if (gpuCopySerial) { + state->gpuCopyQueue.completedSerial = gpuCopySerial; + notifyGpuCopyDone(); + } state->waitForNextFrame = true; lorie_mutex_unlock(&state->lock, &state->lockingPid); @@ -771,11 +947,12 @@ void rendererRedrawLocked(bool* waitingForBuffers) { static inline __always_inline bool rendererShouldWait(bool *waitingForBuffers) { static uint64_t lastRequestedBufferId = 0; - bool buffersChanged; + bool buffersChanged, gpuCopyPending; pthread_spin_lock(&bufferLock); buffersChanged = !xorg_list_is_empty(&addedBuffers) || !xorg_list_is_empty(&removedBuffers); pthread_spin_unlock(&bufferLock); - if (stateChanged || windowChanged || buffersChanged) + gpuCopyPending = state && state->gpuCopyQueue.readIndex != state->gpuCopyQueue.writeIndex; + if (stateChanged || windowChanged || buffersChanged || gpuCopyPending) // If there are pending changes we should process them immediately. return false; @@ -841,8 +1018,14 @@ __noreturn static void* rendererThread(void) { pthread_cond_signal(&stateChangeFinishCond); pthread_mutex_unlock(&stateLock); - if (state && state->surfaceAvailable && !state->waitForNextFrame && (state->drawRequested || state->cursor.moved || state->cursor.updated)) + // Prefer a full redraw over the standalone apply below so a pending GPU copy shares one + // lock+fence with the root/cursor draw, instead of two GPU round trips per frame. + bool gpuCopyPending = state && state->gpuCopyQueue.readIndex != state->gpuCopyQueue.writeIndex; + if (state && state->surfaceAvailable && !state->waitForNextFrame && + (state->drawRequested || state->cursor.moved || state->cursor.updated || gpuCopyPending)) rendererRedrawLocked(&waitingForBuffers); + else if (gpuCopyPending) + rendererApplyPendingGpuCopies(); pthread_spin_lock(&bufferLock); // Remove all buffers which were attached to GL. diff --git a/app/src/main/cpp/patches/xserver.patch b/app/src/main/cpp/patches/xserver.patch index 8e45b2ff5..32366a482 100644 --- a/app/src/main/cpp/patches/xserver.patch +++ b/app/src/main/cpp/patches/xserver.patch @@ -250,7 +250,7 @@ } /* Avoid EINTR during stdio calls */ -+++ b/present/present.h ++++ ./present/present.h @@ -93,6 +93,9 @@ uint64_t target_msc, PixmapPtr pixmap, @@ -269,8 +269,137 @@ present_unflip_ptr unflip; present_check_flip2_ptr check_flip2; -+++ b/present/present_scmd.c -@@ -599,6 +599,8 @@ ++++ ./present/present_execute.c +@@ -67,6 +67,11 @@ + WindowPtr window = vblank->window; + ScreenPtr screen = window->drawable.pScreen; + present_screen_priv_ptr screen_priv = present_screen_priv(screen); ++ /* lorie: screen_x/screen_y is dst's own (0,0) in screen coords; window.xy minus that gives the ++ * window's offset within dst, whether dst is root, itself, or an inherited redirected ancestor. */ ++ PixmapPtr gpuCopyDst = screen->GetWindowPixmap(window); ++ int16_t gpuCopyXOff = (int16_t) (vblank->x_off + window->drawable.x - gpuCopyDst->screen_x); ++ int16_t gpuCopyYOff = (int16_t) (vblank->y_off + window->drawable.y - gpuCopyDst->screen_y); + + /* If present_flip failed, we may have to requeue for the next MSC */ + if (vblank->exec_msc == crtc_msc + 1 && +@@ -79,12 +84,53 @@ + return; + } + +- present_copy_region(&window->drawable, vblank->pixmap, vblank->update, vblank->x_off, vblank->y_off); ++ /* lorie: a GPU copy was already scheduled; poll for completion instead of blocking. Give up ++ * and treat it as done if the renderer connection died (completedSerial would never advance). */ ++ if (vblank->gpu_copy_pending && !lorieGpuCopyIsDone(vblank->gpu_copy_serial) && ++ lorieConnectionAlive() && ++ Success == screen_priv->queue_vblank(screen, window, vblank->crtc, vblank->event_id, crtc_msc + 1)) { ++ vblank->queued = TRUE; ++ return; ++ } ++ ++ if (vblank->gpu_copy_pending) { ++ lorieGpuCopyAck(vblank->pixmap, vblank->gpu_copy_dst_buffer); ++ vblank->gpu_copy_pending = FALSE; ++ } else if (lorieTryScheduleGpuCopy(vblank->pixmap, gpuCopyDst, vblank->update, gpuCopyXOff, gpuCopyYOff, ++ &vblank->gpu_copy_serial, &vblank->gpu_copy_dst_buffer)) { ++ /* lorie: our GPU blit writes the destination pixmap directly, bypassing the normal GC ++ * ops that Damage tracking hooks into, so report it manually - same drawable and region ++ * present_scmd.c's flip success path already uses, so this also reaches Composite's ++ * per-window damage for redirected windows, not just lorie's own root damage. */ ++ RegionPtr damage = vblank->update ? vblank->update : &window->clipList; ++ if (vblank->update) ++ RegionIntersect(damage, damage, &window->clipList); ++ DamageDamageRegion(&window->drawable, damage); ++ ++ /* lorie: copy offloaded to the renderer's GPU context. The region was already ++ * consumed (copied into the shared command queue), so free it like ++ * present_copy_region would have. */ ++ if (vblank->update) { ++ RegionDestroy(vblank->update); ++ vblank->update = NULL; ++ } ++ if (Success == screen_priv->queue_vblank(screen, window, vblank->crtc, vblank->event_id, crtc_msc + 1)) { ++ vblank->gpu_copy_pending = TRUE; ++ vblank->queued = TRUE; ++ return; ++ } ++ /* Failed to requeue for polling (e.g. OOM) - release our extra ref and treat the ++ * presumably still in-flight GPU copy as done; same best-effort fallback as above. */ ++ lorieGpuCopyAck(vblank->pixmap, vblank->gpu_copy_dst_buffer); ++ } else { ++ present_copy_region(&window->drawable, vblank->pixmap, vblank->update, vblank->x_off, vblank->y_off); ++ ++ /* present_copy_region sticks the region into a scratch GC, ++ * which is then freed, freeing the region ++ */ ++ vblank->update = NULL; ++ } + +- /* present_copy_region sticks the region into a scratch GC, +- * which is then freed, freeing the region +- */ +- vblank->update = NULL; + screen_priv->flush(window); + + present_pixmap_idle(vblank->pixmap, vblank->window, vblank->serial, vblank->idle_fence); ++++ ./present/present_priv.h +@@ -24,6 +24,7 @@ + #define _PRESENT_PRIV_H_ + + #include "dix-config.h" ++#include + #include + #include "scrnintstr.h" + #include "misc.h" +@@ -90,8 +91,27 @@ + Bool abort_flip; /* aborting this flip */ + PresentFlipReason reason; /* reason for which flip is not possible */ + Bool has_suboptimal; /* whether client can support SuboptimalCopy mode */ ++ ++ /* lorie: set when present_execute_copy offloaded the copy to the renderer's GPU context; ++ * the vblank stays queued until the copy is acknowledged done. */ ++ Bool gpu_copy_pending; ++ uint64_t gpu_copy_serial; ++ void *gpu_copy_dst_buffer; /* opaque LorieBuffer*, held for lorieGpuCopyAck */ + }; + ++/* lorie: hooks implemented in lorie/InitOutput.c, used by present_execute_copy() to offload the ++ * pixmap->dst copy (dst being whatever GetWindowPixmap(window) is - root, or a Composite-redirected ++ * window's own backing pixmap) to the renderer's GPU context instead of a CPU CopyArea. */ ++extern Bool lorieTryScheduleGpuCopy(PixmapPtr pixmap, PixmapPtr dst, RegionPtr update, int16_t x_off, int16_t y_off, ++ uint64_t *out_serial, void **out_dst_buffer); ++extern Bool lorieGpuCopyIsDone(uint64_t serial); ++extern void lorieGpuCopyAck(PixmapPtr pixmap, void *dst_buffer); ++/* lorie: implemented in lorie/cmdentrypoint.c. Avoids waiting forever on a GPU copy that a dead renderer will never finish. */ ++extern bool lorieConnectionAlive(void); ++ ++/* lorie: implemented in present_scmd.c; re-executes any vblank whose GPU copy has finished. */ ++extern void lorieRecheckGpuCopies(void); ++ + typedef struct present_screen_priv present_screen_priv_rec, *present_screen_priv_ptr; + typedef struct present_window_priv present_window_priv_rec, *present_window_priv_ptr; + ++++ ./present/present_scmd.c +@@ -252,6 +252,19 @@ + } + } + ++/* lorie: called when the renderer notifies that a GPU copy batch finished, instead of waiting ++ * for the next vblank poll. _safe iteration since present_re_execute() may destroy the vblank. */ ++void ++lorieRecheckGpuCopies(void) ++{ ++ present_vblank_ptr vblank, tmp; ++ ++ xorg_list_for_each_entry_safe(vblank, tmp, &present_exec_queue, event_queue) { ++ if (vblank->gpu_copy_pending && lorieGpuCopyIsDone(vblank->gpu_copy_serial)) ++ present_re_execute(vblank); ++ } ++} ++ + static void + present_flip_idle(ScreenPtr screen) + { +@@ -599,6 +612,8 @@ damage = &window->clipList; DamageDamageRegion(&vblank->window->drawable, damage);