renderer: introduced SceneEffect feature

Scene effects are typically applied to modify
the final appearance of a rendered scene,
such as adding a blur effect.

Each effect would have a different number of parameters
to control its visual properties. The Scene::push() interface
 uses variadic arguments to accommodate various cases.

Users should refer to the SceneEffect API documentation
and pass the parameters exactly as required for the specific
effect type. For instance, GaussianBlur expects 3 parameters
which are:

- sigma(float)[greater than 0]
- direction(int)[both: 0 / horizontal: 1 / vertical: 2]
- border(int)[extend: 0 / wrap: 1]
- quality(int)[0 ~ 100]

and, scene->push(SceneEffect::GaussianBlur, 5.0f, 0, 0, 100);

New Experimental APIs:
- SceneEffect::ClearAll
- SceneEffect::GaussianBlur
- Result Scene::push(SceneEffect effect, ...);

Example:
- examples/SceneEffect

issue: https://github.com/thorvg/thorvg/issues/374
This commit is contained in:
Hermet Park 2024-09-20 23:42:46 +09:00 committed by Hermet Park
parent 64df0791df
commit 2558e5dc10
15 changed files with 500 additions and 46 deletions

View file

@ -203,6 +203,23 @@ enum class BlendMethod : uint8_t
};
/**
* @brief Enumeration that defines methods used for Scene Effects.
*
* This enum provides options to apply various post-processing effects to a scene.
* Scene effects are typically applied to modify the final appearance of a rendered scene, such as blurring.
*
* @see Scene::push(SceneEffect effect, ...)
*
* @note Experimental API
*/
enum class SceneEffect : uint8_t
{
ClearAll = 0, ///< Reset all previously applied scene effects, restoring the scene to its original state.
GaussianBlur ///< Apply a blur effect with a Gaussian filter. Param(3) = {sigma(float)[> 0], direction(int)[both: 0 / horizontal: 1 / vertical: 2], border(int)[duplicate: 0 / wrap: 1], quality(int)[0 - 100]}
};
/**
* @brief Enumeration specifying the engine type used for the graphics backend. For multiple backends bitwise operation is allowed.
*/
@ -826,11 +843,11 @@ public:
~Shape();
/**
* @brief Resets the properties of the shape path.
* @brief Resets the shape path.
*
* The transformation matrix, the color, the fill and the stroke properties are retained.
* The transformation matrix, color, fill, and stroke properties are retained.
*
* @note The memory, where the path data is stored, is not deallocated at this stage for caching effect.
* @note The memory where the path data is stored is not deallocated at this stage to allow for caching.
*/
Result reset() noexcept;
@ -1386,7 +1403,7 @@ public:
*
* @warning Please avoid accessing the paints during Scene update/draw. You can access them after calling Canvas::sync().
* @see Canvas::sync()
* @see Scene::push()
* @see Scene::push(std::unique_ptr<Paint> paint)
* @see Scene::clear()
*
* @note Experimental API
@ -1405,6 +1422,20 @@ public:
*/
Result clear(bool free = true) noexcept;
/**
* @brief Apply a post-processing effect to the scene.
*
* This function adds a specified scene effect, such as clearing all effects or applying a Gaussian blur,
* to the scene after it has been rendered. Multiple effects can be applied in sequence.
*
* @param[in] effect The scene effect to apply. Options are defined in the SceneEffect enum.
* For example, use SceneEffect::GaussianBlur to apply a blur with specific parameters.
* @param[in] ... Additional variadic parameters required for certain effects (e.g., sigma and direction for GaussianBlur).
*
* @note Experimental API
*/
Result push(SceneEffect effect, ...) noexcept;
/**
* @brief Creates a new Scene object.
*

View file

@ -1056,6 +1056,20 @@ bool GlRenderer::endComposite(RenderCompositor* cmp)
}
bool GlRenderer::prepare(TVG_UNUSED RenderEffect* effect)
{
//TODO: Return if the current post effect requires the region expansion
return false;
}
bool GlRenderer::effect(TVG_UNUSED RenderCompositor* cmp, TVG_UNUSED const RenderEffect* effect)
{
TVGLOG("GL_ENGINE", "SceneEffect(%d) is not supported", (int)effect->type);
return false;
}
ColorSpace GlRenderer::colorSpace()
{
return ColorSpace::Unsupported;

View file

@ -84,6 +84,9 @@ public:
bool beginComposite(RenderCompositor* cmp, CompositeMethod method, uint8_t opacity) override;
bool endComposite(RenderCompositor* cmp) override;
bool prepare(RenderEffect* effect) override;
bool effect(RenderCompositor* cmp, const RenderEffect* effect) override;
static GlRenderer* gen();
static int init(TVG_UNUSED uint32_t threads);
static int32_t init();

View file

@ -7,10 +7,11 @@ source_file = [
'tvgSwFill.cpp',
'tvgSwImage.cpp',
'tvgSwMath.cpp',
'tvgSwMemPool.cpp',
'tvgSwPostEffect.cpp',
'tvgSwRenderer.h',
'tvgSwRaster.cpp',
'tvgSwRenderer.cpp',
'tvgSwMemPool.cpp',
'tvgSwRle.cpp',
'tvgSwShape.cpp',
'tvgSwStroke.cpp',

View file

@ -564,8 +564,12 @@ bool rasterGradientStroke(SwSurface* surface, SwShape* shape, const Fill* fdata,
bool rasterClear(SwSurface* surface, uint32_t x, uint32_t y, uint32_t w, uint32_t h, pixel_t val = 0);
void rasterPixel32(uint32_t *dst, uint32_t val, uint32_t offset, int32_t len);
void rasterGrayscale8(uint8_t *dst, uint8_t val, uint32_t offset, int32_t len);
void rasterXYFlip(uint32_t* src, uint32_t* dst, int32_t stride, int32_t w, int32_t h, const SwBBox& bbox, bool flipped);
void rasterUnpremultiply(RenderSurface* surface);
void rasterPremultiply(RenderSurface* surface);
bool rasterConvertCS(RenderSurface* surface, ColorSpace to);
bool effectGaussianBlur(SwImage& image, SwImage& buffer, const SwBBox& bbox, const RenderEffectGaussian* params);
bool effectGaussianPrepare(RenderEffectGaussian* effect);
#endif /* _TVG_SW_COMMON_H_ */

