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