From c4285dcb6e8e2a7cfd3b84609e67faea2b14d713 Mon Sep 17 00:00:00 2001 From: Geoffrey McRae Date: Fri, 17 Jul 2026 23:00:08 +1000 Subject: [PATCH] [client] egl: cache post-process configuration Build the active filter chain only when its configuration changes, including filter settings or order, input format, dimensions, output size, and DMA mode. Unchanged frames now execute only the cached active filters, avoiding repeated setup, preparation, and output-resolution queries. Reconfiguration forces a full-frame pass to prevent stale intermediate framebuffer contents. Also refresh CAS and FSR uniforms when loading presets and move downscale pass selection out of the per-frame path. --- client/renderers/EGL/filter.h | 6 +- client/renderers/EGL/filter_downscale.c | 32 ++-- client/renderers/EGL/filter_ffx_cas.c | 5 + client/renderers/EGL/filter_ffx_fsr1.c | 3 +- client/renderers/EGL/postprocess.c | 227 ++++++++++++++++++------ client/renderers/EGL/postprocess.h | 3 + 6 files changed, 199 insertions(+), 77 deletions(-) diff --git a/client/renderers/EGL/filter.h b/client/renderers/EGL/filter.h index a505c137..fe6764b1 100644 --- a/client/renderers/EGL/filter.h +++ b/client/renderers/EGL/filter.h @@ -68,14 +68,14 @@ typedef struct EGL_FilterOps /* reads filter state from options */ void (*loadState)(EGL_Filter * filter); - /* set the input format of the filter + /* configure the input format of the filter when the chain changes * useDMA will be true if the texture provided needs to use samplerExternalOES */ bool (*setup)(EGL_Filter * filter, enum EGL_PixelFormat pixFmt, unsigned int width, unsigned int height, unsigned int desktopWidth, unsigned int desktopHeight, bool useDMA); - /* set the output resolution hint for the filter + /* set the output resolution hint while configuring the chain * this is optional and only a hint */ void (*setOutputResHint)(EGL_Filter * filter, unsigned int x, unsigned int y); @@ -84,7 +84,7 @@ typedef struct EGL_FilterOps void (*getOutputRes)(EGL_Filter * filter, unsigned int *x, unsigned int *y, enum EGL_PixelFormat *pixFmt); - /* prepare the shader for use + /* finalize the filter while configuring the chain * A filter can return false to bypass it */ bool (*prepare)(EGL_Filter * filter); diff --git a/client/renderers/EGL/filter_downscale.c b/client/renderers/EGL/filter_downscale.c index 89e7ec93..4a5dd58b 100644 --- a/client/renderers/EGL/filter_downscale.c +++ b/client/renderers/EGL/filter_downscale.c @@ -379,32 +379,13 @@ static bool egl_filterDownscalePrepare(EGL_Filter * filter) if (this->prepared) return true; + EGL_Shader * shader; + GLenum sampling; switch (this->filter) { case DOWNSCALE_NEAREST: egl_uniform3f(this->uNearest, this->pixelSize, this->vOffset, this->hOffset); - break; - - default: - break; - } - this->prepared = true; - - return true; -} - -static EGL_Texture * egl_filterDownscaleRun(EGL_Filter * filter, - EGL_FilterRects * rects, EGL_Texture * texture) -{ - EGL_FilterDownscale * this = UPCAST(EGL_FilterDownscale, filter); - - EGL_Shader * shader; - GLenum sampling; - - switch (this->filter) - { - case DOWNSCALE_NEAREST: shader = this->nearest; sampling = GL_NEAREST; break; @@ -425,6 +406,15 @@ static EGL_Texture * egl_filterDownscaleRun(EGL_Filter * filter, egl_effectPassSetShader(this->pass, shader); egl_effectPassSetFilter(this->pass, sampling, sampling); + this->prepared = true; + + return true; +} + +static EGL_Texture * egl_filterDownscaleRun(EGL_Filter * filter, + EGL_FilterRects * rects, EGL_Texture * texture) +{ + EGL_FilterDownscale * this = UPCAST(EGL_FilterDownscale, filter); return egl_effectRun(this->effect, rects, texture); } diff --git a/client/renderers/EGL/filter_ffx_cas.c b/client/renderers/EGL/filter_ffx_cas.c index 08639b87..0af01e74 100644 --- a/client/renderers/EGL/filter_ffx_cas.c +++ b/client/renderers/EGL/filter_ffx_cas.c @@ -76,6 +76,9 @@ static void egl_filterFFXCASEarlyInit(void) static void casUpdateConsts(EGL_FilterFFXCAS * this) { + if (!this->width || !this->height) + return; + ffxCasConst((uint32_t *)this->consts, this->sharpness, this->width, this->height, this->width, this->height); @@ -96,6 +99,8 @@ static void egl_filterFFXCASLoadState(EGL_Filter * filter) this->enable = option_get_bool ("eglFilter", "ffxCAS"); this->sharpness = option_get_float("eglFilter", "ffxCASSharpness"); + casUpdateConsts(this); + this->prepared = false; } static bool egl_filterFFXCASInit(EGL_Filter ** filter) diff --git a/client/renderers/EGL/filter_ffx_fsr1.c b/client/renderers/EGL/filter_ffx_fsr1.c index dab3165e..d0d2a8bf 100644 --- a/client/renderers/EGL/filter_ffx_fsr1.c +++ b/client/renderers/EGL/filter_ffx_fsr1.c @@ -101,6 +101,8 @@ static void egl_filterFFXFSR1LoadState(EGL_Filter * filter) this->enable = option_get_bool ("eglFilter", "ffxFSR"); this->sharpness = option_get_float("eglFilter", "ffxFSRSharpness"); + rcasUpdateUniform(this); + this->prepared = false; } static bool egl_filterFFXFSR1Init(EGL_Filter ** filter) @@ -159,7 +161,6 @@ static bool egl_filterFFXFSR1Init(EGL_Filter ** filter) this->uRcasConsts = egl_shaderGetUniform(this->rcas, "uConsts"); egl_filterFFXFSR1LoadState(&this->base); - rcasUpdateUniform(this); *filter = &this->base; return true; diff --git a/client/renderers/EGL/postprocess.c b/client/renderers/EGL/postprocess.c index c45ce82f..a3bdd442 100644 --- a/client/renderers/EGL/postprocess.c +++ b/client/renderers/EGL/postprocess.c @@ -48,12 +48,25 @@ static const EGL_FilterOps * EGL_Filters[] = struct EGL_PostProcess { - Vector filters, internalFilters; + Vector filters, internalFilters, activeFilters; EGL_Texture * output; unsigned int outputX, outputY; _Atomic(bool) modified; + struct + { + bool valid; + EGL_PixelFormat pixFmt; + unsigned int inputX, inputY; + int desktopWidth, desktopHeight; + unsigned int targetX, targetY; + bool useDMA; + unsigned int outputX, outputY; + } + config; + EGL_DesktopRects * rects; + GLfloat matrix[6]; StringList presets; char * presetDir; @@ -256,6 +269,13 @@ static void reorderFilters(struct EGL_PostProcess * this) return; } + if (!*orderStr) + { + stringlist_free(&order); + free(orderStr); + return; + } + char * p = orderStr; while (*p) { @@ -515,7 +535,7 @@ static void configUI(void * opaque, int * id) igSetTooltip(filters[moveIdx]->ops.name); } - if (doMove) + if (doMove && mouseIdx != moveIdx) { EGL_Filter * tmp = filters[moveIdx]; if (mouseIdx > moveIdx) // moving down @@ -530,11 +550,12 @@ static void configUI(void * opaque, int * id) (moveIdx - mouseIdx) * sizeof(EGL_Filter *)); filters[mouseIdx] = tmp; + redraw = true; } if (redraw) { - atomic_store(&this->modified, true); + egl_postProcessInvalidate(this); app_invalidateWindow(true); } } @@ -547,6 +568,7 @@ bool egl_postProcessInit(EGL_PostProcess ** pp) DEBUG_ERROR("Failed to allocate memory"); return false; } + atomic_init(&this->modified, false); if (!vector_create(&this->filters, sizeof(EGL_Filter *), ARRAY_LENGTH(EGL_Filters))) @@ -562,10 +584,17 @@ bool egl_postProcessInit(EGL_PostProcess ** pp) goto error_filters; } + if (!vector_create(&this->activeFilters, + sizeof(EGL_Filter *), ARRAY_LENGTH(EGL_Filters) + 1)) + { + DEBUG_ERROR("Failed to allocate the active filter list"); + goto error_internal; + } + if (!egl_desktopRectsInit(&this->rects, 1)) { DEBUG_ERROR("Failed to initialize the desktop rects"); - goto error_internal; + goto error_active; } loadPresetList(this); @@ -575,6 +604,9 @@ bool egl_postProcessInit(EGL_PostProcess ** pp) *pp = this; return true; +error_active: + vector_destroy(&this->activeFilters); + error_internal: vector_destroy(&this->internalFilters); @@ -593,6 +625,8 @@ void egl_postProcessFree(EGL_PostProcess ** pp) EGL_PostProcess * this = *pp; + vector_destroy(&this->activeFilters); + EGL_Filter ** filter; vector_forEachRef(filter, &this->filters) egl_filterFree(filter); @@ -618,55 +652,62 @@ bool egl_postProcessAdd(EGL_PostProcess * this, const EGL_FilterOps * ops) if (!egl_filterInit(ops, &filter)) return false; - if (ops->type == EGL_FILTER_TYPE_INTERNAL) - vector_push(&this->internalFilters, &filter); - else - vector_push(&this->filters, &filter); + Vector * filters = ops->type == EGL_FILTER_TYPE_INTERNAL ? + &this->internalFilters : &this->filters; + if (!vector_push(filters, &filter)) + { + egl_filterFree(&filter); + return false; + } + + if (ops->type != EGL_FILTER_TYPE_INTERNAL) + reorderFilters(this); + + egl_postProcessInvalidate(this); return true; } +void egl_postProcessInvalidate(EGL_PostProcess * this) +{ + atomic_store_explicit(&this->modified, true, memory_order_release); +} + bool egl_postProcessConfigModified(EGL_PostProcess * this) { - return atomic_load(&this->modified); + return atomic_load_explicit(&this->modified, memory_order_acquire); } -bool egl_postProcessRun(EGL_PostProcess * this, EGL_Texture * tex, - EGL_DesktopRects * rects, int desktopWidth, int desktopHeight, +static bool configMatches(EGL_PostProcess * this, EGL_PixelFormat pixFmt, + unsigned int inputX, unsigned int inputY, + int desktopWidth, int desktopHeight, unsigned int targetX, unsigned int targetY, bool useDMA) { - if (targetX == 0 && targetY == 0) - DEBUG_FATAL("targetX || targetY == 0"); + return + this->config.valid && + this->config.pixFmt == pixFmt && + this->config.inputX == inputX && + this->config.inputY == inputY && + this->config.desktopWidth == desktopWidth && + this->config.desktopHeight == desktopHeight && + this->config.targetX == targetX && + this->config.targetY == targetY && + this->config.useDMA == useDMA; +} - EGL_Filter * lastFilter = NULL; - unsigned int sizeX, sizeY; +static bool configureChain(EGL_PostProcess * this, EGL_PixelFormat pixFmt, + unsigned int inputX, unsigned int inputY, + int desktopWidth, int desktopHeight, + unsigned int targetX, unsigned int targetY, bool useDMA) +{ + this->config.valid = false; + vector_clear(&this->activeFilters); - //TODO: clean this up - GLuint _unused; - EGL_PixelFormat pixFmt; - if (egl_textureGet(tex, &_unused, - &sizeX, &sizeY, &pixFmt) != EGL_TEX_STATUS_OK) - return false; - - if (atomic_exchange(&this->modified, false)) - { - rects = this->rects; - egl_desktopRectsUpdate(rects, NULL, desktopWidth, desktopHeight); - } - - GLfloat matrix[6]; - egl_desktopRectsMatrix(matrix, desktopWidth, desktopHeight, 0.0f, 0.0f, - 1.0f, 1.0f, LG_ROTATE_0); - - EGL_FilterRects filterRects = { - .rects = rects, - .matrix = matrix, - .width = desktopWidth, - .height = desktopHeight, - }; + unsigned int outputX = inputX; + unsigned int outputY = inputY; + EGL_PixelFormat outputFormat = pixFmt; + bool inputDMA = useDMA; EGL_Filter * filter; - EGL_Texture * texture = tex; - const Vector * lists[] = { &this->internalFilters, @@ -679,26 +720,108 @@ bool egl_postProcessRun(EGL_PostProcess * this, EGL_Texture * tex, { egl_filterSetOutputResHint(filter, targetX, targetY); - if (!egl_filterSetup(filter, pixFmt, sizeX, sizeY, - desktopWidth, desktopHeight, useDMA) || + if (!egl_filterSetup(filter, outputFormat, outputX, outputY, + desktopWidth, desktopHeight, inputDMA) || !egl_filterPrepare(filter)) continue; - texture = egl_filterRun(filter, &filterRects, texture); - egl_filterGetOutputRes(filter, &sizeX, &sizeY, &pixFmt); + if (!vector_push(&this->activeFilters, &filter)) + { + vector_clear(&this->activeFilters); + return false; + } - if (lastFilter) - egl_filterRelease(lastFilter); + egl_filterGetOutputRes(filter, &outputX, &outputY, &outputFormat); - lastFilter = filter; - - // the first filter to run will convert to a normal texture - useDMA = false; + /* The first active filter converts external DMA textures into a normal + * texture for every subsequent filter. */ + inputDMA = false; } + egl_desktopRectsMatrix(this->matrix, + desktopWidth, desktopHeight, 0.0f, 0.0f, 1.0f, 1.0f, LG_ROTATE_0); + + this->config.pixFmt = pixFmt; + this->config.inputX = inputX; + this->config.inputY = inputY; + this->config.desktopWidth = desktopWidth; + this->config.desktopHeight = desktopHeight; + this->config.targetX = targetX; + this->config.targetY = targetY; + this->config.useDMA = useDMA; + this->config.outputX = outputX; + this->config.outputY = outputY; + this->config.valid = true; + return true; +} + +bool egl_postProcessRun(EGL_PostProcess * this, EGL_Texture * tex, + EGL_DesktopRects * rects, int desktopWidth, int desktopHeight, + unsigned int targetX, unsigned int targetY, bool useDMA) +{ + if (targetX == 0 && targetY == 0) + DEBUG_FATAL("targetX || targetY == 0"); + + unsigned int inputX, inputY; + GLuint _unused; + EGL_PixelFormat pixFmt; + if (egl_textureGet(tex, &_unused, + &inputX, &inputY, &pixFmt) != EGL_TEX_STATUS_OK) + return false; + + const bool modified = + atomic_load_explicit(&this->modified, memory_order_acquire); + const bool reconfigure = modified || + !configMatches(this, pixFmt, inputX, inputY, + desktopWidth, desktopHeight, targetX, targetY, useDMA); + + if (reconfigure) + { + if (modified) + atomic_exchange_explicit( + &this->modified, false, memory_order_acq_rel); + + if (!configureChain(this, pixFmt, inputX, inputY, + desktopWidth, desktopHeight, targetX, targetY, useDMA)) + { + egl_postProcessInvalidate(this); + return false; + } + + rects = this->rects; + egl_desktopRectsUpdate(rects, NULL, desktopWidth, desktopHeight); + } + + EGL_FilterRects filterRects = { + .rects = rects, + .matrix = this->matrix, + .width = desktopWidth, + .height = desktopHeight, + }; + + EGL_Texture * texture = tex; + EGL_Filter * filter; + EGL_Filter * lastFilter = NULL; + vector_forEach(filter, &this->activeFilters) + { + texture = egl_filterRun(filter, &filterRects, texture); + if (!texture) + { + DEBUG_ERROR("EGL filter '%s' failed", filter->ops.id); + this->output = NULL; + egl_postProcessInvalidate(this); + return false; + } + + if (lastFilter) + egl_filterRelease(lastFilter); + + lastFilter = filter; + } + this->output = texture; - this->outputX = sizeX; - this->outputY = sizeY; + this->outputX = this->config.outputX; + this->outputY = this->config.outputY; return true; } diff --git a/client/renderers/EGL/postprocess.h b/client/renderers/EGL/postprocess.h index ed12c1e4..4a3cef29 100644 --- a/client/renderers/EGL/postprocess.h +++ b/client/renderers/EGL/postprocess.h @@ -34,6 +34,9 @@ void egl_postProcessFree(EGL_PostProcess ** pp); /* create and add a filter to this processor */ bool egl_postProcessAdd(EGL_PostProcess * this, const EGL_FilterOps * ops); +/* mark the filter chain for rebuilding before its next run */ +void egl_postProcessInvalidate(EGL_PostProcess * this); + /* returns true if the configuration was modified since the last run */ bool egl_postProcessConfigModified(EGL_PostProcess * this);