mirror of
https://github.com/thorvg/thorvg.git
synced 2025-06-07 21:23:32 +00:00
1522 lines
No EOL
52 KiB
C++
1522 lines
No EOL
52 KiB
C++
/*
|
|
* Copyright (c) 2020 - 2025 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 <atomic>
|
|
#include "tvgGlCommon.h"
|
|
#include "tvgGlRenderer.h"
|
|
#include "tvgGlGpuBuffer.h"
|
|
#include "tvgGlRenderTask.h"
|
|
#include "tvgGlProgram.h"
|
|
#include "tvgGlShaderSrc.h"
|
|
|
|
/************************************************************************/
|
|
/* Internal Class Implementation */
|
|
/************************************************************************/
|
|
|
|
#define NOISE_LEVEL 0.5f
|
|
|
|
static atomic<int32_t> rendererCnt{-1};
|
|
|
|
void GlRenderer::clearDisposes()
|
|
{
|
|
if (mDisposed.textures.count > 0) {
|
|
glDeleteTextures(mDisposed.textures.count, mDisposed.textures.data);
|
|
mDisposed.textures.clear();
|
|
}
|
|
|
|
ARRAY_FOREACH(p, mRenderPassStack) delete(*p);
|
|
mRenderPassStack.clear();
|
|
}
|
|
|
|
|
|
void GlRenderer::flush()
|
|
{
|
|
clearDisposes();
|
|
|
|
mRootTarget.reset();
|
|
|
|
ARRAY_FOREACH(p, mComposePool) delete(*p);
|
|
mComposePool.clear();
|
|
|
|
ARRAY_FOREACH(p, mBlendPool) delete(*p);
|
|
mBlendPool.clear();
|
|
|
|
ARRAY_FOREACH(p, mComposeStack) delete(*p);
|
|
mComposeStack.clear();
|
|
}
|
|
|
|
|
|
void GlRenderer::currentContext()
|
|
{
|
|
#ifdef __EMSCRIPTEN__
|
|
auto targetContext = (EMSCRIPTEN_WEBGL_CONTEXT_HANDLE)mContext;
|
|
if (emscripten_webgl_get_current_context() != targetContext) {
|
|
emscripten_webgl_make_context_current(targetContext);
|
|
}
|
|
#else
|
|
TVGERR("GL_ENGINE", "Maybe missing MakeCurrent() Call?");
|
|
#endif
|
|
}
|
|
|
|
|
|
GlRenderer::GlRenderer()
|
|
{
|
|
++rendererCnt;
|
|
}
|
|
|
|
|
|
GlRenderer::~GlRenderer()
|
|
{
|
|
--rendererCnt;
|
|
|
|
flush();
|
|
|
|
ARRAY_FOREACH(p, mPrograms) delete(*p);
|
|
|
|
|
|
}
|
|
|
|
|
|
void GlRenderer::initShaders()
|
|
{
|
|
mPrograms.reserve((int)RT_None);
|
|
|
|
#if 1 //for optimization
|
|
#define LINEAR_TOTAL_LENGTH 2770
|
|
#define RADIAL_TOTAL_LENGTH 5272
|
|
#else
|
|
#define COMMON_TOTAL_LENGTH strlen(STR_GRADIENT_FRAG_COMMON_VARIABLES) + strlen(STR_GRADIENT_FRAG_COMMON_FUNCTIONS) + 1
|
|
#define LINEAR_TOTAL_LENGTH strlen(STR_LINEAR_GRADIENT_VARIABLES) + strlen(STR_LINEAR_GRADIENT_MAIN) + COMMON_TOTAL_LENGTH
|
|
#define RADIAL_TOTAL_LENGTH strlen(STR_RADIAL_GRADIENT_VARIABLES) + strlen(STR_RADIAL_GRADIENT_MAIN) + COMMON_TOTAL_LENGTH
|
|
#endif
|
|
|
|
char linearGradientFragShader[LINEAR_TOTAL_LENGTH];
|
|
snprintf(linearGradientFragShader, LINEAR_TOTAL_LENGTH, "%s%s%s%s",
|
|
STR_GRADIENT_FRAG_COMMON_VARIABLES,
|
|
STR_LINEAR_GRADIENT_VARIABLES,
|
|
STR_GRADIENT_FRAG_COMMON_FUNCTIONS,
|
|
STR_LINEAR_GRADIENT_MAIN
|
|
);
|
|
|
|
char radialGradientFragShader[RADIAL_TOTAL_LENGTH];
|
|
snprintf(radialGradientFragShader, RADIAL_TOTAL_LENGTH, "%s%s%s%s",
|
|
STR_GRADIENT_FRAG_COMMON_VARIABLES,
|
|
STR_RADIAL_GRADIENT_VARIABLES,
|
|
STR_GRADIENT_FRAG_COMMON_FUNCTIONS,
|
|
STR_RADIAL_GRADIENT_MAIN
|
|
);
|
|
|
|
mPrograms.push(new GlProgram(COLOR_VERT_SHADER, COLOR_FRAG_SHADER));
|
|
mPrograms.push(new GlProgram(GRADIENT_VERT_SHADER, linearGradientFragShader));
|
|
mPrograms.push(new GlProgram(GRADIENT_VERT_SHADER, radialGradientFragShader));
|
|
mPrograms.push(new GlProgram(IMAGE_VERT_SHADER, IMAGE_FRAG_SHADER));
|
|
|
|
// compose Renderer
|
|
mPrograms.push(new GlProgram(MASK_VERT_SHADER, MASK_ALPHA_FRAG_SHADER));
|
|
mPrograms.push(new GlProgram(MASK_VERT_SHADER, MASK_INV_ALPHA_FRAG_SHADER));
|
|
mPrograms.push(new GlProgram(MASK_VERT_SHADER, MASK_LUMA_FRAG_SHADER));
|
|
mPrograms.push(new GlProgram(MASK_VERT_SHADER, MASK_INV_LUMA_FRAG_SHADER));
|
|
mPrograms.push(new GlProgram(MASK_VERT_SHADER, MASK_ADD_FRAG_SHADER));
|
|
mPrograms.push(new GlProgram(MASK_VERT_SHADER, MASK_SUB_FRAG_SHADER));
|
|
mPrograms.push(new GlProgram(MASK_VERT_SHADER, MASK_INTERSECT_FRAG_SHADER));
|
|
mPrograms.push(new GlProgram(MASK_VERT_SHADER, MASK_DIFF_FRAG_SHADER));
|
|
mPrograms.push(new GlProgram(MASK_VERT_SHADER, MASK_LIGHTEN_FRAG_SHADER));
|
|
mPrograms.push(new GlProgram(MASK_VERT_SHADER, MASK_DARKEN_FRAG_SHADER));
|
|
|
|
// stencil Renderer
|
|
mPrograms.push(new GlProgram(STENCIL_VERT_SHADER, STENCIL_FRAG_SHADER));
|
|
|
|
// blit Renderer
|
|
mPrograms.push(new GlProgram(BLIT_VERT_SHADER, BLIT_FRAG_SHADER));
|
|
|
|
// complex blending Renderer
|
|
mPrograms.push(new GlProgram(MASK_VERT_SHADER, MULTIPLY_BLEND_FRAG));
|
|
mPrograms.push(new GlProgram(MASK_VERT_SHADER, SCREEN_BLEND_FRAG));
|
|
mPrograms.push(new GlProgram(MASK_VERT_SHADER, OVERLAY_BLEND_FRAG));
|
|
mPrograms.push(new GlProgram(MASK_VERT_SHADER, COLOR_DODGE_BLEND_FRAG));
|
|
mPrograms.push(new GlProgram(MASK_VERT_SHADER, COLOR_BURN_BLEND_FRAG));
|
|
mPrograms.push(new GlProgram(MASK_VERT_SHADER, HARD_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, EXCLUSION_BLEND_FRAG));
|
|
|
|
// effects
|
|
mPrograms.push(new GlProgram(EFFECT_VERTEX, GAUSSIAN_VERTICAL));
|
|
mPrograms.push(new GlProgram(EFFECT_VERTEX, GAUSSIAN_HORIZONTAL));
|
|
mPrograms.push(new GlProgram(EFFECT_VERTEX, EFFECT_DROPSHADOW));
|
|
mPrograms.push(new GlProgram(EFFECT_VERTEX, EFFECT_FILL));
|
|
mPrograms.push(new GlProgram(EFFECT_VERTEX, EFFECT_TINT));
|
|
mPrograms.push(new GlProgram(EFFECT_VERTEX, EFFECT_TRITONE));
|
|
}
|
|
|
|
|
|
void GlRenderer::drawPrimitive(GlShape& sdata, const RenderColor& c, RenderUpdateFlag flag, int32_t depth)
|
|
{
|
|
auto vp = currentPass()->getViewport();
|
|
auto bbox = sdata.geometry.viewport;
|
|
|
|
bbox.intersect(vp);
|
|
|
|
auto complexBlend = beginComplexBlending(bbox, sdata.geometry.getBounds());
|
|
|
|
if (complexBlend) {
|
|
vp = currentPass()->getViewport();
|
|
bbox.intersect(vp);
|
|
}
|
|
|
|
auto x = bbox.sx() - vp.sx();
|
|
auto y = bbox.sy() - vp.sy();
|
|
auto w = bbox.sw();
|
|
auto h = bbox.sh();
|
|
|
|
GlRenderTask* task = nullptr;
|
|
if (mBlendMethod != BlendMethod::Normal && !complexBlend) task = new GlSimpleBlendTask(mBlendMethod, mPrograms[RT_Color]);
|
|
else task = new GlRenderTask(mPrograms[RT_Color]);
|
|
|
|
task->setDrawDepth(depth);
|
|
|
|
if (!sdata.geometry.draw(task, &mGpuBuffer, flag)) {
|
|
delete task;
|
|
return;
|
|
}
|
|
|
|
y = vp.sh() - y - h;
|
|
task->setViewport({{x, y}, {x + w, y + h}});
|
|
|
|
GlRenderTask* stencilTask = nullptr;
|
|
|
|
GlStencilMode stencilMode = sdata.geometry.getStencilMode(flag);
|
|
if (stencilMode != GlStencilMode::None) {
|
|
stencilTask = new GlRenderTask(mPrograms[RT_Stencil], task);
|
|
stencilTask->setDrawDepth(depth);
|
|
}
|
|
|
|
auto a = MULTIPLY(c.a, sdata.opacity);
|
|
|
|
if (flag & RenderUpdateFlag::Stroke) {
|
|
float strokeWidth = sdata.rshape->strokeWidth() * getScaleFactor(sdata.geometry.matrix);
|
|
if (strokeWidth < MIN_GL_STROKE_WIDTH) {
|
|
float alpha = strokeWidth / MIN_GL_STROKE_WIDTH;
|
|
a = MULTIPLY(a, static_cast<uint8_t>(alpha * 255));
|
|
}
|
|
}
|
|
|
|
// matrix buffer
|
|
float matrix44[16];
|
|
currentPass()->getMatrix(matrix44, sdata.geometry.matrix);
|
|
auto viewOffset = mGpuBuffer.push(matrix44, 16 * sizeof(float), true);
|
|
|
|
task->addBindResource(GlBindingResource{
|
|
0,
|
|
task->getProgram()->getUniformBlockIndex("Matrix"),
|
|
mGpuBuffer.getBufferId(),
|
|
viewOffset,
|
|
16 * sizeof(float),
|
|
});
|
|
|
|
if (stencilTask) {
|
|
stencilTask->addBindResource(GlBindingResource{
|
|
0,
|
|
stencilTask->getProgram()->getUniformBlockIndex("Matrix"),
|
|
mGpuBuffer.getBufferId(),
|
|
viewOffset,
|
|
16 * sizeof(float),
|
|
});
|
|
}
|
|
|
|
// color
|
|
float color[] = {c.r / 255.f, c.g / 255.f, c.b / 255.f, a / 255.f};
|
|
|
|
task->addBindResource(GlBindingResource{
|
|
1,
|
|
task->getProgram()->getUniformBlockIndex("ColorInfo"),
|
|
mGpuBuffer.getBufferId(),
|
|
mGpuBuffer.push(color, 4 * sizeof(float), true),
|
|
4 * sizeof(float),
|
|
});
|
|
|
|
if (stencilTask) currentPass()->addRenderTask(new GlStencilCoverTask(stencilTask, task, stencilMode));
|
|
else currentPass()->addRenderTask(task);
|
|
|
|
if (complexBlend) {
|
|
auto task = new GlRenderTask(mPrograms[RT_Stencil]);
|
|
sdata.geometry.draw(task, &mGpuBuffer, flag);
|
|
endBlendingCompose(task, sdata.geometry.matrix);
|
|
}
|
|
}
|
|
|
|
|
|
void GlRenderer::drawPrimitive(GlShape& sdata, const Fill* fill, RenderUpdateFlag flag, int32_t depth)
|
|
{
|
|
auto vp = currentPass()->getViewport();
|
|
auto bbox = sdata.geometry.viewport;
|
|
bbox.intersect(vp);
|
|
|
|
const Fill::ColorStop* stops = nullptr;
|
|
auto stopCnt = min(fill->colorStops(&stops), static_cast<uint32_t>(MAX_GRADIENT_STOPS));
|
|
if (stopCnt < 2) return;
|
|
|
|
GlRenderTask* task = nullptr;
|
|
|
|
if (fill->type() == Type::LinearGradient) task = new GlRenderTask(mPrograms[RT_LinGradient]);
|
|
else if (fill->type() == Type::RadialGradient) task = new GlRenderTask(mPrograms[RT_RadGradient]);
|
|
else return;
|
|
|
|
task->setDrawDepth(depth);
|
|
|
|
if (!sdata.geometry.draw(task, &mGpuBuffer, flag)) {
|
|
delete task;
|
|
return;
|
|
}
|
|
|
|
auto complexBlend = beginComplexBlending(bbox, sdata.geometry.getBounds());
|
|
if (complexBlend) vp = currentPass()->getViewport();
|
|
|
|
auto x = bbox.sx() - vp.sx();
|
|
auto y = vp.sh() - (bbox.sy() - vp.sy()) - bbox.sh();
|
|
task->setViewport({{x, y}, {x + bbox.sw(), y + bbox.sh()}});
|
|
|
|
GlRenderTask* stencilTask = nullptr;
|
|
GlStencilMode stencilMode = sdata.geometry.getStencilMode(flag);
|
|
if (stencilMode != GlStencilMode::None) {
|
|
stencilTask = new GlRenderTask(mPrograms[RT_Stencil], task);
|
|
stencilTask->setDrawDepth(depth);
|
|
}
|
|
|
|
// matrix buffer
|
|
float invMat4[16];
|
|
Matrix inv;
|
|
inverse(&fill->transform(), &inv);
|
|
GET_MATRIX44(inv, invMat4);
|
|
|
|
float matrix44[16];
|
|
currentPass()->getMatrix(matrix44, sdata.geometry.matrix);
|
|
|
|
auto viewOffset = mGpuBuffer.push(matrix44, 16 * sizeof(float), true);
|
|
|
|
task->addBindResource(GlBindingResource{
|
|
0,
|
|
task->getProgram()->getUniformBlockIndex("Matrix"),
|
|
mGpuBuffer.getBufferId(),
|
|
viewOffset,
|
|
16 * sizeof(float),
|
|
});
|
|
|
|
if (stencilTask) {
|
|
stencilTask->addBindResource(GlBindingResource{
|
|
0,
|
|
stencilTask->getProgram()->getUniformBlockIndex("Matrix"),
|
|
mGpuBuffer.getBufferId(),
|
|
viewOffset,
|
|
16 * sizeof(float),
|
|
});
|
|
}
|
|
|
|
viewOffset = mGpuBuffer.push(invMat4, 16 * sizeof(float), true);
|
|
|
|
task->addBindResource(GlBindingResource{
|
|
1,
|
|
task->getProgram()->getUniformBlockIndex("InvMatrix"),
|
|
mGpuBuffer.getBufferId(),
|
|
viewOffset,
|
|
16 * sizeof(float),
|
|
});
|
|
|
|
auto alpha = sdata.opacity / 255.f;
|
|
|
|
if (flag & RenderUpdateFlag::GradientStroke) {
|
|
auto strokeWidth = sdata.rshape->strokeWidth();
|
|
if (strokeWidth < MIN_GL_STROKE_WIDTH) {
|
|
alpha = strokeWidth / MIN_GL_STROKE_WIDTH;
|
|
}
|
|
}
|
|
|
|
// gradient block
|
|
GlBindingResource gradientBinding{};
|
|
auto loc = task->getProgram()->getUniformBlockIndex("GradientInfo");
|
|
|
|
if (fill->type() == Type::LinearGradient) {
|
|
auto linearFill = static_cast<const LinearGradient*>(fill);
|
|
|
|
GlLinearGradientBlock gradientBlock;
|
|
|
|
gradientBlock.nStops[1] = NOISE_LEVEL;
|
|
gradientBlock.nStops[2] = static_cast<int32_t>(fill->spread()) * 1.f;
|
|
uint32_t nStops = 0;
|
|
for (uint32_t i = 0; i < stopCnt; ++i) {
|
|
if (i > 0 && gradientBlock.stopPoints[nStops - 1] > stops[i].offset) continue;
|
|
|
|
gradientBlock.stopPoints[i] = stops[i].offset;
|
|
gradientBlock.stopColors[i * 4 + 0] = stops[i].r / 255.f;
|
|
gradientBlock.stopColors[i * 4 + 1] = stops[i].g / 255.f;
|
|
gradientBlock.stopColors[i * 4 + 2] = stops[i].b / 255.f;
|
|
gradientBlock.stopColors[i * 4 + 3] = stops[i].a / 255.f * alpha;
|
|
nStops++;
|
|
}
|
|
gradientBlock.nStops[0] = nStops * 1.f;
|
|
|
|
float x1, x2, y1, y2;
|
|
linearFill->linear(&x1, &y1, &x2, &y2);
|
|
|
|
gradientBlock.startPos[0] = x1;
|
|
gradientBlock.startPos[1] = y1;
|
|
gradientBlock.stopPos[0] = x2;
|
|
gradientBlock.stopPos[1] = y2;
|
|
|
|
gradientBinding = GlBindingResource{
|
|
2,
|
|
loc,
|
|
mGpuBuffer.getBufferId(),
|
|
mGpuBuffer.push(&gradientBlock, sizeof(GlLinearGradientBlock), true),
|
|
sizeof(GlLinearGradientBlock),
|
|
};
|
|
} else {
|
|
auto radialFill = static_cast<const RadialGradient*>(fill);
|
|
|
|
GlRadialGradientBlock gradientBlock;
|
|
|
|
gradientBlock.nStops[1] = NOISE_LEVEL;
|
|
gradientBlock.nStops[2] = static_cast<int32_t>(fill->spread()) * 1.f;
|
|
|
|
uint32_t nStops = 0;
|
|
for (uint32_t i = 0; i < stopCnt; ++i) {
|
|
if (i > 0 && gradientBlock.stopPoints[nStops - 1] > stops[i].offset) continue;
|
|
|
|
gradientBlock.stopPoints[i] = stops[i].offset;
|
|
gradientBlock.stopColors[i * 4 + 0] = stops[i].r / 255.f;
|
|
gradientBlock.stopColors[i * 4 + 1] = stops[i].g / 255.f;
|
|
gradientBlock.stopColors[i * 4 + 2] = stops[i].b / 255.f;
|
|
gradientBlock.stopColors[i * 4 + 3] = stops[i].a / 255.f * alpha;
|
|
nStops++;
|
|
}
|
|
gradientBlock.nStops[0] = nStops * 1.f;
|
|
|
|
float x, y, r, fx, fy, fr;
|
|
radialFill->radial(&x, &y, &r, &fx, &fy, &fr);
|
|
|
|
gradientBlock.centerPos[0] = fx;
|
|
gradientBlock.centerPos[1] = fy;
|
|
gradientBlock.centerPos[2] = x;
|
|
gradientBlock.centerPos[3] = y;
|
|
gradientBlock.radius[0] = fr;
|
|
gradientBlock.radius[1] = r;
|
|
|
|
gradientBinding = GlBindingResource{
|
|
2,
|
|
loc,
|
|
mGpuBuffer.getBufferId(),
|
|
mGpuBuffer.push(&gradientBlock, sizeof(GlRadialGradientBlock), true),
|
|
sizeof(GlRadialGradientBlock),
|
|
};
|
|
}
|
|
|
|
task->addBindResource(gradientBinding);
|
|
|
|
if (stencilTask) {
|
|
currentPass()->addRenderTask(new GlStencilCoverTask(stencilTask, task, stencilMode));
|
|
} else {
|
|
currentPass()->addRenderTask(task);
|
|
}
|
|
|
|
if (complexBlend) {
|
|
auto task = new GlRenderTask(mPrograms[RT_Stencil]);
|
|
sdata.geometry.draw(task, &mGpuBuffer, flag);
|
|
endBlendingCompose(task, sdata.geometry.matrix);
|
|
}
|
|
}
|
|
|
|
|
|
void GlRenderer::drawClip(Array<RenderData>& clips)
|
|
{
|
|
Array<float> identityVertex(4 * 2);
|
|
float left = -1.f;
|
|
float top = 1.f;
|
|
float right = 1.f;
|
|
float bottom = -1.f;
|
|
|
|
identityVertex.push(left);
|
|
identityVertex.push(top);
|
|
identityVertex.push(left);
|
|
identityVertex.push(bottom);
|
|
identityVertex.push(right);
|
|
identityVertex.push(top);
|
|
identityVertex.push(right);
|
|
identityVertex.push(bottom);
|
|
|
|
Array<uint32_t> identityIndex(6);
|
|
identityIndex.push(0);
|
|
identityIndex.push(1);
|
|
identityIndex.push(2);
|
|
identityIndex.push(2);
|
|
identityIndex.push(1);
|
|
identityIndex.push(3);
|
|
|
|
float mat4[16];
|
|
memset(mat4, 0, sizeof(float) * 16);
|
|
mat4[0] = 1.f;
|
|
mat4[5] = 1.f;
|
|
mat4[10] = 1.f;
|
|
mat4[15] = 1.f;
|
|
|
|
auto identityVertexOffset = mGpuBuffer.push(identityVertex.data, 8 * sizeof(float));
|
|
auto identityIndexOffset = mGpuBuffer.pushIndex(identityIndex.data, 6 * sizeof(uint32_t));
|
|
auto mat4Offset = mGpuBuffer.push(mat4, 16 * sizeof(float), true);
|
|
|
|
Array<int32_t> clipDepths(clips.count);
|
|
clipDepths.count = clips.count;
|
|
|
|
for (int32_t i = clips.count - 1; i >= 0; i--) {
|
|
clipDepths[i] = currentPass()->nextDrawDepth();
|
|
}
|
|
|
|
const auto& vp = currentPass()->getViewport();
|
|
|
|
for (uint32_t i = 0; i < clips.count; ++i) {
|
|
auto sdata = static_cast<GlShape*>(clips[i]);
|
|
auto clipTask = new GlRenderTask(mPrograms[RT_Stencil]);
|
|
clipTask->setDrawDepth(clipDepths[i]);
|
|
|
|
auto flag = (sdata->geometry.stroke.vertex.count > 0) ? RenderUpdateFlag::Stroke : RenderUpdateFlag::Path;
|
|
sdata->geometry.draw(clipTask, &mGpuBuffer, flag);
|
|
|
|
auto bbox = sdata->geometry.viewport;
|
|
bbox.intersect(vp);
|
|
|
|
auto x = bbox.sx() - vp.sx();
|
|
auto y = vp.sh() - (bbox.sy() - vp.sy()) - bbox.sh();
|
|
clipTask->setViewport({{x, y}, {x + bbox.sw(), y + bbox.sh()}});
|
|
|
|
float matrix44[16];
|
|
currentPass()->getMatrix(matrix44, sdata->geometry.matrix);
|
|
|
|
auto loc = clipTask->getProgram()->getUniformBlockIndex("Matrix");
|
|
auto viewOffset = mGpuBuffer.push(matrix44, 16 * sizeof(float), true);
|
|
|
|
clipTask->addBindResource(GlBindingResource{0, loc, mGpuBuffer.getBufferId(), viewOffset, 16 * sizeof(float), });
|
|
|
|
auto maskTask = new GlRenderTask(mPrograms[RT_Stencil]);
|
|
|
|
maskTask->setDrawDepth(clipDepths[i]);
|
|
maskTask->addVertexLayout(GlVertexLayout{0, 2, 2 * sizeof(float), identityVertexOffset});
|
|
maskTask->addBindResource(GlBindingResource{0, loc, mGpuBuffer.getBufferId(), mat4Offset, 16 * sizeof(float), });
|
|
maskTask->setDrawRange(identityIndexOffset, 6);
|
|
maskTask->setViewport({{0, 0}, {vp.sw(), vp.sh()}});
|
|
|
|
currentPass()->addRenderTask(new GlClipTask(clipTask, maskTask));
|
|
}
|
|
}
|
|
|
|
GlRenderPass* GlRenderer::currentPass()
|
|
{
|
|
if (mRenderPassStack.empty()) return nullptr;
|
|
return mRenderPassStack.last();
|
|
}
|
|
|
|
bool GlRenderer::beginComplexBlending(const RenderRegion& vp, RenderRegion bounds)
|
|
{
|
|
if (vp.invalid()) return false;
|
|
|
|
bounds.intersect(vp);
|
|
if (bounds.invalid()) return false;
|
|
|
|
if (mBlendMethod == BlendMethod::Normal || mBlendMethod == BlendMethod::Add || mBlendMethod == BlendMethod::Darken || mBlendMethod == BlendMethod::Lighten) return false;
|
|
|
|
if (mBlendPool.empty()) mBlendPool.push(new GlRenderTargetPool(surface.w, surface.h));
|
|
|
|
auto blendFbo = mBlendPool[0]->getRenderTarget(bounds);
|
|
|
|
mRenderPassStack.push(new GlRenderPass(blendFbo));
|
|
|
|
return true;
|
|
}
|
|
|
|
void GlRenderer::endBlendingCompose(GlRenderTask* stencilTask, const Matrix& matrix)
|
|
{
|
|
auto blendPass = mRenderPassStack.last();
|
|
mRenderPassStack.pop();
|
|
|
|
blendPass->setDrawDepth(currentPass()->nextDrawDepth());
|
|
|
|
auto composeTask = blendPass->endRenderPass<GlComposeTask>(nullptr, currentPass()->getFboId());
|
|
|
|
const auto& vp = blendPass->getViewport();
|
|
if (mBlendPool.count < 2) mBlendPool.push(new GlRenderTargetPool(surface.w, surface.h));
|
|
auto dstCopyFbo = mBlendPool[1]->getRenderTarget(vp);
|
|
|
|
auto x = vp.sx();
|
|
auto y = currentPass()->getViewport().sh() - vp.sy() - vp.sh();
|
|
stencilTask->setViewport({{x, y}, {x + vp.sw(), y + vp.sh()}});
|
|
|
|
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());
|
|
task->setDrawDepth(currentPass()->nextDrawDepth());
|
|
|
|
// src and dst texture
|
|
task->addBindResource(GlBindingResource{1, blendPass->getFbo()->getColorTexture(), task->getProgram()->getUniformLocation("uSrcTexture")});
|
|
task->addBindResource(GlBindingResource{2, dstCopyFbo->getColorTexture(), task->getProgram()->getUniformLocation("uDstTexture")});
|
|
|
|
currentPass()->addRenderTask(task);
|
|
|
|
delete(blendPass);
|
|
}
|
|
|
|
GlProgram* GlRenderer::getBlendProgram()
|
|
{
|
|
switch (mBlendMethod) {
|
|
case BlendMethod::Multiply: return mPrograms[RT_MultiplyBlend];
|
|
case BlendMethod::Screen: return mPrograms[RT_ScreenBlend];
|
|
case BlendMethod::Overlay: return mPrograms[RT_OverlayBlend];
|
|
case BlendMethod::ColorDodge: return mPrograms[RT_ColorDodgeBlend];
|
|
case BlendMethod::ColorBurn: return mPrograms[RT_ColorBurnBlend];
|
|
case BlendMethod::HardLight: return mPrograms[RT_HardLightBlend];
|
|
case BlendMethod::SoftLight: return mPrograms[RT_SoftLightBlend];
|
|
case BlendMethod::Difference: return mPrograms[RT_DifferenceBlend];
|
|
case BlendMethod::Exclusion: return mPrograms[RT_ExclusionBlend];
|
|
default: return nullptr;
|
|
}
|
|
}
|
|
|
|
|
|
void GlRenderer::prepareBlitTask(GlBlitTask* task)
|
|
{
|
|
prepareCmpTask(task, {{0, 0}, {int32_t(surface.w), int32_t(surface.h)}}, surface.w, surface.h);
|
|
task->addBindResource(GlBindingResource{0, task->getColorTexture(), task->getProgram()->getUniformLocation("uSrcTexture")});
|
|
}
|
|
|
|
|
|
void GlRenderer::prepareCmpTask(GlRenderTask* task, const RenderRegion& vp, uint32_t cmpWidth, uint32_t cmpHeight)
|
|
{
|
|
// we use 1:1 blit mapping since compositor fbo is same size as root fbo
|
|
Array<float> vertices(4 * 4);
|
|
|
|
const auto& passVp = currentPass()->getViewport();
|
|
|
|
auto taskVp = vp;
|
|
taskVp.intersect(passVp);
|
|
|
|
auto x = taskVp.sx() - passVp.sx();
|
|
auto y = taskVp.sy() - passVp.sy();
|
|
auto w = taskVp.sw();
|
|
auto h = taskVp.sh();
|
|
|
|
float rw = static_cast<float>(passVp.w());
|
|
float rh = static_cast<float>(passVp.h());
|
|
|
|
float l = static_cast<float>(x);
|
|
float t = static_cast<float>(rh - y);
|
|
float r = static_cast<float>(x + w);
|
|
float b = static_cast<float>(rh - y - h);
|
|
|
|
// map vp ltrp to -1:1
|
|
float left = (l / rw) * 2.f - 1.f;
|
|
float top = (t / rh) * 2.f - 1.f;
|
|
float right = (r / rw) * 2.f - 1.f;
|
|
float bottom = (b / rh) * 2.f - 1.f;
|
|
|
|
float uw = static_cast<float>(w) / static_cast<float>(cmpWidth);
|
|
float uh = static_cast<float>(h) / static_cast<float>(cmpHeight);
|
|
|
|
// left top point
|
|
vertices.push(left);
|
|
vertices.push(top);
|
|
vertices.push(0.f);
|
|
vertices.push(uh);
|
|
// left bottom point
|
|
vertices.push(left);
|
|
vertices.push(bottom);
|
|
vertices.push(0.f);
|
|
vertices.push(0.f);
|
|
// right top point
|
|
vertices.push(right);
|
|
vertices.push(top);
|
|
vertices.push(uw);
|
|
vertices.push(uh);
|
|
// right bottom point
|
|
vertices.push(right);
|
|
vertices.push(bottom);
|
|
vertices.push(uw);
|
|
vertices.push(0.f);
|
|
|
|
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, 4 * sizeof(float), vertexOffset});
|
|
task->addVertexLayout(GlVertexLayout{1, 2, 4 * sizeof(float), vertexOffset + 2 * sizeof(float)});
|
|
task->setDrawRange(indexOffset, indices.count);
|
|
y = (passVp.sh() - y - h);
|
|
task->setViewport({{x, y}, {x + w, y + h}});
|
|
}
|
|
|
|
|
|
void GlRenderer::endRenderPass(RenderCompositor* cmp)
|
|
{
|
|
auto glCmp = static_cast<GlCompositor*>(cmp);
|
|
if (cmp->method != MaskMethod::None) {
|
|
auto selfPass = mRenderPassStack.last();
|
|
mRenderPassStack.pop();
|
|
|
|
// mask is pushed first
|
|
auto maskPass = mRenderPassStack.last();
|
|
mRenderPassStack.pop();
|
|
|
|
GlProgram* program = nullptr;
|
|
switch(cmp->method) {
|
|
case MaskMethod::Alpha: program = mPrograms[RT_MaskAlpha]; break;
|
|
case MaskMethod::InvAlpha: program = mPrograms[RT_MaskAlphaInv]; break;
|
|
case MaskMethod::Luma: program = mPrograms[RT_MaskLuma]; break;
|
|
case MaskMethod::InvLuma: program = mPrograms[RT_MaskLumaInv]; break;
|
|
case MaskMethod::Add: program = mPrograms[RT_MaskAdd]; break;
|
|
case MaskMethod::Subtract: program = mPrograms[RT_MaskSub]; break;
|
|
case MaskMethod::Intersect: program = mPrograms[RT_MaskIntersect]; break;
|
|
case MaskMethod::Difference: program = mPrograms[RT_MaskDifference]; break;
|
|
case MaskMethod::Lighten: program = mPrograms[RT_MaskLighten]; break;
|
|
case MaskMethod::Darken: program = mPrograms[RT_MaskDarken]; break;
|
|
default: break;
|
|
}
|
|
|
|
if (program && !selfPass->isEmpty() && !maskPass->isEmpty()) {
|
|
auto prev_task = maskPass->endRenderPass<GlComposeTask>(nullptr, currentPass()->getFboId());
|
|
prev_task->setDrawDepth(currentPass()->nextDrawDepth());
|
|
prev_task->setRenderSize(glCmp->bbox.w(), glCmp->bbox.h());
|
|
prev_task->setViewport(glCmp->bbox);
|
|
|
|
auto compose_task = selfPass->endRenderPass<GlDrawBlitTask>(program, currentPass()->getFboId());
|
|
compose_task->setRenderSize(glCmp->bbox.w(), glCmp->bbox.h());
|
|
compose_task->setPrevTask(prev_task);
|
|
|
|
prepareCmpTask(compose_task, glCmp->bbox, selfPass->getFboWidth(), selfPass->getFboHeight());
|
|
|
|
compose_task->addBindResource(GlBindingResource{0, selfPass->getTextureId(), program->getUniformLocation("uSrcTexture")});
|
|
compose_task->addBindResource(GlBindingResource{1, maskPass->getTextureId(), program->getUniformLocation("uMaskTexture")});
|
|
|
|
compose_task->setDrawDepth(currentPass()->nextDrawDepth());
|
|
compose_task->setParentSize(currentPass()->getViewport().w(), currentPass()->getViewport().h());
|
|
currentPass()->addRenderTask(compose_task);
|
|
}
|
|
|
|
delete(selfPass);
|
|
delete(maskPass);
|
|
} else {
|
|
|
|
auto renderPass = mRenderPassStack.last();
|
|
mRenderPassStack.pop();
|
|
|
|
if (!renderPass->isEmpty()) {
|
|
auto task = renderPass->endRenderPass<GlDrawBlitTask>(mPrograms[RT_Image], currentPass()->getFboId());
|
|
task->setRenderSize(glCmp->bbox.w(), glCmp->bbox.h());
|
|
prepareCmpTask(task, glCmp->bbox, renderPass->getFboWidth(), renderPass->getFboHeight());
|
|
task->setDrawDepth(currentPass()->nextDrawDepth());
|
|
|
|
// matrix buffer
|
|
float matrix[16] = {1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1};
|
|
|
|
task->addBindResource(GlBindingResource{
|
|
0,
|
|
task->getProgram()->getUniformBlockIndex("Matrix"),
|
|
mGpuBuffer.getBufferId(),
|
|
mGpuBuffer.push(matrix, 16 * sizeof(float), true),
|
|
16 * sizeof(float),
|
|
});
|
|
|
|
// image info
|
|
uint32_t info[4] = {(uint32_t)ColorSpace::ABGR8888, 0, cmp->opacity, 0};
|
|
|
|
task->addBindResource(GlBindingResource{
|
|
1,
|
|
task->getProgram()->getUniformBlockIndex("ColorInfo"),
|
|
mGpuBuffer.getBufferId(),
|
|
mGpuBuffer.push(info, 4 * sizeof(uint32_t), true),
|
|
4 * sizeof(uint32_t),
|
|
});
|
|
|
|
// texture id
|
|
task->addBindResource(GlBindingResource{0, renderPass->getTextureId(), task->getProgram()->getUniformLocation("uTexture")});
|
|
task->setParentSize(currentPass()->getViewport().w(), currentPass()->getViewport().h());
|
|
currentPass()->addRenderTask(std::move(task));
|
|
}
|
|
delete(renderPass);
|
|
}
|
|
}
|
|
|
|
|
|
/************************************************************************/
|
|
/* External Class Implementation */
|
|
/************************************************************************/
|
|
|
|
bool GlRenderer::clear()
|
|
{
|
|
if (mRootTarget.invalid()) return false;
|
|
|
|
mClearBuffer = true;
|
|
return true;
|
|
}
|
|
|
|
|
|
bool GlRenderer::target(void* context, int32_t id, uint32_t w, uint32_t h)
|
|
{
|
|
//assume the context zero is invalid
|
|
if (!context || id == GL_INVALID_VALUE || w == 0 || h == 0) return false;
|
|
|
|
if (mContext) currentContext();
|
|
|
|
flush();
|
|
|
|
surface.stride = w;
|
|
surface.w = w;
|
|
surface.h = h;
|
|
|
|
mContext = context;
|
|
mTargetFboId = static_cast<GLint>(id);
|
|
|
|
currentContext();
|
|
|
|
mRootTarget.setViewport({{0, 0}, {int32_t(surface.w), int32_t(surface.h)}});
|
|
mRootTarget.init(surface.w, surface.h, mTargetFboId);
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
bool GlRenderer::sync()
|
|
{
|
|
//nothing to be done.
|
|
if (mRenderPassStack.empty()) return true;
|
|
|
|
currentContext();
|
|
|
|
// Blend function for straight alpha
|
|
GL_CHECK(glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA));
|
|
GL_CHECK(glEnable(GL_BLEND));
|
|
GL_CHECK(glEnable(GL_SCISSOR_TEST));
|
|
GL_CHECK(glCullFace(GL_FRONT_AND_BACK));
|
|
GL_CHECK(glFrontFace(GL_CCW));
|
|
GL_CHECK(glEnable(GL_DEPTH_TEST));
|
|
GL_CHECK(glDepthFunc(GL_GREATER));
|
|
|
|
auto task = mRenderPassStack.first()->endRenderPass<GlBlitTask>(mPrograms[RT_Blit], mTargetFboId);
|
|
|
|
prepareBlitTask(task);
|
|
|
|
task->mClearBuffer = mClearBuffer;
|
|
task->setTargetViewport({{0, 0}, {int32_t(surface.w), int32_t(surface.h)}});
|
|
|
|
if (mGpuBuffer.flushToGPU()) {
|
|
mGpuBuffer.bind();
|
|
task->run();
|
|
}
|
|
|
|
mGpuBuffer.unbind();
|
|
|
|
GL_CHECK(glDisable(GL_SCISSOR_TEST));
|
|
|
|
clearDisposes();
|
|
|
|
delete task;
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
RenderRegion GlRenderer::region(RenderData data)
|
|
{
|
|
if (currentPass()->isEmpty()) return {};
|
|
|
|
auto shape = reinterpret_cast<GlShape*>(data);
|
|
auto bounds = shape->geometry.getBounds();
|
|
|
|
auto const& vp = currentPass()->getViewport();
|
|
bounds.intersect(vp);
|
|
|
|
return bounds;
|
|
}
|
|
|
|
|
|
bool GlRenderer::preRender()
|
|
{
|
|
if (mRootTarget.invalid()) return false;
|
|
|
|
currentContext();
|
|
if (mPrograms.empty()) initShaders();
|
|
mRenderPassStack.push(new GlRenderPass(&mRootTarget));
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
bool GlRenderer::postRender()
|
|
{
|
|
return true;
|
|
}
|
|
|
|
|
|
RenderCompositor* GlRenderer::target(const RenderRegion& region, TVG_UNUSED ColorSpace cs, TVG_UNUSED CompositionFlag flags)
|
|
{
|
|
auto vp = region;
|
|
if (currentPass()->isEmpty()) return nullptr;
|
|
|
|
vp.intersect(currentPass()->getViewport());
|
|
|
|
mComposeStack.push(new GlCompositor(vp));
|
|
return mComposeStack.last();
|
|
}
|
|
|
|
|
|
bool GlRenderer::beginComposite(RenderCompositor* cmp, MaskMethod method, uint8_t opacity)
|
|
{
|
|
if (!cmp) return false;
|
|
|
|
cmp->method = method;
|
|
cmp->opacity = opacity;
|
|
|
|
uint32_t index = mRenderPassStack.count - 1;
|
|
if (index >= mComposePool.count) mComposePool.push( new GlRenderTargetPool(surface.w, surface.h));
|
|
|
|
auto glCmp = static_cast<GlCompositor*>(cmp);
|
|
if (glCmp->bbox.valid()) mRenderPassStack.push(new GlRenderPass(mComposePool[index]->getRenderTarget(glCmp->bbox)));
|
|
else mRenderPassStack.push(new GlRenderPass(nullptr));
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
bool GlRenderer::endComposite(RenderCompositor* cmp)
|
|
{
|
|
if (mComposeStack.empty()) return false;
|
|
if (mComposeStack.last() != cmp) return false;
|
|
|
|
// end current render pass;
|
|
auto curCmp = mComposeStack.last();
|
|
mComposeStack.pop();
|
|
|
|
assert(cmp == curCmp);
|
|
|
|
endRenderPass(curCmp);
|
|
|
|
delete(curCmp);
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
void GlRenderer::effectGaussianBlurUpdate(RenderEffectGaussianBlur* effect, const Matrix& transform)
|
|
{
|
|
GlGaussianBlur* blur = (GlGaussianBlur*)effect->rd;
|
|
if (!blur) blur = tvg::malloc<GlGaussianBlur*>(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;
|
|
}
|
|
|
|
|
|
void GlRenderer::effectDropShadowUpdate(RenderEffectDropShadow* effect, const Matrix& transform)
|
|
{
|
|
GlDropShadow* dropShadow = (GlDropShadow*)effect->rd;
|
|
if (!dropShadow) dropShadow = tvg::malloc<GlDropShadow*>(sizeof(GlDropShadow));
|
|
const float sigma = effect->sigma;
|
|
const float scale = std::sqrt(transform.e11 * transform.e11 + transform.e12 * transform.e12);
|
|
const float radian = tvg::deg2rad(90.0f - effect->angle);
|
|
const Point offset = {
|
|
-effect->distance * cosf(radian) * scale,
|
|
-effect->distance * sinf(radian) * scale
|
|
};
|
|
dropShadow->sigma = sigma;
|
|
dropShadow->scale = scale;
|
|
dropShadow->level = int(GL_GAUSSIAN_MAX_LEVEL * ((effect->quality - 1) * 0.01f)) + 1;
|
|
dropShadow->color[3] = effect->color[3] / 255.0f;
|
|
//Drop shadow effect applies blending in the shader (GL_BLEND disabled), so the color should be premultiplied:
|
|
dropShadow->color[0] = effect->color[0] / 255.0f * dropShadow->color[3];
|
|
dropShadow->color[1] = effect->color[1] / 255.0f * dropShadow->color[3];
|
|
dropShadow->color[2] = effect->color[2] / 255.0f * dropShadow->color[3];
|
|
dropShadow->offset[0] = offset.x;
|
|
dropShadow->offset[1] = offset.y;
|
|
dropShadow->extend = 2 * std::max(sigma * scale + std::abs(offset.x), sigma * scale + std::abs(offset.y));
|
|
effect->rd = dropShadow;
|
|
effect->valid = true;
|
|
}
|
|
|
|
|
|
void GlRenderer::effectFillUpdate(RenderEffectFill* effect, const Matrix& transform)
|
|
{
|
|
auto params = (GlEffectParams*)effect->rd;
|
|
if (!params) params = tvg::malloc<GlEffectParams*>(sizeof(GlEffectParams));
|
|
params->params[0] = effect->color[0] / 255.0f;
|
|
params->params[1] = effect->color[1] / 255.0f;
|
|
params->params[2] = effect->color[2] / 255.0f;
|
|
params->params[3] = effect->color[3] / 255.0f;
|
|
effect->rd = params;
|
|
effect->valid = true;
|
|
}
|
|
|
|
|
|
void GlRenderer::effectTintUpdate(RenderEffectTint* effect, const Matrix& transform)
|
|
{
|
|
auto params = (GlEffectParams*)effect->rd;
|
|
if (!params) params = tvg::malloc<GlEffectParams*>(sizeof(GlEffectParams));
|
|
params->params[0] = effect->black[0] / 255.0f;
|
|
params->params[1] = effect->black[1] / 255.0f;
|
|
params->params[2] = effect->black[2] / 255.0f;
|
|
params->params[3] = 0.0f;
|
|
params->params[4] = effect->white[0] / 255.0f;
|
|
params->params[5] = effect->white[1] / 255.0f;
|
|
params->params[6] = effect->white[2] / 255.0f;
|
|
params->params[7] = 0.0f;
|
|
params->params[8] = effect->intensity / 255.0f;
|
|
effect->rd = params;
|
|
effect->valid = true;
|
|
}
|
|
|
|
|
|
void GlRenderer::effectTritoneUpdate(RenderEffectTritone* effect, const Matrix& transform)
|
|
{
|
|
auto params = (GlEffectParams*)effect->rd;
|
|
if (!params) params = tvg::malloc<GlEffectParams*>(sizeof(GlEffectParams));
|
|
params->params[0] = effect->shadow[0] / 255.0f;
|
|
params->params[1] = effect->shadow[1] / 255.0f;
|
|
params->params[2] = effect->shadow[2] / 255.0f;
|
|
params->params[3] = 0.0f;
|
|
params->params[4] = effect->midtone[0] / 255.0f;
|
|
params->params[5] = effect->midtone[1] / 255.0f;
|
|
params->params[6] = effect->midtone[2] / 255.0f;
|
|
params->params[7] = 0.0f;
|
|
params->params[8] = effect->highlight[0] / 255.0f;
|
|
params->params[9] = effect->highlight[1] / 255.0f;
|
|
params->params[10] = effect->highlight[2] / 255.0f;
|
|
params->params[11] = 0.0f;
|
|
effect->rd = params;
|
|
effect->valid = true;
|
|
}
|
|
|
|
|
|
bool GlRenderer::effectGaussianBlurRegion(RenderEffectGaussianBlur* effect)
|
|
{
|
|
auto gaussianBlur = (GlGaussianBlur*)effect->rd;
|
|
if (effect->direction != 2) {
|
|
effect->extend.min.x = -gaussianBlur->extend;
|
|
effect->extend.max.x = +gaussianBlur->extend;
|
|
}
|
|
if (effect->direction != 1) {
|
|
effect->extend.min.y = -gaussianBlur->extend;
|
|
effect->extend.max.y = +gaussianBlur->extend;
|
|
}
|
|
return true;
|
|
};
|
|
|
|
|
|
bool GlRenderer::effectDropShadowRegion(RenderEffectDropShadow* effect)
|
|
{
|
|
auto gaussianBlur = (GlDropShadow*)effect->rd;
|
|
effect->extend.min.x = -gaussianBlur->extend;
|
|
effect->extend.max.x = +gaussianBlur->extend;
|
|
effect->extend.min.y = -gaussianBlur->extend;
|
|
effect->extend.max.y = +gaussianBlur->extend;
|
|
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<RenderEffectGaussianBlur*>(effect), transform); break;
|
|
case SceneEffect::DropShadow : effectDropShadowUpdate(static_cast<RenderEffectDropShadow*>(effect), transform); break;
|
|
case SceneEffect::Fill: effectFillUpdate(static_cast<RenderEffectFill*>(effect), transform); break;
|
|
case SceneEffect::Tint: effectTintUpdate(static_cast<RenderEffectTint*>(effect), transform); break;
|
|
case SceneEffect::Tritone: effectTritoneUpdate(static_cast<RenderEffectTritone*>(effect), transform); break;
|
|
default: break;
|
|
}
|
|
effect->valid = true;
|
|
}
|
|
|
|
|
|
bool GlRenderer::region(RenderEffect* effect)
|
|
{
|
|
switch (effect->type) {
|
|
case SceneEffect::GaussianBlur: return effectGaussianBlurRegion(static_cast<RenderEffectGaussianBlur*>(effect));
|
|
case SceneEffect::DropShadow : return effectDropShadowRegion(static_cast<RenderEffectDropShadow*>(effect));
|
|
case SceneEffect::Fill:
|
|
case SceneEffect::Tint:
|
|
case SceneEffect::Tritone: return true;
|
|
default: return false;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
|
|
bool GlRenderer::render(TVG_UNUSED RenderCompositor* cmp, const RenderEffect* effect, bool direct)
|
|
{
|
|
// 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.sw(), vp.sh()}});
|
|
// 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);
|
|
} else if (effect->type == SceneEffect::DropShadow) {
|
|
// get programs
|
|
GlProgram* program = mPrograms[RT_DropShadow];
|
|
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
|
|
GlDropShadow* params = (GlDropShadow*)(effect->rd);
|
|
auto paramsOffset = mGpuBuffer.push(params, sizeof(GlDropShadow), true);
|
|
|
|
// create gaussian blur tasks
|
|
auto task = new GlEffectDropShadowTask(program, dstFbo, dstCopyFbo0, dstCopyFbo1);
|
|
task->effect = (RenderEffectDropShadow*)effect;
|
|
task->setViewport({{0, 0}, {vp.sw(), vp.sh()}});
|
|
task->addBindResource(GlBindingResource{0, program->getUniformBlockIndex("DropShadow"), mGpuBuffer.getBufferId(), paramsOffset, sizeof(GlDropShadow)});
|
|
task->addVertexLayout(GlVertexLayout{0, 2, 2 * sizeof(float), voffset});
|
|
task->setDrawRange(ioffset, 6);
|
|
// horizontal blur task and geometry
|
|
task->horzTask = new GlRenderTask(programHorz);
|
|
task->horzTask->addBindResource(GlBindingResource{0, programHorz->getUniformBlockIndex("Gaussian"), mGpuBuffer.getBufferId(), paramsOffset, sizeof(GlGaussianBlur)});
|
|
task->horzTask->addVertexLayout(GlVertexLayout{0, 2, 2 * sizeof(float), voffset});
|
|
task->horzTask->setDrawRange(ioffset, 6);
|
|
// vertical blur task and geometry
|
|
task->vertTask = new GlRenderTask(programVert);
|
|
task->vertTask->addBindResource(GlBindingResource{0, programVert->getUniformBlockIndex("Gaussian"), mGpuBuffer.getBufferId(), paramsOffset, sizeof(GlGaussianBlur)});
|
|
task->vertTask->addVertexLayout(GlVertexLayout{0, 2, 2 * sizeof(float), voffset});
|
|
task->vertTask->setDrawRange(ioffset, 6);
|
|
// add task to render pipeline
|
|
pass->addRenderTask(task);
|
|
} else if ((effect->type == SceneEffect::Fill) || (effect->type == SceneEffect::Tint) || (effect->type == SceneEffect::Tritone)) {
|
|
GlProgram* program{};
|
|
if (effect->type == SceneEffect::Fill) program = mPrograms[RT_EffectFill];
|
|
else if (effect->type == SceneEffect::Tint) program = mPrograms[RT_EffectTint];
|
|
else if (effect->type == SceneEffect::Tritone) program = mPrograms[RT_EffectTritone];
|
|
else return false;
|
|
// get current and intermidiate framebuffers
|
|
auto dstFbo = pass->getFbo();
|
|
auto dstCopyFbo = mBlendPool[0]->getRenderTarget(vp);
|
|
// add uniform data
|
|
auto params = (GlEffectParams*)(effect->rd);
|
|
auto paramsOffset = mGpuBuffer.push(params, sizeof(GlEffectParams), true);
|
|
// create and setup task
|
|
auto task = new GlEffectColorTransformTask(program, dstFbo, dstCopyFbo);
|
|
task->setViewport({{0, 0}, {vp.sw(), vp.sh()}});
|
|
task->addBindResource(GlBindingResource{0, program->getUniformBlockIndex("Params"), mGpuBuffer.getBufferId(), paramsOffset, sizeof(GlEffectParams)});
|
|
task->addVertexLayout(GlVertexLayout{0, 2, 2 * sizeof(float), voffset});
|
|
task->setDrawRange(ioffset, 6);
|
|
// add task to render pipeline
|
|
pass->addRenderTask(task);
|
|
}
|
|
return false;
|
|
}
|
|
|
|
|
|
void GlRenderer::dispose(RenderEffect* effect)
|
|
{
|
|
tvg::free(effect->rd);
|
|
effect->rd = nullptr;
|
|
}
|
|
|
|
|
|
ColorSpace GlRenderer::colorSpace()
|
|
{
|
|
return ColorSpace::Unknown;
|
|
}
|
|
|
|
|
|
const RenderSurface* GlRenderer::mainSurface()
|
|
{
|
|
return &surface;
|
|
}
|
|
|
|
|
|
bool GlRenderer::blend(BlendMethod method)
|
|
{
|
|
if (method == mBlendMethod) return true;
|
|
|
|
mBlendMethod = method;
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
bool GlRenderer::renderImage(void* data)
|
|
{
|
|
auto sdata = static_cast<GlShape*>(data);
|
|
if (!sdata) return false;
|
|
|
|
if (currentPass()->isEmpty() || !(sdata->updateFlag & RenderUpdateFlag::Image)) return true;
|
|
|
|
auto vp = currentPass()->getViewport();
|
|
auto bbox = sdata->geometry.viewport;
|
|
bbox.intersect(vp);
|
|
if (bbox.invalid()) return true;
|
|
|
|
auto x = bbox.sx() - vp.sx();
|
|
auto y = bbox.sy() - vp.sy();
|
|
auto drawDepth = currentPass()->nextDrawDepth();
|
|
|
|
if (!sdata->clips.empty()) drawClip(sdata->clips);
|
|
|
|
auto task = new GlRenderTask(mPrograms[RT_Image]);
|
|
task->setDrawDepth(drawDepth);
|
|
|
|
if (!sdata->geometry.draw(task, &mGpuBuffer, RenderUpdateFlag::Image)) {
|
|
delete task;
|
|
return true;
|
|
}
|
|
|
|
bool complexBlend = beginComplexBlending(bbox, sdata->geometry.getBounds());
|
|
if (complexBlend) vp = currentPass()->getViewport();
|
|
|
|
// matrix buffer
|
|
float matrix44[16];
|
|
currentPass()->getMatrix(matrix44, sdata->geometry.matrix);
|
|
|
|
task->addBindResource(GlBindingResource{
|
|
0,
|
|
task->getProgram()->getUniformBlockIndex("Matrix"),
|
|
mGpuBuffer.getBufferId(),
|
|
mGpuBuffer.push(matrix44, 16 * sizeof(float), true),
|
|
16 * sizeof(float),
|
|
});
|
|
|
|
// image info
|
|
uint32_t info[4] = {(uint32_t)sdata->texColorSpace, sdata->texFlipY, sdata->opacity, 0};
|
|
|
|
task->addBindResource(GlBindingResource{
|
|
1,
|
|
task->getProgram()->getUniformBlockIndex("ColorInfo"),
|
|
mGpuBuffer.getBufferId(),
|
|
mGpuBuffer.push(info, 4 * sizeof(uint32_t), true),
|
|
4 * sizeof(uint32_t),
|
|
});
|
|
|
|
// texture id
|
|
task->addBindResource(GlBindingResource{0, sdata->texId, task->getProgram()->getUniformLocation("uTexture")});
|
|
|
|
y = vp.sh() - y - bbox.sh();
|
|
auto x2 = x + bbox.sw();
|
|
auto y2 = y + bbox.sh();
|
|
|
|
task->setViewport({{x, y}, {x2, y2}});
|
|
|
|
currentPass()->addRenderTask(task);
|
|
|
|
if (complexBlend) {
|
|
auto task = new GlRenderTask(mPrograms[RT_Stencil]);
|
|
sdata->geometry.draw(task, &mGpuBuffer, RenderUpdateFlag::Image);
|
|
endBlendingCompose(task, sdata->geometry.matrix);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
bool GlRenderer::renderShape(RenderData data)
|
|
{
|
|
if (currentPass()->isEmpty()) return true;
|
|
|
|
auto sdata = static_cast<GlShape*>(data);
|
|
if (sdata->updateFlag == RenderUpdateFlag::None) return true;
|
|
|
|
auto bbox = sdata->geometry.viewport;
|
|
bbox.intersect(currentPass()->getViewport());
|
|
if (bbox.invalid()) return true;
|
|
|
|
int32_t drawDepth1 = 0, drawDepth2 = 0;
|
|
|
|
if (sdata->updateFlag == RenderUpdateFlag::None) return false;
|
|
if (sdata->updateFlag & (RenderUpdateFlag::Gradient | RenderUpdateFlag::Color)) drawDepth1 = currentPass()->nextDrawDepth();
|
|
if (sdata->updateFlag & (RenderUpdateFlag::Stroke | RenderUpdateFlag::GradientStroke)) drawDepth2 = currentPass()->nextDrawDepth();
|
|
|
|
if (!sdata->clips.empty()) drawClip(sdata->clips);
|
|
|
|
auto processFill = [&]() {
|
|
if (sdata->updateFlag & (RenderUpdateFlag::Color | RenderUpdateFlag::Gradient)) {
|
|
if (const auto& gradient = sdata->rshape->fill) {
|
|
drawPrimitive(*sdata, gradient, RenderUpdateFlag::Gradient, drawDepth1);
|
|
} else if (sdata->rshape->color.a > 0) {
|
|
drawPrimitive(*sdata, sdata->rshape->color, RenderUpdateFlag::Color, drawDepth1);
|
|
}
|
|
}
|
|
};
|
|
|
|
auto processStroke = [&]() {
|
|
if (!sdata->rshape->stroke) return;
|
|
if (sdata->updateFlag & (RenderUpdateFlag::Stroke | RenderUpdateFlag::GradientStroke)) {
|
|
if (const auto& gradient = sdata->rshape->strokeFill()) {
|
|
drawPrimitive(*sdata, gradient, RenderUpdateFlag::GradientStroke, drawDepth2);
|
|
} else if (sdata->rshape->stroke->color.a > 0) {
|
|
drawPrimitive(*sdata, sdata->rshape->stroke->color, RenderUpdateFlag::Stroke, drawDepth2);
|
|
}
|
|
}
|
|
};
|
|
|
|
if (sdata->rshape->strokeFirst()) {
|
|
processStroke();
|
|
processFill();
|
|
} else {
|
|
processFill();
|
|
processStroke();
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
void GlRenderer::dispose(RenderData data)
|
|
{
|
|
auto sdata = static_cast<GlShape*>(data);
|
|
|
|
//dispose the non thread-safety resources on clearDisposes() call
|
|
if (sdata->texId) {
|
|
ScopedLock lock(mDisposed.key);
|
|
mDisposed.textures.push(sdata->texId);
|
|
}
|
|
|
|
delete sdata;
|
|
}
|
|
|
|
static GLuint _genTexture(RenderSurface* image)
|
|
{
|
|
GLuint tex = 0;
|
|
|
|
GL_CHECK(glGenTextures(1, &tex));
|
|
|
|
GL_CHECK(glBindTexture(GL_TEXTURE_2D, tex));
|
|
GL_CHECK(glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, image->w, image->h, 0, GL_RGBA, GL_UNSIGNED_BYTE, image->data));
|
|
|
|
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));
|
|
|
|
return tex;
|
|
}
|
|
|
|
|
|
RenderData GlRenderer::prepare(RenderSurface* image, RenderData data, const Matrix& transform, Array<RenderData>& clips, uint8_t opacity, RenderUpdateFlag flags)
|
|
{
|
|
if (flags == RenderUpdateFlag::None) return data;
|
|
|
|
auto sdata = static_cast<GlShape*>(data);
|
|
|
|
if (!sdata) sdata = new GlShape;
|
|
|
|
sdata->viewWd = static_cast<float>(surface.w);
|
|
sdata->viewHt = static_cast<float>(surface.h);
|
|
sdata->updateFlag = RenderUpdateFlag::Image;
|
|
|
|
if (sdata->texId == 0) {
|
|
sdata->texId = _genTexture(image);
|
|
sdata->opacity = opacity;
|
|
sdata->texColorSpace = image->cs;
|
|
sdata->texFlipY = 1;
|
|
sdata->geometry = GlGeometry();
|
|
}
|
|
|
|
sdata->geometry.matrix = transform;
|
|
sdata->geometry.viewport = mViewport;
|
|
|
|
sdata->geometry.tesselate(image, flags);
|
|
|
|
if (!clips.empty()) {
|
|
sdata->clips.clear();
|
|
sdata->clips.push(clips);
|
|
}
|
|
|
|
return sdata;
|
|
}
|
|
|
|
|
|
RenderData GlRenderer::prepare(const RenderShape& rshape, RenderData data, const Matrix& transform, Array<RenderData>& clips, uint8_t opacity, RenderUpdateFlag flags, bool clipper)
|
|
{
|
|
// If prepare for clip, only path is meaningful.
|
|
if (clipper) flags = RenderUpdateFlag::Path;
|
|
|
|
//prepare shape data
|
|
GlShape* sdata = static_cast<GlShape*>(data);
|
|
if (!sdata) {
|
|
sdata = new GlShape;
|
|
sdata->rshape = &rshape;
|
|
}
|
|
|
|
sdata->viewWd = static_cast<float>(surface.w);
|
|
sdata->viewHt = static_cast<float>(surface.h);
|
|
sdata->updateFlag = RenderUpdateFlag::None;
|
|
|
|
sdata->geometry = GlGeometry();
|
|
sdata->opacity = opacity;
|
|
|
|
//invisible?
|
|
auto alphaF = rshape.color.a;
|
|
auto alphaS = rshape.stroke ? rshape.stroke->color.a : 0;
|
|
|
|
if ( ((flags & RenderUpdateFlag::Gradient) == 0) &&
|
|
((flags & RenderUpdateFlag::Color) && alphaF == 0) &&
|
|
((flags & RenderUpdateFlag::Stroke) && alphaS == 0) )
|
|
{
|
|
return sdata;
|
|
}
|
|
|
|
if (clipper) {
|
|
sdata->updateFlag = (rshape.stroke && (rshape.stroke->width > 0)) ? RenderUpdateFlag::Stroke : RenderUpdateFlag::Path;
|
|
} else {
|
|
if (alphaF) sdata->updateFlag = (RenderUpdateFlag::Color | sdata->updateFlag);
|
|
if (rshape.fill) sdata->updateFlag = (RenderUpdateFlag::Gradient | sdata->updateFlag);
|
|
if (alphaS) sdata->updateFlag = (RenderUpdateFlag::Stroke | sdata->updateFlag);
|
|
if (rshape.strokeFill()) sdata->updateFlag = (RenderUpdateFlag::GradientStroke | sdata->updateFlag);
|
|
}
|
|
|
|
if (sdata->updateFlag == RenderUpdateFlag::None) return sdata;
|
|
|
|
sdata->geometry.matrix = transform;
|
|
sdata->geometry.viewport = mViewport;
|
|
|
|
if (sdata->updateFlag & (RenderUpdateFlag::Color | RenderUpdateFlag::Stroke | RenderUpdateFlag::Gradient | RenderUpdateFlag::GradientStroke | RenderUpdateFlag::Transform | RenderUpdateFlag::Path))
|
|
{
|
|
if (!sdata->geometry.tesselate(rshape, sdata->updateFlag)) return sdata;
|
|
}
|
|
|
|
if (!clipper && !clips.empty()) {
|
|
sdata->clips.clear();
|
|
sdata->clips.push(clips);
|
|
}
|
|
|
|
return sdata;
|
|
}
|
|
|
|
|
|
RenderRegion GlRenderer::viewport()
|
|
{
|
|
return mViewport;
|
|
}
|
|
|
|
|
|
bool GlRenderer::viewport(const RenderRegion& vp)
|
|
{
|
|
mViewport = vp;
|
|
return true;
|
|
}
|
|
|
|
|
|
bool GlRenderer::preUpdate()
|
|
{
|
|
if (mRootTarget.invalid()) return false;
|
|
|
|
currentContext();
|
|
return true;
|
|
}
|
|
|
|
|
|
bool GlRenderer::postUpdate()
|
|
{
|
|
return true;
|
|
}
|
|
|
|
|
|
bool GlRenderer::term()
|
|
{
|
|
if (rendererCnt > 0) return false;
|
|
|
|
glTerm();
|
|
|
|
rendererCnt = -1;
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
GlRenderer* GlRenderer::gen(TVG_UNUSED uint32_t threads)
|
|
{
|
|
//initialize engine
|
|
if (rendererCnt == -1) {
|
|
if (!glInit()) {
|
|
TVGERR("GL_ENGINE", "Failed GL initialization!");
|
|
return nullptr;
|
|
}
|
|
rendererCnt = 0;
|
|
}
|
|
|
|
return new GlRenderer;
|
|
} |