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);