View file

@ -0,0 +1,202 @@
/*
* Copyright (c) 2024 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 "tvgSwCommon.h"
/************************************************************************/
/* Gaussian Filter Implementation */
/************************************************************************/
struct SwGaussianBlur
{
static constexpr int MAX_LEVEL = 3;
int level;
int kernel[MAX_LEVEL];
};
static void _gaussianExtendRegion(RenderRegion& region, int extra, int8_t direction)
{
//bbox region expansion for feathering
if (direction == 0 || direction == 1) {
region.x = -extra;
region.y = -extra;
}
if (direction == 0 || direction == 2) {
region.w = extra * 2;
region.h = extra * 2;
}
}
static int _gaussianRemap(int end, int idx, int border)
{
//wrap
if (border == 1) return idx % end;
//duplicate
if (idx < 0) return 0;
else if (idx >= end) return end - 1;
return idx;
}
//TODO: SIMD OPTIMIZATION?
static void _gaussianBlur(uint8_t* src, uint8_t* dst, int32_t stride, int32_t w, int32_t h, const SwBBox& bbox, int32_t dimension, int border, bool flipped)
{
if (flipped) {
src += ((bbox.min.x * stride) + bbox.min.y) << 2;
dst += ((bbox.min.x * stride) + bbox.min.y) << 2;
} else {
src += ((bbox.min.y * stride) + bbox.min.x) << 2;
dst += ((bbox.min.y * stride) + bbox.min.x) << 2;
}
auto iarr = 1.0f / (dimension + dimension + 1);
for (int x = 0; x < h; x++) {
auto p = x * stride;
auto i = p * 4; //current index
auto l = -(dimension + 1); //left index
auto r = dimension; //right index
int acc[4] = {0, 0, 0, 0}; //sliding accumulator
//initial acucmulation
for (int x2 = l; x2 < r; ++x2) {
auto id = (_gaussianRemap(w, x2, border) + p) * 4;
acc[0] += src[id++];
acc[1] += src[id++];
acc[2] += src[id++];
acc[3] += src[id];
}
//perform filtering
for (int x2 = 0; x2 < w; ++x2, ++r, ++l) {
auto rid = (_gaussianRemap(w, r, border) + p) * 4;
auto lid = (_gaussianRemap(w, l, border) + p) * 4;
acc[0] += src[rid++] - src[lid++];
acc[1] += src[rid++] - src[lid++];
acc[2] += src[rid++] - src[lid++];
acc[3] += src[rid] - src[lid];
dst[i++] = static_cast<uint8_t>(acc[0] * iarr + 0.5f);
dst[i++] = static_cast<uint8_t>(acc[1] * iarr + 0.5f);
dst[i++] = static_cast<uint8_t>(acc[2] * iarr + 0.5f);
dst[i++] = static_cast<uint8_t>(acc[3] * iarr + 0.5f);
}
}
}
static int _gaussianInit(int* kernel, float sigma, int level)
{
//compute the kernel
auto wl = (int) sqrt((12 * sigma / level) + 1);
if (wl % 2 == 0) --wl;
auto wu = wl + 2;
auto mi = (12 * sigma - level * wl * wl - 4 * level * wl - 3 * level) / (-4 * wl - 4);
auto m = int(mi + 0.5f);
auto extends = 0;
for (int i = 0; i < level; i++) {
kernel[i] = ((i < m ? wl : wu) - 1) / 2;
extends += kernel[i];
}
return extends;
}
bool effectGaussianPrepare(RenderEffectGaussian* params)
{
auto data = (SwGaussianBlur*)malloc(sizeof(SwGaussianBlur));
//compute box kernel sizes
data->level = int(SwGaussianBlur::MAX_LEVEL * ((params->quality - 1) * 0.01f)) + 1;
auto extends = _gaussianInit(data->kernel, params->sigma * params->sigma, data->level);
//skip, if the parameters are invalid.
if (extends == 0) {
params->invalid = true;
free(data);
return false;
}
_gaussianExtendRegion(params->extend, extends, params->direction);
params->rd = data;
return true;
}
/* It is best to take advantage of the Gaussian blurs separable property
by dividing the process into two passes. horizontal and vertical.
We can expect fewer calculations. */
bool effectGaussianBlur(SwImage& image, SwImage& buffer, const SwBBox& bbox, const RenderEffectGaussian* params)
{
if (params->invalid) return false;
if (image.channelSize != sizeof(uint32_t)) {
TVGERR("SW_ENGINE", "Not supported grayscale Gaussian Blur!");
return false;
}
auto data = static_cast<SwGaussianBlur*>(params->rd);
auto w = (bbox.max.x - bbox.min.x);
auto h = (bbox.max.y - bbox.min.y);
auto stride = image.stride;
auto front = image.buf8;
auto back = buffer.buf8;
auto swapped = false;
TVGLOG("SW_ENGINE", "GaussianFilter region(%ld, %ld, %ld, %ld) params(%f %d %d), level(%d)", bbox.min.x, bbox.min.y, bbox.max.x, bbox.max.y, params->sigma, params->direction, params->border, data->level);
//horizontal
if (params->direction == 0 || params->direction == 1) {
for (int i = 0; i < data->level; ++i) {
if (data->kernel[i] == 0) continue;
_gaussianBlur(front, back, stride, w, h, bbox, data->kernel[i], params->border, false);
std::swap(front, back);
swapped = !swapped;
}
}
//vertical. x/y flipping and horionztal access is pretty compatible with the memory architecture.
if (params->direction == 0 || params->direction == 2) {
rasterXYFlip(reinterpret_cast<uint32_t*>(front), reinterpret_cast<uint32_t*>(back), stride, w, h, bbox, false);
std::swap(front, back);
for (int i = 0; i < data->level; ++i) {
if (data->kernel[i] == 0) continue;
_gaussianBlur(front, back, stride, h, w, bbox, data->kernel[i], params->border, true);
std::swap(front, back);
swapped = !swapped;
}
rasterXYFlip(reinterpret_cast<uint32_t*>(front), reinterpret_cast<uint32_t*>(back), stride, h, w, bbox, true);
std::swap(front, back);
}
if (swapped) std::swap(image.buf8, buffer.buf8);
return true;
}

