diff --git a/inc/thorvg.h b/inc/thorvg.h index 5cc8b0cb..28bd7602 100644 --- a/inc/thorvg.h +++ b/inc/thorvg.h @@ -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. */ @@ -832,11 +849,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; @@ -1392,7 +1409,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) * @see Scene::clear() * * @note Experimental API @@ -1411,6 +1428,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. * diff --git a/src/renderer/gl_engine/tvgGlRenderer.cpp b/src/renderer/gl_engine/tvgGlRenderer.cpp index eb90e107..d1cd1787 100644 --- a/src/renderer/gl_engine/tvgGlRenderer.cpp +++ b/src/renderer/gl_engine/tvgGlRenderer.cpp @@ -1055,6 +1055,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; diff --git a/src/renderer/gl_engine/tvgGlRenderer.h b/src/renderer/gl_engine/tvgGlRenderer.h index 7397ef85..a5965a9b 100644 --- a/src/renderer/gl_engine/tvgGlRenderer.h +++ b/src/renderer/gl_engine/tvgGlRenderer.h @@ -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(); diff --git a/src/renderer/sw_engine/meson.build b/src/renderer/sw_engine/meson.build index f390d039..896adcca 100644 --- a/src/renderer/sw_engine/meson.build +++ b/src/renderer/sw_engine/meson.build @@ -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', diff --git a/src/renderer/sw_engine/tvgSwCommon.h b/src/renderer/sw_engine/tvgSwCommon.h index 60b14f9d..aeec3a12 100644 --- a/src/renderer/sw_engine/tvgSwCommon.h +++ b/src/renderer/sw_engine/tvgSwCommon.h @@ -568,8 +568,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_ */ diff --git a/src/renderer/sw_engine/tvgSwPostEffect.cpp b/src/renderer/sw_engine/tvgSwPostEffect.cpp new file mode 100644 index 00000000..ec856328 --- /dev/null +++ b/src/renderer/sw_engine/tvgSwPostEffect.cpp @@ -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(acc[0] * iarr + 0.5f); + dst[i++] = static_cast(acc[1] * iarr + 0.5f); + dst[i++] = static_cast(acc[2] * iarr + 0.5f); + dst[i++] = static_cast(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 blur’s 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(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(front), reinterpret_cast(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(front), reinterpret_cast(back), stride, h, w, bbox, true); + std::swap(front, back); + } + + if (swapped) std::swap(image.buf8, buffer.buf8); + + return true; +} \ No newline at end of file diff --git a/src/renderer/sw_engine/tvgSwRaster.cpp b/src/renderer/sw_engine/tvgSwRaster.cpp index 3ed225e7..cd6bc59c 100644 --- a/src/renderer/sw_engine/tvgSwRaster.cpp +++ b/src/renderer/sw_engine/tvgSwRaster.cpp @@ -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; + } + } + } +} diff --git a/src/renderer/sw_engine/tvgSwRenderer.cpp b/src/renderer/sw_engine/tvgSwRenderer.cpp index 7559879a..d70b9d87 100644 --- a/src/renderer/sw_engine/tvgSwRenderer.cpp +++ b/src/renderer/sw_engine/tvgSwRenderer.cpp @@ -534,6 +534,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; @@ -546,32 +583,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); @@ -582,14 +598,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. */ @@ -624,6 +632,27 @@ bool SwRenderer::endComposite(RenderCompositor* cmp) } +bool SwRenderer::prepare(RenderEffect* effect) +{ + switch (effect->type) { + case SceneEffect::GaussianBlur: return effectGaussianPrepare(static_cast(effect)); + default: return false; + } +} + + +bool SwRenderer::effect(RenderCompositor* cmp, const RenderEffect* effect) +{ + auto p = static_cast(cmp); + auto& buffer = request(surface->channelSize)->compositor->image; + + switch (effect->type) { + case SceneEffect::GaussianBlur: return effectGaussianBlur(p->image, buffer, p->bbox, static_cast(effect)); + default: return false; + } +} + + ColorSpace SwRenderer::colorSpace() { if (surface) return surface->cs; diff --git a/src/renderer/sw_engine/tvgSwRenderer.h b/src/renderer/sw_engine/tvgSwRenderer.h index 43f50477..a5d63028 100644 --- a/src/renderer/sw_engine/tvgSwRenderer.h +++ b/src/renderer/sw_engine/tvgSwRenderer.h @@ -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& clips, uint8_t opacity, RenderUpdateFlag flags); }; diff --git a/src/renderer/tvgPaint.cpp b/src/renderer/tvgPaint.cpp index da68e65f..d5a70c0e 100644 --- a/src/renderer/tvgPaint.cpp +++ b/src/renderer/tvgPaint.cpp @@ -366,6 +366,7 @@ void Paint::Impl::reset() free(compData); compData = nullptr; } + tvg::identity(&tr.m); tr.degree = 0.0f; tr.scale = 1.0f; diff --git a/src/renderer/tvgRender.h b/src/renderer/tvgRender.h index 7f315d71..030e7822 100644 --- a/src/renderer/tvgRender.h +++ b/src/renderer/tvgRender.h @@ -24,6 +24,7 @@ #define _TVG_RENDER_H_ #include +#include #include "tvgCommon.h" #include "tvgArray.h" #include "tvgLock.h" @@ -258,11 +259,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: @@ -294,6 +329,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) @@ -362,7 +400,6 @@ static inline uint8_t MULTIPLY(uint8_t c, uint8_t a) return (((c) * (a) + 0xff) >> 8); } - } #endif //_TVG_RENDER_H_ diff --git a/src/renderer/tvgScene.cpp b/src/renderer/tvgScene.cpp index b8577816..0b165c71 100644 --- a/src/renderer/tvgScene.cpp +++ b/src/renderer/tvgScene.cpp @@ -20,8 +20,26 @@ * SOFTWARE. */ +#include #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 */ /************************************************************************/ @@ -83,4 +101,31 @@ Result Scene::clear(bool free) noexcept list& Scene::paints() noexcept { return pImpl->paints; -} \ No newline at end of file +} + + +Result Scene::push(SceneEffect effect, ...) noexcept +{ + if (effect == SceneEffect::ClearAll) return pImpl->resetEffects(); + + if (!pImpl->effects) pImpl->effects = new Array; + + 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; +} diff --git a/src/renderer/tvgScene.h b/src/renderer/tvgScene.h index 10488996..674f5a0f 100644 --- a/src/renderer/tvgScene.h +++ b/src/renderer/tvgScene.h @@ -23,10 +23,9 @@ #ifndef _TVG_SCENE_H_ #define _TVG_SCENE_H_ -#include +#include "tvgMath.h" #include "tvgPaint.h" - struct SceneIterator : Iterator { list* paints; @@ -61,8 +60,9 @@ struct Scene::Impl list paints; RenderData rd = nullptr; Scene* scene = nullptr; - uint8_t opacity; //for composition - bool needComp = false; //composite or not + Array* 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_ diff --git a/src/renderer/wg_engine/tvgWgRenderer.cpp b/src/renderer/wg_engine/tvgWgRenderer.cpp index 6abc2815..ae47c17f 100755 --- a/src/renderer/wg_engine/tvgWgRenderer.cpp +++ b/src/renderer/wg_engine/tvgWgRenderer.cpp @@ -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(); diff --git a/src/renderer/wg_engine/tvgWgRenderer.h b/src/renderer/wg_engine/tvgWgRenderer.h index f3be84c6..9f22e368 100755 --- a/src/renderer/wg_engine/tvgWgRenderer.h +++ b/src/renderer/wg_engine/tvgWgRenderer.h @@ -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();