From 9c0caca3fa173a1f629bc2137902ab4355a13d0f Mon Sep 17 00:00:00 2001 From: Geoffrey McRae Date: Wed, 12 Aug 2026 04:13:00 +1000 Subject: [PATCH] [client] render: bound software surface update backlog --- client/src/main.c | 47 ++- client/src/render_queue.c | 641 +++++++++++++++++++++++++++---- client/src/render_queue.h | 6 + client/tests/render_queue_test.c | 42 +- 4 files changed, 638 insertions(+), 98 deletions(-) diff --git a/client/src/main.c b/client/src/main.c index 92a93759..cf26572a 100644 --- a/client/src/main.c +++ b/client/src/main.c @@ -1732,6 +1732,27 @@ static void videoSourceClearCursor(LG_VideoSource source) renderQueue_sourceClearCursor(renderQueueSource(source)); } +static void videoSourceReject(void * opaque, RenderQueueSource queueSource, + uint64_t serial) +{ + (void)opaque; + + LG_VideoSource source = LG_VIDEO_SOURCE_NONE; + if (queueSource == RENDER_QUEUE_SOURCE_PRIMARY) + source = LG_VIDEO_SOURCE_PRIMARY; + else if (queueSource == RENDER_QUEUE_SOURCE_FALLBACK) + source = LG_VIDEO_SOURCE_FALLBACK; + + if (source == LG_VIDEO_SOURCE_NONE) + return; + + struct VideoSourceState * state = &g_state.videoSource[source]; + if (atomic_compare_exchange_strong_explicit(&state->transitionSerial, + &serial, 0, memory_order_acq_rel, memory_order_relaxed)) + atomic_store_explicit( + &state->transitionPending, false, memory_order_release); +} + static bool videoSourcePrepare(void * opaque, RenderQueueSource queueSource, uint64_t generation, uint64_t serial) { @@ -1799,10 +1820,7 @@ static bool videoSourcePrepare(void * opaque, RenderQueueSource queueSource, return true; reject: - if (atomic_compare_exchange_strong_explicit(&state->transitionSerial, - &serial, 0, memory_order_acq_rel, memory_order_relaxed)) - atomic_store_explicit( - &state->transitionPending, false, memory_order_release); + videoSourceReject(opaque, queueSource, serial); return false; } @@ -1892,6 +1910,7 @@ static void swSurfaceConfigure(LG_VideoSource source, unsigned int width, unsigned int height) { struct VideoSourceState * state = &g_state.videoSource[source]; + uint64_t generation; LG_LOCK(g_state.videoSourceLock); if (!swSurfaceEventAdmitted(source)) { @@ -1900,10 +1919,25 @@ static void swSurfaceConfigure(LG_VideoSource source, } videoSourceBeginLocked(source); + generation = atomic_load_explicit( + &state->generation, memory_order_acquire); atomic_store_explicit(&state->width, width, memory_order_relaxed); atomic_store_explicit(&state->height, height, memory_order_relaxed); atomic_store_explicit(&state->rotate, LG_ROTATE_0, memory_order_release); state->configurePending = true; + LG_UNLOCK(g_state.videoSourceLock); + + if (!renderQueue_sourceSwSurfaceConfigure(renderQueueSource(source), + generation, width, height)) + return; + + LG_LOCK(g_state.videoSourceLock); + if (atomic_load_explicit( + &state->generation, memory_order_acquire) != generation) + { + LG_UNLOCK(g_state.videoSourceLock); + return; + } atomic_store_explicit(&state->ready, true, memory_order_release); LG_UNLOCK(g_state.videoSourceLock); @@ -3002,7 +3036,8 @@ static int lg_run(void) LG_LOCK_INIT(g_state.videoSourceLock); LG_LOCK_INIT(g_state.videoSplashLock); renderQueue_init(); - renderQueue_setSourceFns(videoSourcePrepare, videoSourceApplied, NULL); + renderQueue_setSourceFns( + videoSourcePrepare, videoSourceReject, videoSourceApplied, NULL); atomic_store_explicit(&g_state.videoSourceRequested, LG_VIDEO_SOURCE_PRIMARY, memory_order_relaxed); atomic_store_explicit(&g_state.videoSourceApplied, @@ -3552,7 +3587,7 @@ static void lg_shutdown(void) g_state.overlays = NULL; } - renderQueue_setSourceFns(NULL, NULL, NULL); + renderQueue_setSourceFns(NULL, NULL, NULL, NULL); renderQueue_free(); LG_LOCK_FREE(g_state.videoSourceLock); LG_LOCK_FREE(g_state.videoSplashLock); diff --git a/client/src/render_queue.c b/client/src/render_queue.c index 8dbe066d..86111807 100644 --- a/client/src/render_queue.c +++ b/client/src/render_queue.c @@ -20,6 +20,7 @@ #include "render_queue.h" +#include #include #include #include @@ -40,8 +41,7 @@ typedef struct RenderCommand enum { SW_SURFACE_OP_CONFIGURE_TRANSITION, - SW_SURFACE_OP_DRAW_FILL, - SW_SURFACE_OP_DRAW_BITMAP, + SW_SURFACE_OP_UPDATE, SURFACE_OP_FORMAT, CURSOR_OP_STATE, CURSOR_OP_IMAGE, @@ -56,26 +56,15 @@ typedef struct RenderCommand struct { int width, height; + uint64_t epoch; } swSurfaceConfigureTransition; struct { - int x, y; - int width, height; - uint32_t color; + uint64_t epoch; } - swSurfaceDrawFill; - - struct - { - int x , y; - int width, height; - int stride; - uint8_t * data; - bool topDown; - } - swSurfaceDrawBitmap; + swSurfaceUpdate; struct { @@ -139,6 +128,44 @@ static LG_Lock l_renderQueueLock; static bool l_renderQueueInitialized; static RenderQueueInvalidate l_renderQueueInvalidate; +typedef struct RenderQueueSwSurface +{ + LG_Lock lock; + + /* Transport bitmap storage is borrowed, so callbacks compose it into this + * persistent image before returning. The renderer consumes it synchronously + * while lock is held. */ + uint64_t generation; + uint64_t epoch; + int width; + int height; + int pitch; + uint8_t * data; + + bool damageFull; + int damageCount; + FrameDamageRect damage[LG_MAX_FRAME_DAMAGE_RECTS]; + + bool updateQueued; + RenderCommand updateCommand; +} +RenderQueueSwSurface; + +static RenderQueueSwSurface l_swSurface[RENDER_QUEUE_SOURCE_COUNT]; +static RenderQueueSource l_swSurfaceResidentSource; +static uint64_t l_swSurfaceResidentGeneration; +static uint64_t l_swSurfaceResidentEpoch; + +static RenderQueueSwSurface * swSurfaceFromUpdateCommand( + const RenderCommand * cmd) +{ + for (int i = 0; i < RENDER_QUEUE_SOURCE_COUNT; ++i) + if (cmd == &l_swSurface[i].updateCommand) + return &l_swSurface[i]; + + return NULL; +} + static bool l_showSwSurface; static bool l_surfaceFormatValid; static bool l_rendererSupportsNativeHDR; @@ -154,6 +181,7 @@ static uint64_t l_appliedGeneration; static bool l_appliedSwSurface; static RenderQueueSourcePrepareFn l_sourcePrepareFn; +static RenderQueueSourceRejectFn l_sourceRejectFn; static RenderQueueSourceAppliedFn l_sourceAppliedFn; static void * l_sourceCallbackOpaque; @@ -242,8 +270,119 @@ static bool commandValid(const RenderCommand * cmd) return generationValid(cmd->source, cmd->generation); } +static uint64_t swSurfaceDamageArea(const FrameDamageRect * rect) +{ + return (uint64_t)rect->width * rect->height; +} + +static FrameDamageRect swSurfaceDamageUnion( + const FrameDamageRect * a, const FrameDamageRect * b) +{ + const uint32_t left = min(a->x, b->x); + const uint32_t top = min(a->y, b->y); + const uint32_t right = max(a->x + a->width, b->x + b->width); + const uint32_t bottom = max(a->y + a->height, b->y + b->height); + + return (FrameDamageRect) + { + .x = left, + .y = top, + .width = right - left, + .height = bottom - top, + }; +} + +static bool swSurfaceDamageTouches( + const FrameDamageRect * a, const FrameDamageRect * b) +{ + return a->x <= b->x + b->width && b->x <= a->x + a->width && + a->y <= b->y + b->height && b->y <= a->y + a->height; +} + +static void swSurfaceDamageReset(RenderQueueSwSurface * surface) +{ + surface->damageFull = false; + surface->damageCount = 0; +} + +static bool swSurfaceDamagePending(const RenderQueueSwSurface * surface) +{ + return surface->damageFull || surface->damageCount; +} + +static void swSurfaceDamageAdd(RenderQueueSwSurface * surface, + const FrameDamageRect * damage) +{ + if (surface->damageFull) + return; + + if (damage->x == 0 && damage->y == 0 && + damage->width == (uint32_t)surface->width && + damage->height == (uint32_t)surface->height) + { + surface->damageFull = true; + surface->damageCount = 0; + return; + } + + FrameDamageRect rect = *damage; + for (;;) + { + for (int i = 0; i < surface->damageCount;) + { + if (!swSurfaceDamageTouches(&rect, &surface->damage[i])) + { + ++i; + continue; + } + + rect = swSurfaceDamageUnion(&rect, &surface->damage[i]); + surface->damage[i] = surface->damage[--surface->damageCount]; + i = 0; + } + + if (surface->damageCount < LG_MAX_FRAME_DAMAGE_RECTS) + break; + + int best = 0; + uint64_t bestCost = UINT64_MAX; + for (int i = 0; i < surface->damageCount; ++i) + { + const FrameDamageRect merged = + swSurfaceDamageUnion(&rect, &surface->damage[i]); + const uint64_t cost = swSurfaceDamageArea(&merged) - + swSurfaceDamageArea(&surface->damage[i]); + if (cost < bestCost) + { + best = i; + bestCost = cost; + } + } + + rect = swSurfaceDamageUnion(&rect, &surface->damage[best]); + surface->damage[best] = surface->damage[--surface->damageCount]; + } + + surface->damage[surface->damageCount++] = rect; + + uint64_t area = 0; + for (int i = 0; i < surface->damageCount; ++i) + area += swSurfaceDamageArea(&surface->damage[i]); + + const uint64_t fullArea = + (uint64_t)surface->width * surface->height; + if (area >= fullArea - fullArea / 4) + { + surface->damageFull = true; + surface->damageCount = 0; + } +} + static void freeCommand(RenderCommand * cmd) { + if (cmd->op == SW_SURFACE_OP_UPDATE) + return; + switch (cmd->op) { case CURSOR_OP_IMAGE: @@ -283,6 +422,23 @@ static bool queueCommand(RenderCommand * cmd, return wake; } +/* The surface lock must be held while its embedded command is queued. */ +static bool queueSwSurfaceUpdate(RenderQueueSource source, + RenderQueueSwSurface * surface) +{ + if (surface->updateQueued || !surface->data || + !swSurfaceDamagePending(surface)) + return false; + + RenderCommand * cmd = &surface->updateCommand; + cmd->source = source; + cmd->generation = surface->generation; + cmd->op = SW_SURFACE_OP_UPDATE; + cmd->swSurfaceUpdate.epoch = surface->epoch; + surface->updateQueued = true; + return queueCommand(cmd, RENDER_QUEUE_INVALIDATE_PARTIAL); +} + static void wakeQueue(RenderQueueInvalidate invalidate, bool wake) { if (wake) @@ -391,16 +547,26 @@ void renderQueue_init(void) l_appliedSource = RENDER_QUEUE_SOURCE_NONE; l_appliedGeneration = 0; l_appliedSwSurface = false; + + l_swSurfaceResidentSource = RENDER_QUEUE_SOURCE_NONE; + l_swSurfaceResidentGeneration = 0; + l_swSurfaceResidentEpoch = 0; + l_sourcePrepareFn = NULL; + l_sourceRejectFn = NULL; l_sourceAppliedFn = NULL; l_sourceCallbackOpaque = NULL; l_pendingTransition = (RenderQueueTransition) {}; memset(&l_surfaceFormat, 0, sizeof(l_surfaceFormat)); memset(l_cursor, 0, sizeof(l_cursor)); memset(l_format, 0, sizeof(l_format)); + memset(l_swSurface, 0, sizeof(l_swSurface)); for (int i = 0; i < RENDER_QUEUE_SOURCE_COUNT; ++i) + { atomic_store_explicit(&l_sourceGeneration[i], 0, memory_order_relaxed); + LG_LOCK_INIT(l_swSurface[i].lock); + } atomic_store_explicit(&l_transitionSerial, 0, memory_order_relaxed); LG_LOCK_INIT(l_renderQueueLock); LG_LOCK_INIT(l_sourceLock); @@ -419,6 +585,9 @@ void renderQueue_free(void) { free(l_cursor[i].data); l_cursor[i].data = NULL; + free(l_swSurface[i].data); + l_swSurface[i].data = NULL; + LG_LOCK_FREE(l_swSurface[i].lock); } l_renderQueueInitialized = false; @@ -433,15 +602,25 @@ void renderQueue_clear(void) while(cmd) { RenderCommand * next = cmd->next; - freeCommand(cmd); + RenderQueueSwSurface * surface = swSurfaceFromUpdateCommand(cmd); + if (surface) + { + LG_LOCK(surface->lock); + surface->updateQueued = false; + LG_UNLOCK(surface->lock); + } + else + freeCommand(cmd); cmd = next; } } void renderQueue_setSourceFns(RenderQueueSourcePrepareFn prepare, + RenderQueueSourceRejectFn reject, RenderQueueSourceAppliedFn applied, void * opaque) { l_sourcePrepareFn = prepare; + l_sourceRejectFn = reject; l_sourceAppliedFn = applied; l_sourceCallbackOpaque = opaque; } @@ -452,6 +631,7 @@ uint64_t renderQueue_sourceBegin(RenderQueueSource source) return 0; LG_LOCK(l_sourceLock); + LG_LOCK(l_swSurface[source].lock); const uint64_t previous = atomic_load_explicit( &l_sourceGeneration[source], memory_order_relaxed); const uint64_t generation = previous + 1; @@ -467,6 +647,7 @@ uint64_t renderQueue_sourceBegin(RenderQueueSource source) cursor->colorGeneration = generation; if (cursor->whiteValid && cursor->whiteGeneration == previous) cursor->whiteGeneration = generation; + LG_UNLOCK(l_swSurface[source].lock); LG_UNLOCK(l_sourceLock); return generation; } @@ -477,6 +658,7 @@ void renderQueue_sourceInvalidate(RenderQueueSource source, if (!sourceValid(source) || !generation) return; LG_LOCK(l_sourceLock); + LG_LOCK(l_swSurface[source].lock); const uint64_t invalidGeneration = generation + 1; if (atomic_compare_exchange_strong_explicit(&l_sourceGeneration[source], &generation, invalidGeneration, memory_order_acq_rel, @@ -492,6 +674,7 @@ void renderQueue_sourceInvalidate(RenderQueueSource source, if (cursor->whiteValid && cursor->whiteGeneration == generation) cursor->whiteGeneration = invalidGeneration; } + LG_UNLOCK(l_swSurface[source].lock); LG_UNLOCK(l_sourceLock); } @@ -506,6 +689,99 @@ void renderQueue_sourceClearCursor(RenderQueueSource source) LG_UNLOCK(l_sourceLock); } +static bool configureSwSurface(RenderQueueSource source, + uint64_t generation, int width, int height, uint64_t * epoch) +{ + if (width <= 0 || height <= 0 || width > INT_MAX / 4) + { + DEBUG_ERROR("Invalid software surface dimensions: %dx%d", width, height); + return false; + } + + const int pitch = width * 4; + if ((size_t)height > SIZE_MAX / (size_t)pitch) + { + DEBUG_ERROR("Software surface size overflows: %dx%d", width, height); + return false; + } + + RenderQueueSwSurface * surface = &l_swSurface[source]; + LG_LOCK(surface->lock); + if (generationValid(source, generation) && surface->data && + surface->generation == generation && + surface->width == width && surface->height == height) + { + *epoch = surface->epoch; + LG_UNLOCK(surface->lock); + return true; + } + LG_UNLOCK(surface->lock); + + uint8_t * data = calloc((size_t)height, (size_t)pitch); + if (!data) + { + DEBUG_ERROR("Failed to allocate software surface: %dx%d", width, height); + return false; + } + + LG_LOCK(surface->lock); + if (!generationValid(source, generation)) + { + LG_UNLOCK(surface->lock); + free(data); + return false; + } + + if (surface->data && surface->generation == generation && + surface->width == width && surface->height == height) + { + *epoch = surface->epoch; + LG_UNLOCK(surface->lock); + free(data); + return true; + } + + uint64_t nextEpoch = surface->epoch + 1; + if (!nextEpoch) + ++nextEpoch; + + free(surface->data); + surface->generation = generation; + surface->epoch = nextEpoch; + surface->width = width; + surface->height = height; + surface->pitch = pitch; + surface->data = data; + swSurfaceDamageReset(surface); + *epoch = nextEpoch; + LG_UNLOCK(surface->lock); + return true; +} + +bool renderQueue_sourceSwSurfaceConfigure(RenderQueueSource source, + uint64_t generation, int width, int height) +{ + if (!generationValid(source, generation)) + return false; + + uint64_t epoch; + return configureSwSurface(source, generation, width, height, &epoch); +} + +static bool getSwSurfaceConfiguration(RenderQueueSource source, + uint64_t generation, int width, int height, uint64_t * epoch) +{ + RenderQueueSwSurface * surface = &l_swSurface[source]; + LG_LOCK(surface->lock); + const bool valid = generationValid(source, generation) && surface->data && + surface->generation == generation && + surface->width == width && surface->height == height; + if (valid) + *epoch = surface->epoch; + LG_UNLOCK(surface->lock); + return valid; +} + static uint64_t enqueueTransition(RenderCommand * cmd, atomic_uint_least64_t * publishedSerial) { @@ -558,33 +834,86 @@ uint64_t renderQueue_sourceSwSurfaceConfigureTransition( if (!cmd) return 0; + uint64_t epoch; + if (!getSwSurfaceConfiguration( + source, generation, width, height, &epoch)) + { + free(cmd); + return 0; + } + setCommandSource(cmd, source, generation); cmd->op = SW_SURFACE_OP_CONFIGURE_TRANSITION; cmd->swSurfaceConfigureTransition.width = width; cmd->swSurfaceConfigureTransition.height = height; + cmd->swSurfaceConfigureTransition.epoch = epoch; return enqueueTransition(cmd, publishedSerial); } +static bool clipSwSurfaceRect(const RenderQueueSwSurface * surface, + int * x, int * y, int * width, int * height, + int * sourceX, int * sourceY) +{ + const int64_t left = max((int64_t)*x, 0); + const int64_t top = max((int64_t)*y, 0); + const int64_t right = min((int64_t)*x + *width, + (int64_t)surface->width); + const int64_t bottom = min((int64_t)*y + *height, + (int64_t)surface->height); + if (right <= left || bottom <= top) + return false; + + if (sourceX) + *sourceX = (int)(left - *x); + if (sourceY) + *sourceY = (int)(top - *y); + *x = (int)left; + *y = (int)top; + *width = (int)(right - left); + *height = (int)(bottom - top); + return true; +} + void renderQueue_sourceSwSurfaceDrawFill(RenderQueueSource source, uint64_t generation, int x, int y, int width, int height, uint32_t color) { - if (!generationValid(source, generation)) + if (!generationValid(source, generation) || width <= 0 || height <= 0) return; - RenderCommand * cmd = malloc(sizeof(*cmd)); - if (!cmd) + RenderQueueSwSurface * surface = &l_swSurface[source]; + LG_LOCK(surface->lock); + if (surface->generation != generation || + !generationValid(source, generation) || + !clipSwSurfaceRect(surface, + &x, &y, &width, &height, NULL, NULL)) + { + LG_UNLOCK(surface->lock); return; + } - setCommandSource(cmd, source, generation); - cmd->op = SW_SURFACE_OP_DRAW_FILL; - cmd->swSurfaceDrawFill.x = x; - cmd->swSurfaceDrawFill.y = y; - cmd->swSurfaceDrawFill.width = width; - cmd->swSurfaceDrawFill.height = height; - cmd->swSurfaceDrawFill.color = color; - enqueueCommand(cmd, RENDER_QUEUE_INVALIDATE_PARTIAL); + uint8_t * row = surface->data + + (size_t)y * surface->pitch + (size_t)x * 4; + for (int dy = 0; dy < height; ++dy) + { + uint32_t * dst = (uint32_t *)row; + for (int dx = 0; dx < width; ++dx) + dst[dx] = color; + row += surface->pitch; + } + + const FrameDamageRect damage = + { + .x = (uint32_t)x, + .y = (uint32_t)y, + .width = (uint32_t)width, + .height = (uint32_t)height, + }; + swSurfaceDamageAdd(surface, &damage); + const bool wake = queueSwSurfaceUpdate(source, surface); + LG_UNLOCK(surface->lock); + wakeQueue(RENDER_QUEUE_INVALIDATE_PARTIAL, wake); } void renderQueue_sourceSwSurfaceDrawBitmap(RenderQueueSource source, @@ -616,41 +945,51 @@ void renderQueue_sourceSwSurfaceDrawBitmap(RenderQueueSource source, return; } - const size_t rowSize = (size_t)width * 4; - if ((size_t)height > (SIZE_MAX - sizeof(RenderCommand)) / rowSize) + if ((size_t)height > SIZE_MAX / (size_t)stride) { DEBUG_ERROR("Software surface bitmap size overflows: " - "width: %d, height: %d", width, height); + "height: %d, stride: %d", height, stride); return; } - const size_t size = (size_t)height * rowSize; - RenderCommand * cmd = malloc(sizeof(*cmd) + size); - if (!cmd) + RenderQueueSwSurface * surface = &l_swSurface[source]; + LG_LOCK(surface->lock); + int sourceX; + int sourceY; + const int sourceHeight = height; + if (surface->generation != generation || + !generationValid(source, generation) || + !clipSwSurfaceRect(surface, + &x, &y, &width, &height, &sourceX, &sourceY)) { - DEBUG_ERROR("Failed to allocate %zu bytes for software surface bitmap", - size); + LG_UNLOCK(surface->lock); return; } - uint8_t * copy = (uint8_t *)(cmd + 1); - if ((size_t)stride == rowSize) - memcpy(copy, data, size); - else - for(int row = 0; row < height; ++row) - memcpy(copy + (size_t)row * rowSize, - (const uint8_t *)data + (size_t)row * stride, rowSize); + const size_t copySize = (size_t)width * 4; + uint8_t * dst = surface->data + + (size_t)y * surface->pitch + (size_t)x * 4; + for (int dy = 0; dy < height; ++dy) + { + const int sourceRow = topDown ? + sourceY + dy : sourceHeight - sourceY - dy - 1; + const uint8_t * src = (const uint8_t *)data + + (size_t)sourceRow * stride + (size_t)sourceX * 4; + memcpy(dst, src, copySize); + dst += surface->pitch; + } - setCommandSource(cmd, source, generation); - cmd->op = SW_SURFACE_OP_DRAW_BITMAP; - cmd->swSurfaceDrawBitmap.x = x; - cmd->swSurfaceDrawBitmap.y = y; - cmd->swSurfaceDrawBitmap.width = width; - cmd->swSurfaceDrawBitmap.height = height; - cmd->swSurfaceDrawBitmap.stride = (int)rowSize; - cmd->swSurfaceDrawBitmap.data = copy; - cmd->swSurfaceDrawBitmap.topDown = topDown; - enqueueCommand(cmd, RENDER_QUEUE_INVALIDATE_PARTIAL); + const FrameDamageRect damage = + { + .x = (uint32_t)x, + .y = (uint32_t)y, + .width = (uint32_t)width, + .height = (uint32_t)height, + }; + swSurfaceDamageAdd(surface, &damage); + const bool wake = queueSwSurfaceUpdate(source, surface); + LG_UNLOCK(surface->lock); + wakeQueue(RENDER_QUEUE_INVALIDATE_PARTIAL, wake); } void renderQueue_sourceSurfaceFormat(RenderQueueSource source, @@ -756,6 +1095,135 @@ void renderQueue_sourceCursorWhiteLevel(RenderQueueSource source, enqueueCommand(cmd, RENDER_QUEUE_INVALIDATE_NONE); } +static bool swSurfaceCommandMatches(const RenderQueueSwSurface * surface, + const RenderCommand * cmd, uint64_t epoch) +{ + return surface->data && surface->generation == cmd->generation && + surface->epoch == epoch; +} + +static void uploadSwSurfaceFull(RenderQueueSource source, + RenderQueueSwSurface * surface) +{ + RENDERER(swSurfaceConfigure, surface->width, surface->height); + RENDERER(swSurfaceDrawBitmap, + 0, 0, surface->width, surface->height, + surface->pitch, surface->data, true); + swSurfaceDamageReset(surface); + l_swSurfaceResidentSource = source; + l_swSurfaceResidentGeneration = surface->generation; + l_swSurfaceResidentEpoch = surface->epoch; +} + +static bool swSurfaceResident(RenderQueueSource source, + const RenderQueueSwSurface * surface) +{ + return l_swSurfaceResidentSource == source && + l_swSurfaceResidentGeneration == surface->generation && + l_swSurfaceResidentEpoch == surface->epoch; +} + +static void uploadSwSurfaceDamage(RenderQueueSource source, + RenderQueueSwSurface * surface) +{ + if (!swSurfaceResident(source, surface)) + { + uploadSwSurfaceFull(source, surface); + return; + } + + if (surface->damageFull) + RENDERER(swSurfaceDrawBitmap, + 0, 0, surface->width, surface->height, + surface->pitch, surface->data, true); + else + for (int i = 0; i < surface->damageCount; ++i) + { + const FrameDamageRect * damage = &surface->damage[i]; + uint8_t * data = surface->data + + (size_t)damage->y * surface->pitch + (size_t)damage->x * 4; + RENDERER(swSurfaceDrawBitmap, + damage->x, damage->y, damage->width, damage->height, + surface->pitch, data, true); + } + + swSurfaceDamageReset(surface); +} + +static bool swSurfaceTransitionMatches( + const RenderQueueSwSurface * surface, const RenderCommand * cmd, + bool configure) +{ + const uint64_t epoch = configure ? + cmd->swSurfaceConfigureTransition.epoch : surface->epoch; + return swSurfaceCommandMatches(surface, cmd, epoch) && + (!configure || + (surface->width == cmd->swSurfaceConfigureTransition.width && + surface->height == cmd->swSurfaceConfigureTransition.height)); +} + +static bool prepareSwSurfaceTransition(const RenderCommand * cmd, + bool configure) +{ + RenderQueueSwSurface * surface = &l_swSurface[cmd->source]; + LG_LOCK(surface->lock); + + const bool valid = swSurfaceTransitionMatches(surface, cmd, configure); + if (valid) + { + if (configure || !swSurfaceResident(cmd->source, surface)) + uploadSwSurfaceFull(cmd->source, surface); + else + uploadSwSurfaceDamage(cmd->source, surface); + } + + LG_UNLOCK(surface->lock); + return valid; +} + +static bool swSurfaceTransitionReady(const RenderCommand * cmd, + bool configure) +{ + RenderQueueSwSurface * surface = &l_swSurface[cmd->source]; + LG_LOCK(surface->lock); + const bool ready = swSurfaceTransitionMatches(surface, cmd, configure); + LG_UNLOCK(surface->lock); + return ready; +} + +/* Called with l_sourceLock held. The marker may be recycled into the live + * queue after an older generation was detached from it. */ +static bool processSwSurfaceUpdate(RenderCommand * cmd, bool commandValid) +{ + RenderQueueSwSurface * surface = &l_swSurface[cmd->source]; + LG_LOCK(surface->lock); + + if (!surface->updateQueued) + { + LG_UNLOCK(surface->lock); + return false; + } + + surface->updateQueued = false; + const bool matches = commandValid && + swSurfaceCommandMatches(surface, cmd, cmd->swSurfaceUpdate.epoch); + const bool active = l_appliedSwSurface && + l_appliedSource == cmd->source && + l_appliedGeneration == cmd->generation; + if (matches && active) + uploadSwSurfaceDamage(cmd->source, surface); + + bool wake = false; + if (swSurfaceDamagePending(surface) && + l_appliedSwSurface && l_appliedSource == cmd->source && + l_appliedGeneration == surface->generation && + generationValid(cmd->source, surface->generation)) + wake = queueSwSurfaceUpdate(cmd->source, surface); + + LG_UNLOCK(surface->lock); + return wake; +} + static void applyCursor(RenderQueueSource source, uint64_t generation) { RenderQueueCursor * cursor = &l_cursor[source]; @@ -828,6 +1296,13 @@ static bool prepareSourceTransition(const RenderCommand * cmd) cmd->source, cmd->generation, cmd->transitionSerial); } +static void rejectSourceTransition(const RenderCommand * cmd) +{ + if (l_sourceRejectFn) + l_sourceRejectFn(l_sourceCallbackOpaque, + cmd->source, cmd->transitionSerial); +} + void renderQueue_process(void) { RenderCommand * cmd = detachCommands(); @@ -835,16 +1310,21 @@ void renderQueue_process(void) { RenderCommand * next = cmd->next; const bool transition = transitionCommand(cmd); + bool wake = false; if (transition) LG_LOCK(l_transitionLock); LG_LOCK(l_sourceLock); - if (!commandValid(cmd)) + const bool validCommand = commandValid(cmd); + if (!validCommand) { + if (cmd->op == SW_SURFACE_OP_UPDATE) + wake = processSwSurfaceUpdate(cmd, false); LG_UNLOCK(l_sourceLock); if (transition) LG_UNLOCK(l_transitionLock); freeCommand(cmd); + wakeQueue(RENDER_QUEUE_INVALIDATE_PARTIAL, wake); cmd = next; continue; } @@ -852,30 +1332,24 @@ void renderQueue_process(void) switch(cmd->op) { case SW_SURFACE_OP_CONFIGURE_TRANSITION: + if (!swSurfaceTransitionReady(cmd, true)) + { + DEBUG_ERROR("Software surface configuration is unavailable"); + rejectSourceTransition(cmd); + break; + } if (!prepareSourceTransition(cmd)) break; - RENDERER(swSurfaceConfigure, - cmd->swSurfaceConfigureTransition.width, - cmd->swSurfaceConfigureTransition.height); - RENDERER(swSurfaceDrawFill, 0, 0, - cmd->swSurfaceConfigureTransition.width, - cmd->swSurfaceConfigureTransition.height, 0); + if (!prepareSwSurfaceTransition(cmd, true)) + { + rejectSourceTransition(cmd); + break; + } applySourceTransition(cmd, true); break; - case SW_SURFACE_OP_DRAW_FILL: - RENDERER(swSurfaceDrawFill, - cmd->swSurfaceDrawFill.x , cmd->swSurfaceDrawFill.y, - cmd->swSurfaceDrawFill.width, cmd->swSurfaceDrawFill.height, - cmd->swSurfaceDrawFill.color); - break; - - case SW_SURFACE_OP_DRAW_BITMAP: - RENDERER(swSurfaceDrawBitmap, - cmd->swSurfaceDrawBitmap.x , cmd->swSurfaceDrawBitmap.y, - cmd->swSurfaceDrawBitmap.width , cmd->swSurfaceDrawBitmap.height, - cmd->swSurfaceDrawBitmap.stride, cmd->swSurfaceDrawBitmap.data, - cmd->swSurfaceDrawBitmap.topDown); + case SW_SURFACE_OP_UPDATE: + wake = processSwSurfaceUpdate(cmd, true); break; case SURFACE_OP_FORMAT: @@ -978,17 +1452,32 @@ void renderQueue_process(void) } case SOURCE_OP_TRANSITION: + { + const bool swSurface = + cmd->source != RENDER_QUEUE_SOURCE_NONE && + cmd->sourceTransition.swSurface; + if (swSurface && !swSurfaceTransitionReady(cmd, false)) + { + DEBUG_ERROR("Software surface source is unavailable"); + rejectSourceTransition(cmd); + break; + } if (!prepareSourceTransition(cmd)) break; - applySourceTransition(cmd, - cmd->source != RENDER_QUEUE_SOURCE_NONE && - cmd->sourceTransition.swSurface); + if (swSurface && !prepareSwSurfaceTransition(cmd, false)) + { + rejectSourceTransition(cmd); + break; + } + applySourceTransition(cmd, swSurface); break; + } } LG_UNLOCK(l_sourceLock); if (transition) LG_UNLOCK(l_transitionLock); freeCommand(cmd); + wakeQueue(RENDER_QUEUE_INVALIDATE_PARTIAL, wake); cmd = next; } } diff --git a/client/src/render_queue.h b/client/src/render_queue.h index 9ee9d7a2..12d8a6c3 100644 --- a/client/src/render_queue.h +++ b/client/src/render_queue.h @@ -36,6 +36,8 @@ typedef void (*RenderQueueSourceAppliedFn)(void * opaque, bool swSurface); typedef bool (*RenderQueueSourcePrepareFn)(void * opaque, RenderQueueSource source, uint64_t generation, uint64_t serial); +typedef void (*RenderQueueSourceRejectFn)(void * opaque, + RenderQueueSource source, uint64_t serial); void renderQueue_init(void); void renderQueue_free(void); @@ -44,6 +46,7 @@ void renderQueue_process(void); void renderQueue_presented(void); void renderQueue_setSourceFns(RenderQueueSourcePrepareFn prepare, + RenderQueueSourceRejectFn reject, RenderQueueSourceAppliedFn applied, void * opaque); /* Starting a source lifecycle makes commands from its prior generation stale. @@ -57,6 +60,9 @@ uint64_t renderQueue_sourceTransition(RenderQueueSource source, uint64_t generation, bool swSurface, atomic_uint_least64_t * publishedSerial); +bool renderQueue_sourceSwSurfaceConfigure(RenderQueueSource source, + uint64_t generation, int width, int height); + uint64_t renderQueue_sourceSwSurfaceConfigureTransition( RenderQueueSource source, uint64_t generation, int width, int height, atomic_uint_least64_t * publishedSerial); diff --git a/client/tests/render_queue_test.c b/client/tests/render_queue_test.c index fc8f1d93..3e554efd 100644 --- a/client/tests/render_queue_test.c +++ b/client/tests/render_queue_test.c @@ -243,12 +243,12 @@ static void start(void) g_state.ds = &f.ds; renderQueue_init(); - renderQueue_setSourceFns(prep, applied, &f); + renderQueue_setSourceFns(prep, NULL, applied, &f); } static void stop(void) { - renderQueue_setSourceFns(NULL, NULL, NULL); + renderQueue_setSourceFns(NULL, NULL, NULL, NULL); renderQueue_free(); } @@ -296,6 +296,8 @@ static void testLatest(void) renderQueue_sourceBegin(RENDER_QUEUE_SOURCE_PRIMARY); const uint64_t fallback = renderQueue_sourceBegin(RENDER_QUEUE_SOURCE_FALLBACK); + CHECK(renderQueue_sourceSwSurfaceConfigure( + RENDER_QUEUE_SOURCE_FALLBACK, fallback, 2, 1)); atomic_uint_least64_t published; atomic_init(&published, 0); @@ -332,9 +334,11 @@ static void testPrepare(void) const uint64_t generation = renderQueue_sourceBegin(RENDER_QUEUE_SOURCE_PRIMARY); + CHECK(renderQueue_sourceSwSurfaceConfigure( + RENDER_QUEUE_SOURCE_PRIMARY, generation, 4, 4)); f.prepResult = false; CHECK(renderQueue_sourceSwSurfaceConfigureTransition( - RENDER_QUEUE_SOURCE_PRIMARY, generation, 640, 480, NULL) != 0); + RENDER_QUEUE_SOURCE_PRIMARY, generation, 4, 4, NULL) != 0); renderQueue_process(); renderQueue_presented(); @@ -345,19 +349,23 @@ static void testPrepare(void) CHECK(f.appliedCount == 0); f.prepResult = true; + CHECK(renderQueue_sourceSwSurfaceConfigure( + RENDER_QUEUE_SOURCE_PRIMARY, generation, 3, 2)); const uint64_t serial = renderQueue_sourceSwSurfaceConfigureTransition( - RENDER_QUEUE_SOURCE_PRIMARY, generation, 320, 200, NULL); + RENDER_QUEUE_SOURCE_PRIMARY, generation, 3, 2, NULL); CHECK(serial != 0); renderQueue_process(); CHECK(f.configCount == 1); - CHECK(f.configWidth == 320); - CHECK(f.configHeight == 200); - CHECK(f.fillCount == 1); - CHECK(f.fillX == 0); - CHECK(f.fillY == 0); - CHECK(f.fillWidth == 320); - CHECK(f.fillHeight == 200); - CHECK(f.fillColor == 0); + CHECK(f.configWidth == 3); + CHECK(f.configHeight == 2); + CHECK(f.fillCount == 0); + CHECK(f.bitmapCount == 1); + CHECK(f.bitmapX == 0); + CHECK(f.bitmapY == 0); + CHECK(f.bitmapWidth == 3); + CHECK(f.bitmapHeight == 2); + CHECK(f.bitmapStride == 12); + CHECK(f.bitmapTopDown); CHECK(f.showCount == 1); CHECK(f.show); @@ -406,6 +414,8 @@ static void testPayload(void) const uint64_t generation = renderQueue_sourceBegin(RENDER_QUEUE_SOURCE_PRIMARY); + CHECK(renderQueue_sourceSwSurfaceConfigure( + RENDER_QUEUE_SOURCE_PRIMARY, generation, 2, 2)); uint8_t bitmap[] = { 1, 2, 3, 4, 5, 6, 7, 8, @@ -427,7 +437,7 @@ static void testPayload(void) }; renderQueue_sourceSwSurfaceDrawBitmap(RENDER_QUEUE_SOURCE_PRIMARY, - generation, 5, 6, 2, 2, 8, bitmap, true); + generation, 0, 0, 2, 2, 8, bitmap, true); renderQueue_sourceCursorImage(RENDER_QUEUE_SOURCE_PRIMARY, generation, LG_CURSOR_COLOR, 2, 2, 4, shape); renderQueue_sourceCursorColorTransform(RENDER_QUEUE_SOURCE_PRIMARY, @@ -435,7 +445,7 @@ static void testPayload(void) renderQueue_sourceCursorState(RENDER_QUEUE_SOURCE_PRIMARY, generation, true, 20, 21, 1, 2); CHECK(renderQueue_sourceTransition(RENDER_QUEUE_SOURCE_PRIMARY, - generation, false, NULL) != 0); + generation, true, NULL) != 0); memset(bitmap, 0, sizeof(bitmap)); memset(shape, 0, sizeof(shape)); @@ -446,8 +456,8 @@ static void testPayload(void) renderQueue_process(); CHECK(f.bitmapCount == 1); - CHECK(f.bitmapX == 5); - CHECK(f.bitmapY == 6); + CHECK(f.bitmapX == 0); + CHECK(f.bitmapY == 0); CHECK(f.bitmapWidth == 2); CHECK(f.bitmapHeight == 2); CHECK(f.bitmapStride == 8);