View file

@ -1817,3 +1817,38 @@ bool rasterConvertCS(RenderSurface* surface, ColorSpace to)
}
return false;
}
//TODO: SIMD OPTIMIZATION?
void rasterXYFlip(uint32_t* src, uint32_t* dst, int32_t stride, int32_t w, int32_t h, const SwBBox& bbox, bool flipped)
{
constexpr int BLOCK = 8; //experimental decision
if (flipped) {
src += ((bbox.min.x * stride) + bbox.min.y);
dst += ((bbox.min.y * stride) + bbox.min.x);
} else {
src += ((bbox.min.y * stride) + bbox.min.x);
dst += ((bbox.min.x * stride) + bbox.min.y);
}
for (int x = 0; x < w; x += BLOCK) {
auto bx = std::min(w, x + BLOCK) - x;
auto in = &src[x];
auto out = &dst[x * stride];
for (int y = 0; y < h; y += BLOCK) {
auto p = &in[y * stride];
auto q = &out[y];
auto by = std::min(h, y + BLOCK) - y;
for (int xx = 0; xx < bx; ++xx) {
for (int yy = 0; yy < by; ++yy) {
*q = *p;
p += stride;
++q;
}
p += 1 - by * stride;
q += stride - by;
}
}
}
}

View file

@ -529,6 +529,43 @@ const RenderSurface* SwRenderer::mainSurface()
}
SwSurface* SwRenderer::request(int channelSize)
{
SwSurface* cmp = nullptr;
//Use cached data
for (auto p = compositors.begin(); p < compositors.end(); ++p) {
if ((*p)->compositor->valid && (*p)->compositor->image.channelSize == channelSize) {
cmp = *p;
break;
}
}
//New Composition
if (!cmp) {
//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.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);
}
//Sync. This may have been modified by post-processing.
cmp->data = cmp->compositor->image.data;
return cmp;
}
RenderCompositor* SwRenderer::target(const RenderRegion& region, ColorSpace cs)
{
auto x = region.x;
@ -541,32 +578,11 @@ RenderCompositor* SwRenderer::target(const RenderRegion& region, ColorSpace cs)
//Out of boundary
if (x >= sw || y >= sh || x + w < 0 || y + h < 0) return nullptr;
SwSurface* cmp = nullptr;
auto reqChannelSize = CHANNEL_SIZE(cs);
//Use cached data
for (auto p = compositors.begin(); p < compositors.end(); ++p) {
if ((*p)->compositor->valid && (*p)->compositor->image.channelSize == reqChannelSize) {
cmp = *p;
break;
}
}
//New Composition
if (!cmp) {
//Inherits attributes from main surface
cmp = new SwSurface(surface);
cmp->compositor = new SwCompositor;
//TODO: We can optimize compositor surface size from (surface->stride x surface->h) to Parameter(w x h)
cmp->compositor->image.data = (pixel_t*)malloc(reqChannelSize * surface->stride * surface->h);
cmp->channelSize = cmp->compositor->image.channelSize = reqChannelSize;
compositors.push(cmp);
}
auto cmp = request(CHANNEL_SIZE(cs));
//Boundary Check
if (x < 0) x = 0;
if (y < 0) y = 0;
if (x + w > sw) w = (sw - x);
if (y + h > sh) h = (sh - y);
@ -577,14 +593,6 @@ RenderCompositor* SwRenderer::target(const RenderRegion& region, ColorSpace cs)
cmp->compositor->bbox.min.y = y;
cmp->compositor->bbox.max.x = x + w;
cmp->compositor->bbox.max.y = y + h;
cmp->compositor->image.stride = surface->stride;
cmp->compositor->image.w = surface->w;
cmp->compositor->image.h = surface->h;
cmp->compositor->image.direct = true;
cmp->data = cmp->compositor->image.data;
cmp->w = cmp->compositor->image.w;
cmp->h = cmp->compositor->image.h;
/* TODO: Currently, only blending might work.
Blending and composition must be handled together. */
@ -619,6 +627,27 @@ bool SwRenderer::endComposite(RenderCompositor* cmp)
}
bool SwRenderer::prepare(RenderEffect* effect)
{
switch (effect->type) {
case SceneEffect::GaussianBlur: return effectGaussianPrepare(static_cast<RenderEffectGaussian*>(effect));
default: return false;
}
}
bool SwRenderer::effect(RenderCompositor* cmp, const RenderEffect* effect)
{
auto p = static_cast<SwCompositor*>(cmp);
auto& buffer = request(surface->channelSize)->compositor->image;
switch (effect->type) {
case SceneEffect::GaussianBlur: return effectGaussianBlur(p->image, buffer, p->bbox, static_cast<const RenderEffectGaussian*>(effect));
default: return false;
}
}
ColorSpace SwRenderer::colorSpace()
{
if (surface) return surface->cs;

View file

@ -60,6 +60,9 @@ public:
bool endComposite(RenderCompositor* cmp) override;
void clearCompositors();
bool prepare(RenderEffect* effect) override;
bool effect(RenderCompositor* cmp, const RenderEffect* effect) override;
static SwRenderer* gen();
static bool init(uint32_t threads);
static int32_t init();
@ -76,6 +79,7 @@ private:
SwRenderer();
~SwRenderer();
SwSurface* request(int channelSize);
RenderData prepareCommon(SwTask* task, const Matrix& transform, const Array<RenderData>& clips, uint8_t opacity, RenderUpdateFlag flags);
};

