gl_engine: support basic alpha composite

* introduce a new class GlRenderPass to hold off-screen rendering result
* add basic alpha composite support with begin/end render-pass
This commit is contained in:
RuiwenTang 2023-10-13 18:29:59 +08:00 committed by Hermet Park
parent 81e3025ad9
commit a46af19ccb
8 changed files with 452 additions and 26 deletions

View file

@ -4,6 +4,7 @@ source_file = [
'tvgGlGpuBuffer.h',
'tvgGlProgram.h',
'tvgGlRenderer.h',
'tvgGlRenderPass.h',
'tvgGlRenderTask.h',
'tvgGlShader.h',
'tvgGlShaderSrc.h',
@ -11,6 +12,7 @@ source_file = [
'tvgGlGpuBuffer.cpp',
'tvgGlProgram.cpp',
'tvgGlRenderer.cpp',
'tvgGlRenderPass.cpp',
'tvgGlRenderTask.cpp',
'tvgGlShader.cpp',
'tvgGlShaderSrc.cpp',

View file

@ -57,8 +57,8 @@ struct GlShape
const RenderShape* rshape = nullptr;
float viewWd;
float viewHt;
uint32_t opacity = 0;
GLuint texId = 0;
uint32_t texOpacity = 0;
uint32_t texFlipY = 0;
ColorSpace texColorSpace = ColorSpace::ABGR8888;
RenderUpdateFlag updateFlag = None;

View file

@ -0,0 +1,74 @@
/*
* Copyright (c) 2023 the ThorVG project. All rights reserved.
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
* The above copyright notice and this permission notice shall be included in
all
* copies or substantial portions of the Software.
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
#include "tvgGlRenderPass.h"
#include "tvgGlRenderTask.h"
GlRenderTarget::GlRenderTarget(uint32_t width, uint32_t height): mWidth(width), mHeight(height) {}
GlRenderTarget::~GlRenderTarget()
{
if (mFbo == 0) return;
GL_CHECK(glBindFramebuffer(GL_FRAMEBUFFER, 0));
GL_CHECK(glDeleteFramebuffers(1, &mFbo));
if (mColorTex) GL_CHECK(glDeleteTextures(1, &mColorTex));
}
void GlRenderTarget::init(GLint resolveId)
{
if (mFbo != 0 || mWidth == 0 || mHeight == 0) return;
GL_CHECK(glGenFramebuffers(1, &mFbo));
GL_CHECK(glBindFramebuffer(GL_FRAMEBUFFER, mFbo));
GL_CHECK(glGenTextures(1, &mColorTex));
GL_CHECK(glBindTexture(GL_TEXTURE_2D, mColorTex));
GL_CHECK(glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, mWidth, mHeight, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr));
GL_CHECK(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE));
GL_CHECK(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE));
GL_CHECK(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR));
GL_CHECK(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR));
GL_CHECK(glBindTexture(GL_TEXTURE_2D, 0));
GL_CHECK(glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, mColorTex, 0));
GL_CHECK(glBindFramebuffer(GL_FRAMEBUFFER, resolveId));
}
GlRenderPass::GlRenderPass(GlRenderTarget* fbo): mFbo(fbo), mTasks() {}
GlRenderPass::GlRenderPass(GlRenderPass&& other): mFbo(other.mFbo), mTasks(std::move(other.mTasks)) {}
GlRenderPass::~GlRenderPass() {}
void GlRenderPass::addRenderTask(unique_ptr<GlRenderTask> task)
{
mTasks.emplace_back(std::move(task));
}

View file

@ -0,0 +1,74 @@
/*
* Copyright (c) 2023 the ThorVG project. All rights reserved.
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
#ifndef _TVG_GL_RENDER_PASS_H_
#define _TVG_GL_RENDER_PASS_H_
#include <memory>
#include <vector>
#include "tvgGlCommon.h"
class GlRenderTask;
class GlProgram;
class GlRenderTarget
{
public:
GlRenderTarget(uint32_t width, uint32_t height);
~GlRenderTarget();
void init(GLint resolveId);
GLuint getFboId() { return mFbo; }
GLuint getColorTexture() { return mColorTex; }
private:
uint32_t mWidth = 0;
uint32_t mHeight = 0;
GLuint mFbo = 0;
GLuint mColorTex = 0;
};
class GlRenderPass
{
public:
GlRenderPass(GlRenderTarget* fbo);
GlRenderPass(GlRenderPass&& other);
~GlRenderPass();
void addRenderTask(unique_ptr<GlRenderTask> task);
GLuint getFboId() { return mFbo->getFboId(); }
template <class T>
unique_ptr<T> endRenderPass(GlProgram* program, GLuint targetFbo) {
return make_unique<T>(program, targetFbo, mFbo->getFboId(), std::move(mTasks));
}
private:
GlRenderTarget* mFbo;
vector<unique_ptr<GlRenderTask>> mTasks = {};
};
#endif // _TVG_GL_RENDER_PASS_H_

View file

@ -27,7 +27,7 @@
/* External Class Implementation */
/************************************************************************/
void GlRenderTask::run()
void GlRenderTask::run()
{
// bind shader
mProgram->load();
@ -89,3 +89,59 @@ void GlRenderTask::setViewport(const RenderRegion &viewport)
{
mViewport = viewport;
}
GlComposeTask::GlComposeTask(GlProgram* program, GLuint target, GLuint selfFbo, vector<unique_ptr<GlRenderTask>> tasks)
:GlRenderTask(program) ,mTargetFbo(target), mSelfFbo(selfFbo), mTasks(std::move(tasks))
{
}
void GlComposeTask::run()
{
GL_CHECK(glBindFramebuffer(GL_FRAMEBUFFER, getSelfFbo()));
// clear this fbo
GLenum color_buffer = GL_COLOR_ATTACHMENT0;
const float transparent[] = {0.f, 0.f, 0.f, 0.f};
GL_CHECK(glDrawBuffers(1, &color_buffer));
GL_CHECK(glClearBufferfv(GL_COLOR, 0, transparent));
for(auto& task : mTasks) {
task->run();
}
}
GlBlitTask::GlBlitTask(GlProgram* program, GLuint target, GLuint compose, vector<unique_ptr<GlRenderTask>> tasks)
: GlComposeTask(program, target, compose, std::move(tasks))
{
}
void GlBlitTask::setSize(uint32_t width, uint32_t height)
{
mWidth = width;
mHeight = height;
}
void GlBlitTask::run()
{
GlComposeTask::run();
GL_CHECK(glBindFramebuffer(GL_FRAMEBUFFER, getTargetFbo()));
GL_CHECK(glBindFramebuffer(GL_READ_FRAMEBUFFER, getSelfFbo()));
GL_CHECK(glBlitFramebuffer(0, 0, mWidth, mHeight, 0, 0, mWidth, mHeight, GL_COLOR_BUFFER_BIT, GL_NEAREST));
}
GlDrawBlitTask::GlDrawBlitTask(GlProgram* program, GLuint target, GLuint compose, vector<unique_ptr<GlRenderTask>> tasks)
: GlComposeTask(program, target, compose, std::move(tasks))
{
}
void GlDrawBlitTask::run()
{
GlComposeTask::run();
GL_CHECK(glBindFramebuffer(GL_FRAMEBUFFER, getTargetFbo()));
GlRenderTask::run();
}

View file

@ -23,6 +23,9 @@
#ifndef _TVG_GL_RENDER_TASK_H_
#define _TVG_GL_RENDER_TASK_H_
#include <memory>
#include <vector>
#include "tvgGlCommon.h"
#include "tvgGlProgram.h"
@ -69,14 +72,13 @@ struct GlBindingResource
}
};
class GlRenderTask
{
public:
GlRenderTask(GlProgram* program): mProgram(program) {}
~GlRenderTask() = default;
virtual ~GlRenderTask() = default;
void run();
virtual void run();
void addVertexLayout(const GlVertexLayout& layout);
void addBindResource(const GlBindingResource& binding);
@ -93,4 +95,48 @@ private:
Array<GlBindingResource> mBindingResources = {};
};
class GlComposeTask : public GlRenderTask
{
public:
GlComposeTask(GlProgram* program, GLuint target, GLuint selfFbo, vector<unique_ptr<GlRenderTask>> tasks);
~GlComposeTask() override = default;
void run() override;
protected:
GLuint getTargetFbo() { return mTargetFbo; }
GLuint getSelfFbo() { return mSelfFbo; }
private:
GLuint mTargetFbo;
GLuint mSelfFbo;
vector<unique_ptr<GlRenderTask>> mTasks;
};
class GlBlitTask : public GlComposeTask
{
public:
GlBlitTask(GlProgram*, GLuint target, GLuint compose, vector<unique_ptr<GlRenderTask>> tasks);
~GlBlitTask() override = default;
void setSize(uint32_t width, uint32_t height);
void run() override;
private:
uint32_t mWidth = 0;
uint32_t mHeight = 0;
};
class GlDrawBlitTask : public GlComposeTask
{
public:
GlDrawBlitTask(GlProgram*, GLuint target, GLuint compose, vector<unique_ptr<GlRenderTask>> tasks);
~GlDrawBlitTask() override = default;
void run() override;
};
#endif /* _TVG_GL_RENDER_TASK_H_ */

View file

@ -68,6 +68,14 @@ bool GlRenderer::target(TVG_UNUSED uint32_t* buffer, uint32_t stride, uint32_t w
mViewport.w = surface.w;
mViewport.h = surface.h;
// get current binded framebuffer id
// EFL seems has a seperate framebuffer for evagl view
//TODO: introduce a new api to specify which fbo this canvas is binded
GL_CHECK(glGetIntegerv(GL_FRAMEBUFFER_BINDING, &mTargetFboId));
mRootTarget = make_unique<GlRenderTarget>(surface.w, surface.h);
mRootTarget->init(mTargetFboId);
return true;
}
@ -83,16 +91,22 @@ bool GlRenderer::sync()
mGpuBuffer->bind();
for(auto& task: mRenderTasks) {
task->run();
}
assert(mRenderPassStack.size() == 1);
auto task = mRenderPassStack.front().endRenderPass<GlBlitTask>(nullptr, mTargetFboId);
task->setSize(surface.w, surface.h);
task->run();
mGpuBuffer->unbind();
mRenderTasks.clear();
GL_CHECK(glDisable(GL_SCISSOR_TEST));
mRenderPassStack.clear();
mPoolIndex = 0;
return true;
}
@ -110,6 +124,8 @@ bool GlRenderer::preRender()
initShaders();
}
mRenderPassStack.emplace_back(GlRenderPass(mRootTarget.get()));
return true;
}
@ -122,22 +138,46 @@ bool GlRenderer::postRender()
Compositor* GlRenderer::target(TVG_UNUSED const RenderRegion& region, TVG_UNUSED ColorSpace cs)
{
//TODO: Prepare frameBuffer & Setup render target for composition
return nullptr;
mComposeStack.emplace_back(make_unique<tvg::Compositor>());
return mComposeStack.back().get();
}
bool GlRenderer::beginComposite(TVG_UNUSED Compositor* cmp, CompositeMethod method, uint8_t opacity)
bool GlRenderer::beginComposite(Compositor* cmp, CompositeMethod method, uint8_t opacity)
{
//TODO: delete the given compositor and restore the context
return false;
if (!cmp) return false;
// TODO handle other composite method with recursive begin composite
cmp->method = method;
cmp->opacity = opacity;
if (cmp->method == CompositeMethod::None) {
if (mPoolIndex >= mComposePool.size()) {
mComposePool.emplace_back(make_unique<GlRenderTarget>(surface.w, surface.h));
mComposePool.back()->init(mTargetFboId);
}
mRenderPassStack.emplace_back(GlRenderPass(mComposePool[mPoolIndex++].get()));
}
return true;
}
bool GlRenderer::endComposite(TVG_UNUSED Compositor* cmp)
{
//TODO: delete the given compositor and restore the context
return false;
if (mComposeStack.empty()) return false;
if (mComposeStack.back().get() != cmp) return false;
// end current render pass;
auto currCmp = std::move(mComposeStack.back());
mComposeStack.pop_back();
assert(cmp == currCmp.get());
endRenderPass(currCmp.get());
return true;
}
@ -181,7 +221,7 @@ bool GlRenderer::renderImage(void* data)
}
// image info
{
uint32_t info[4] = {sdata->texColorSpace, sdata->texFlipY, sdata->texOpacity, 0};
uint32_t info[4] = {sdata->texColorSpace, sdata->texFlipY, sdata->opacity, 0};
uint32_t loc = task->getProgram()->getUniformBlockIndex("ColorInfo");
task->addBindResource(GlBindingResource{
@ -198,7 +238,7 @@ bool GlRenderer::renderImage(void* data)
task->addBindResource(GlBindingResource{0, sdata->texId, loc});
}
mRenderTasks.emplace_back(std::move(task));
currentPass()->addRenderTask(std::move(task));
return true;
}
@ -283,7 +323,7 @@ RenderData GlRenderer::prepare(Surface* image, const RenderMesh* mesh, RenderDat
sdata->updateFlag = flags;
sdata->texId = _genTexture(image);
sdata->texOpacity = opacity;
sdata->opacity = opacity;
sdata->texColorSpace = image->cs;
sdata->texFlipY = (mesh && mesh->triangleCnt) ? 0 : 1;
sdata->geometry = make_unique<GlGeometry>();
@ -309,7 +349,7 @@ RenderData GlRenderer::prepare(TVG_UNUSED const Array<RenderData>& scene, TVG_UN
}
RenderData GlRenderer::prepare(const RenderShape& rshape, RenderData data, const RenderTransform* transform, Array<RenderData>& clips, TVG_UNUSED uint8_t opacity, RenderUpdateFlag flags, TVG_UNUSED bool clipper)
RenderData GlRenderer::prepare(const RenderShape& rshape, RenderData data, const RenderTransform* transform, Array<RenderData>& clips, uint8_t opacity, RenderUpdateFlag flags, TVG_UNUSED bool clipper)
{
//prepare shape data
GlShape* sdata = static_cast<GlShape*>(data);
@ -325,6 +365,7 @@ RenderData GlRenderer::prepare(const RenderShape& rshape, RenderData data, const
if (sdata->updateFlag == RenderUpdateFlag::None) return sdata;
sdata->geometry = make_unique<GlGeometry>();
sdata->opacity = opacity;
//invisible?
uint8_t alphaF = 0, alphaS = 0;
@ -400,14 +441,12 @@ GlRenderer* GlRenderer::gen()
return new GlRenderer();
}
GlRenderer::GlRenderer() :mViewport() ,mGpuBuffer(new GlStageBuffer), mPrograms(), mRenderTasks()
GlRenderer::GlRenderer() :mViewport() ,mGpuBuffer(new GlStageBuffer), mPrograms()
{
}
GlRenderer::~GlRenderer()
{
mRenderTasks.clear();
--rendererCnt;
if (rendererCnt == 0 && initEngineCnt == 0) _termEngine();
@ -435,6 +474,8 @@ void GlRenderer::drawPrimitive(GlShape& sdata, uint8_t r, uint8_t g, uint8_t b,
auto task = make_unique<GlRenderTask>(mPrograms[RT_Color].get());
if (!sdata.geometry->draw(task.get(), mGpuBuffer.get(), flag)) return;
a = MULTIPLY(a, sdata.opacity);
// matrix buffer
{
@ -464,7 +505,7 @@ void GlRenderer::drawPrimitive(GlShape& sdata, uint8_t r, uint8_t g, uint8_t b,
});
}
mRenderTasks.emplace_back(std::move(task));
currentPass()->addRenderTask(std::move(task));
}
@ -558,6 +599,128 @@ void GlRenderer::drawPrimitive(GlShape& sdata, const Fill* fill, RenderUpdateFla
task->addBindResource(gradientBinding);
}
mRenderTasks.emplace_back(std::move(task));
currentPass()->addRenderTask(std::move(task));
}
GlRenderPass* GlRenderer::currentPass()
{
if (mRenderPassStack.empty()) return nullptr;
return &mRenderPassStack.back();
}
void GlRenderer::prepareCmpTask(GlRenderTask* task)
{
// we use 1:1 blit mapping since compositor fbo is same size as root fbo
Array<float> vertices;
vertices.reserve(5 * 4);
float left = -1.f;
float top = 1.f;
float right = 1.f;
float bottom = -1.f;
// left top point
vertices.push(left);
vertices.push(top);
vertices.push(1.f);
vertices.push(0.f);
vertices.push(1.f);
// left bottom point
vertices.push(left);
vertices.push(bottom);
vertices.push(1.f);
vertices.push(0.f);
vertices.push(0.f);
// right top point
vertices.push(right);
vertices.push(top);
vertices.push(1.f);
vertices.push(1.f);
vertices.push(1.f);
// right bottom point
vertices.push(right);
vertices.push(bottom);
vertices.push(1.f);
vertices.push(1.f);
vertices.push(0.f);
Array<uint32_t> indices;
indices.reserve(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->push(indices.data, vertices.count * sizeof(uint32_t));
task->addVertexLayout(GlVertexLayout{0, 3, 5 * sizeof(float), vertexOffset});
task->addVertexLayout(GlVertexLayout{1, 2, 5 * sizeof(float), vertexOffset + 3 * sizeof(float)});
task->setDrawRange(indexOffset, indices.count);
task->setViewport(RenderRegion{
mViewport.x,
static_cast<int32_t>((surface.h - mViewport.y - mViewport.h)),
mViewport.w,
mViewport.h,
});
}
void GlRenderer::endRenderPass(Compositor* cmp)
{
if (cmp->method != CompositeMethod::None) {
//TODO support advance composite method with recursive compositor
return;
}
auto renderPass = std::move(mRenderPassStack.back());
mRenderPassStack.pop_back();
auto task = renderPass.endRenderPass<GlDrawBlitTask>(
mPrograms[RT_Image].get(), currentPass()->getFboId());
prepareCmpTask(task.get());
// matrix buffer
{
float matrix[16];
memset(matrix, 0, 16 * sizeof(float));
matrix[0] = 1.f;
matrix[5] = 1.f;
matrix[10] = 1.f;
matrix[15] = 1.f;
uint32_t loc = task->getProgram()->getUniformBlockIndex("Matrix");
task->addBindResource(GlBindingResource{
0,
loc,
mGpuBuffer->getBufferId(),
mGpuBuffer->push(matrix, 16 * sizeof(float), true),
16 * sizeof(float),
});
}
// image info
{
uint32_t info[4] = {ABGR8888, 0, cmp->opacity, 0};
uint32_t loc = task->getProgram()->getUniformBlockIndex("ColorInfo");
task->addBindResource(GlBindingResource{
1,
loc,
mGpuBuffer->getBufferId(),
mGpuBuffer->push(info, 4 * sizeof(uint32_t), true),
4 * sizeof(uint32_t),
});
}
// texture id
{
uint32_t loc = task->getProgram()->getUniformLocation("uTexture");
task->addBindResource(GlBindingResource{0, renderPass.getFboId(), loc});
}
currentPass()->addRenderTask(std::move(task));
}

View file

@ -27,6 +27,7 @@
#include "tvgGlRenderTask.h"
#include "tvgGlGpuBuffer.h"
#include "tvgGlRenderPass.h"
class GlRenderer : public RenderMethod
{
@ -78,10 +79,20 @@ private:
void drawPrimitive(GlShape& sdata, uint8_t r, uint8_t g, uint8_t b, uint8_t a, RenderUpdateFlag flag);
void drawPrimitive(GlShape& sdata, const Fill* fill, RenderUpdateFlag flag);
GlRenderPass* currentPass();
void prepareCmpTask(GlRenderTask* task);
void endRenderPass(Compositor* cmp);
GLint mTargetFboId = 0;
RenderRegion mViewport;
std::unique_ptr<GlStageBuffer> mGpuBuffer;
vector<std::unique_ptr<GlProgram>> mPrograms;
vector<std::unique_ptr<GlRenderTask>> mRenderTasks;
unique_ptr<GlRenderTarget> mRootTarget = {};
vector<unique_ptr<GlRenderTarget>> mComposePool = {};
size_t mPoolIndex = 0;
vector<GlRenderPass> mRenderPassStack = {};
vector<unique_ptr<Compositor>> mComposeStack = {};
};
#endif /* _TVG_GL_RENDERER_H_ */