From 595769257e291620da39cad57bd75b6dc7b201fc Mon Sep 17 00:00:00 2001 From: Sergii Liebodkin Date: Mon, 7 Apr 2025 12:12:55 +0000 Subject: [PATCH] gl_engine: Introduce gaussian blur effect issue: https://github.com/thorvg/thorvg/issues/3054 --- src/renderer/gl_engine/tvgGlCommon.h | 8 ++ src/renderer/gl_engine/tvgGlRenderTask.cpp | 61 ++++++++++++- src/renderer/gl_engine/tvgGlRenderTask.h | 18 ++++ src/renderer/gl_engine/tvgGlRenderer.cpp | 99 ++++++++++++++++++++-- src/renderer/gl_engine/tvgGlRenderer.h | 8 +- src/renderer/gl_engine/tvgGlShaderSrc.cpp | 83 ++++++++++++++++++ src/renderer/gl_engine/tvgGlShaderSrc.h | 3 + 7 files changed, 268 insertions(+), 12 deletions(-) diff --git a/src/renderer/gl_engine/tvgGlCommon.h b/src/renderer/gl_engine/tvgGlCommon.h index 990911e1..b9f6f0c5 100644 --- a/src/renderer/gl_engine/tvgGlCommon.h +++ b/src/renderer/gl_engine/tvgGlCommon.h @@ -200,4 +200,12 @@ struct GlCompositor : RenderCompositor GlCompositor(const RenderRegion& box) : bbox(box) {} }; +#define GL_GAUSSIAN_MAX_LEVEL 3 +struct GlGaussianBlur { + int level{}; + float sigma{}; + float scale{}; + float extend{}; +}; + #endif /* _TVG_GL_COMMON_H_ */ diff --git a/src/renderer/gl_engine/tvgGlRenderTask.cpp b/src/renderer/gl_engine/tvgGlRenderTask.cpp index 6e4f91bc..d94ec81f 100644 --- a/src/renderer/gl_engine/tvgGlRenderTask.cpp +++ b/src/renderer/gl_engine/tvgGlRenderTask.cpp @@ -173,12 +173,12 @@ GlComposeTask::~GlComposeTask() void GlComposeTask::run() { GL_CHECK(glBindFramebuffer(GL_FRAMEBUFFER, getSelfFbo())); - GL_CHECK(glViewport(0, 0, mRenderWidth, mRenderHeight)); - - GL_CHECK(glScissor(0, 0, mRenderWidth, mRenderHeight)); // clear this fbo if (mClearBuffer) { + // we must clear all area of fbo + GL_CHECK(glViewport(0, 0, mFbo->getWidth(), mFbo->getHeight())); + GL_CHECK(glScissor(0, 0, mFbo->getWidth(), mFbo->getHeight())); GL_CHECK(glClearColor(0, 0, 0, 0)); GL_CHECK(glClearStencil(0)); GL_CHECK(glClearDepthf(0.0)); @@ -188,6 +188,9 @@ void GlComposeTask::run() GL_CHECK(glDepthMask(0)); } + GL_CHECK(glViewport(0, 0, mRenderWidth, mRenderHeight)); + GL_CHECK(glScissor(0, 0, mRenderWidth, mRenderHeight)); + ARRAY_FOREACH(p, mTasks) { (*p)->run(); } @@ -379,3 +382,55 @@ void GlComplexBlendTask::normalizeDrawDepth(int32_t maxDepth) mStencilTask->normalizeDrawDepth(maxDepth); GlRenderTask::normalizeDrawDepth(maxDepth); } + +void GlGaussianBlurTask::run() +{ + const auto vp = getViewport(); + const auto width = mDstFbo->getWidth(); + const auto height = mDstFbo->getHeight(); + + // get targets handles + GLuint dstCopyTexId0 = mDstCopyFbo0->getColorTexture(); + GLuint dstCopyTexId1 = mDstCopyFbo1->getColorTexture(); + // get programs properties + GlProgram* programHorz = horzTask->getProgram(); + GlProgram* programVert = vertTask->getProgram(); + GLint horzSrcTextureLoc = programHorz->getUniformLocation("uSrcTexture"); + GLint vertSrcTextureLoc = programVert->getUniformLocation("uSrcTexture"); + + GL_CHECK(glViewport(0, 0, width, height)); + GL_CHECK(glScissor(0, 0, width, height)); + // we need to make a full copy of dst to intermediate buffers to be sure that they don’t contain prev data. + GL_CHECK(glBindFramebuffer(GL_READ_FRAMEBUFFER, mDstFbo->getFboId())); + GL_CHECK(glBindFramebuffer(GL_DRAW_FRAMEBUFFER, mDstCopyFbo0->getResolveFboId())); + GL_CHECK(glBlitFramebuffer(0, 0, width, height, 0, 0, width, height, GL_COLOR_BUFFER_BIT, GL_NEAREST)); + GL_CHECK(glBindFramebuffer(GL_FRAMEBUFFER, mDstFbo->getFboId())); + + GL_CHECK(glDisable(GL_BLEND)); + if (effect->direction == 0) { + GL_CHECK(glBindFramebuffer(GL_READ_FRAMEBUFFER, mDstFbo->getFboId())); + GL_CHECK(glBindFramebuffer(GL_DRAW_FRAMEBUFFER, mDstCopyFbo1->getResolveFboId())); + GL_CHECK(glBlitFramebuffer(0, 0, width, height, 0, 0, width, height, GL_COLOR_BUFFER_BIT, GL_NEAREST)); + // horizontal blur + GL_CHECK(glBindFramebuffer(GL_FRAMEBUFFER, mDstCopyFbo1->getResolveFboId())); + horzTask->setViewport(vp); + horzTask->addBindResource({ 0, dstCopyTexId0, horzSrcTextureLoc }); + horzTask->run(); + // vertical blur + GL_CHECK(glBindFramebuffer(GL_FRAMEBUFFER, mDstFbo->getFboId())); + vertTask->setViewport(vp); + vertTask->addBindResource({ 0, dstCopyTexId1, vertSrcTextureLoc }); + vertTask->run(); + } // horizontal + else if (effect->direction == 1) { + horzTask->setViewport(vp); + horzTask->addBindResource({ 0, dstCopyTexId0, horzSrcTextureLoc }); + horzTask->run(); + } // vertical + else if (effect->direction == 2) { + vertTask->setViewport(vp); + vertTask->addBindResource({ 0, dstCopyTexId0, vertSrcTextureLoc }); + vertTask->run(); + } + GL_CHECK(glEnable(GL_BLEND)); +} diff --git a/src/renderer/gl_engine/tvgGlRenderTask.h b/src/renderer/gl_engine/tvgGlRenderTask.h index 5ca99843..1ccc0a66 100644 --- a/src/renderer/gl_engine/tvgGlRenderTask.h +++ b/src/renderer/gl_engine/tvgGlRenderTask.h @@ -219,4 +219,22 @@ private: GlComposeTask* mComposeTask; }; +class GlGaussianBlurTask: public GlRenderTask +{ +public: + GlGaussianBlurTask(GlRenderTarget* dstFbo, GlRenderTarget* dstCopyFbo0, GlRenderTarget* dstCopyFbo1): + GlRenderTask(nullptr), mDstFbo(dstFbo), mDstCopyFbo0(dstCopyFbo0), mDstCopyFbo1(dstCopyFbo1) {}; + ~GlGaussianBlurTask(){ delete horzTask; delete vertTask; }; + + void run() override; + + GlRenderTask* horzTask; + GlRenderTask* vertTask; + RenderEffectGaussianBlur* effect; +private: + GlRenderTarget* mDstFbo; + GlRenderTarget* mDstCopyFbo0; + GlRenderTarget* mDstCopyFbo1; +}; + #endif /* _TVG_GL_RENDER_TASK_H_ */ diff --git a/src/renderer/gl_engine/tvgGlRenderer.cpp b/src/renderer/gl_engine/tvgGlRenderer.cpp index ff8a0285..593dddec 100644 --- a/src/renderer/gl_engine/tvgGlRenderer.cpp +++ b/src/renderer/gl_engine/tvgGlRenderer.cpp @@ -140,6 +140,10 @@ void GlRenderer::initShaders() mPrograms.push(new GlProgram(MASK_VERT_SHADER, SOFT_LIGHT_BLEND_FRAG)); mPrograms.push(new GlProgram(MASK_VERT_SHADER, DIFFERENCE_BLEND_FRAG)); mPrograms.push(new GlProgram(MASK_VERT_SHADER, EXCLUSION_BLEND_FRAG)); + + // effects + mPrograms.push(new GlProgram(EFFECT_VERTEX, GAUSSIAN_VERTICAL)); + mPrograms.push(new GlProgram(EFFECT_VERTEX, GAUSSIAN_HORIZONTAL)); } @@ -944,29 +948,108 @@ bool GlRenderer::endComposite(RenderCompositor* cmp) } -void GlRenderer::prepare(TVG_UNUSED RenderEffect* effect, TVG_UNUSED const Matrix& transform) +void GlRenderer::effectGaussianBlurUpdate(RenderEffectGaussianBlur* effect, const Matrix& transform) { - //TODO: prepare the effect + GlGaussianBlur* blur = (GlGaussianBlur*)effect->rd; + if (!blur) blur = tvg::malloc(sizeof(GlGaussianBlur)); + blur->sigma = effect->sigma; + blur->scale = std::sqrt(transform.e11 * transform.e11 + transform.e12 * transform.e12); + blur->extend = 2 * blur->sigma * blur->scale; + blur->level = int(GL_GAUSSIAN_MAX_LEVEL * ((effect->quality - 1) * 0.01f)) + 1; + effect->rd = blur; + effect->valid = true; } -bool GlRenderer::region(TVG_UNUSED RenderEffect* effect) +bool GlRenderer::effectGaussianBlurRegion(RenderEffectGaussianBlur* effect) { - //TODO: Return if the current post effect requires the region expansion + auto gaussianBlur = (GlGaussianBlur*)effect->rd; + if (effect->direction != 2) { + effect->extend.x = -gaussianBlur->extend; + effect->extend.w = +gaussianBlur->extend * 2; + } + if (effect->direction != 1) { + effect->extend.y = -gaussianBlur->extend; + effect->extend.h = +gaussianBlur->extend * 2; + } + return true; +}; + + +void GlRenderer::prepare(RenderEffect* effect, const Matrix& transform) +{ + // we must be sure, that we have intermidiate FBOs + if (mBlendPool.count < 1) mBlendPool.push(new GlRenderTargetPool(surface.w, surface.h)); + if (mBlendPool.count < 2) mBlendPool.push(new GlRenderTargetPool(surface.w, surface.h)); + + switch (effect->type) { + case SceneEffect::GaussianBlur: effectGaussianBlurUpdate(static_cast(effect), transform); break; + default: break; + } + effect->valid = true; +} + + +bool GlRenderer::region(RenderEffect* effect) +{ + switch (effect->type) { + case SceneEffect::GaussianBlur: return effectGaussianBlurRegion(static_cast(effect)); + default: return false; + } return false; } -bool GlRenderer::render(TVG_UNUSED RenderCompositor* cmp, TVG_UNUSED const RenderEffect* effect, TVG_UNUSED bool direct) +bool GlRenderer::render(TVG_UNUSED RenderCompositor* cmp, const RenderEffect* effect, bool direct) { - TVGLOG("GL_ENGINE", "SceneEffect(%d) is not supported", (int)effect->type); + // get current pass and properties + auto pass = currentPass(); + if (pass->isEmpty()) return false; + auto vp = pass->getViewport(); + + // add render geometry + const float vdata[] = {-1.0f, +1.0f, +1.0f, +1.0f, +1.0f, -1.0f, -1.0f, -1.0f}; + const uint32_t idata[] = { 0, 1, 2, 0, 2, 3 }; + auto voffset = mGpuBuffer.push((void*)vdata, sizeof(vdata)); + auto ioffset = mGpuBuffer.pushIndex((void*)idata, sizeof(idata)); + + if (effect->type == SceneEffect::GaussianBlur) { + // get gaussian programs + GlProgram* programHorz = mPrograms[RT_GaussianHorz]; + GlProgram* programVert = mPrograms[RT_GaussianVert]; + // get current and intermidiate framebuffers + auto dstFbo = pass->getFbo(); + auto dstCopyFbo0 = mBlendPool[0]->getRenderTarget(vp); + auto dstCopyFbo1 = mBlendPool[1]->getRenderTarget(vp); + // add uniform data + GlGaussianBlur* blur = (GlGaussianBlur*)(effect->rd); + auto blurOffset = mGpuBuffer.push(blur, sizeof(GlGaussianBlur), true); + + // create gaussian blur tasks + auto gaussianTask = new GlGaussianBlurTask(dstFbo, dstCopyFbo0, dstCopyFbo1); + gaussianTask->effect = (RenderEffectGaussianBlur*)effect; + gaussianTask->setViewport({0, 0, vp.w, vp.h}); + // horizontal blur task and geometry + gaussianTask->horzTask = new GlRenderTask(programHorz); + gaussianTask->horzTask->addBindResource(GlBindingResource{0, programHorz->getUniformBlockIndex("Gaussian"), mGpuBuffer.getBufferId(), blurOffset, sizeof(GlGaussianBlur)}); + gaussianTask->horzTask->addVertexLayout(GlVertexLayout{0, 2, 2 * sizeof(float), voffset}); + gaussianTask->horzTask->setDrawRange(ioffset, 6); + // vertical blur task and geometry + gaussianTask->vertTask = new GlRenderTask(programVert); + gaussianTask->vertTask->addBindResource(GlBindingResource{0, programVert->getUniformBlockIndex("Gaussian"), mGpuBuffer.getBufferId(), blurOffset, sizeof(GlGaussianBlur)}); + gaussianTask->vertTask->addVertexLayout(GlVertexLayout{0, 2, 2 * sizeof(float), voffset}); + gaussianTask->vertTask->setDrawRange(ioffset, 6); + // add task to render pipeline + pass->addRenderTask(gaussianTask); + } return false; } -void GlRenderer::dispose(TVG_UNUSED RenderEffect* effect) +void GlRenderer::dispose(RenderEffect* effect) { - //TODO: dispose the effect + tvg::free(effect->rd); + effect->rd = nullptr; } diff --git a/src/renderer/gl_engine/tvgGlRenderer.h b/src/renderer/gl_engine/tvgGlRenderer.h index edcc16af..c938c6e7 100644 --- a/src/renderer/gl_engine/tvgGlRenderer.h +++ b/src/renderer/gl_engine/tvgGlRenderer.h @@ -60,6 +60,9 @@ public: RT_DifferenceBlend, RT_ExclusionBlend, + RT_GaussianVert, + RT_GaussianHorz, + RT_None, }; @@ -90,7 +93,7 @@ public: void prepare(RenderEffect* effect, const Matrix& transform) override; bool region(RenderEffect* effect) override; bool render(RenderCompositor* cmp, const RenderEffect* effect, bool direct) override; - void dispose(TVG_UNUSED RenderEffect* effect) override; + void dispose(RenderEffect* effect) override; static GlRenderer* gen(); static bool init(TVG_UNUSED uint32_t threads); @@ -116,6 +119,9 @@ private: void prepareCmpTask(GlRenderTask* task, const RenderRegion& vp, uint32_t cmpWidth, uint32_t cmpHeight); void endRenderPass(RenderCompositor* cmp); + void effectGaussianBlurUpdate(RenderEffectGaussianBlur* effect, const Matrix& transform); + bool effectGaussianBlurRegion(RenderEffectGaussianBlur* effect); + void flush(); void clearDisposes(); void currentContext(); diff --git a/src/renderer/gl_engine/tvgGlShaderSrc.cpp b/src/renderer/gl_engine/tvgGlShaderSrc.cpp index e123cfb6..f2101953 100644 --- a/src/renderer/gl_engine/tvgGlShaderSrc.cpp +++ b/src/renderer/gl_engine/tvgGlShaderSrc.cpp @@ -737,3 +737,86 @@ const char* EXCLUSION_BLEND_FRAG = COMPLEX_BLEND_HEADER R"( FragColor = dstColor + srcColor - (2.0 * dstColor * srcColor); } )"; + +const char* EFFECT_VERTEX = R"( +layout(location = 0) in vec2 aLocation; +out vec2 vUV; + +void main() +{ + vUV = aLocation * 0.5 + 0.5; + gl_Position = vec4(aLocation, 0.0, 1.0); +} +)"; + +const char* GAUSSIAN_VERTICAL = R"( +uniform sampler2D uSrcTexture; +layout(std140) uniform Gaussian { + int level; + float sigma; + float scale; + float extend; +} uGaussian; + +in vec2 vUV; +out vec4 FragColor; + +float gaussian(float x, float sigma) { + float exponent = -x * x / (2.0 * sigma * sigma); + return exp(exponent) / (sqrt(2.0 * 3.141592) * sigma); +} + +void main() +{ + vec2 texelSize = 1.0 / vec2(textureSize(uSrcTexture, 0)); + vec4 colorSum = vec4(0.0); + float sigma = uGaussian.sigma * uGaussian.scale; + float weightSum = 0.0; + int radius = int(uGaussian.extend); + + for (int y = -radius; y <= radius; ++y) { + float weight = gaussian(float(y), sigma); + vec2 offset = vec2(0.0, float(y) * texelSize.y); + colorSum += texture(uSrcTexture, vUV + offset) * weight; + weightSum += weight; + } + + FragColor = colorSum / weightSum; +} +)"; + +const char* GAUSSIAN_HORIZONTAL = R"( +uniform sampler2D uSrcTexture; +layout(std140) uniform Gaussian { + int level; + float sigma; + float scale; + float extend; +} uGaussian; + +in vec2 vUV; +out vec4 FragColor; + +float gaussian(float x, float sigma) { + float exponent = -x * x / (2.0 * sigma * sigma); + return exp(exponent) / (sqrt(2.0 * 3.141592) * sigma); +} + +void main() +{ + vec2 texelSize = 1.0 / vec2(textureSize(uSrcTexture, 0)); + vec4 colorSum = vec4(0.0); + float sigma = uGaussian.sigma * uGaussian.scale; + float weightSum = 0.0; + int radius = int(uGaussian.extend); + + for (int y = -radius; y <= radius; ++y) { + float weight = gaussian(float(y), sigma); + vec2 offset = vec2(float(y) * texelSize.x, 0.0); + colorSum += texture(uSrcTexture, vUV + offset) * weight; + weightSum += weight; + } + + FragColor = colorSum / weightSum; +} +)"; diff --git a/src/renderer/gl_engine/tvgGlShaderSrc.h b/src/renderer/gl_engine/tvgGlShaderSrc.h index dd81363a..c1b66f7e 100644 --- a/src/renderer/gl_engine/tvgGlShaderSrc.h +++ b/src/renderer/gl_engine/tvgGlShaderSrc.h @@ -54,5 +54,8 @@ extern const char* HARD_LIGHT_BLEND_FRAG; extern const char* SOFT_LIGHT_BLEND_FRAG; extern const char* DIFFERENCE_BLEND_FRAG; extern const char* EXCLUSION_BLEND_FRAG; +extern const char* EFFECT_VERTEX; +extern const char* GAUSSIAN_VERTICAL; +extern const char* GAUSSIAN_HORIZONTAL; #endif /* _TVG_GL_SHADERSRC_H_ */