View file

@ -366,6 +366,7 @@ void Paint::Impl::reset()
free(compData);
compData = nullptr;
}
tvg::identity(&tr.m);
tr.degree = 0.0f;
tr.scale = 1.0f;

View file

@ -24,6 +24,7 @@
#define _TVG_RENDER_H_
#include <math.h>
#include <cstdarg>
#include "tvgCommon.h"
#include "tvgArray.h"
#include "tvgLock.h"
@ -256,11 +257,45 @@ struct RenderShape
float strokeMiterlimit() const
{
if (!stroke) return 4.0f;
return stroke->miterlimit;;
}
};
struct RenderEffect
{
RenderData rd = nullptr;
RenderRegion extend = {0, 0, 0, 0};
SceneEffect type;
bool invalid = false;
virtual ~RenderEffect()
{
free(rd);
}
};
struct RenderEffectGaussian : RenderEffect
{
float sigma;
uint8_t direction; //0: both, 1: horizontal, 2: vertical
uint8_t border; //0: duplicate, 1: wrap
uint8_t quality; //0 ~ 100 (optional)
static RenderEffectGaussian* gen(va_list& args)
{
auto sigma = (float) va_arg(args, double);
if (sigma <= 0) return nullptr;
auto inst = new RenderEffectGaussian;
inst->sigma = sigma;
inst->direction = std::min(va_arg(args, int), 2);
inst->border = std::min(va_arg(args, int), 1);
inst->quality = std::min(va_arg(args, int), 100);
inst->type = SceneEffect::GaussianBlur;
return inst;
}
};
class RenderMethod
{
private:
@ -292,6 +327,9 @@ public:
virtual RenderCompositor* target(const RenderRegion& region, ColorSpace cs) = 0;
virtual bool beginComposite(RenderCompositor* cmp, CompositeMethod method, uint8_t opacity) = 0;
virtual bool endComposite(RenderCompositor* cmp) = 0;
virtual bool prepare(RenderEffect* effect) = 0;
virtual bool effect(RenderCompositor* cmp, const RenderEffect* effect) = 0;
};
static inline bool MASK_REGION_MERGING(CompositeMethod method)
@ -360,7 +398,6 @@ static inline uint8_t MULTIPLY(uint8_t c, uint8_t a)
return (((c) * (a) + 0xff) >> 8);
}
}
#endif //_TVG_RENDER_H_

