From ad7c65d2eadd51a2168ce5291a24ffcd95c098c3 Mon Sep 17 00:00:00 2001 From: Hermet Park Date: Fri, 20 Sep 2024 23:42:46 +0900 Subject: [PATCH] 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 --- inc/thorvg.h | 39 +++- src/renderer/gl_engine/tvgGlRenderer.cpp | 14 ++ src/renderer/gl_engine/tvgGlRenderer.h | 3 + src/renderer/sw_engine/meson.build | 3 +- src/renderer/sw_engine/tvgSwCommon.h | 4 + src/renderer/sw_engine/tvgSwPostEffect.cpp | 202 +++++++++++++++++++++ src/renderer/sw_engine/tvgSwRaster.cpp | 35 ++++ src/renderer/sw_engine/tvgSwRenderer.cpp | 93 ++++++---- src/renderer/sw_engine/tvgSwRenderer.h | 4 + src/renderer/tvgPaint.cpp | 1 + src/renderer/tvgRender.h | 41 ++++- src/renderer/tvgScene.cpp | 47 ++++- src/renderer/tvgScene.h | 43 ++++- src/renderer/wg_engine/tvgWgRenderer.cpp | 14 ++ src/renderer/wg_engine/tvgWgRenderer.h | 3 + 15 files changed, 500 insertions(+), 46 deletions(-) create mode 100644 src/renderer/sw_engine/tvgSwPostEffect.cpp 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();