gl_engine: rewrite the blending shader

Completely rewrite the GL backend shader to support advance blending and
correct the blending behavior.
Now the GL backend has the same blending result as SKIA and CanvasRenderingContext2D in browers did.

The formula is referenced from:
https://www.w3.org/TR/compositing-1/#porterduffcompositingoperators

issue: https://github.com/thorvg/thorvg/issues/2799
This commit is contained in:
Hermet Park 2025-05-12 13:27:14 +09:00
parent 3fbb55440a
commit 3c884232fd
13 changed files with 256 additions and 150 deletions

View file

@ -165,7 +165,7 @@ struct GlRadialGradientBlock
struct GlCompositor : RenderCompositor struct GlCompositor : RenderCompositor
{ {
RenderRegion bbox = {}; RenderRegion bbox = {};
BlendMethod blend = BlendMethod::Normal;
GlCompositor(const RenderRegion& box) : bbox(box) {} GlCompositor(const RenderRegion& box) : bbox(box) {}
}; };

View file

@ -101,13 +101,17 @@ int32_t GlProgram::getAttributeLocation(const char* name)
int32_t GlProgram::getUniformLocation(const char* name) int32_t GlProgram::getUniformLocation(const char* name)
{ {
if (mUniformLocation.count(name)) return mUniformLocation[name];
GL_CHECK(int32_t location = glGetUniformLocation(mProgramObj, name)); GL_CHECK(int32_t location = glGetUniformLocation(mProgramObj, name));
mUniformLocation[name] = location;
return location; return location;
} }
int32_t GlProgram::getUniformBlockIndex(const char* name) int32_t GlProgram::getUniformBlockIndex(const char* name)
{ {
if (mUniformBlock.count(name)) return mUniformBlock[name];
GL_CHECK(int32_t index = glGetUniformBlockIndex(mProgramObj, name)); GL_CHECK(int32_t index = glGetUniformBlockIndex(mProgramObj, name));
mUniformBlock[name] = index;
return index; return index;
} }

View file

@ -25,6 +25,9 @@
#include "tvgGlShader.h" #include "tvgGlShader.h"
#include <unordered_map>
#include <string>
class GlProgram class GlProgram
{ {
public: public:
@ -49,6 +52,8 @@ public:
private: private:
uint32_t mProgramObj; uint32_t mProgramObj;
std::unordered_map<std::string, int32_t> mUniformLocation;
std::unordered_map<std::string, int32_t> mUniformBlock;
static uint32_t mCurrentProgram; static uint32_t mCurrentProgram;
}; };

View file

