1 /*
2  * Copyright (c) 2020 - 2024 the ThorVG project. All rights reserved.
3 
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:
10 
11  * The above copyright notice and this permission notice shall be included in all
12  * copies or substantial portions of the Software.
13 
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
20  * SOFTWARE.
21  */
22 
23 #include "../../lv_conf_internal.h"
24 #if LV_USE_THORVG_INTERNAL
25 
26 #ifdef THORVG_SW_OPENMP_SUPPORT
27     #include <omp.h>
28 #endif
29 #include <algorithm>
30 #include "tvgMath.h"
31 #include "tvgSwCommon.h"
32 #include "tvgTaskScheduler.h"
33 #include "tvgSwRenderer.h"
34 
35 /************************************************************************/
36 /* Internal Class Implementation                                        */
37 /************************************************************************/
38 static int32_t initEngineCnt = false;
39 static int32_t rendererCnt = 0;
40 static SwMpool* globalMpool = nullptr;
41 static uint32_t threadsCnt = 0;
42 
43 struct SwTask : Task
44 {
45     SwSurface* surface = nullptr;
46     SwMpool* mpool = nullptr;
47     SwBBox bbox;                          //Rendering Region
48     Matrix transform;
49     Array<RenderData> clips;
50     RenderUpdateFlag flags = RenderUpdateFlag::None;
51     uint8_t opacity;
52     bool pushed = false;                  //Pushed into task list?
53     bool disposed = false;                //Disposed task?
54 
boundsSwTask55     RenderRegion bounds()
56     {
57         //Can we skip the synchronization?
58         done();
59 
60         RenderRegion region;
61 
62         //Range over?
63         region.x = bbox.min.x > 0 ? bbox.min.x : 0;
64         region.y = bbox.min.y > 0 ? bbox.min.y : 0;
65         region.w = bbox.max.x - region.x;
66         region.h = bbox.max.y - region.y;
67         if (region.w < 0) region.w = 0;
68         if (region.h < 0) region.h = 0;
69 
70         return region;
71     }
72 
73     virtual void dispose() = 0;
74     virtual bool clip(SwRle* target) = 0;
~SwTaskSwTask75     virtual ~SwTask() {}
76 };
77 
78 
79 struct SwShapeTask : SwTask
80 {
81     SwShape shape;
82     const RenderShape* rshape = nullptr;
83     bool clipper = false;
84 
85     /* We assume that if the stroke width is greater than 2,
86        the shape's outline beneath the stroke could be adequately covered by the stroke drawing.
87        Therefore, antialiasing is disabled under this condition.
88        Additionally, the stroke style should not be dashed. */
antialiasingSwShapeTask89     bool antialiasing(float strokeWidth)
90     {
91         return strokeWidth < 2.0f || rshape->stroke->dashCnt > 0 || rshape->stroke->strokeFirst || rshape->strokeTrim() || rshape->stroke->color[3] < 255;;
92     }
93 
validStrokeWidthSwShapeTask94     float validStrokeWidth()
95     {
96         if (!rshape->stroke) return 0.0f;
97 
98         auto width = rshape->stroke->width;
99         if (tvg::zero(width)) return 0.0f;
100 
101         if (!rshape->stroke->fill && (MULTIPLY(rshape->stroke->color[3], opacity) == 0)) return 0.0f;
102         if (tvg::zero(rshape->stroke->trim.begin - rshape->stroke->trim.end)) return 0.0f;
103 
104         return (width * sqrt(transform.e11 * transform.e11 + transform.e12 * transform.e12));
105     }
106 
clipSwShapeTask107     bool clip(SwRle* target) override
108     {
109         if (shape.fastTrack) rleClip(target, &bbox);
110         else if (shape.rle) rleClip(target, shape.rle);
111         else return false;
112 
113         return true;
114     }
115 
runSwShapeTask116     void run(unsigned tid) override
117     {
118         //Invisible
119         if (opacity == 0 && !clipper) {
120             bbox.reset();
121             return;
122         }
123 
124         auto strokeWidth = validStrokeWidth();
125         SwBBox renderRegion{};
126         auto visibleFill = false;
127 
128         //This checks also for the case, if the invisible shape turned to visible by alpha.
129         auto prepareShape = false;
130         if (!shapePrepared(&shape) && (flags & RenderUpdateFlag::Color)) prepareShape = true;
131 
132         //Shape
133         if (flags & (RenderUpdateFlag::Path | RenderUpdateFlag::Transform) || prepareShape) {
134             uint8_t alpha = 0;
135             rshape->fillColor(nullptr, nullptr, nullptr, &alpha);
136             alpha = MULTIPLY(alpha, opacity);
137             visibleFill = (alpha > 0 || rshape->fill);
138             shapeReset(&shape);
139             if (visibleFill || clipper) {
140                 if (!shapePrepare(&shape, rshape, transform, bbox, renderRegion, mpool, tid, clips.count > 0 ? true : false)) {
141                     visibleFill = false;
142                     renderRegion.reset();
143                 }
144             }
145         }
146         //Fill
147         if (flags & (RenderUpdateFlag::Path |RenderUpdateFlag::Gradient | RenderUpdateFlag::Transform | RenderUpdateFlag::Color)) {
148             if (visibleFill || clipper) {
149                 if (!shapeGenRle(&shape, rshape, antialiasing(strokeWidth))) goto err;
150             }
151             if (auto fill = rshape->fill) {
152                 auto ctable = (flags & RenderUpdateFlag::Gradient) ? true : false;
153                 if (ctable) shapeResetFill(&shape);
154                 if (!shapeGenFillColors(&shape, fill, transform, surface, opacity, ctable)) goto err;
155             } else {
156                 shapeDelFill(&shape);
157             }
158         }
159         //Stroke
160         if (flags & (RenderUpdateFlag::Path | RenderUpdateFlag::Stroke | RenderUpdateFlag::Transform)) {
161             if (strokeWidth > 0.0f) {
162                 shapeResetStroke(&shape, rshape, transform);
163 
164                 if (!shapeGenStrokeRle(&shape, rshape, transform, bbox, renderRegion, mpool, tid)) goto err;
165                 if (auto fill = rshape->strokeFill()) {
166                     auto ctable = (flags & RenderUpdateFlag::GradientStroke) ? true : false;
167                     if (ctable) shapeResetStrokeFill(&shape);
168                     if (!shapeGenStrokeFillColors(&shape, fill, transform, surface, opacity, ctable)) goto err;
169                 } else {
170                     shapeDelStrokeFill(&shape);
171                 }
172             } else {
173                 shapeDelStroke(&shape);
174             }
175         }
176 
177         //Clear current task memorypool here if the clippers would use the same memory pool
178         shapeDelOutline(&shape, mpool, tid);
179 
180         //Clip Path
181         for (auto clip = clips.begin(); clip < clips.end(); ++clip) {
182             auto clipper = static_cast<SwTask*>(*clip);
183             //Clip shape rle
184             if (shape.rle && !clipper->clip(shape.rle)) goto err;
185             //Clip stroke rle
186             if (shape.strokeRle && !clipper->clip(shape.strokeRle)) goto err;
187         }
188 
189         bbox = renderRegion; //sync
190 
191         return;
192 
193     err:
194         bbox.reset();
195         shapeReset(&shape);
196         shapeDelOutline(&shape, mpool, tid);
197     }
198 
disposeSwShapeTask199     void dispose() override
200     {
201        shapeFree(&shape);
202     }
203 };
204 
205 
206 struct SwImageTask : SwTask
207 {
208     SwImage image;
209     RenderSurface* source;                //Image source
210 
clipSwImageTask211     bool clip(SwRle* target) override
212     {
213         TVGERR("SW_ENGINE", "Image is used as ClipPath?");
214         return true;
215     }
216 
runSwImageTask217     void run(unsigned tid) override
218     {
219         auto clipRegion = bbox;
220 
221         //Convert colorspace if it's not aligned.
222         rasterConvertCS(source, surface->cs);
223         rasterPremultiply(source);
224 
225         image.data = source->data;
226         image.w = source->w;
227         image.h = source->h;
228         image.stride = source->stride;
229         image.channelSize = source->channelSize;
230 
231         //Invisible shape turned to visible by alpha.
232         if ((flags & (RenderUpdateFlag::Image | RenderUpdateFlag::Transform | RenderUpdateFlag::Color)) && (opacity > 0)) {
233             imageReset(&image);
234             if (!image.data || image.w == 0 || image.h == 0) goto end;
235 
236             if (!imagePrepare(&image, transform, clipRegion, bbox, mpool, tid)) goto end;
237 
238             if (clips.count > 0) {
239                 if (!imageGenRle(&image, bbox, false)) goto end;
240                 if (image.rle) {
241                     //Clear current task memorypool here if the clippers would use the same memory pool
242                     imageDelOutline(&image, mpool, tid);
243                     for (auto clip = clips.begin(); clip < clips.end(); ++clip) {
244                         auto clipper = static_cast<SwTask*>(*clip);
245                         if (!clipper->clip(image.rle)) goto err;
246                     }
247                     return;
248                 }
249             }
250         }
251         goto end;
252     err:
253         rleReset(image.rle);
254     end:
255         imageDelOutline(&image, mpool, tid);
256     }
257 
disposeSwImageTask258     void dispose() override
259     {
260        imageFree(&image);
261     }
262 };
263 
264 
_termEngine()265 static void _termEngine()
266 {
267     if (rendererCnt > 0) return;
268 
269     mpoolTerm(globalMpool);
270     globalMpool = nullptr;
271 }
272 
273 
_renderFill(SwShapeTask * task,SwSurface * surface,uint8_t opacity)274 static void _renderFill(SwShapeTask* task, SwSurface* surface, uint8_t opacity)
275 {
276     uint8_t r, g, b, a;
277     if (auto fill = task->rshape->fill) {
278         rasterGradientShape(surface, &task->shape, fill, opacity);
279     } else {
280         task->rshape->fillColor(&r, &g, &b, &a);
281         a = MULTIPLY(opacity, a);
282         if (a > 0) rasterShape(surface, &task->shape, r, g, b, a);
283     }
284 }
285 
_renderStroke(SwShapeTask * task,SwSurface * surface,uint8_t opacity)286 static void _renderStroke(SwShapeTask* task, SwSurface* surface, uint8_t opacity)
287 {
288     uint8_t r, g, b, a;
289     if (auto strokeFill = task->rshape->strokeFill()) {
290         rasterGradientStroke(surface, &task->shape, strokeFill, opacity);
291     } else {
292         if (task->rshape->strokeColor(&r, &g, &b, &a)) {
293             a = MULTIPLY(opacity, a);
294             if (a > 0) rasterStroke(surface, &task->shape, r, g, b, a);
295         }
296     }
297 }
298 
299 /************************************************************************/
300 /* External Class Implementation                                        */
301 /************************************************************************/
302 
~SwRenderer()303 SwRenderer::~SwRenderer()
304 {
305     clearCompositors();
306 
307     delete(surface);
308 
309     if (!sharedMpool) mpoolTerm(mpool);
310 
311     --rendererCnt;
312 
313     if (rendererCnt == 0 && initEngineCnt == 0) _termEngine();
314 }
315 
316 
clear()317 bool SwRenderer::clear()
318 {
319     for (auto task = tasks.begin(); task < tasks.end(); ++task) {
320         if ((*task)->disposed) {
321             delete(*task);
322         } else {
323             (*task)->done();
324             (*task)->pushed = false;
325         }
326     }
327     tasks.clear();
328 
329     if (!sharedMpool) mpoolClear(mpool);
330 
331     if (surface) {
332         vport.x = vport.y = 0;
333         vport.w = surface->w;
334         vport.h = surface->h;
335     }
336 
337     return true;
338 }
339 
340 
sync()341 bool SwRenderer::sync()
342 {
343     return true;
344 }
345 
346 
viewport()347 RenderRegion SwRenderer::viewport()
348 {
349     return vport;
350 }
351 
352 
viewport(const RenderRegion & vp)353 bool SwRenderer::viewport(const RenderRegion& vp)
354 {
355     vport = vp;
356     return true;
357 }
358 
359 
target(pixel_t * data,uint32_t stride,uint32_t w,uint32_t h,ColorSpace cs)360 bool SwRenderer::target(pixel_t* data, uint32_t stride, uint32_t w, uint32_t h, ColorSpace cs)
361 {
362     if (!data || stride == 0 || w == 0 || h == 0 || w > stride) return false;
363 
364     clearCompositors();
365 
366     if (!surface) surface = new SwSurface;
367 
368     surface->data = data;
369     surface->stride = stride;
370     surface->w = w;
371     surface->h = h;
372     surface->cs = cs;
373     surface->channelSize = CHANNEL_SIZE(cs);
374     surface->premultiplied = true;
375 
376     return rasterCompositor(surface);
377 }
378 
379 
preRender()380 bool SwRenderer::preRender()
381 {
382     return true;
383 }
384 
385 
clearCompositors()386 void SwRenderer::clearCompositors()
387 {
388     //Free Composite Caches
389     for (auto comp = compositors.begin(); comp < compositors.end(); ++comp) {
390         free((*comp)->compositor->image.data);
391         delete((*comp)->compositor);
392         delete(*comp);
393     }
394     compositors.reset();
395 }
396 
397 
postRender()398 bool SwRenderer::postRender()
399 {
400     //Unmultiply alpha if needed
401     if (surface->cs == ColorSpace::ABGR8888S || surface->cs == ColorSpace::ARGB8888S) {
402         rasterUnpremultiply(surface);
403     }
404 
405     for (auto task = tasks.begin(); task < tasks.end(); ++task) {
406         if ((*task)->disposed) delete(*task);
407         else (*task)->pushed = false;
408     }
409     tasks.clear();
410 
411     return true;
412 }
413 
414 
renderImage(RenderData data)415 bool SwRenderer::renderImage(RenderData data)
416 {
417     auto task = static_cast<SwImageTask*>(data);
418     task->done();
419 
420     if (task->opacity == 0) return true;
421 
422     return rasterImage(surface, &task->image, task->transform, task->bbox, task->opacity);
423 }
424 
425 
renderShape(RenderData data)426 bool SwRenderer::renderShape(RenderData data)
427 {
428     auto task = static_cast<SwShapeTask*>(data);
429     if (!task) return false;
430 
431     task->done();
432 
433     if (task->opacity == 0) return true;
434 
435     //Main raster stage
436     if (task->rshape->stroke && task->rshape->stroke->strokeFirst) {
437         _renderStroke(task, surface, task->opacity);
438         _renderFill(task, surface, task->opacity);
439     } else {
440         _renderFill(task, surface, task->opacity);
441         _renderStroke(task, surface, task->opacity);
442     }
443 
444     return true;
445 }
446 
447 
blend(BlendMethod method)448 bool SwRenderer::blend(BlendMethod method)
449 {
450     if (surface->blendMethod == method) return true;
451     surface->blendMethod = method;
452 
453     switch (method) {
454         case BlendMethod::Normal:
455             surface->blender = nullptr;
456             break;
457         case BlendMethod::Multiply:
458             surface->blender = opBlendMultiply;
459             break;
460         case BlendMethod::Screen:
461             surface->blender = opBlendScreen;
462             break;
463         case BlendMethod::Overlay:
464             surface->blender = opBlendOverlay;
465             break;
466         case BlendMethod::Darken:
467             surface->blender = opBlendDarken;
468             break;
469         case BlendMethod::Lighten:
470             surface->blender = opBlendLighten;
471             break;
472         case BlendMethod::ColorDodge:
473             surface->blender = opBlendColorDodge;
474             break;
475         case BlendMethod::ColorBurn:
476             surface->blender = opBlendColorBurn;
477             break;
478         case BlendMethod::HardLight:
479             surface->blender = opBlendHardLight;
480             break;
481         case BlendMethod::SoftLight:
482             surface->blender = opBlendSoftLight;
483             break;
484         case BlendMethod::Difference:
485             surface->blender = opBlendDifference;
486             break;
487         case BlendMethod::Exclusion:
488             surface->blender = opBlendExclusion;
489             break;
490         case BlendMethod::Add:
491             surface->blender = opBlendAdd;
492             break;
493         default:
494             TVGLOG("SW_ENGINE", "Non supported blending option = %d", (int) method);
495             surface->blender = nullptr;
496             break;
497     }
498     return false;
499 }
500 
501 
region(RenderData data)502 RenderRegion SwRenderer::region(RenderData data)
503 {
504     return static_cast<SwTask*>(data)->bounds();
505 }
506 
507 
beginComposite(RenderCompositor * cmp,CompositeMethod method,uint8_t opacity)508 bool SwRenderer::beginComposite(RenderCompositor* cmp, CompositeMethod method, uint8_t opacity)
509 {
510     if (!cmp) return false;
511     auto p = static_cast<SwCompositor*>(cmp);
512 
513     p->method = method;
514     p->opacity = opacity;
515 
516     //Current Context?
517     if (p->method != CompositeMethod::None) {
518         surface = p->recoverSfc;
519         surface->compositor = p;
520     }
521 
522     return true;
523 }
524 
525 
mempool(bool shared)526 bool SwRenderer::mempool(bool shared)
527 {
528     if (shared == sharedMpool) return true;
529 
530     if (shared) {
531         if (!sharedMpool) {
532             if (!mpoolTerm(mpool)) return false;
533             mpool = globalMpool;
534         }
535     } else {
536         if (sharedMpool) mpool = mpoolInit(threadsCnt);
537     }
538 
539     sharedMpool = shared;
540 
541     if (mpool) return true;
542     return false;
543 }
544 
545 
mainSurface()546 const RenderSurface* SwRenderer::mainSurface()
547 {
548     return surface;
549 }
550 
551 
request(int channelSize)552 SwSurface* SwRenderer::request(int channelSize)
553 {
554     SwSurface* cmp = nullptr;
555 
556     //Use cached data
557     for (auto p = compositors.begin(); p < compositors.end(); ++p) {
558         if ((*p)->compositor->valid && (*p)->compositor->image.channelSize == channelSize) {
559             cmp = *p;
560             break;
561         }
562     }
563 
564     //New Composition
565     if (!cmp) {
566         //Inherits attributes from main surface
567         cmp = new SwSurface(surface);
568         cmp->compositor = new SwCompositor;
569         cmp->compositor->image.data = (pixel_t*)malloc(channelSize * surface->stride * surface->h);
570         cmp->compositor->image.w = surface->w;
571         cmp->compositor->image.h = surface->h;
572         cmp->compositor->image.stride = surface->stride;
573         cmp->compositor->image.direct = true;
574         cmp->compositor->valid = true;
575         cmp->channelSize = cmp->compositor->image.channelSize = channelSize;
576         cmp->w = cmp->compositor->image.w;
577         cmp->h = cmp->compositor->image.h;
578 
579         compositors.push(cmp);
580     }
581 
582     //Sync. This may have been modified by post-processing.
583     cmp->data = cmp->compositor->image.data;
584 
585     return cmp;
586 }
587 
588 
target(const RenderRegion & region,ColorSpace cs)589 RenderCompositor* SwRenderer::target(const RenderRegion& region, ColorSpace cs)
590 {
591     auto x = region.x;
592     auto y = region.y;
593     auto w = region.w;
594     auto h = region.h;
595     auto sw = static_cast<int32_t>(surface->w);
596     auto sh = static_cast<int32_t>(surface->h);
597 
598     //Out of boundary
599     if (x >= sw || y >= sh || x + w < 0 || y + h < 0) return nullptr;
600 
601     auto cmp = request(CHANNEL_SIZE(cs));
602 
603     //Boundary Check
604     if (x < 0) x = 0;
605     if (y < 0) y = 0;
606     if (x + w > sw) w = (sw - x);
607     if (y + h > sh) h = (sh - y);
608 
609     cmp->compositor->recoverSfc = surface;
610     cmp->compositor->recoverCmp = surface->compositor;
611     cmp->compositor->valid = false;
612     cmp->compositor->bbox.min.x = x;
613     cmp->compositor->bbox.min.y = y;
614     cmp->compositor->bbox.max.x = x + w;
615     cmp->compositor->bbox.max.y = y + h;
616 
617     /* TODO: Currently, only blending might work.
618        Blending and composition must be handled together. */
619     auto color = (surface->blender && !surface->compositor) ? 0x00ffffff : 0x00000000;
620     rasterClear(cmp, x, y, w, h, color);
621 
622     //Switch render target
623     surface = cmp;
624 
625     return cmp->compositor;
626 }
627 
628 
endComposite(RenderCompositor * cmp)629 bool SwRenderer::endComposite(RenderCompositor* cmp)
630 {
631     if (!cmp) return false;
632 
633     auto p = static_cast<SwCompositor*>(cmp);
634     p->valid = true;
635 
636     //Recover Context
637     surface = p->recoverSfc;
638     surface->compositor = p->recoverCmp;
639 
640     //Default is alpha blending
641     if (p->method == CompositeMethod::None) {
642         Matrix m = {1, 0, 0, 0, 1, 0, 0, 0, 1};
643         return rasterImage(surface, &p->image, m, p->bbox, p->opacity);
644     }
645 
646     return true;
647 }
648 
649 
prepare(RenderEffect * effect)650 bool SwRenderer::prepare(RenderEffect* effect)
651 {
652     switch (effect->type) {
653         case SceneEffect::GaussianBlur: return effectGaussianPrepare(static_cast<RenderEffectGaussian*>(effect));
654         default: return false;
655     }
656 }
657 
658 
effect(RenderCompositor * cmp,const RenderEffect * effect)659 bool SwRenderer::effect(RenderCompositor* cmp, const RenderEffect* effect)
660 {
661     auto p = static_cast<SwCompositor*>(cmp);
662     auto& buffer = request(surface->channelSize)->compositor->image;
663 
664     switch (effect->type) {
665         case SceneEffect::GaussianBlur: return effectGaussianBlur(p->image, buffer, p->bbox, static_cast<const RenderEffectGaussian*>(effect));
666         default: return false;
667     }
668 }
669 
670 
colorSpace()671 ColorSpace SwRenderer::colorSpace()
672 {
673     if (surface) return surface->cs;
674     else return ColorSpace::Unsupported;
675 }
676 
677 
dispose(RenderData data)678 void SwRenderer::dispose(RenderData data)
679 {
680     auto task = static_cast<SwTask*>(data);
681     if (!task) return;
682     task->done();
683     task->dispose();
684 
685     if (task->pushed) task->disposed = true;
686     else delete(task);
687 }
688 
689 
prepareCommon(SwTask * task,const Matrix & transform,const Array<RenderData> & clips,uint8_t opacity,RenderUpdateFlag flags)690 void* SwRenderer::prepareCommon(SwTask* task, const Matrix& transform, const Array<RenderData>& clips, uint8_t opacity, RenderUpdateFlag flags)
691 {
692     if (!surface) return task;
693     if (flags == RenderUpdateFlag::None) return task;
694 
695     //TODO: Failed threading them. It would be better if it's possible.
696     //See: https://github.com/thorvg/thorvg/issues/1409
697     //Guarantee composition targets get ready.
698     for (auto clip = clips.begin(); clip < clips.end(); ++clip) {
699         static_cast<SwTask*>(*clip)->done();
700     }
701 
702     task->clips = clips;
703     task->transform = transform;
704 
705     //zero size?
706     if (task->transform.e11 == 0.0f && task->transform.e12 == 0.0f) return task; //zero width
707     if (task->transform.e21 == 0.0f && task->transform.e22 == 0.0f) return task; //zero height
708 
709     task->opacity = opacity;
710     task->surface = surface;
711     task->mpool = mpool;
712     task->flags = flags;
713     task->bbox.min.x = std::max(static_cast<SwCoord>(0), static_cast<SwCoord>(vport.x));
714     task->bbox.min.y = std::max(static_cast<SwCoord>(0), static_cast<SwCoord>(vport.y));
715     task->bbox.max.x = std::min(static_cast<SwCoord>(surface->w), static_cast<SwCoord>(vport.x + vport.w));
716     task->bbox.max.y = std::min(static_cast<SwCoord>(surface->h), static_cast<SwCoord>(vport.y + vport.h));
717 
718     if (!task->pushed) {
719         task->pushed = true;
720         tasks.push(task);
721     }
722 
723     TaskScheduler::request(task);
724 
725     return task;
726 }
727 
728 
prepare(RenderSurface * surface,RenderData data,const Matrix & transform,Array<RenderData> & clips,uint8_t opacity,RenderUpdateFlag flags)729 RenderData SwRenderer::prepare(RenderSurface* surface, RenderData data, const Matrix& transform, Array<RenderData>& clips, uint8_t opacity, RenderUpdateFlag flags)
730 {
731     //prepare task
732     auto task = static_cast<SwImageTask*>(data);
733     if (!task) task = new SwImageTask;
734     else task->done();
735 
736     task->source = surface;
737 
738     return prepareCommon(task, transform, clips, opacity, flags);
739 }
740 
741 
prepare(const RenderShape & rshape,RenderData data,const Matrix & transform,Array<RenderData> & clips,uint8_t opacity,RenderUpdateFlag flags,bool clipper)742 RenderData SwRenderer::prepare(const RenderShape& rshape, RenderData data, const Matrix& transform, Array<RenderData>& clips, uint8_t opacity, RenderUpdateFlag flags, bool clipper)
743 {
744     //prepare task
745     auto task = static_cast<SwShapeTask*>(data);
746     if (!task) task = new SwShapeTask;
747     else task->done();
748 
749     task->rshape = &rshape;
750     task->clipper = clipper;
751 
752     return prepareCommon(task, transform, clips, opacity, flags);
753 }
754 
755 
SwRenderer()756 SwRenderer::SwRenderer():mpool(globalMpool)
757 {
758 }
759 
760 
init(uint32_t threads)761 bool SwRenderer::init(uint32_t threads)
762 {
763     if ((initEngineCnt++) > 0) return true;
764 
765     threadsCnt = threads;
766 
767     //Share the memory pool among the renderer
768     globalMpool = mpoolInit(threads);
769     if (!globalMpool) {
770         --initEngineCnt;
771         return false;
772     }
773 
774     return true;
775 }
776 
777 
init()778 int32_t SwRenderer::init()
779 {
780 #ifdef THORVG_SW_OPENMP_SUPPORT
781     omp_set_num_threads(TaskScheduler::threads());
782 #endif
783 
784     return initEngineCnt;
785 }
786 
787 
term()788 bool SwRenderer::term()
789 {
790     if ((--initEngineCnt) > 0) return true;
791 
792     initEngineCnt = 0;
793 
794    _termEngine();
795 
796     return true;
797 }
798 
gen()799 SwRenderer* SwRenderer::gen()
800 {
801     ++rendererCnt;
802     return new SwRenderer();
803 }
804 
805 #endif /* LV_USE_THORVG_INTERNAL */
806 
807