common: improve the rendering pipeline

enhanced the rendering composition target to better support features such as
alpha blending, blending, masking, post effects, and more.
This allows rasterizers to prepare the composition context with more precise control.

Additionally, resolved a crash in the software engine's post-effect process
caused by a buffer size mismatch during XY buffer flipping.

issue: https://github.com/thorvg/thorvg/issues/3009
issue: https://github.com/thorvg/thorvg/issues/2984
This commit is contained in:
Hermet Park 2024-12-04 17:31:52 +09:00 committed by Hermet Park
parent bb5ba81149
commit 445000ba66
15 changed files with 102 additions and 82 deletions

View file

@ -919,7 +919,7 @@ bool GlRenderer::postRender()
}
RenderCompositor* GlRenderer::target(const RenderRegion& region, TVG_UNUSED ColorSpace cs)
RenderCompositor* GlRenderer::target(const RenderRegion& region, TVG_UNUSED ColorSpace cs, TVG_UNUSED CompositionFlag flags)
{
auto vp = region;
if (currentPass()->isEmpty()) return nullptr;

View file

@ -82,7 +82,7 @@ public:
bool sync() override;
bool clear() override;
RenderCompositor* target(const RenderRegion& region, ColorSpace cs) override;
RenderCompositor* target(const RenderRegion& region, ColorSpace cs, CompositionFlag flags) override;
bool beginComposite(RenderCompositor* cmp, MaskMethod method, uint8_t opacity) override;
bool endComposite(RenderCompositor* cmp) override;

View file

@ -540,15 +540,27 @@ const RenderSurface* SwRenderer::mainSurface()
}
SwSurface* SwRenderer::request(int channelSize)
SwSurface* SwRenderer::request(int channelSize, bool square)
{
SwSurface* cmp = nullptr;
uint32_t w, h;
if (square) {
//Same Dimensional Size is demanded for the Post Processing Fast Flipping
w = h = std::max(surface->w, surface->h);
} else {
w = surface->w;
h = surface->h;
}
//Use cached data
for (auto p = compositors.begin(); p < compositors.end(); ++p) {
if ((*p)->compositor->valid && (*p)->compositor->image.channelSize == channelSize) {
cmp = *p;
break;
auto cur = *p;
if (cur->compositor->valid && cur->compositor->image.channelSize == channelSize) {
if (w == cur->w && h == cur->h) {
cmp = *p;
break;
}
}
}
@ -557,15 +569,13 @@ SwSurface* SwRenderer::request(int channelSize)
//Inherits attributes from main surface
cmp = new SwSurface(surface);
cmp->compositor = new SwCompositor;
cmp->compositor->image.data = (pixel_t*)malloc(channelSize * surface->stride * surface->h);
cmp->compositor->image.w = surface->w;
cmp->compositor->image.h = surface->h;
cmp->compositor->image.stride = surface->stride;
cmp->compositor->image.data = (pixel_t*)malloc(channelSize * w * h);
cmp->w = cmp->compositor->image.w = w;
cmp->h = cmp->compositor->image.h = h;
cmp->compositor->image.stride = w;
cmp->compositor->image.direct = true;
cmp->compositor->valid = true;
cmp->channelSize = cmp->compositor->image.channelSize = channelSize;
cmp->w = cmp->compositor->image.w;
cmp->h = cmp->compositor->image.h;
compositors.push(cmp);
}
@ -577,7 +587,7 @@ SwSurface* SwRenderer::request(int channelSize)
}
RenderCompositor* SwRenderer::target(const RenderRegion& region, ColorSpace cs)
RenderCompositor* SwRenderer::target(const RenderRegion& region, ColorSpace cs, CompositionFlag flags)
{
auto x = region.x;
auto y = region.y;
@ -589,7 +599,7 @@ RenderCompositor* SwRenderer::target(const RenderRegion& region, ColorSpace cs)
//Out of boundary
if (x >= sw || y >= sh || x + w < 0 || y + h < 0) return nullptr;
auto cmp = request(CHANNEL_SIZE(cs));
auto cmp = request(CHANNEL_SIZE(cs), (flags & CompositionFlag::PostProcessing));
//Boundary Check
if (x < 0) x = 0;
@ -658,12 +668,12 @@ bool SwRenderer::effect(RenderCompositor* cmp, const RenderEffect* effect, uint8
switch (effect->type) {
case SceneEffect::GaussianBlur: {
return effectGaussianBlur(p, request(surface->channelSize), static_cast<const RenderEffectGaussianBlur*>(effect));
return effectGaussianBlur(p, request(surface->channelSize, true), static_cast<const RenderEffectGaussianBlur*>(effect));
}
case SceneEffect::DropShadow: {
auto cmp1 = request(surface->channelSize);
auto cmp1 = request(surface->channelSize, true);
cmp1->compositor->valid = false;
auto cmp2 = request(surface->channelSize);
auto cmp2 = request(surface->channelSize, true);
SwSurface* surfaces[] = {cmp1, cmp2};
auto ret = effectDropShadow(p, surfaces, static_cast<const RenderEffectDropShadow*>(effect), opacity, direct);
cmp1->compositor->valid = true;

View file

@ -55,7 +55,7 @@ public:
bool target(pixel_t* data, uint32_t stride, uint32_t w, uint32_t h, ColorSpace cs);
bool mempool(bool shared);
RenderCompositor* target(const RenderRegion& region, ColorSpace cs) override;
RenderCompositor* target(const RenderRegion& region, ColorSpace cs, CompositionFlag flags) override;
bool beginComposite(RenderCompositor* cmp, MaskMethod method, uint8_t opacity) override;
bool endComposite(RenderCompositor* cmp) override;
void clearCompositors();
@ -79,7 +79,7 @@ private:
SwRenderer();
~SwRenderer();
SwSurface* request(int channelSize);
SwSurface* request(int channelSize, bool square);
RenderData prepareCommon(SwTask* task, const Matrix& transform, const Array<RenderData>& clips, uint8_t opacity, RenderUpdateFlag flags);
};

View file

@ -216,7 +216,7 @@ bool Paint::Impl::render(RenderMethod* renderer)
if (MASK_REGION_MERGING(maskData->method)) region.add(P(maskData->target)->bounds(renderer));
if (region.w == 0 || region.h == 0) return true;
cmp = renderer->target(region, MASK_TO_COLORSPACE(renderer, maskData->method));
cmp = renderer->target(region, MASK_TO_COLORSPACE(renderer, maskData->method), CompositionFlag::Masking);
if (renderer->beginComposite(cmp, MaskMethod::None, 255)) {
maskData->target->pImpl->render(renderer);
}
@ -374,7 +374,7 @@ void Paint::Impl::reset()
blendMethod = BlendMethod::Normal;
renderFlag = RenderUpdateFlag::None;
ctxFlag = ContextFlag::Invalid;
ctxFlag = ContextFlag::Default;
opacity = 255;
paint->id = 0;
}

View file

@ -28,7 +28,7 @@
namespace tvg
{
enum ContextFlag : uint8_t {Invalid = 0, FastTrack = 1};
enum ContextFlag : uint8_t {Default = 0, FastTrack = 1};
struct Iterator
{

View file

@ -56,16 +56,18 @@ RenderUpdateFlag Picture::Impl::load()
}
bool Picture::Impl::needComposition(uint8_t opacity)
void Picture::Impl::queryComposition(uint8_t opacity)
{
cFlag = CompositionFlag::Invalid;
//In this case, paint(scene) would try composition itself.
if (opacity < 255) return false;
if (opacity < 255) return;
//Composition test
const Paint* target;
picture->mask(&target);
if (!target || target->pImpl->opacity == 255 || target->pImpl->opacity == 0) return false;
return true;
if (!target || target->pImpl->opacity == 255 || target->pImpl->opacity == 0) return;
cFlag = CompositionFlag::Opacity;
}
@ -77,8 +79,8 @@ bool Picture::Impl::render(RenderMethod* renderer)
if (surface) return renderer->renderImage(rd);
else if (paint) {
RenderCompositor* cmp = nullptr;
if (needComp) {
cmp = renderer->target(bounds(renderer), renderer->colorSpace());
if (cFlag) {
cmp = renderer->target(bounds(renderer), renderer->colorSpace(), static_cast<CompositionFlag>(cFlag));
renderer->beginComposite(cmp, MaskMethod::None, 255);
}
ret = paint->pImpl->render(renderer);

View file

@ -64,10 +64,10 @@ struct Picture::Impl
RenderData rd = nullptr; //engine data
float w = 0, h = 0;
Picture* picture = nullptr;
uint8_t cFlag = CompositionFlag::Invalid;
bool resizing = false;
bool needComp = false; //need composition
bool needComposition(uint8_t opacity);
void queryComposition(uint8_t opacity);
bool render(RenderMethod* renderer);
bool size(float w, float h);
RenderRegion bounds(RenderMethod* renderer);
@ -107,7 +107,7 @@ struct Picture::Impl
loader->resize(paint, w, h);
resizing = false;
}
needComp = needComposition(opacity) ? true : false;
queryComposition(opacity);
rd = paint->pImpl->update(renderer, transform, clips, opacity, flag, false);
}
return rd;

View file

@ -36,6 +36,7 @@ using RenderData = void*;
using pixel_t = uint32_t;
enum RenderUpdateFlag : uint8_t {None = 0, Path = 1, Color = 2, Gradient = 4, Stroke = 8, Transform = 16, Image = 32, GradientStroke = 64, Blend = 128, All = 255};
enum CompositionFlag : uint8_t {Invalid = 0, Opacity = 1, Blending = 2, Masking = 4, PostProcessing = 8}; //Composition Purpose
struct RenderSurface
{
@ -338,7 +339,7 @@ public:
virtual bool clear() = 0;
virtual bool sync() = 0;
virtual RenderCompositor* target(const RenderRegion& region, ColorSpace cs) = 0;
virtual RenderCompositor* target(const RenderRegion& region, ColorSpace cs, CompositionFlag flags) = 0;
virtual bool beginComposite(RenderCompositor* cmp, MaskMethod method, uint8_t opacity) = 0;
virtual bool endComposite(RenderCompositor* cmp) = 0;

View file

@ -63,8 +63,8 @@ struct Scene::Impl
Scene* scene = nullptr;
RenderRegion vport = {0, 0, INT32_MAX, INT32_MAX};
Array<RenderEffect*>* effects = nullptr;
uint8_t compFlag = CompositionFlag::Invalid;
uint8_t opacity; //for composition
bool needComp = false; //composite or not
Impl(Scene* s) : scene(s)
{
@ -81,33 +81,35 @@ struct Scene::Impl
}
}
bool needComposition(uint8_t opacity)
uint8_t needComposition(uint8_t opacity)
{
if (opacity == 0 || paints.empty()) return false;
compFlag = CompositionFlag::Invalid;
//post effects requires composition
if (effects) return true;
if (opacity == 0 || paints.empty()) return 0;
//Masking / Blending may require composition (even if opacity == 255)
if (scene->mask(nullptr) != MaskMethod::None) return true;
if (PP(scene)->blendMethod != BlendMethod::Normal) return true;
//post effects, masking, blending may require composition
if (effects) compFlag |= CompositionFlag::PostProcessing;
if (scene->mask(nullptr) != MaskMethod::None) compFlag |= CompositionFlag::Masking;
if (PP(scene)->blendMethod != BlendMethod::Normal) compFlag |= CompositionFlag::Blending;
//Half translucent requires intermediate composition.
if (opacity == 255) return false;
if (opacity == 255) return compFlag;
//If scene has several children or only scene, it may require composition.
//OPTIMIZE: the bitmap type of the picture would not need the composition.
//OPTIMIZE: a single paint of a scene would not need the composition.
if (paints.size() == 1 && paints.front()->type() == Type::Shape) return false;
if (paints.size() == 1 && paints.front()->type() == Type::Shape) return compFlag;
return true;
compFlag |= CompositionFlag::Opacity;
return 1;
}
RenderData update(RenderMethod* renderer, const Matrix& transform, Array<RenderData>& clips, uint8_t opacity, RenderUpdateFlag flag, TVG_UNUSED bool clipper)
{
this->vport = renderer->viewport();
if ((needComp = needComposition(opacity))) {
if (needComposition(opacity)) {
/* Overriding opacity value. If this scene is half-translucent,
It must do intermediate composition with that opacity value. */
this->opacity = opacity;
@ -127,8 +129,8 @@ struct Scene::Impl
renderer->blend(PP(scene)->blendMethod);
if (needComp) {
cmp = renderer->target(bounds(renderer), renderer->colorSpace());
if (compFlag) {
cmp = renderer->target(bounds(renderer), renderer->colorSpace(), static_cast<CompositionFlag>(compFlag));
renderer->beginComposite(cmp, MaskMethod::None, opacity);
}

View file

@ -60,7 +60,7 @@ Result Shape::reset() noexcept
pImpl->rs.path.cmds.clear();
pImpl->rs.path.pts.clear();
pImpl->flag |= RenderUpdateFlag::Path;
pImpl->rFlag |= RenderUpdateFlag::Path;
return Result::Success;
}
@ -87,7 +87,7 @@ Result Shape::appendPath(const PathCommand *cmds, uint32_t cmdCnt, const Point*
pImpl->grow(cmdCnt, ptsCnt);
pImpl->append(cmds, cmdCnt, pts, ptsCnt);
pImpl->flag |= RenderUpdateFlag::Path;
pImpl->rFlag |= RenderUpdateFlag::Path;
return Result::Success;
}
@ -105,7 +105,7 @@ Result Shape::lineTo(float x, float y) noexcept
{
pImpl->lineTo(x, y);
pImpl->flag |= RenderUpdateFlag::Path;
pImpl->rFlag |= RenderUpdateFlag::Path;
return Result::Success;
}
@ -115,7 +115,7 @@ Result Shape::cubicTo(float cx1, float cy1, float cx2, float cy2, float x, float
{
pImpl->cubicTo(cx1, cy1, cx2, cy2, x, y);
pImpl->flag |= RenderUpdateFlag::Path;
pImpl->rFlag |= RenderUpdateFlag::Path;
return Result::Success;
}
@ -125,7 +125,7 @@ Result Shape::close() noexcept
{
pImpl->close();
pImpl->flag |= RenderUpdateFlag::Path;
pImpl->rFlag |= RenderUpdateFlag::Path;
return Result::Success;
}
@ -144,7 +144,7 @@ Result Shape::appendCircle(float cx, float cy, float rx, float ry) noexcept
pImpl->cubicTo(cx + rxKappa, cy - ry, cx + rx, cy - ryKappa, cx + rx, cy);
pImpl->close();
pImpl->flag |= RenderUpdateFlag::Path;
pImpl->rFlag |= RenderUpdateFlag::Path;
return Result::Success;
}
@ -184,7 +184,7 @@ Result Shape::appendRect(float x, float y, float w, float h, float rx, float ry)
pImpl->close();
}
pImpl->flag |= RenderUpdateFlag::Path;
pImpl->rFlag |= RenderUpdateFlag::Path;
return Result::Success;
}
@ -195,14 +195,14 @@ Result Shape::fill(uint8_t r, uint8_t g, uint8_t b, uint8_t a) noexcept
if (pImpl->rs.fill) {
delete(pImpl->rs.fill);
pImpl->rs.fill = nullptr;
pImpl->flag |= RenderUpdateFlag::Gradient;
pImpl->rFlag |= RenderUpdateFlag::Gradient;
}
if (r == pImpl->rs.color.r && g == pImpl->rs.color.g && b == pImpl->rs.color.b && a == pImpl->rs.color.a) return Result::Success;
pImpl->rs.color = {r, g, b, a};
pImpl->flag |= RenderUpdateFlag::Color;
pImpl->rFlag |= RenderUpdateFlag::Color;
return Result::Success;
}
@ -214,7 +214,7 @@ Result Shape::fill(Fill* f) noexcept
if (pImpl->rs.fill && pImpl->rs.fill != f) delete(pImpl->rs.fill);
pImpl->rs.fill = f;
pImpl->flag |= RenderUpdateFlag::Gradient;
pImpl->rFlag |= RenderUpdateFlag::Gradient;
return Result::Success;
}

View file

@ -33,10 +33,9 @@ struct Shape::Impl
RenderShape rs; //shape data
RenderData rd = nullptr; //engine data
Shape* shape;
uint8_t flag = RenderUpdateFlag::None;
uint8_t rFlag = RenderUpdateFlag::None;
uint8_t cFlag = CompositionFlag::Invalid;
uint8_t opacity; //for composition
bool needComp = false; //composite or not
Impl(Shape* s) : shape(s)
{
@ -57,8 +56,8 @@ struct Shape::Impl
renderer->blend(PP(shape)->blendMethod);
if (needComp) {
cmp = renderer->target(bounds(renderer), renderer->colorSpace());
if (cFlag) {
cmp = renderer->target(bounds(renderer), renderer->colorSpace(), static_cast<CompositionFlag>(cFlag));
renderer->beginComposite(cmp, MaskMethod::None, opacity);
}
@ -69,6 +68,8 @@ struct Shape::Impl
bool needComposition(uint8_t opacity)
{
cFlag = CompositionFlag::Invalid;
if (opacity == 0) return false;
//Shape composition is only necessary when stroking & fill are valid.
@ -76,7 +77,10 @@ struct Shape::Impl
if (!rs.fill && rs.color.a == 0) return false;
//translucent fill & stroke
if (opacity < 255) return true;
if (opacity < 255) {
cFlag = CompositionFlag::Opacity;
return true;
}
//Composition test
const Paint* target;
@ -96,22 +100,23 @@ struct Shape::Impl
}
}
cFlag = CompositionFlag::Masking;
return true;
}
RenderData update(RenderMethod* renderer, const Matrix& transform, Array<RenderData>& clips, uint8_t opacity, RenderUpdateFlag pFlag, bool clipper)
{
if (static_cast<RenderUpdateFlag>(pFlag | flag) == RenderUpdateFlag::None) return rd;
if (static_cast<RenderUpdateFlag>(pFlag | rFlag) == RenderUpdateFlag::None) return rd;
if ((needComp = needComposition(opacity))) {
if (needComposition(opacity)) {
/* Overriding opacity value. If this scene is half-translucent,
It must do intermediate composition with that opacity value. */
this->opacity = opacity;
opacity = 255;
}
rd = renderer->prepare(rs, rd, transform, clips, opacity, static_cast<RenderUpdateFlag>(pFlag | flag), clipper);
flag = RenderUpdateFlag::None;
rd = renderer->prepare(rs, rd, transform, clips, opacity, static_cast<RenderUpdateFlag>(pFlag | rFlag), clipper);
rFlag = RenderUpdateFlag::None;
return rd;
}
@ -208,7 +213,7 @@ struct Shape::Impl
{
if (!rs.stroke) rs.stroke = new RenderStroke();
rs.stroke->width = width;
flag |= RenderUpdateFlag::Stroke;
rFlag |= RenderUpdateFlag::Stroke;
}
void strokeTrim(float begin, float end, bool simultaneous)
@ -224,7 +229,7 @@ struct Shape::Impl
rs.stroke->trim.begin = begin;
rs.stroke->trim.end = end;
rs.stroke->trim.simultaneous = simultaneous;
flag |= RenderUpdateFlag::Stroke;
rFlag |= RenderUpdateFlag::Stroke;
}
bool strokeTrim(float* begin, float* end)
@ -244,21 +249,21 @@ struct Shape::Impl
{
if (!rs.stroke) rs.stroke = new RenderStroke();
rs.stroke->cap = cap;
flag |= RenderUpdateFlag::Stroke;
rFlag |= RenderUpdateFlag::Stroke;
}
void strokeJoin(StrokeJoin join)
{
if (!rs.stroke) rs.stroke = new RenderStroke();
rs.stroke->join = join;
flag |= RenderUpdateFlag::Stroke;
rFlag |= RenderUpdateFlag::Stroke;
}
void strokeMiterlimit(float miterlimit)
{
if (!rs.stroke) rs.stroke = new RenderStroke();
rs.stroke->miterlimit = miterlimit;
flag |= RenderUpdateFlag::Stroke;
rFlag |= RenderUpdateFlag::Stroke;
}
void strokeFill(uint8_t r, uint8_t g, uint8_t b, uint8_t a)
@ -267,12 +272,12 @@ struct Shape::Impl
if (rs.stroke->fill) {
delete(rs.stroke->fill);
rs.stroke->fill = nullptr;
flag |= RenderUpdateFlag::GradientStroke;
rFlag |= RenderUpdateFlag::GradientStroke;
}
rs.stroke->color = {r, g, b, a};
flag |= RenderUpdateFlag::Stroke;
rFlag |= RenderUpdateFlag::Stroke;
}
Result strokeFill(Fill* f)
@ -284,8 +289,8 @@ struct Shape::Impl
rs.stroke->fill = f;
rs.stroke->color.a = 0;
flag |= RenderUpdateFlag::Stroke;
flag |= RenderUpdateFlag::GradientStroke;
rFlag |= RenderUpdateFlag::Stroke;
rFlag |= RenderUpdateFlag::GradientStroke;
return Result::Success;
}
@ -320,7 +325,7 @@ struct Shape::Impl
}
rs.stroke->dashCnt = cnt;
rs.stroke->dashOffset = offset;
flag |= RenderUpdateFlag::Stroke;
rFlag |= RenderUpdateFlag::Stroke;
return Result::Success;
}
@ -335,12 +340,12 @@ struct Shape::Impl
{
if (!rs.stroke) rs.stroke = new RenderStroke();
rs.stroke->strokeFirst = strokeFirst;
flag |= RenderUpdateFlag::Stroke;
rFlag |= RenderUpdateFlag::Stroke;
}
void update(RenderUpdateFlag flag)
{
this->flag |= flag;
rFlag |= flag;
}
Paint* duplicate(Paint* ret)
@ -353,7 +358,7 @@ struct Shape::Impl
delete(dup->rs.fill);
//Default Properties
dup->flag = RenderUpdateFlag::All;
dup->rFlag = RenderUpdateFlag::All;
dup->rs.rule = rs.rule;
dup->rs.color = rs.color;