@ -25,9 +25,9 @@
#include "tvgGlRenderPass.h" #include "tvgGlRenderPass.h"
#include "tvgGlRenderTask.h" #include "tvgGlRenderTask.h"
GlRenderPass::GlRenderPass(GlRenderTarget* fbo): mFbo(fbo), mTasks(), mDrawDepth(0) {} GlRenderPass::GlRenderPass(GlRenderTarget* fbo, RenderRegion viewport): mFbo(fbo), mViewport(viewport), mTasks(), mDrawDepth(0) {}
GlRenderPass::GlRenderPass(GlRenderPass&& other): mFbo(other.mFbo), mTasks(), mDrawDepth(0) GlRenderPass::GlRenderPass(GlRenderPass&& other): mFbo(other.mFbo), mViewport(other.mViewport), mTasks(), mDrawDepth(0)
{ {
mTasks.push(other.mTasks); mTasks.push(other.mTasks);

View file

@ -32,7 +32,7 @@ class GlProgram;
class GlRenderPass class GlRenderPass
{ {
public: public:
GlRenderPass(GlRenderTarget* fbo); GlRenderPass(GlRenderTarget* fbo, RenderRegion viewport);
GlRenderPass(GlRenderPass&& other); GlRenderPass(GlRenderPass&& other);
~GlRenderPass(); ~GlRenderPass();
@ -45,7 +45,7 @@ public:
GLuint getTextureId() { return mFbo->getColorTexture(); } GLuint getTextureId() { return mFbo->getColorTexture(); }
const RenderRegion& getViewport() const { return mFbo->getViewport(); } const RenderRegion& getViewport() const { return mViewport; }
uint32_t getFboWidth() const { return mFbo->getWidth(); } uint32_t getFboWidth() const { return mFbo->getWidth(); }
@ -63,7 +63,7 @@ public:
auto task = new T(program, targetFbo, mFbo, std::move(mTasks)); auto task = new T(program, targetFbo, mFbo, std::move(mTasks));
const auto& vp = mFbo->getViewport(); const auto& vp = mViewport;
task->setRenderSize(static_cast<uint32_t>(vp.w), static_cast<uint32_t>(vp.h)); task->setRenderSize(static_cast<uint32_t>(vp.w), static_cast<uint32_t>(vp.h));
@ -77,6 +77,7 @@ public:
GlRenderTarget* getFbo() { return mFbo; } GlRenderTarget* getFbo() { return mFbo; }
private: private:
GlRenderTarget* mFbo; GlRenderTarget* mFbo;
RenderRegion mViewport;
Array<GlRenderTask*> mTasks = {}; Array<GlRenderTask*> mTasks = {};
int32_t mDrawDepth = 0; int32_t mDrawDepth = 0;
}; };

View file

@ -29,6 +29,7 @@ GlRenderTarget::~GlRenderTarget()
if (mFbo == 0) return; if (mFbo == 0) return;
GL_CHECK(glBindFramebuffer(GL_FRAMEBUFFER, 0)); GL_CHECK(glBindFramebuffer(GL_FRAMEBUFFER, 0));
GL_CHECK(glDeleteFramebuffers(1, &mFbo)); GL_CHECK(glDeleteFramebuffers(1, &mFbo));
GL_CHECK(glDeleteFramebuffers(1, &mResolveFbo));
if (mColorTex != 0) { if (mColorTex != 0) {
GL_CHECK(glDeleteTextures(1, &mColorTex)); GL_CHECK(glDeleteTextures(1, &mColorTex));
@ -36,6 +37,9 @@ GlRenderTarget::~GlRenderTarget()
if (mDepthStencilBuffer != 0) { if (mDepthStencilBuffer != 0) {
GL_CHECK(glDeleteRenderbuffers(1, &mDepthStencilBuffer)); GL_CHECK(glDeleteRenderbuffers(1, &mDepthStencilBuffer));
} }
if (mColorBuffer!= 0) {
GL_CHECK(glDeleteRenderbuffers(1, &mColorBuffer));
}
} }
void GlRenderTarget::init(GLint resolveId) void GlRenderTarget::init(GLint resolveId)
@ -119,15 +123,15 @@ GlRenderTarget* GlRenderTargetPool::getRenderTarget(const RenderRegion& vp, GLui
for (uint32_t i = 0; i < mPool.count; i++) { for (uint32_t i = 0; i < mPool.count; i++) {
auto rt = mPool[i]; auto rt = mPool[i];
if (rt->getWidth() == width && rt->getHeight() == height) { if (rt->getWidth() == width && rt->getHeight() == height && !rt->isInUse()) {
rt->setViewport(vp); rt->setInUse(true);
return rt; return rt;
} }
} }
auto rt = new GlRenderTarget(width, height); auto rt = new GlRenderTarget(width, height);
rt->init(resolveId); rt->init(resolveId);
rt->setViewport(vp); rt->setInUse(true);
mPool.push(rt); mPool.push(rt);
return rt; return rt;
} }

View file

@ -45,16 +45,19 @@ public:
const RenderRegion& getViewport() const { return mViewport; } const RenderRegion& getViewport() const { return mViewport; }
bool invalid() const { return mFbo == GL_INVALID_VALUE; } bool invalid() const { return mFbo == GL_INVALID_VALUE; }
bool isInUse() const { return mInUse; }
void setInUse(bool inUse) { mInUse = inUse; }
private: private:
uint32_t mWidth = 0; uint32_t mWidth;
uint32_t mHeight = 0; uint32_t mHeight;
RenderRegion mViewport{}; RenderRegion mViewport{};
GLuint mFbo = GL_INVALID_VALUE; GLuint mFbo = GL_INVALID_VALUE;
GLuint mColorBuffer = 0; GLuint mColorBuffer = 0;
GLuint mDepthStencilBuffer = 0; GLuint mDepthStencilBuffer = 0;
GLuint mResolveFbo = 0; GLuint mResolveFbo = 0;
GLuint mColorTex = 0; GLuint mColorTex = 0;
bool mInUse = false;
}; };
class GlRenderTargetPool { class GlRenderTargetPool {

View file

@ -332,12 +332,11 @@ void GlSimpleBlendTask::run()
glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA); glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
} }
GlComplexBlendTask::GlComplexBlendTask(GlProgram* program, GlRenderTarget* dstFbo, GlRenderTarget* dstCopyFbo, GlRenderTask* stencilTask, GlComposeTask* composeTask) GlComplexBlendTask::GlComplexBlendTask(GlProgram* program, GlRenderTarget* dstFbo, GlRenderTarget* dstCopyFbo, GlComposeTask* composeTask)
: GlRenderTask(program), mDstFbo(dstFbo), mDstCopyFbo(dstCopyFbo), mStencilTask(stencilTask), mComposeTask(composeTask) {} : GlRenderTask(program), mDstFbo(dstFbo), mDstCopyFbo(dstCopyFbo), mComposeTask(composeTask) {}
GlComplexBlendTask::~GlComplexBlendTask() GlComplexBlendTask::~GlComplexBlendTask()
{ {
delete mStencilTask;
delete mComposeTask; delete mComposeTask;
} }
@ -349,8 +348,8 @@ void GlComplexBlendTask::run()
GL_CHECK(glBindFramebuffer(GL_READ_FRAMEBUFFER, mDstFbo->getFboId())); GL_CHECK(glBindFramebuffer(GL_READ_FRAMEBUFFER, mDstFbo->getFboId()));
GL_CHECK(glBindFramebuffer(GL_DRAW_FRAMEBUFFER, mDstCopyFbo->getResolveFboId())); GL_CHECK(glBindFramebuffer(GL_DRAW_FRAMEBUFFER, mDstCopyFbo->getResolveFboId()));
GL_CHECK(glViewport(0, 0, mDstFbo->getViewport().w, mDstFbo->getViewport().h)); GL_CHECK(glViewport(0, 0, mViewport.w, mViewport.h));
GL_CHECK(glScissor(0, 0, mDstFbo->getViewport().w, mDstFbo->getViewport().h)); GL_CHECK(glScissor(0, 0, mViewport.w, mViewport.h));
const auto& vp = getViewport(); const auto& vp = getViewport();
@ -358,21 +357,6 @@ void GlComplexBlendTask::run()
GL_CHECK(glBindFramebuffer(GL_FRAMEBUFFER, mDstFbo->getFboId())); GL_CHECK(glBindFramebuffer(GL_FRAMEBUFFER, mDstFbo->getFboId()));
GL_CHECK(glEnable(GL_STENCIL_TEST));
GL_CHECK(glColorMask(0, 0, 0, 0));
GL_CHECK(glStencilFuncSeparate(GL_FRONT, GL_ALWAYS, 0x0, 0xFF));
GL_CHECK(glStencilOpSeparate(GL_FRONT, GL_KEEP, GL_KEEP, GL_INCR_WRAP));
GL_CHECK(glStencilFuncSeparate(GL_BACK, GL_ALWAYS, 0x0, 0xFF));
GL_CHECK(glStencilOpSeparate(GL_BACK, GL_KEEP, GL_KEEP, GL_DECR_WRAP));
mStencilTask->run();
GL_CHECK(glColorMask(1, 1, 1, 1));
GL_CHECK(glStencilFunc(GL_NOTEQUAL, 0x0, 0xFF));
GL_CHECK(glStencilOp(GL_REPLACE, GL_KEEP, GL_REPLACE));
GL_CHECK(glBlendFunc(GL_ONE, GL_ZERO)); GL_CHECK(glBlendFunc(GL_ONE, GL_ZERO));
GlRenderTask::run(); GlRenderTask::run();
@ -383,7 +367,6 @@ void GlComplexBlendTask::run()
void GlComplexBlendTask::normalizeDrawDepth(int32_t maxDepth) void GlComplexBlendTask::normalizeDrawDepth(int32_t maxDepth)
{ {
mStencilTask->normalizeDrawDepth(maxDepth);
GlRenderTask::normalizeDrawDepth(maxDepth); GlRenderTask::normalizeDrawDepth(maxDepth);
} }