View file

@ -20,8 +20,26 @@
* SOFTWARE.
*/
#include <cstdarg>
#include "tvgScene.h"
/************************************************************************/
/* Internal Class Implementation */
/************************************************************************/
Result Scene::Impl::resetEffects()
{
if (effects) {
for (auto e = effects->begin(); e < effects->end(); ++e) {
delete(*e);
}
delete(effects);
effects = nullptr;
}
return Result::Success;
}
/************************************************************************/
/* External Class Implementation */
/************************************************************************/
@ -78,3 +96,30 @@ list<Paint*>& Scene::paints() noexcept
{
return pImpl->paints;
}
Result Scene::push(SceneEffect effect, ...) noexcept
{
if (effect == SceneEffect::ClearAll) return pImpl->resetEffects();
if (!pImpl->effects) pImpl->effects = new Array<RenderEffect*>;
va_list args;
va_start(args, effect);
RenderEffect* re = nullptr;
switch (effect) {
case SceneEffect::GaussianBlur: {
re = RenderEffectGaussian::gen(args);
break;
}
default: break;
}
if (!re) return Result::InvalidArguments;
pImpl->effects->push(re);
return Result::Success;
}

View file

@ -23,10 +23,9 @@
#ifndef _TVG_SCENE_H_
#define _TVG_SCENE_H_
#include <float.h>
#include "tvgMath.h"
#include "tvgPaint.h"
struct SceneIterator : Iterator
{
list<Paint*>* paints;
@ -61,8 +60,9 @@ struct Scene::Impl
list<Paint*> paints;
RenderData rd = nullptr;
Scene* scene = nullptr;
uint8_t opacity; //for composition
bool needComp = false; //composite or not
Array<RenderEffect*>* effects = nullptr;
uint8_t opacity; //for composition
bool needComp = false; //composite or not
Impl(Scene* s) : scene(s)
{
@ -70,6 +70,8 @@ struct Scene::Impl
~Impl()
{
resetEffects();
for (auto paint : paints) {
if (P(paint)->unref() == 0) delete(paint);
}
@ -83,6 +85,9 @@ struct Scene::Impl
{
if (opacity == 0 || paints.empty()) return false;
//post effects requires composition
if (effects) return true;
//Masking may require composition (even if opacity == 255)
auto compMethod = scene->composite(nullptr);
if (compMethod != CompositeMethod::None && compMethod != CompositeMethod::ClipPath) return true;
@ -112,6 +117,7 @@ struct Scene::Impl
for (auto paint : paints) {
paint->pImpl->update(renderer, transform, clips, opacity, flag, false);
}
return nullptr;
}
@ -131,7 +137,15 @@ struct Scene::Impl
ret &= paint->pImpl->render(renderer);
}
if (cmp) renderer->endComposite(cmp);
if (cmp) {
//Apply post effects if any.
if (effects) {
for (auto e = effects->begin(); e < effects->end(); ++e) {
renderer->effect(cmp, *e);
}
}
renderer->endComposite(cmp);
}
return ret;
}
@ -155,7 +169,20 @@ struct Scene::Impl
if (y2 < region.y + region.h) y2 = (region.y + region.h);
}
return {x1, y1, (x2 - x1), (y2 - y1)};
//Extends the render region if post effects require
int32_t ex = 0, ey = 0, ew = 0, eh = 0;
if (effects) {
for (auto e = effects->begin(); e < effects->end(); ++e) {
auto effect = *e;
if (effect->rd || renderer->prepare(effect)) {
ex = std::min(ex, effect->extend.x);
ey = std::min(ey, effect->extend.y);
ew = std::max(ew, effect->extend.w);
eh = std::max(eh, effect->extend.h);
}
}
}
return {x1 + ex, y1 + ey, (x2 - x1) + ew, (y2 - y1) + eh};
}
bool bounds(float* px, float* py, float* pw, float* ph, bool stroking)
@ -203,6 +230,8 @@ struct Scene::Impl
dup->paints.push_back(cdup);
}
if (effects) TVGERR("RENDERER", "TODO: Duplicate Effects?");
return scene;
}
@ -218,6 +247,8 @@ struct Scene::Impl
{
return new SceneIterator(&paints);
}
Result resetEffects();
};
#endif //_TVG_SCENE_H_

View file

@ -411,6 +411,20 @@ bool WgRenderer::endComposite(RenderCompositor* cmp)
}
bool WgRenderer::prepare(TVG_UNUSED RenderEffect* effect)
{
//TODO: Return if the current post effect requires the region expansion
return false;
}
bool WgRenderer::effect(TVG_UNUSED RenderCompositor* cmp, TVG_UNUSED const RenderEffect* effect)
{
TVGLOG("WG_ENGINE", "SceneEffect(%d) is not supported", (int)effect->type);
return false;
}
WgRenderer* WgRenderer::gen()
{
return new WgRenderer();

View file

@ -52,6 +52,9 @@ public:
bool beginComposite(RenderCompositor* cmp, CompositeMethod method, uint8_t opacity) override;
bool endComposite(RenderCompositor* cmp) override;
bool prepare(RenderEffect* effect) override;
bool effect(RenderCompositor* cmp, const RenderEffect* effect) override;
static WgRenderer* gen();
static bool init(uint32_t threads);
static bool term();