From ded5b1d29bbc6778170c5cf8482783b702f9380e Mon Sep 17 00:00:00 2001 From: Leon Styhre Date: Wed, 17 Mar 2021 20:29:43 +0100 Subject: [PATCH] Greatly improved the shader post processing code and fixed some related bugs. --- es-core/src/Window.cpp | 60 ++++--- es-core/src/renderers/Renderer.h | 4 +- es-core/src/renderers/Renderer_GL21.cpp | 215 +++++++++++++----------- 3 files changed, 159 insertions(+), 120 deletions(-) diff --git a/es-core/src/Window.cpp b/es-core/src/Window.cpp index 88cc4e210..c7cfb9da5 100644 --- a/es-core/src/Window.cpp +++ b/es-core/src/Window.cpp @@ -323,30 +323,39 @@ void Window::render() // Generate a cache texture of the shaded background when opening the menu, which // will remain valid until the menu is closed. This is way faster than having to // render the shaders for every frame. +// const auto backgroundStartTime = std::chrono::system_clock::now(); + std::shared_ptr mPostprocessedBackground; mPostprocessedBackground = TextureResource::get(""); unsigned char* processedTexture = new unsigned char[Renderer::getScreenWidth() * Renderer::getScreenHeight() * 4]; - // Defocus the background using three passes of gaussian blur. - Renderer::shaderParameters blurParameters; - blurParameters.shaderPasses = 3; + // Defocus the background using multiple passes of gaussian blur, with the number + // of iterations relative to the screen resolution. + Renderer::shaderParameters backgroundParameters; + float heightModifier = Renderer::getScreenHeightModifier(); + + if (heightModifier < 1) + backgroundParameters.blurPasses = 2; // Below 1080 + else if (heightModifier >= 4) + backgroundParameters.blurPasses = 12; // 8K + else if (heightModifier >= 2.9) + backgroundParameters.blurPasses = 10; // 6K + else if (heightModifier >= 2.6) + backgroundParameters.blurPasses = 8; // 5K + else if (heightModifier >= 2) + backgroundParameters.blurPasses = 5; // 4K + else if (heightModifier >= 1.3) + backgroundParameters.blurPasses = 3; // 1440 + else if (heightModifier >= 1) + backgroundParameters.blurPasses = 2; // 1080 + + // Also dim the background slightly. + backgroundParameters.fragmentDimValue = 0.60f; + Renderer::shaderPostprocessing(Renderer::SHADER_BLUR_HORIZONTAL | - Renderer::SHADER_BLUR_VERTICAL, blurParameters, processedTexture); - - mPostprocessedBackground->initFromPixels(processedTexture, - Renderer::getScreenWidth(), Renderer::getScreenHeight()); - - mBackgroundOverlay->setImage(mPostprocessedBackground); - mBackgroundOverlay->render(transform); - - // Dim the background. We need to do this as a separate step as combining - // it with the blurring leads to very strange and severe artifacts. - // This is for sure a bug that needs to be resolved at some later date. - Renderer::shaderParameters blackParameters; - blackParameters.fragmentDimValue = 0.6f; - Renderer::shaderPostprocessing(Renderer::SHADER_DIM, - blackParameters, processedTexture); + Renderer::SHADER_BLUR_VERTICAL | Renderer::SHADER_DIM, + backgroundParameters, processedTexture); mPostprocessedBackground->initFromPixels(processedTexture, Renderer::getScreenWidth(), Renderer::getScreenHeight()); @@ -365,11 +374,18 @@ void Window::render() delete[] processedTexture; mCachedBackground = true; + +// const auto backgroundEndTime = std::chrono::system_clock::now(); +// LOG(LogDebug) << "Window::render(): Time to create cached background: " << +// std::chrono::duration_cast +// (backgroundEndTime - backgroundStartTime).count() << " ms"; + } + // Fade in the cached background, unless the menu is set to open without any animation. + if (Settings::getInstance()->getString("MenuOpeningEffect") != "none") { + mBackgroundOverlay->setOpacity(mBackgroundOverlayOpacity); + if (mBackgroundOverlayOpacity < 255) + mBackgroundOverlayOpacity = Math::clamp(mBackgroundOverlayOpacity + 30, 0, 255); } - // Fade in the cached background. - mBackgroundOverlay->setOpacity(mBackgroundOverlayOpacity); - if (mBackgroundOverlayOpacity < 255) - mBackgroundOverlayOpacity = Math::clamp(mBackgroundOverlayOpacity + 30, 0, 255); #endif mBackgroundOverlay->render(transform); diff --git a/es-core/src/renderers/Renderer.h b/es-core/src/renderers/Renderer.h index 56f7fe04b..8d5f99aa4 100644 --- a/es-core/src/renderers/Renderer.h +++ b/es-core/src/renderers/Renderer.h @@ -36,7 +36,7 @@ namespace Renderer float fragmentSaturation; float fragmentDimValue; float fragmentOpacity; - unsigned int shaderPasses; + unsigned int blurPasses; shaderParameters() : textureSize({0.0f, 0.0f}), @@ -44,7 +44,7 @@ namespace Renderer fragmentSaturation(1.0f), fragmentDimValue(0.4f), fragmentOpacity(1.0f), - shaderPasses(1) + blurPasses(1) {}; }; diff --git a/es-core/src/renderers/Renderer_GL21.cpp b/es-core/src/renderers/Renderer_GL21.cpp index ade739504..60956ad19 100644 --- a/es-core/src/renderers/Renderer_GL21.cpp +++ b/es-core/src/renderers/Renderer_GL21.cpp @@ -259,81 +259,79 @@ namespace Renderer GL_CHECK_ERROR(glDrawArrays(GL_TRIANGLE_STRIP, 0, _numVertices)); } else { - for (unsigned int i = 0; i < _parameters.shaderPasses; i++) { - // If saturation is set below the maximum (default) value, run the - // desaturation shader. - if (_vertices->saturation < 1.0 || _parameters.fragmentSaturation < 1.0) { - Shader* runShader = getShaderProgram(SHADER_DESATURATE); - // Only try to use the shader if it has been loaded properly. - if (runShader) { - runShader->activateShaders(); - runShader->setModelViewProjectionMatrix(getProjectionMatrix() * _trans); - runShader->setSaturation(_vertices->saturation); - GL_CHECK_ERROR(glDrawArrays(GL_TRIANGLE_STRIP, 0, _numVertices)); - runShader->deactivateShaders(); - } + // If saturation is set below the maximum (default) value, run the + // desaturation shader. + if (_vertices->saturation < 1.0 || _parameters.fragmentSaturation < 1.0) { + Shader* runShader = getShaderProgram(SHADER_DESATURATE); + // Only try to use the shader if it has been loaded properly. + if (runShader) { + runShader->activateShaders(); + runShader->setModelViewProjectionMatrix(getProjectionMatrix() * _trans); + runShader->setSaturation(_vertices->saturation); + GL_CHECK_ERROR(glDrawArrays(GL_TRIANGLE_STRIP, 0, _numVertices)); + runShader->deactivateShaders(); } + } - if (_vertices->shaders & SHADER_OPACITY) { - Shader* runShader = getShaderProgram(SHADER_OPACITY); - if (runShader) { - runShader->activateShaders(); - runShader->setModelViewProjectionMatrix(getProjectionMatrix() * _trans); - _vertices->opacity < 1.0 ? - runShader->setOpacity(_vertices->opacity) : - runShader->setOpacity(_parameters.fragmentOpacity); - GL_CHECK_ERROR(glDrawArrays(GL_TRIANGLE_STRIP, 0, _numVertices)); - runShader->deactivateShaders(); - } + if (_vertices->shaders & SHADER_OPACITY) { + Shader* runShader = getShaderProgram(SHADER_OPACITY); + if (runShader) { + runShader->activateShaders(); + runShader->setModelViewProjectionMatrix(getProjectionMatrix() * _trans); + _vertices->opacity < 1.0 ? + runShader->setOpacity(_vertices->opacity) : + runShader->setOpacity(_parameters.fragmentOpacity); + GL_CHECK_ERROR(glDrawArrays(GL_TRIANGLE_STRIP, 0, _numVertices)); + runShader->deactivateShaders(); } + } - // Check if any other shaders are set to be used and if so, run them. - if (_vertices->shaders & SHADER_DIM) { - Shader* runShader = getShaderProgram(SHADER_DIM); - if (runShader) { - runShader->activateShaders(); - runShader->setModelViewProjectionMatrix(getProjectionMatrix() * _trans); - runShader->setDimValue(_parameters.fragmentDimValue); - GL_CHECK_ERROR(glDrawArrays(GL_TRIANGLE_STRIP, 0, _numVertices)); - runShader->deactivateShaders(); - } + // Check if any other shaders are set to be used and if so, run them. + if (_vertices->shaders & SHADER_DIM) { + Shader* runShader = getShaderProgram(SHADER_DIM); + if (runShader) { + runShader->activateShaders(); + runShader->setModelViewProjectionMatrix(getProjectionMatrix() * _trans); + runShader->setDimValue(_parameters.fragmentDimValue); + GL_CHECK_ERROR(glDrawArrays(GL_TRIANGLE_STRIP, 0, _numVertices)); + runShader->deactivateShaders(); } + } - if (_vertices->shaders & SHADER_BLUR_HORIZONTAL) { - Shader* runShader = getShaderProgram(SHADER_BLUR_HORIZONTAL); - if (runShader) { - runShader->activateShaders(); - runShader->setModelViewProjectionMatrix(getProjectionMatrix() * _trans); - runShader->setTextureSize({width, height}); - GL_CHECK_ERROR(glDrawArrays(GL_TRIANGLE_STRIP, 0, _numVertices)); - runShader->deactivateShaders(); - } + if (_vertices->shaders & SHADER_BLUR_HORIZONTAL) { + Shader* runShader = getShaderProgram(SHADER_BLUR_HORIZONTAL); + if (runShader) { + runShader->activateShaders(); + runShader->setModelViewProjectionMatrix(getProjectionMatrix() * _trans); + runShader->setTextureSize({width, height}); + GL_CHECK_ERROR(glDrawArrays(GL_TRIANGLE_STRIP, 0, _numVertices)); + runShader->deactivateShaders(); } + } - if (_vertices->shaders & SHADER_BLUR_VERTICAL) { - Shader* runShader = getShaderProgram(SHADER_BLUR_VERTICAL); - if (runShader) { - runShader->activateShaders(); - runShader->setModelViewProjectionMatrix(getProjectionMatrix() * _trans); - runShader->setTextureSize({width, height}); - GL_CHECK_ERROR(glDrawArrays(GL_TRIANGLE_STRIP, 0, _numVertices)); - runShader->deactivateShaders(); - } + if (_vertices->shaders & SHADER_BLUR_VERTICAL) { + Shader* runShader = getShaderProgram(SHADER_BLUR_VERTICAL); + if (runShader) { + runShader->activateShaders(); + runShader->setModelViewProjectionMatrix(getProjectionMatrix() * _trans); + runShader->setTextureSize({width, height}); + GL_CHECK_ERROR(glDrawArrays(GL_TRIANGLE_STRIP, 0, _numVertices)); + runShader->deactivateShaders(); } + } - if (_vertices->shaders & SHADER_SCANLINES) { - Shader* runShader = getShaderProgram(SHADER_SCANLINES); - float shaderWidth = width * 1.2f; - // Workaround to get the scanlines to render somehow proportional to the - // resolution. A better solution is for sure needed. - float shaderHeight = height + height / (static_cast(height) >> 7) * 2.0f; - if (runShader) { - runShader->activateShaders(); - runShader->setModelViewProjectionMatrix(getProjectionMatrix() * _trans); - runShader->setTextureSize({shaderWidth, shaderHeight}); - GL_CHECK_ERROR(glDrawArrays(GL_TRIANGLE_STRIP, 0, _numVertices)); - runShader->deactivateShaders(); - } + if (_vertices->shaders & SHADER_SCANLINES) { + Shader* runShader = getShaderProgram(SHADER_SCANLINES); + float shaderWidth = width * 1.2f; + // Workaround to get the scanlines to render somehow proportional to the + // resolution. A better solution is for sure needed. + float shaderHeight = height + height / (static_cast(height) >> 7) * 2.0f; + if (runShader) { + runShader->activateShaders(); + runShader->setModelViewProjectionMatrix(getProjectionMatrix() * _trans); + runShader->setTextureSize({shaderWidth, shaderHeight}); + GL_CHECK_ERROR(glDrawArrays(GL_TRIANGLE_STRIP, 0, _numVertices)); + runShader->deactivateShaders(); } } } @@ -403,6 +401,7 @@ namespace Renderer unsigned char* textureRGBA) { Vertex vertices[4]; + std::vectorshaderList; GLuint width = getScreenWidth(); GLuint height = getScreenHeight(); float widthf = static_cast(width); @@ -415,7 +414,18 @@ namespace Renderer vertices[2] = { { widthf, 0 }, { 1, 1 }, 0 }; vertices[3] = { { widthf, heightf }, { 1, 0 }, 0}; - vertices[0].shaders = shaders; + if (shaders & Renderer::SHADER_DESATURATE) + shaderList.push_back(Renderer::SHADER_DESATURATE); + if (shaders & Renderer::SHADER_OPACITY) + shaderList.push_back(Renderer::SHADER_OPACITY); + if (shaders & Renderer::SHADER_DIM) + shaderList.push_back(Renderer::SHADER_DIM); + if (shaders & Renderer::SHADER_BLUR_HORIZONTAL) + shaderList.push_back(Renderer::SHADER_BLUR_HORIZONTAL); + if (shaders & Renderer::SHADER_BLUR_VERTICAL) + shaderList.push_back(Renderer::SHADER_BLUR_VERTICAL); + if (shaders & Renderer::SHADER_SCANLINES) + shaderList.push_back(Renderer::SHADER_SCANLINES); if (parameters.fragmentSaturation < 1.0) vertices[0].saturation = parameters.fragmentSaturation; @@ -424,40 +434,53 @@ namespace Renderer GLuint screenTexture = createTexture(Texture::RGBA, false, false, width, height, nullptr); GL_CHECK_ERROR(glBindFramebuffer(GL_READ_FRAMEBUFFER, 0)); - GL_CHECK_ERROR(glBindFramebuffer(GL_DRAW_FRAMEBUFFER, shaderFBO)); - // Attach the texture to the shader framebuffer. - GL_CHECK_ERROR(glFramebufferTexture2D( - GL_FRAMEBUFFER, - GL_COLOR_ATTACHMENT0, - GL_TEXTURE_2D, - screenTexture, - 0)); + for (int i = 0; i < shaderList.size(); i++) { + vertices[0].shaders = shaderList[i]; + int shaderPasses = 1; + // For the blur shaders there is an optional variable to set the number of passes + // to execute, which proportionally affects the blur amount. + if (shaderList[i] == Renderer::SHADER_BLUR_HORIZONTAL || + shaderList[i] == Renderer::SHADER_BLUR_VERTICAL) + shaderPasses = parameters.blurPasses; - // Blit the screen contents to screenTexture. - GL_CHECK_ERROR(glBlitFramebuffer(0, 0, width, height, 0, 0, width, height, - GL_COLOR_BUFFER_BIT, GL_NEAREST)); + for (int p = 0; p < shaderPasses; p++) { + GL_CHECK_ERROR(glBindFramebuffer(GL_DRAW_FRAMEBUFFER, shaderFBO)); - // Apply/render the shaders. - drawTriangleStrips(vertices, 4, Transform4x4f::Identity(), - Blend::SRC_ALPHA, Blend::ONE_MINUS_SRC_ALPHA, parameters); + // Attach the texture to the shader framebuffer. + GL_CHECK_ERROR(glFramebufferTexture2D( + GL_FRAMEBUFFER, + GL_COLOR_ATTACHMENT0, + GL_TEXTURE_2D, + screenTexture, + 0)); - // If textureRGBA has an address, it means that the output should go to this texture - // rather than to the screen. The glReadPixels() function is slow, but since this would - // typically only run every now and then to create a cached screen texture, it doesn't - // really matter. - if (textureRGBA) { - GL_CHECK_ERROR(glBindFramebuffer(GL_READ_FRAMEBUFFER, shaderFBO)); - GL_CHECK_ERROR(glReadPixels(0, 0, width, height, - GL_RGBA, GL_UNSIGNED_BYTE, textureRGBA)); - GL_CHECK_ERROR(glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0)); - } - else { - // Blit the resulting postprocessed texture back to the primary framebuffer. - GL_CHECK_ERROR(glBindFramebuffer(GL_READ_FRAMEBUFFER, shaderFBO)); - GL_CHECK_ERROR(glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0)); - GL_CHECK_ERROR(glBlitFramebuffer(0, 0, width, height, 0, 0, width, height, - GL_COLOR_BUFFER_BIT, GL_NEAREST)); + // Blit the screen contents to screenTexture. + GL_CHECK_ERROR(glBlitFramebuffer(0, 0, width, height, 0, 0, width, height, + GL_COLOR_BUFFER_BIT, GL_NEAREST)); + + // Apply/render the shaders. + drawTriangleStrips(vertices, 4, Transform4x4f::Identity(), + Blend::SRC_ALPHA, Blend::ONE_MINUS_SRC_ALPHA, parameters); + + // If textureRGBA has an address, it means that the output should go to this + // texture rather than to the screen. The glReadPixels() function is slow, but + // since this will typically only run every now and then to create a cached + // screen texture, it doesn't really matter. + if (textureRGBA) { + GL_CHECK_ERROR(glBindFramebuffer(GL_READ_FRAMEBUFFER, shaderFBO)); + GL_CHECK_ERROR(glReadPixels(0, 0, width, height, + GL_RGBA, GL_UNSIGNED_BYTE, textureRGBA)); + GL_CHECK_ERROR(glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0)); + } + else { + // Blit the resulting postprocessed texture back to the primary framebuffer. + GL_CHECK_ERROR(glBindFramebuffer(GL_READ_FRAMEBUFFER, shaderFBO)); + GL_CHECK_ERROR(glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0)); + GL_CHECK_ERROR(glBlitFramebuffer(0, 0, width, height, 0, 0, width, height, + GL_COLOR_BUFFER_BIT, GL_NEAREST)); + } + } } GL_CHECK_ERROR(glBindFramebuffer(GL_READ_FRAMEBUFFER, 0));