View file

@ -206,17 +206,21 @@ private:
class GlComplexBlendTask: public GlRenderTask class GlComplexBlendTask: public GlRenderTask
{ {
public: public:
GlComplexBlendTask(GlProgram* program, GlRenderTarget* dstFbo, GlRenderTarget* dstCopyFbo, GlRenderTask* stencilTask, GlComposeTask* composeTask); GlComplexBlendTask(GlProgram* program, GlRenderTarget* dstFbo, GlRenderTarget* dstCopyFbo, GlComposeTask* composeTask);
~GlComplexBlendTask() override; ~GlComplexBlendTask() override;
void run() override; void run() override;
void normalizeDrawDepth(int32_t maxDepth) override; void normalizeDrawDepth(int32_t maxDepth) override;
void setViewport(const RenderRegion& viewport) { mViewport = viewport; }
void setCopyViewport(const RenderRegion& viewport) { mCopyViewport = viewport; }
private: private:
GlRenderTarget* mDstFbo; GlRenderTarget* mDstFbo;
GlRenderTarget* mDstCopyFbo; GlRenderTarget* mDstCopyFbo;
GlRenderTask* mStencilTask;
GlComposeTask* mComposeTask; GlComposeTask* mComposeTask;
RenderRegion mViewport = {};
RenderRegion mCopyViewport = {};
}; };
class GlGaussianBlurTask: public GlRenderTask class GlGaussianBlurTask: public GlRenderTask

View file