View file

@ -113,7 +113,7 @@ struct Text::Impl
//transform the gradient coordinates based on the final scaled font.
auto fill = P(shape)->rs.fill;
if (fill && P(shape)->flag & RenderUpdateFlag::Gradient) {
if (fill && P(shape)->rFlag & RenderUpdateFlag::Gradient) {
auto scale = 1.0f / loader->scale;
if (fill->type() == Type::LinearGradient) {
P(static_cast<LinearGradient*>(fill))->x1 *= scale;

View file

@ -375,7 +375,7 @@ bool WgRenderer::surfaceConfigure(WGPUSurface surface, WgContext& context, uint3
}
RenderCompositor* WgRenderer::target(TVG_UNUSED const RenderRegion& region, TVG_UNUSED ColorSpace cs)
RenderCompositor* WgRenderer::target(const RenderRegion& region, TVG_UNUSED ColorSpace cs, TVG_UNUSED CompositionFlag flags)
{
mCompositorStack.push(new WgCompose);
mCompositorStack.last()->aabb = region;

View file

@ -47,7 +47,7 @@ public:
bool target(WGPUDevice device, WGPUInstance instance, void* target, uint32_t width, uint32_t height, int type = 0);
RenderCompositor* target(const RenderRegion& region, ColorSpace cs) override;
RenderCompositor* target(const RenderRegion& region, ColorSpace cs, CompositionFlag flags) override;
bool beginComposite(RenderCompositor* cmp, MaskMethod method, uint8_t opacity) override;
bool endComposite(RenderCompositor* cmp) override;