mirror of
https://github.com/thorvg/thorvg.git
synced 2025-06-07 21:23:32 +00:00
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:
parent
64df0791df
commit
2558e5dc10
15 changed files with 500 additions and 46 deletions
39
inc/thorvg.h
39
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.
|
* @brief Enumeration specifying the engine type used for the graphics backend. For multiple backends bitwise operation is allowed.
|
||||||
*/
|
*/
|
||||||
|
@ -826,11 +843,11 @@ public:
|
||||||
~Shape();
|
~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;
|
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().
|
* @warning Please avoid accessing the paints during Scene update/draw. You can access them after calling Canvas::sync().
|
||||||
* @see Canvas::sync()
|
* @see Canvas::sync()
|
||||||
* @see Scene::push()
|
* @see Scene::push(std::unique_ptr<Paint> paint)
|
||||||
* @see Scene::clear()
|
* @see Scene::clear()
|
||||||
*
|
*
|
||||||
* @note Experimental API
|
* @note Experimental API
|
||||||
|
@ -1405,6 +1422,20 @@ public:
|
||||||
*/
|
*/
|
||||||
Result clear(bool free = true) noexcept;
|
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.
|
* @brief Creates a new Scene object.
|
||||||
*
|
*
|
||||||
|
|
|
@ -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()
|
ColorSpace GlRenderer::colorSpace()
|
||||||
{
|
{
|
||||||
return ColorSpace::Unsupported;
|
return ColorSpace::Unsupported;
|
||||||
|
|
|
@ -84,6 +84,9 @@ public:
|
||||||
bool beginComposite(RenderCompositor* cmp, CompositeMethod method, uint8_t opacity) override;
|
bool beginComposite(RenderCompositor* cmp, CompositeMethod method, uint8_t opacity) override;
|
||||||
bool endComposite(RenderCompositor* cmp) override;
|
bool endComposite(RenderCompositor* cmp) override;
|
||||||
|
|
||||||
|
bool prepare(RenderEffect* effect) override;
|
||||||
|
bool effect(RenderCompositor* cmp, const RenderEffect* effect) override;
|
||||||
|
|
||||||
static GlRenderer* gen();
|
static GlRenderer* gen();
|
||||||
static int init(TVG_UNUSED uint32_t threads);
|
static int init(TVG_UNUSED uint32_t threads);
|
||||||
static int32_t init();
|
static int32_t init();
|
||||||
|
|
|
@ -7,10 +7,11 @@ source_file = [
|
||||||
'tvgSwFill.cpp',
|
'tvgSwFill.cpp',
|
||||||
'tvgSwImage.cpp',
|
'tvgSwImage.cpp',
|
||||||
'tvgSwMath.cpp',
|
'tvgSwMath.cpp',
|
||||||
|
'tvgSwMemPool.cpp',
|
||||||
|
'tvgSwPostEffect.cpp',
|
||||||
'tvgSwRenderer.h',
|
'tvgSwRenderer.h',
|
||||||
'tvgSwRaster.cpp',
|
'tvgSwRaster.cpp',
|
||||||
'tvgSwRenderer.cpp',
|
'tvgSwRenderer.cpp',
|
||||||
'tvgSwMemPool.cpp',
|
|
||||||
'tvgSwRle.cpp',
|
'tvgSwRle.cpp',
|
||||||
'tvgSwShape.cpp',
|
'tvgSwShape.cpp',
|
||||||
'tvgSwStroke.cpp',
|
'tvgSwStroke.cpp',
|
||||||
|
|
|
@ -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);
|
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 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 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 rasterUnpremultiply(RenderSurface* surface);
|
||||||
void rasterPremultiply(RenderSurface* surface);
|
void rasterPremultiply(RenderSurface* surface);
|
||||||
bool rasterConvertCS(RenderSurface* surface, ColorSpace to);
|
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_ */
|
#endif /* _TVG_SW_COMMON_H_ */
|
||||||
|
|
202
src/renderer/sw_engine/tvgSwPostEffect.cpp
Normal file
202
src/renderer/sw_engine/tvgSwPostEffect.cpp
Normal 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 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<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;
|
||||||
|
}
|
|
@ -1817,3 +1817,38 @@ bool rasterConvertCS(RenderSurface* surface, ColorSpace to)
|
||||||
}
|
}
|
||||||
return false;
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -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)
|
RenderCompositor* SwRenderer::target(const RenderRegion& region, ColorSpace cs)
|
||||||
{
|
{
|
||||||
auto x = region.x;
|
auto x = region.x;
|
||||||
|
@ -541,32 +578,11 @@ RenderCompositor* SwRenderer::target(const RenderRegion& region, ColorSpace cs)
|
||||||
//Out of boundary
|
//Out of boundary
|
||||||
if (x >= sw || y >= sh || x + w < 0 || y + h < 0) return nullptr;
|
if (x >= sw || y >= sh || x + w < 0 || y + h < 0) return nullptr;
|
||||||
|
|
||||||
SwSurface* cmp = nullptr;
|
auto cmp = request(CHANNEL_SIZE(cs));
|
||||||
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
|
|
||||||
//Boundary Check
|
//Boundary Check
|
||||||
|
if (x < 0) x = 0;
|
||||||
|
if (y < 0) y = 0;
|
||||||
if (x + w > sw) w = (sw - x);
|
if (x + w > sw) w = (sw - x);
|
||||||
if (y + h > sh) h = (sh - y);
|
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.min.y = y;
|
||||||
cmp->compositor->bbox.max.x = x + w;
|
cmp->compositor->bbox.max.x = x + w;
|
||||||
cmp->compositor->bbox.max.y = y + h;
|
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.
|
/* TODO: Currently, only blending might work.
|
||||||
Blending and composition must be handled together. */
|
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()
|
ColorSpace SwRenderer::colorSpace()
|
||||||
{
|
{
|
||||||
if (surface) return surface->cs;
|
if (surface) return surface->cs;
|
||||||
|
|
|
@ -60,6 +60,9 @@ public:
|
||||||
bool endComposite(RenderCompositor* cmp) override;
|
bool endComposite(RenderCompositor* cmp) override;
|
||||||
void clearCompositors();
|
void clearCompositors();
|
||||||
|
|
||||||
|
bool prepare(RenderEffect* effect) override;
|
||||||
|
bool effect(RenderCompositor* cmp, const RenderEffect* effect) override;
|
||||||
|
|
||||||
static SwRenderer* gen();
|
static SwRenderer* gen();
|
||||||
static bool init(uint32_t threads);
|
static bool init(uint32_t threads);
|
||||||
static int32_t init();
|
static int32_t init();
|
||||||
|
@ -76,6 +79,7 @@ private:
|
||||||
SwRenderer();
|
SwRenderer();
|
||||||
~SwRenderer();
|
~SwRenderer();
|
||||||
|
|
||||||
|
SwSurface* request(int channelSize);
|
||||||
RenderData prepareCommon(SwTask* task, const Matrix& transform, const Array<RenderData>& clips, uint8_t opacity, RenderUpdateFlag flags);
|
RenderData prepareCommon(SwTask* task, const Matrix& transform, const Array<RenderData>& clips, uint8_t opacity, RenderUpdateFlag flags);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -366,6 +366,7 @@ void Paint::Impl::reset()
|
||||||
free(compData);
|
free(compData);
|
||||||
compData = nullptr;
|
compData = nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
tvg::identity(&tr.m);
|
tvg::identity(&tr.m);
|
||||||
tr.degree = 0.0f;
|
tr.degree = 0.0f;
|
||||||
tr.scale = 1.0f;
|
tr.scale = 1.0f;
|
||||||
|
|
|
@ -24,6 +24,7 @@
|
||||||
#define _TVG_RENDER_H_
|
#define _TVG_RENDER_H_
|
||||||
|
|
||||||
#include <math.h>
|
#include <math.h>
|
||||||
|
#include <cstdarg>
|
||||||
#include "tvgCommon.h"
|
#include "tvgCommon.h"
|
||||||
#include "tvgArray.h"
|
#include "tvgArray.h"
|
||||||
#include "tvgLock.h"
|
#include "tvgLock.h"
|
||||||
|
@ -256,11 +257,45 @@ struct RenderShape
|
||||||
float strokeMiterlimit() const
|
float strokeMiterlimit() const
|
||||||
{
|
{
|
||||||
if (!stroke) return 4.0f;
|
if (!stroke) return 4.0f;
|
||||||
|
|
||||||
return stroke->miterlimit;;
|
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
|
class RenderMethod
|
||||||
{
|
{
|
||||||
private:
|
private:
|
||||||
|
@ -292,6 +327,9 @@ public:
|
||||||
virtual RenderCompositor* target(const RenderRegion& region, ColorSpace cs) = 0;
|
virtual RenderCompositor* target(const RenderRegion& region, ColorSpace cs) = 0;
|
||||||
virtual bool beginComposite(RenderCompositor* cmp, CompositeMethod method, uint8_t opacity) = 0;
|
virtual bool beginComposite(RenderCompositor* cmp, CompositeMethod method, uint8_t opacity) = 0;
|
||||||
virtual bool endComposite(RenderCompositor* cmp) = 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)
|
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);
|
return (((c) * (a) + 0xff) >> 8);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#endif //_TVG_RENDER_H_
|
#endif //_TVG_RENDER_H_
|
||||||
|
|
|
@ -20,8 +20,26 @@
|
||||||
* SOFTWARE.
|
* SOFTWARE.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
#include <cstdarg>
|
||||||
#include "tvgScene.h"
|
#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 */
|
/* External Class Implementation */
|
||||||
/************************************************************************/
|
/************************************************************************/
|
||||||
|
@ -77,4 +95,31 @@ Result Scene::clear(bool free) noexcept
|
||||||
list<Paint*>& Scene::paints() noexcept
|
list<Paint*>& Scene::paints() noexcept
|
||||||
{
|
{
|
||||||
return pImpl->paints;
|
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;
|
||||||
|
}
|
||||||
|
|
|
@ -23,10 +23,9 @@
|
||||||
#ifndef _TVG_SCENE_H_
|
#ifndef _TVG_SCENE_H_
|
||||||
#define _TVG_SCENE_H_
|
#define _TVG_SCENE_H_
|
||||||
|
|
||||||
#include <float.h>
|
#include "tvgMath.h"
|
||||||
#include "tvgPaint.h"
|
#include "tvgPaint.h"
|
||||||
|
|
||||||
|
|
||||||
struct SceneIterator : Iterator
|
struct SceneIterator : Iterator
|
||||||
{
|
{
|
||||||
list<Paint*>* paints;
|
list<Paint*>* paints;
|
||||||
|
@ -61,8 +60,9 @@ struct Scene::Impl
|
||||||
list<Paint*> paints;
|
list<Paint*> paints;
|
||||||
RenderData rd = nullptr;
|
RenderData rd = nullptr;
|
||||||
Scene* scene = nullptr;
|
Scene* scene = nullptr;
|
||||||
uint8_t opacity; //for composition
|
Array<RenderEffect*>* effects = nullptr;
|
||||||
bool needComp = false; //composite or not
|
uint8_t opacity; //for composition
|
||||||
|
bool needComp = false; //composite or not
|
||||||
|
|
||||||
Impl(Scene* s) : scene(s)
|
Impl(Scene* s) : scene(s)
|
||||||
{
|
{
|
||||||
|
@ -70,6 +70,8 @@ struct Scene::Impl
|
||||||
|
|
||||||
~Impl()
|
~Impl()
|
||||||
{
|
{
|
||||||
|
resetEffects();
|
||||||
|
|
||||||
for (auto paint : paints) {
|
for (auto paint : paints) {
|
||||||
if (P(paint)->unref() == 0) delete(paint);
|
if (P(paint)->unref() == 0) delete(paint);
|
||||||
}
|
}
|
||||||
|
@ -83,6 +85,9 @@ struct Scene::Impl
|
||||||
{
|
{
|
||||||
if (opacity == 0 || paints.empty()) return false;
|
if (opacity == 0 || paints.empty()) return false;
|
||||||
|
|
||||||
|
//post effects requires composition
|
||||||
|
if (effects) return true;
|
||||||
|
|
||||||
//Masking may require composition (even if opacity == 255)
|
//Masking may require composition (even if opacity == 255)
|
||||||
auto compMethod = scene->composite(nullptr);
|
auto compMethod = scene->composite(nullptr);
|
||||||
if (compMethod != CompositeMethod::None && compMethod != CompositeMethod::ClipPath) return true;
|
if (compMethod != CompositeMethod::None && compMethod != CompositeMethod::ClipPath) return true;
|
||||||
|
@ -112,6 +117,7 @@ struct Scene::Impl
|
||||||
for (auto paint : paints) {
|
for (auto paint : paints) {
|
||||||
paint->pImpl->update(renderer, transform, clips, opacity, flag, false);
|
paint->pImpl->update(renderer, transform, clips, opacity, flag, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -131,7 +137,15 @@ struct Scene::Impl
|
||||||
ret &= paint->pImpl->render(renderer);
|
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;
|
return ret;
|
||||||
}
|
}
|
||||||
|
@ -155,7 +169,20 @@ struct Scene::Impl
|
||||||
if (y2 < region.y + region.h) y2 = (region.y + region.h);
|
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)
|
bool bounds(float* px, float* py, float* pw, float* ph, bool stroking)
|
||||||
|
@ -203,6 +230,8 @@ struct Scene::Impl
|
||||||
dup->paints.push_back(cdup);
|
dup->paints.push_back(cdup);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (effects) TVGERR("RENDERER", "TODO: Duplicate Effects?");
|
||||||
|
|
||||||
return scene;
|
return scene;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -218,6 +247,8 @@ struct Scene::Impl
|
||||||
{
|
{
|
||||||
return new SceneIterator(&paints);
|
return new SceneIterator(&paints);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Result resetEffects();
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif //_TVG_SCENE_H_
|
#endif //_TVG_SCENE_H_
|
||||||
|
|
|
@ -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()
|
WgRenderer* WgRenderer::gen()
|
||||||
{
|
{
|
||||||
return new WgRenderer();
|
return new WgRenderer();
|
||||||
|
|
|
@ -52,6 +52,9 @@ public:
|
||||||
bool beginComposite(RenderCompositor* cmp, CompositeMethod method, uint8_t opacity) override;
|
bool beginComposite(RenderCompositor* cmp, CompositeMethod method, uint8_t opacity) override;
|
||||||
bool endComposite(RenderCompositor* cmp) override;
|
bool endComposite(RenderCompositor* cmp) override;
|
||||||
|
|
||||||
|
bool prepare(RenderEffect* effect) override;
|
||||||
|
bool effect(RenderCompositor* cmp, const RenderEffect* effect) override;
|
||||||
|
|
||||||
static WgRenderer* gen();
|
static WgRenderer* gen();
|
||||||
static bool init(uint32_t threads);
|
static bool init(uint32_t threads);
|
||||||
static bool term();
|
static bool term();
|
||||||
|
|
Loading…
Add table
Reference in a new issue