@ -156,6 +156,8 @@ void GlRenderer::initShaders()
mPrograms.push(new GlProgram(MASK_VERT_SHADER, SOFT_LIGHT_BLEND_FRAG)); 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, DIFFERENCE_BLEND_FRAG));
mPrograms.push(new GlProgram(MASK_VERT_SHADER, EXCLUSION_BLEND_FRAG)); mPrograms.push(new GlProgram(MASK_VERT_SHADER, EXCLUSION_BLEND_FRAG));
mPrograms.push(new GlProgram(MASK_VERT_SHADER, LIGHTEN_BLEND_FRAG));
mPrograms.push(new GlProgram(MASK_VERT_SHADER, DARKEN_BLEND_FRAG));
// effects // effects
mPrograms.push(new GlProgram(EFFECT_VERTEX, GAUSSIAN_VERTICAL)); mPrograms.push(new GlProgram(EFFECT_VERTEX, GAUSSIAN_VERTICAL));
@ -174,7 +176,7 @@ void GlRenderer::drawPrimitive(GlShape& sdata, const RenderColor& c, RenderUpdat
bbox.intersect(vp); bbox.intersect(vp);
auto complexBlend = beginComplexBlending(bbox, sdata.geometry.getBounds()); auto complexBlend = beginComplexBlending(mBlendMethod, bbox, sdata.geometry.getBounds());
if (complexBlend) { if (complexBlend) {
vp = currentPass()->getViewport(); vp = currentPass()->getViewport();
@ -254,11 +256,7 @@ void GlRenderer::drawPrimitive(GlShape& sdata, const RenderColor& c, RenderUpdat
if (stencilTask) currentPass()->addRenderTask(new GlStencilCoverTask(stencilTask, task, stencilMode)); if (stencilTask) currentPass()->addRenderTask(new GlStencilCoverTask(stencilTask, task, stencilMode));
else currentPass()->addRenderTask(task); else currentPass()->addRenderTask(task);
if (complexBlend) { if (complexBlend) endBlendingCompose(mBlendMethod);
auto task = new GlRenderTask(mPrograms[RT_Stencil]);
sdata.geometry.draw(task, &mGpuBuffer, flag);
endBlendingCompose(task, sdata.geometry.matrix);
}
} }
@ -290,7 +288,7 @@ void GlRenderer::drawPrimitive(GlShape& sdata, const Fill* fill, RenderUpdateFla
return; return;
} }
auto complexBlend = beginComplexBlending(bbox, sdata.geometry.getBounds()); auto complexBlend = beginComplexBlending(mBlendMethod, bbox, sdata.geometry.getBounds());
if (complexBlend) vp = currentPass()->getViewport(); if (complexBlend) vp = currentPass()->getViewport();
@ -441,11 +439,7 @@ void GlRenderer::drawPrimitive(GlShape& sdata, const Fill* fill, RenderUpdateFla
currentPass()->addRenderTask(task); currentPass()->addRenderTask(task);
} }
if (complexBlend) { if (complexBlend) endBlendingCompose(mBlendMethod);
auto task = new GlRenderTask(mPrograms[RT_Stencil]);
sdata.geometry.draw(task, &mGpuBuffer, flag);
endBlendingCompose(task, sdata.geometry.matrix);
}
} }
@ -539,7 +533,7 @@ GlRenderPass* GlRenderer::currentPass()
return mRenderPassStack.last(); return mRenderPassStack.last();
} }
bool GlRenderer::beginComplexBlending(const RenderRegion& vp, RenderRegion bounds) bool GlRenderer::beginComplexBlending(BlendMethod blend, const RenderRegion& vp, RenderRegion bounds)
{ {
if (vp.w == 0 || vp.h == 0) return false; if (vp.w == 0 || vp.h == 0) return false;
@ -547,18 +541,18 @@ bool GlRenderer::beginComplexBlending(const RenderRegion& vp, RenderRegion bound
if (bounds.w == 0 || bounds.h == 0) return false; if (bounds.w == 0 || bounds.h == 0) return false;
if (mBlendMethod == BlendMethod::Normal || mBlendMethod == BlendMethod::Add || mBlendMethod == BlendMethod::Darken || mBlendMethod == BlendMethod::Lighten) return false; if (blend == BlendMethod::Normal || blend == BlendMethod::Add) return false;
if (mBlendPool.empty()) mBlendPool.push(new GlRenderTargetPool(surface.w, surface.h)); if (mBlendPool.empty()) mBlendPool.push(new GlRenderTargetPool(surface.w, surface.h));
auto blendFbo = mBlendPool[0]->getRenderTarget(bounds); auto blendFbo = mBlendPool[0]->getRenderTarget(bounds);
mRenderPassStack.push(new GlRenderPass(blendFbo)); mRenderPassStack.push(new GlRenderPass(blendFbo, bounds));
return true; return true;
} }
void GlRenderer::endBlendingCompose(GlRenderTask* stencilTask, const Matrix& matrix) void GlRenderer::endBlendingCompose(BlendMethod blend)
{ {
auto blendPass = mRenderPassStack.last(); auto blendPass = mRenderPassStack.last();
mRenderPassStack.pop(); mRenderPassStack.pop();
@ -571,34 +565,10 @@ void GlRenderer::endBlendingCompose(GlRenderTask* stencilTask, const Matrix& mat
if (mBlendPool.count < 2) mBlendPool.push(new GlRenderTargetPool(surface.w, surface.h)); if (mBlendPool.count < 2) mBlendPool.push(new GlRenderTargetPool(surface.w, surface.h));
auto dstCopyFbo = mBlendPool[1]->getRenderTarget(vp); auto dstCopyFbo = mBlendPool[1]->getRenderTarget(vp);
{ auto task = new GlComplexBlendTask(getBlendProgram(blend), currentPass()->getFbo(), dstCopyFbo, composeTask);
const auto& passVp = currentPass()->getViewport();
auto x = vp.x; task->setViewport(currentPass()->getViewport());
auto y = vp.y; task->setCopyViewport(blendPass->getViewport());
auto w = vp.w;
auto h = vp.h;
stencilTask->setViewport({x, passVp.h - y - h, w, h});
}
stencilTask->setDrawDepth(currentPass()->nextDrawDepth());
{
// set view matrix
float matrix44[16];
currentPass()->getMatrix(matrix44, matrix);
uint32_t viewOffset = mGpuBuffer.push(matrix44, 16 * sizeof(float), true);
stencilTask->addBindResource(GlBindingResource{
0,
stencilTask->getProgram()->getUniformBlockIndex("Matrix"),
mGpuBuffer.getBufferId(),
viewOffset,
16 * sizeof(float),
});
}
auto task = new GlComplexBlendTask(getBlendProgram(), currentPass()->getFbo(), dstCopyFbo, stencilTask, composeTask);
prepareCmpTask(task, vp, blendPass->getFboWidth(), blendPass->getFboHeight()); prepareCmpTask(task, vp, blendPass->getFboWidth(), blendPass->getFboHeight());
@ -613,9 +583,9 @@ void GlRenderer::endBlendingCompose(GlRenderTask* stencilTask, const Matrix& mat
delete(blendPass); delete(blendPass);
} }
GlProgram* GlRenderer::getBlendProgram() GlProgram* GlRenderer::getBlendProgram(BlendMethod blend)
{ {
switch (mBlendMethod) { switch (blend) {
case BlendMethod::Multiply: return mPrograms[RT_MultiplyBlend]; case BlendMethod::Multiply: return mPrograms[RT_MultiplyBlend];
case BlendMethod::Screen: return mPrograms[RT_ScreenBlend]; case BlendMethod::Screen: return mPrograms[RT_ScreenBlend];
case BlendMethod::Overlay: return mPrograms[RT_OverlayBlend]; case BlendMethod::Overlay: return mPrograms[RT_OverlayBlend];
@ -625,6 +595,8 @@ GlProgram* GlRenderer::getBlendProgram()
case BlendMethod::SoftLight: return mPrograms[RT_SoftLightBlend]; case BlendMethod::SoftLight: return mPrograms[RT_SoftLightBlend];
case BlendMethod::Difference: return mPrograms[RT_DifferenceBlend]; case BlendMethod::Difference: return mPrograms[RT_DifferenceBlend];
case BlendMethod::Exclusion: return mPrograms[RT_ExclusionBlend]; case BlendMethod::Exclusion: return mPrograms[RT_ExclusionBlend];
case BlendMethod::Lighten: return mPrograms[RT_LightenBlend];
case BlendMethod::Darken: return mPrograms[RT_DarkenBlend];
default: return nullptr; default: return nullptr;
} }
} }
@ -711,6 +683,41 @@ void GlRenderer::prepareCmpTask(GlRenderTask* task, const RenderRegion& vp, uint
task->setViewport({x, static_cast<int32_t>((passVp.h - y - h)), w, h}); task->setViewport({x, static_cast<int32_t>((passVp.h - y - h)), w, h});
} }
void GlRenderer::prepareCmpStencilTask(GlRenderTask* task, RenderRegion& vp)
{
auto x = 0;
auto y = 0;
auto w = vp.w;
auto h = vp.h;
Array<float> vertices(4 * 2);
// left top
vertices.push(x);
vertices.push(y);
// left bottom
vertices.push(x);
vertices.push(y + h);
// right top
vertices.push(x + w);
vertices.push(y);
// right bottom
vertices.push(x + w);
vertices.push(y + h);
Array<uint32_t> indices(6);
indices.push(0);
indices.push(1);
indices.push(2);
indices.push(2);
indices.push(1);
indices.push(3);
uint32_t vertexOffset = mGpuBuffer.push(vertices.data, vertices.count * sizeof(float));
uint32_t indexOffset = mGpuBuffer.pushIndex(indices.data, indices.count * sizeof(uint32_t));
task->addVertexLayout(GlVertexLayout{0, 2, 2 * sizeof(float), vertexOffset});
task->setDrawRange(indexOffset, indices.count);
}
void GlRenderer::endRenderPass(RenderCompositor* cmp) void GlRenderer::endRenderPass(RenderCompositor* cmp)
{ {
@ -723,6 +730,8 @@ void GlRenderer::endRenderPass(RenderCompositor* cmp)
auto maskPass = mRenderPassStack.last(); auto maskPass = mRenderPassStack.last();
mRenderPassStack.pop(); mRenderPassStack.pop();
bool complexBlend = beginComplexBlending(glCmp->blend, glCmp->bbox, glCmp->bbox);
GlProgram* program = nullptr; GlProgram* program = nullptr;
switch(cmp->method) { switch(cmp->method) {
case MaskMethod::Alpha: program = mPrograms[RT_MaskAlpha]; break; case MaskMethod::Alpha: program = mPrograms[RT_MaskAlpha]; break;
@ -758,6 +767,11 @@ void GlRenderer::endRenderPass(RenderCompositor* cmp)
currentPass()->addRenderTask(compose_task); currentPass()->addRenderTask(compose_task);
} }
if (complexBlend) endBlendingCompose(glCmp->blend);
maskPass->getFbo()->setInUse(false);
selfPass->getFbo()->setInUse(false);
delete(selfPass); delete(selfPass);
delete(maskPass); delete(maskPass);
} else { } else {
@ -766,6 +780,8 @@ void GlRenderer::endRenderPass(RenderCompositor* cmp)
mRenderPassStack.pop(); mRenderPassStack.pop();
if (!renderPass->isEmpty()) { if (!renderPass->isEmpty()) {
bool complexBlend = beginComplexBlending(glCmp->blend, glCmp->bbox, glCmp->bbox);
auto task = renderPass->endRenderPass<GlDrawBlitTask>(mPrograms[RT_Image], currentPass()->getFboId()); auto task = renderPass->endRenderPass<GlDrawBlitTask>(mPrograms[RT_Image], currentPass()->getFboId());
task->setRenderSize(static_cast<uint32_t>(glCmp->bbox.w), static_cast<uint32_t>(glCmp->bbox.h)); task->setRenderSize(static_cast<uint32_t>(glCmp->bbox.w), static_cast<uint32_t>(glCmp->bbox.h));
prepareCmpTask(task, glCmp->bbox, renderPass->getFboWidth(), renderPass->getFboHeight()); prepareCmpTask(task, glCmp->bbox, renderPass->getFboWidth(), renderPass->getFboHeight());
@ -793,10 +809,22 @@ void GlRenderer::endRenderPass(RenderCompositor* cmp)
4 * sizeof(uint32_t), 4 * sizeof(uint32_t),
}); });
task->addBindResource(GlBindingResource{
1,
task->getProgram()->getUniformBlockIndex("ColorInfo"),
mGpuBuffer.getBufferId(),
mGpuBuffer.push(info, 4 * sizeof(uint32_t), true),
4 * sizeof(uint32_t),
});
// texture id // texture id
task->addBindResource(GlBindingResource{0, renderPass->getTextureId(), task->getProgram()->getUniformLocation("uTexture")}); task->addBindResource(GlBindingResource{0, renderPass->getTextureId(), task->getProgram()->getUniformLocation("uTexture")});
task->setParentSize(static_cast<uint32_t>(currentPass()->getViewport().w), static_cast<uint32_t>(currentPass()->getViewport().h)); task->setParentSize(static_cast<uint32_t>(currentPass()->getViewport().w), static_cast<uint32_t>(currentPass()->getViewport().h));
currentPass()->addRenderTask(std::move(task)); currentPass()->addRenderTask(std::move(task));
if (complexBlend) endBlendingCompose(glCmp->blend);
renderPass->getFbo()->setInUse(false);
} }
delete(renderPass); delete(renderPass);
} }
@ -902,7 +930,7 @@ bool GlRenderer::preRender()
currentContext(); currentContext();
if (mPrograms.empty()) initShaders(); if (mPrograms.empty()) initShaders();
mRenderPassStack.push(new GlRenderPass(&mRootTarget)); mRenderPassStack.push(new GlRenderPass(&mRootTarget, RenderRegion{0, 0, static_cast<int32_t>(surface.w), static_cast<int32_t>(surface.h)}));
return true; return true;
} }
@ -940,13 +968,14 @@ bool GlRenderer::beginComposite(RenderCompositor* cmp, MaskMethod method, uint8_
} }
auto glCmp = static_cast<GlCompositor*>(cmp); auto glCmp = static_cast<GlCompositor*>(cmp);
glCmp->blend = mBlendMethod;
if (glCmp->bbox.w > 0 && glCmp->bbox.h > 0) { if (glCmp->bbox.w > 0 && glCmp->bbox.h > 0) {
auto renderTarget = mComposePool[index]->getRenderTarget(glCmp->bbox); auto renderTarget = mComposePool[index]->getRenderTarget(glCmp->bbox);
mRenderPassStack.push(new GlRenderPass(renderTarget)); mRenderPassStack.push(new GlRenderPass(renderTarget, glCmp->bbox));
} else { } else {
// empty render pass // empty render pass
mRenderPassStack.push(new GlRenderPass(nullptr)); mRenderPassStack.push(new GlRenderPass(nullptr, glCmp->bbox));
} }
return true; return true;
@ -1265,6 +1294,10 @@ bool GlRenderer::renderImage(void* data)
auto bbox = sdata->geometry.viewport; auto bbox = sdata->geometry.viewport;
bool complexBlend = beginComplexBlending(mBlendMethod, bbox, sdata->geometry.getBounds());
if (complexBlend) vp = currentPass()->getViewport();
bbox.intersect(vp); bbox.intersect(vp);
if (bbox.w <= 0 || bbox.h <= 0) return true; if (bbox.w <= 0 || bbox.h <= 0) return true;
@ -1277,7 +1310,10 @@ bool GlRenderer::renderImage(void* data)
if (!sdata->clips.empty()) drawClip(sdata->clips); if (!sdata->clips.empty()) drawClip(sdata->clips);
auto task = new GlRenderTask(mPrograms[RT_Image]); GlRenderTask* task = nullptr;
if (mBlendMethod != BlendMethod::Normal && !complexBlend) task = new GlSimpleBlendTask(mBlendMethod, mPrograms[RT_Image]);
else task = new GlRenderTask(mPrograms[RT_Image]);
task->setDrawDepth(drawDepth); task->setDrawDepth(drawDepth);
if (!sdata->geometry.draw(task, &mGpuBuffer, RenderUpdateFlag::Image)) { if (!sdata->geometry.draw(task, &mGpuBuffer, RenderUpdateFlag::Image)) {
@ -1285,10 +1321,6 @@ bool GlRenderer::renderImage(void* data)
return true; return true;
} }
bool complexBlend = beginComplexBlending(bbox, sdata->geometry.getBounds());
if (complexBlend) vp = currentPass()->getViewport();
// matrix buffer // matrix buffer
float matrix44[16]; float matrix44[16];
currentPass()->getMatrix(matrix44, sdata->geometry.matrix); currentPass()->getMatrix(matrix44, sdata->geometry.matrix);
@ -1319,11 +1351,7 @@ bool GlRenderer::renderImage(void* data)
currentPass()->addRenderTask(task); currentPass()->addRenderTask(task);
if (complexBlend) { if (complexBlend) endBlendingCompose(mBlendMethod);
auto task = new GlRenderTask(mPrograms[RT_Stencil]);
sdata->geometry.draw(task, &mGpuBuffer, RenderUpdateFlag::Image);
endBlendingCompose(task, sdata->geometry.matrix);
}
return true; return true;
} }

