2 * Copyright (c) 2020 - 2023 the ThorVG project. All rights reserved.
4 * Permission is hereby granted, free of charge, to any person obtaining a copy
5 * of this software and associated documentation files (the "Software"), to deal
6 * in the Software without restriction, including without limitation the rights
7 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8 * copies of the Software, and to permit persons to whom the Software is
9 * furnished to do so, subject to the following conditions:
11 * The above copyright notice and this permission notice shall be included in all
12 * copies or substantial portions of the Software.
14 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
24 #include "tvgSwCommon.h"
25 #include "tvgTaskScheduler.h"
26 #include "tvgSwRenderer.h"
28 /************************************************************************/
29 /* Internal Class Implementation */
30 /************************************************************************/
31 static int32_t initEngineCnt = false;
32 static int32_t rendererCnt = 0;
33 static SwMpool* globalMpool = nullptr;
34 static uint32_t threadsCnt = 0;
38 Matrix* transform = nullptr;
39 SwSurface* surface = nullptr;
40 SwMpool* mpool = nullptr;
41 RenderUpdateFlag flags = RenderUpdateFlag::None;
42 Array<RenderData> clips;
44 SwBBox bbox = {{0, 0}, {0, 0}}; //Whole Rendering Region
45 bool pushed = false; //Pushed into task list?
46 bool disposed = false; //Disposed task?
48 RenderRegion bounds() const
53 region.x = bbox.min.x > 0 ? bbox.min.x : 0;
54 region.y = bbox.min.y > 0 ? bbox.min.y : 0;
55 region.w = bbox.max.x - region.x;
56 region.h = bbox.max.y - region.y;
57 if (region.w < 0) region.w = 0;
58 if (region.h < 0) region.h = 0;
63 virtual bool dispose() = 0;
72 struct SwShapeTask : SwTask
75 const Shape* sdata = nullptr;
76 bool cmpStroking = false;
78 void run(unsigned tid) override
80 if (opacity == 0) return; //Invisible
82 uint8_t strokeAlpha = 0;
83 auto visibleStroke = false;
84 bool visibleFill = false;
85 auto clipRegion = bbox;
87 if (HALF_STROKE(sdata->strokeWidth()) > 0) {
88 sdata->strokeColor(nullptr, nullptr, nullptr, &strokeAlpha);
89 visibleStroke = sdata->strokeFill() || (static_cast<uint32_t>(strokeAlpha * opacity / 255) > 0);
92 //This checks also for the case, if the invisible shape turned to visible by alpha.
93 auto prepareShape = false;
94 if (!shapePrepared(&shape) && (flags & RenderUpdateFlag::Color)) prepareShape = true;
97 if (flags & (RenderUpdateFlag::Path | RenderUpdateFlag::Transform) || prepareShape) {
99 sdata->fillColor(nullptr, nullptr, nullptr, &alpha);
100 alpha = static_cast<uint8_t>(static_cast<uint32_t>(alpha) * opacity / 255);
101 visibleFill = (alpha > 0 || sdata->fill());
102 if (visibleFill || visibleStroke) {
104 if (!shapePrepare(&shape, sdata, transform, clipRegion, bbox, mpool, tid, clips.count > 0 ? true : false)) goto err;
108 //Decide Stroking Composition
109 if (visibleStroke && visibleFill && opacity < 255) cmpStroking = true;
110 else cmpStroking = false;
113 if (flags & (RenderUpdateFlag::Gradient | RenderUpdateFlag::Transform | RenderUpdateFlag::Color)) {
115 /* We assume that if stroke width is bigger than 2,
116 shape outline below stroke could be full covered by stroke drawing.
117 Thus it turns off antialising in that condition.
118 Also, it shouldn't be dash style. */
119 auto antiAlias = (strokeAlpha == 255 && sdata->strokeWidth() > 2 && sdata->strokeDash(nullptr) == 0) ? false : true;
121 if (!shapeGenRle(&shape, sdata, antiAlias)) goto err;
123 if (auto fill = sdata->fill()) {
124 auto ctable = (flags & RenderUpdateFlag::Gradient) ? true : false;
125 if (ctable) shapeResetFill(&shape);
126 if (!shapeGenFillColors(&shape, fill, transform, surface, cmpStroking ? 255 : opacity, ctable)) goto err;
128 shapeDelFill(&shape);
133 if (flags & (RenderUpdateFlag::Stroke | RenderUpdateFlag::Transform)) {
135 shapeResetStroke(&shape, sdata, transform);
136 if (!shapeGenStrokeRle(&shape, sdata, transform, clipRegion, bbox, mpool, tid)) goto err;
138 if (auto fill = sdata->strokeFill()) {
139 auto ctable = (flags & RenderUpdateFlag::GradientStroke) ? true : false;
140 if (ctable) shapeResetStrokeFill(&shape);
141 if (!shapeGenStrokeFillColors(&shape, fill, transform, surface, cmpStroking ? 255 : opacity, ctable)) goto err;
143 shapeDelStrokeFill(&shape);
146 shapeDelStroke(&shape);
151 for (auto clip = clips.data; clip < (clips.data + clips.count); ++clip) {
152 auto clipper = &static_cast<SwShapeTask*>(*clip)->shape;
155 if (clipper->fastTrack) rleClipRect(shape.rle, &clipper->bbox);
156 else if (clipper->rle) rleClipPath(shape.rle, clipper->rle);
160 if (shape.strokeRle) {
161 if (clipper->fastTrack) rleClipRect(shape.strokeRle, &clipper->bbox);
162 else if (clipper->rle) rleClipPath(shape.strokeRle, clipper->rle);
171 shapeDelOutline(&shape, mpool, tid);
174 bool dispose() override
182 struct SwImageTask : SwTask
186 uint32_t triangleCnt;
188 void run(unsigned tid) override
190 auto clipRegion = bbox;
192 //Invisible shape turned to visible by alpha.
193 if ((flags & (RenderUpdateFlag::Image | RenderUpdateFlag::Transform | RenderUpdateFlag::Color)) && (opacity > 0)) {
195 if (!image.data || image.w == 0 || image.h == 0) goto end;
197 if (!imagePrepare(&image, triangles, triangleCnt, transform, clipRegion, bbox, mpool, tid)) goto end;
199 // TODO: How do we clip the triangle mesh? Only clip non-meshed images for now
200 if (triangleCnt == 0 && clips.count > 0) {
201 if (!imageGenRle(&image, bbox, false)) goto end;
203 for (auto clip = clips.data; clip < (clips.data + clips.count); ++clip) {
204 auto clipper = &static_cast<SwShapeTask*>(*clip)->shape;
205 if (clipper->fastTrack) rleClipRect(image.rle, &clipper->bbox);
206 else if (clipper->rle) rleClipPath(image.rle, clipper->rle);
217 imageDelOutline(&image, mpool, tid);
220 bool dispose() override
228 static void _termEngine()
230 if (rendererCnt > 0) return;
232 mpoolTerm(globalMpool);
233 globalMpool = nullptr;
237 /************************************************************************/
238 /* External Class Implementation */
239 /************************************************************************/
241 SwRenderer::~SwRenderer()
245 if (surface) delete(surface);
247 if (!sharedMpool) mpoolTerm(mpool);
251 if (rendererCnt == 0 && initEngineCnt == 0) _termEngine();
255 bool SwRenderer::clear()
257 for (auto task = tasks.data; task < (tasks.data + tasks.count); ++task) {
258 if ((*task)->disposed) {
262 (*task)->pushed = false;
267 if (!sharedMpool) mpoolClear(mpool);
270 vport.x = vport.y = 0;
271 vport.w = surface->w;
272 vport.h = surface->h;
279 bool SwRenderer::sync()
285 RenderRegion SwRenderer::viewport()
291 bool SwRenderer::viewport(const RenderRegion& vp)
298 bool SwRenderer::target(uint32_t* buffer, uint32_t stride, uint32_t w, uint32_t h, uint32_t cs)
300 if (!buffer || stride == 0 || w == 0 || h == 0 || w > stride) return false;
302 if (!surface) surface = new SwSurface;
304 surface->buffer = buffer;
305 surface->stride = stride;
310 vport.x = vport.y = 0;
311 vport.w = surface->w;
312 vport.h = surface->h;
314 return rasterCompositor(surface);
318 bool SwRenderer::preRender()
320 return rasterClear(surface);
323 void SwRenderer::clearCompositors()
325 //Free Composite Caches
326 for (auto comp = compositors.data; comp < (compositors.data + compositors.count); ++comp) {
327 free((*comp)->compositor->image.data);
328 delete((*comp)->compositor);
335 bool SwRenderer::postRender()
337 //Unmultiply alpha if needed
338 if (surface->cs == SwCanvas::ABGR8888_STRAIGHT || surface->cs == SwCanvas::ARGB8888_STRAIGHT) {
339 rasterUnpremultiply(surface);
342 for (auto task = tasks.data; task < (tasks.data + tasks.count); ++task) {
343 (*task)->pushed = false;
352 bool SwRenderer::renderImage(RenderData data)
354 auto task = static_cast<SwImageTask*>(data);
357 if (task->opacity == 0) return true;
359 return rasterImage(surface, &task->image, task->transform, task->bbox, task->opacity);
363 bool SwRenderer::renderImageMesh(RenderData data)
365 auto task = static_cast<SwImageTask*>(data);
368 if (task->opacity == 0) return true;
370 return rasterImageMesh(surface, &task->image, task->triangles, task->triangleCnt, task->transform, task->bbox, task->opacity);
374 bool SwRenderer::renderShape(RenderData data)
376 auto task = static_cast<SwShapeTask*>(data);
377 if (!task) return false;
381 if (task->opacity == 0) return true;
384 Compositor* cmp = nullptr;
386 //Do Stroking Composition
387 if (task->cmpStroking) {
389 cmp = target(task->bounds());
390 beginComposite(cmp, CompositeMethod::None, task->opacity);
391 //No Stroking Composition
393 opacity = task->opacity;
399 if (auto fill = task->sdata->fill()) {
400 rasterGradientShape(surface, &task->shape, fill->identifier());
402 task->sdata->fillColor(&r, &g, &b, &a);
403 a = static_cast<uint8_t>((opacity * (uint32_t) a) / 255);
404 if (a > 0) rasterShape(surface, &task->shape, r, g, b, a);
407 if (auto strokeFill = task->sdata->strokeFill()) {
408 rasterGradientStroke(surface, &task->shape, strokeFill->identifier());
410 if (task->sdata->strokeColor(&r, &g, &b, &a) == Result::Success) {
411 a = static_cast<uint8_t>((opacity * (uint32_t) a) / 255);
412 if (a > 0) rasterStroke(surface, &task->shape, r, g, b, a);
416 if (task->cmpStroking) endComposite(cmp);
422 RenderRegion SwRenderer::region(RenderData data)
424 return static_cast<SwTask*>(data)->bounds();
428 bool SwRenderer::beginComposite(Compositor* cmp, CompositeMethod method, uint32_t opacity)
430 if (!cmp) return false;
431 auto p = static_cast<SwCompositor*>(cmp);
434 p->opacity = opacity;
437 if (p->method != CompositeMethod::None) {
438 surface = p->recoverSfc;
439 surface->compositor = p;
446 bool SwRenderer::mempool(bool shared)
448 if (shared == sharedMpool) return true;
452 if (!mpoolTerm(mpool)) return false;
456 if (sharedMpool) mpool = mpoolInit(threadsCnt);
459 sharedMpool = shared;
461 if (mpool) return true;
466 Compositor* SwRenderer::target(const RenderRegion& region)
472 auto sw = static_cast<int32_t>(surface->w);
473 auto sh = static_cast<int32_t>(surface->h);
476 if (x > sw || y > sh) return nullptr;
478 SwSurface* cmp = nullptr;
481 for (auto p = compositors.data; p < (compositors.data + compositors.count); ++p) {
482 if ((*p)->compositor->valid) {
493 //Inherits attributes from main surface
496 cmp->compositor = new SwCompositor;
497 if (!cmp->compositor) goto err;
499 //SwImage, Optimize Me: Surface size from MainSurface(WxH) to Parameter W x H
500 cmp->compositor->image.data = (uint32_t*) malloc(sizeof(uint32_t) * surface->stride * surface->h);
501 if (!cmp->compositor->image.data) goto err;
502 compositors.push(cmp);
506 if (x + w > sw) w = (sw - x);
507 if (y + h > sh) h = (sh - y);
509 TVGLOG("SW_ENGINE", "Using intermediate composition [Region: %d %d %d %d]", x, y, w, h);
511 cmp->compositor->recoverSfc = surface;
512 cmp->compositor->recoverCmp = surface->compositor;
513 cmp->compositor->valid = false;
514 cmp->compositor->bbox.min.x = x;
515 cmp->compositor->bbox.min.y = y;
516 cmp->compositor->bbox.max.x = x + w;
517 cmp->compositor->bbox.max.y = y + h;
518 cmp->compositor->image.stride = surface->stride;
519 cmp->compositor->image.w = surface->w;
520 cmp->compositor->image.h = surface->h;
521 cmp->compositor->image.direct = true;
523 //We know partial clear region
524 cmp->buffer = cmp->compositor->image.data + (cmp->stride * y + x);
531 cmp->buffer = cmp->compositor->image.data;
532 cmp->w = cmp->compositor->image.w;
533 cmp->h = cmp->compositor->image.h;
535 //Switch render target
538 return cmp->compositor;
542 if (cmp->compositor) delete(cmp->compositor);
550 bool SwRenderer::endComposite(Compositor* cmp)
552 if (!cmp) return false;
554 auto p = static_cast<SwCompositor*>(cmp);
558 surface = p->recoverSfc;
559 surface->compositor = p->recoverCmp;
561 //Default is alpha blending
562 if (p->method == CompositeMethod::None) {
563 return rasterImage(surface, &p->image, nullptr, p->bbox, p->opacity);
570 bool SwRenderer::dispose(RenderData data)
572 auto task = static_cast<SwTask*>(data);
573 if (!task) return true;
577 if (task->pushed) task->disposed = true;
584 void* SwRenderer::prepareCommon(SwTask* task, const RenderTransform* transform, uint32_t opacity, const Array<RenderData>& clips, RenderUpdateFlag flags)
586 if (!surface) return task;
587 if (flags == RenderUpdateFlag::None) return task;
589 //Finish previous task if it has duplicated request.
592 if (clips.count > 0) {
593 //Guarantee composition targets get ready.
594 for (auto clip = clips.data; clip < (clips.data + clips.count); ++clip) {
595 static_cast<SwShapeTask*>(*clip)->done();
601 if (!task->transform) task->transform = static_cast<Matrix*>(malloc(sizeof(Matrix)));
602 *task->transform = transform->m;
604 if (task->transform) free(task->transform);
605 task->transform = nullptr;
608 task->opacity = opacity;
609 task->surface = surface;
612 task->bbox.min.x = mathMax(static_cast<SwCoord>(0), static_cast<SwCoord>(vport.x));
613 task->bbox.min.y = mathMax(static_cast<SwCoord>(0), static_cast<SwCoord>(vport.y));
614 task->bbox.max.x = mathMin(static_cast<SwCoord>(surface->w), static_cast<SwCoord>(vport.x + vport.w));
615 task->bbox.max.y = mathMin(static_cast<SwCoord>(surface->h), static_cast<SwCoord>(vport.y + vport.h));
622 TaskScheduler::request(task);
628 RenderData SwRenderer::prepare(Surface* image, Polygon* triangles, uint32_t triangleCnt, RenderData data, const RenderTransform* transform, uint32_t opacity, Array<RenderData>& clips, RenderUpdateFlag flags)
631 auto task = static_cast<SwImageTask*>(data);
632 if (!task) task = new SwImageTask;
633 if (flags & RenderUpdateFlag::Image) {
634 task->image.data = image->buffer;
635 task->image.w = image->w;
636 task->image.h = image->h;
637 task->image.stride = image->stride;
638 task->triangles = triangles;
639 task->triangleCnt = triangleCnt;
641 return prepareCommon(task, transform, opacity, clips, flags);
645 RenderData SwRenderer::prepare(const Shape& sdata, RenderData data, const RenderTransform* transform, uint32_t opacity, Array<RenderData>& clips, RenderUpdateFlag flags)
648 auto task = static_cast<SwShapeTask*>(data);
650 task = new SwShapeTask;
651 task->sdata = &sdata;
653 return prepareCommon(task, transform, opacity, clips, flags);
657 SwRenderer::SwRenderer():mpool(globalMpool)
662 bool SwRenderer::init(uint32_t threads)
664 if ((initEngineCnt++) > 0) return true;
666 threadsCnt = threads;
668 //Share the memory pool among the renderer
669 globalMpool = mpoolInit(threads);
679 int32_t SwRenderer::init()
681 return initEngineCnt;
685 bool SwRenderer::term()
687 if ((--initEngineCnt) > 0) return true;
696 SwRenderer* SwRenderer::gen()
699 return new SwRenderer();