View file

@ -59,6 +59,8 @@ public:
RT_SoftLightBlend, RT_SoftLightBlend,
RT_DifferenceBlend, RT_DifferenceBlend,
RT_ExclusionBlend, RT_ExclusionBlend,
RT_LightenBlend,
RT_DarkenBlend,
RT_GaussianVert, RT_GaussianVert,
RT_GaussianHorz, RT_GaussianHorz,
@ -113,12 +115,13 @@ private:
GlRenderPass* currentPass(); GlRenderPass* currentPass();
bool beginComplexBlending(const RenderRegion& vp, RenderRegion bounds); bool beginComplexBlending(BlendMethod blend, const RenderRegion& vp, RenderRegion bounds);
void endBlendingCompose(GlRenderTask* stencilTask, const Matrix& matrix); void endBlendingCompose(BlendMethod blend);
GlProgram* getBlendProgram(); GlProgram* getBlendProgram(BlendMethod blend);
void prepareBlitTask(GlBlitTask* task); void prepareBlitTask(GlBlitTask* task);
void prepareCmpTask(GlRenderTask* task, const RenderRegion& vp, uint32_t cmpWidth, uint32_t cmpHeight); void prepareCmpTask(GlRenderTask* task, const RenderRegion& vp, uint32_t cmpWidth, uint32_t cmpHeight);
void prepareCmpStencilTask(GlRenderTask* task, RenderRegion& vp);
void endRenderPass(RenderCompositor* cmp); void endRenderPass(RenderCompositor* cmp);
void effectGaussianBlurUpdate(RenderEffectGaussianBlur* effect, const Matrix& transform); void effectGaussianBlurUpdate(RenderEffectGaussianBlur* effect, const Matrix& transform);

View file

@ -598,131 +598,199 @@ const char* BLIT_FRAG_SHADER = TVG_COMPOSE_SHADER(
\ \
in vec2 vUV; \ in vec2 vUV; \
out vec4 FragColor; \ out vec4 FragColor; \
// 1/1024.
const float kEhCloseEnoughHalf = 0.0009765625;
)" )"
const char* MULTIPLY_BLEND_FRAG = COMPLEX_BLEND_HEADER R"( const char* MULTIPLY_BLEND_FRAG = COMPLEX_BLEND_HEADER R"(
void main() void main()
{ {
vec4 srcColor = texture(uSrcTexture, vUV); vec4 src = texture(uSrcTexture, vUV);
vec4 dstColor = texture(uDstTexture, vUV); vec4 dst = texture(uDstTexture, vUV);
FragColor = srcColor * dstColor;
FragColor = vec4((1.0 - src.a)*dst.rgb + (1.0 - dst.a)*src.rgb + src.rgb*dst.rgb, src.a + (1.0 - src.a)*dst.a);
} }
)"; )";
#define SCREEN_BLEND_FUNC R"( \ const char* SCREEN_BLEND_FRAG = COMPLEX_BLEND_HEADER R"(
vec4 screenBlend(vec4 srcColor, vec4 dstColor) \
{ \
return dstColor + srcColor - (dstColor * srcColor); \
} \
)"
#define HARD_LIGHT_BLEND_FUNC R"( \
vec4 hardLightBlend(vec4 srcColor, vec4 dstColor) \
{ \
return vec4(srcColor.r < 0.5 ? 2.0 * srcColor.r * dstColor.r : 1.0 - 2.0 * (1.0 - srcColor.r) * (1.0 - dstColor.r), \
srcColor.g < 0.5 ? 2.0 * srcColor.g * dstColor.g : 1.0 - 2.0 * (1.0 - srcColor.g) * (1.0 - dstColor.g), \
srcColor.b < 0.5 ? 2.0 * srcColor.b * dstColor.b : 1.0 - 2.0 * (1.0 - srcColor.b) * (1.0 - dstColor.b), \
1.0); \
} \
)"
#define SOFT_LIGHT_BLEND_FUNC R"( \
float softLightD(float v) \
{ \
if (v <= 0.25) return ((16.0 * v - 12.0) * v + 4.0) * v; \
else return sqrt(v); \
} \
)"
const char* SCREEN_BLEND_FRAG = COMPLEX_BLEND_HEADER SCREEN_BLEND_FUNC R"(
void main() void main()
{ {
vec4 srcColor = texture(uSrcTexture, vUV); vec4 srcColor = texture(uSrcTexture, vUV);
vec4 dstColor = texture(uDstTexture, vUV); vec4 dstColor = texture(uDstTexture, vUV);
FragColor = screenBlend(srcColor, dstColor); FragColor = srcColor + (1.0 - srcColor) * dstColor;
} }
)"; )";
const char* OVERLAY_BLEND_FRAG = COMPLEX_BLEND_HEADER HARD_LIGHT_BLEND_FUNC R"( #define BLEND_OVERLAY_FUNC R"(
float blend_overlay_component(vec2 s, vec2 d)
{
return (2.0 * d.x <= d.y) ? 2.0 * s.x * d.x
: s.y * d.y - 2.0 * (d.y - d.x) * (s.y - s.x);
}
vec4 blend_overlay(vec4 src, vec4 dst)
{
vec4 result = vec4(
blend_overlay_component(src.ra, dst.ra),
blend_overlay_component(src.ga, dst.ga),
blend_overlay_component(src.ba, dst.ba),
src.a + (1.0 - src.a) * dst.a
);
result.rgb += dst.rgb*(1 - src.a) + src.rgb*(1 - dst.a);
return result;
}
)"
const char* OVERLAY_BLEND_FRAG = COMPLEX_BLEND_HEADER BLEND_OVERLAY_FUNC R"(
void main() void main()
{ {
vec4 srcColor = texture(uSrcTexture, vUV); vec4 srcColor = texture(uSrcTexture, vUV);
vec4 dstColor = texture(uDstTexture, vUV); vec4 dstColor = texture(uDstTexture, vUV);
FragColor = hardLightBlend(dstColor, srcColor);
FragColor = blend_overlay(srcColor, dstColor);
} }
)"; )";
const char* COLOR_DODGE_BLEND_FRAG = COMPLEX_BLEND_HEADER R"( const char* COLOR_DODGE_BLEND_FRAG = COMPLEX_BLEND_HEADER R"(
float color_dodge_component(vec2 s, vec2 d)
{
float dxScale = d.x == 0.0 ? 0.0 : 1.0;
float delta = dxScale * min(d.y, abs(s.y - s.x) >= kEhCloseEnoughHalf ? (d.x * s.y / (s.y - s.x)) : d.y);
return delta * s.y + s.x * (1.0 - d.y) + d.x * (1.0 - s.y);
}
void main() void main()
{ {
vec4 srcColor = texture(uSrcTexture, vUV); vec4 srcColor = texture(uSrcTexture, vUV);
vec4 dstColor = texture(uDstTexture, vUV); vec4 dstColor = texture(uDstTexture, vUV);
FragColor = vec4( FragColor = vec4(
srcColor.r < 1.0 ? dstColor.r / (1.0 - srcColor.r) : (dstColor.r > 0.0 ? 1.0 : 0.0), color_dodge_component(srcColor.ra, dstColor.ra),
srcColor.g < 1.0 ? dstColor.g / (1.0 - srcColor.g) : (dstColor.g > 0.0 ? 1.0 : 0.0), color_dodge_component(srcColor.ga, dstColor.ga),
srcColor.b < 1.0 ? dstColor.b / (1.0 - srcColor.b) : (dstColor.b > 0.0 ? 1.0 : 0.0), color_dodge_component(srcColor.ba, dstColor.ba),
1.0 srcColor.a + (1.0 - srcColor.a) * dstColor.a
); );
} }
)"; )";
const char* COLOR_BURN_BLEND_FRAG = COMPLEX_BLEND_HEADER R"( const char* COLOR_BURN_BLEND_FRAG = COMPLEX_BLEND_HEADER R"(
float color_burn_component(vec2 s, vec2 d)
{
float dyTerm = d.y == d.x ? d.y : 0.0;
float delta = abs(s.x) >= kEhCloseEnoughHalf ? d.y - min(d.y, ((d.y - d.x) * s.y / s.x)) : dyTerm;
return delta * s.y + s.x * (1.0 - d.y) + d.x * (1.0 - s.y);
}
void main() void main()
{ {
vec4 srcColor = texture(uSrcTexture, vUV); vec4 srcColor = texture(uSrcTexture, vUV);
vec4 dstColor = texture(uDstTexture, vUV); vec4 dstColor = texture(uDstTexture, vUV);
FragColor = vec4( FragColor = vec4(
srcColor.r > 0.0 ? (1.0 - (1.0 - dstColor.r) / srcColor.r) : (dstColor.r < 1.0 ? 0.0 : 1.0), color_burn_component(srcColor.ra, dstColor.ra),
srcColor.g > 0.0 ? (1.0 - (1.0 - dstColor.g) / srcColor.g) : (dstColor.g < 1.0 ? 0.0 : 1.0), color_burn_component(srcColor.ga, dstColor.ga),
srcColor.b > 0.0 ? (1.0 - (1.0 - dstColor.b) / srcColor.b) : (dstColor.b < 1.0 ? 0.0 : 1.0), color_burn_component(srcColor.ba, dstColor.ba),
1.0 srcColor.a + (1.0 - srcColor.a) * dstColor.a
); );
} }
)"; )";
const char* HARD_LIGHT_BLEND_FRAG = COMPLEX_BLEND_HEADER HARD_LIGHT_BLEND_FUNC R"( const char* HARD_LIGHT_BLEND_FRAG = COMPLEX_BLEND_HEADER BLEND_OVERLAY_FUNC R"(
void main() void main()
{ {
vec4 srcColor = texture(uSrcTexture, vUV); vec4 srcColor = texture(uSrcTexture, vUV);
vec4 dstColor = texture(uDstTexture, vUV); vec4 dstColor = texture(uDstTexture, vUV);
FragColor = hardLightBlend(srcColor, dstColor);
FragColor = blend_overlay(dstColor, srcColor);
} }
)"; )";
const char* SOFT_LIGHT_BLEND_FRAG = COMPLEX_BLEND_HEADER SOFT_LIGHT_BLEND_FUNC R"( const char* SOFT_LIGHT_BLEND_FRAG = COMPLEX_BLEND_HEADER R"(
float soft_light_component(vec2 s, vec2 d)
{
if (2.0 * s.x <= s.y) {
return (d.x * d.x * (s.y - 2.0 * s.x) / d.y) + (1.0 - d.y) * s.x + d.x * (-s.y + 2.0 * s.x + 1.0);
} else if(4.0 * d.x <= d.y) {
float DSqd = d.x * d.x;
float DCub = DSqd * d.x;
float DaSqd = d.y * d.y;
float DaCub = DaSqd * d.y;
return (DaSqd * (s.x - d.x * (3.0 * s.y - 6.0 * s.x - 1.0)) + 12.0 * d.y * DSqd * (s.y - 2.0 * s.x) - 16.0 * DCub * (s.y - 2.0 * s.x) - DaCub * s.x) / DaSqd;
} else {
return d.x * (s.y - 2.0 * s.x + 1.0) + s.x - sqrt(d.y * d.x) * (s.y - 2.0 * s.x) - d.y * s.x;
}
}
void main() void main()
{ {
vec4 srcColor = texture(uSrcTexture, vUV); vec4 srcColor = texture(uSrcTexture, vUV);
vec4 dstColor = texture(uDstTexture, vUV); vec4 dstColor = texture(uDstTexture, vUV);
FragColor = vec4( FragColor = dstColor.a == 0.0 ? srcColor : vec4(
srcColor.r <= 0.5 ? dstColor.r - (1.0 - 2.0 * srcColor.r) * dstColor.r * (1.0 - dstColor.r) : dstColor.r + (2.0 * srcColor.r - 1.0) * (softLightD(dstColor.r) - dstColor.r), soft_light_component(srcColor.ra, dstColor.ra),
srcColor.g <= 0.5 ? dstColor.g - (1.0 - 2.0 * srcColor.g) * dstColor.g * (1.0 - dstColor.g) : dstColor.g + (2.0 * srcColor.g - 1.0) * (softLightD(dstColor.g) - dstColor.g), soft_light_component(srcColor.ga, dstColor.ga),
srcColor.b <= 0.5 ? dstColor.b - (1.0 - 2.0 * srcColor.b) * dstColor.b * (1.0 - dstColor.b) : dstColor.b + (2.0 * srcColor.b - 1.0) * (softLightD(dstColor.b) - dstColor.b), soft_light_component(srcColor.ba, dstColor.ba),
1.0 srcColor.a + (1.0 - srcColor.a) * dstColor.a
); );
} }
)"; )";
const char* DIFFERENCE_BLEND_FRAG = COMPLEX_BLEND_HEADER R"( const char* DIFFERENCE_BLEND_FRAG = COMPLEX_BLEND_HEADER R"(
void main() void main()
{ {
vec4 srcColor = texture(uSrcTexture, vUV); vec4 src = texture(uSrcTexture, vUV);
vec4 dstColor = texture(uDstTexture, vUV); vec4 dst = texture(uDstTexture, vUV);
FragColor = abs(dstColor - srcColor); FragColor = vec4(src.rgb + dst.rgb - 2.0 * min(src.rgb * dst.a, dst.rgb * src.a), src.a + (1.0 - src.a) * dst.a);
} }
)"; )";
const char* EXCLUSION_BLEND_FRAG = COMPLEX_BLEND_HEADER R"( const char* EXCLUSION_BLEND_FRAG = COMPLEX_BLEND_HEADER R"(
void main()
{
vec4 src = texture(uSrcTexture, vUV);
vec4 dst = texture(uDstTexture, vUV);
FragColor = vec4(dst.rgb + src.rgb - 2.0 * dst.rgb * src.rgb, src.a + (1.0 - src.a) * dst.a);
}
)";
#define LD_BLEND_FUNC R"(
vec4 blendSrcOver(vec4 src, vec4 dst) { return src + (1 - src.a)*dst; }
vec4 blendLightDarken(float mode, vec4 src, vec4 dst)
{
vec4 a = blendSrcOver(src, dst);
vec3 b = (1.0 - dst.a) * src.rgb + dst.rgb;
a.rgb = mode * min(a.rgb * mode, b.rgb * mode);
return a;
}
)"
const char* LIGHTEN_BLEND_FRAG = COMPLEX_BLEND_HEADER LD_BLEND_FUNC R"(
void main() void main()
{ {
vec4 srcColor = texture(uSrcTexture, vUV); vec4 srcColor = texture(uSrcTexture, vUV);
vec4 dstColor = texture(uDstTexture, vUV); vec4 dstColor = texture(uDstTexture, vUV);
FragColor = dstColor + srcColor - (2.0 * dstColor * srcColor); FragColor = blendLightDarken(-1.0, srcColor, dstColor);
}
)";
const char* DARKEN_BLEND_FRAG = COMPLEX_BLEND_HEADER LD_BLEND_FUNC R"(
void main()
{
vec4 srcColor = texture(uSrcTexture, vUV);
vec4 dstColor = texture(uDstTexture, vUV);
FragColor = blendLightDarken(1.0, srcColor, dstColor);
} }
)"; )";

View file

@ -58,12 +58,15 @@ extern const char* HARD_LIGHT_BLEND_FRAG;
extern const char* SOFT_LIGHT_BLEND_FRAG; extern const char* SOFT_LIGHT_BLEND_FRAG;
extern const char* DIFFERENCE_BLEND_FRAG; extern const char* DIFFERENCE_BLEND_FRAG;
extern const char* EXCLUSION_BLEND_FRAG; extern const char* EXCLUSION_BLEND_FRAG;
extern const char* EFFECT_VERTEX; extern const char* LIGHTEN_BLEND_FRAG;
extern const char* DARKEN_BLEND_FRAG;
extern const char* GAUSSIAN_VERTICAL; extern const char* GAUSSIAN_VERTICAL;
extern const char* GAUSSIAN_HORIZONTAL; extern const char* GAUSSIAN_HORIZONTAL;
extern const char* EFFECT_VERTEX;
extern const char* EFFECT_DROPSHADOW; extern const char* EFFECT_DROPSHADOW;
extern const char* EFFECT_FILL; extern const char* EFFECT_FILL;
extern const char* EFFECT_TINT; extern const char* EFFECT_TINT;
extern const char* EFFECT_TRITONE; extern const char* EFFECT_TRITONE;
#endif /* _TVG_GL_SHADERSRC_H_ */ #endif /* _TVG_GL_SHADERSRC_H_ */