/* * Copyright (c) 2020 Samsung Electronics Co., Ltd. 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 "vraster.h" #include #include #include #include "config.h" #include "v_ft_raster.h" #include "v_ft_stroker.h" #include "vdebug.h" #include "vmatrix.h" #include "vpath.h" #include "vrle.h" V_BEGIN_NAMESPACE template class dyn_array { public: explicit dyn_array(size_t size) : mCapacity(size), mData(std::make_unique(mCapacity)) { } void reserve(size_t size) { if (mCapacity > size) return; mCapacity = size; mData = std::make_unique(mCapacity); } T * data() const { return mData.get(); } dyn_array &operator=(dyn_array &&) noexcept = delete; private: size_t mCapacity{0}; std::unique_ptr mData{nullptr}; }; struct FTOutline { public: void reset(); void grow(size_t, size_t); void convert(const VPath &path); void convert(CapStyle, JoinStyle, float, float); void moveTo(const VPointF &pt); void lineTo(const VPointF &pt); void cubicTo(const VPointF &ctr1, const VPointF &ctr2, const VPointF end); void close(); void end(); void transform(const VMatrix &m); SW_FT_Pos TO_FT_COORD(float x) { return SW_FT_Pos(x * 64); } // to freetype 26.6 coordinate. SW_FT_Outline ft; bool closed{false}; SW_FT_Stroker_LineCap ftCap; SW_FT_Stroker_LineJoin ftJoin; SW_FT_Fixed ftWidth; SW_FT_Fixed ftMiterLimit; dyn_array mPointMemory{100}; dyn_array mTagMemory{100}; dyn_array mContourMemory{10}; dyn_array mContourFlagMemory{10}; }; void FTOutline::reset() { ft.n_points = ft.n_contours = 0; ft.flags = 0x0; } void FTOutline::grow(size_t points, size_t segments) { reset(); mPointMemory.reserve(points + segments); mTagMemory.reserve(points + segments); mContourMemory.reserve(segments); mContourFlagMemory.reserve(segments); ft.points = mPointMemory.data(); ft.tags = mTagMemory.data(); ft.contours = mContourMemory.data(); ft.contours_flag = mContourFlagMemory.data(); } void FTOutline::convert(const VPath &path) { const std::vector &elements = path.elements(); const std::vector & points = path.points(); grow(points.size(), path.segments()); size_t index = 0; for (auto element : elements) { switch (element) { case VPath::Element::MoveTo: moveTo(points[index]); index++; break; case VPath::Element::LineTo: lineTo(points[index]); index++; break; case VPath::Element::CubicTo: cubicTo(points[index], points[index + 1], points[index + 2]); index = index + 3; break; case VPath::Element::Close: close(); break; } } end(); } void FTOutline::convert(CapStyle cap, JoinStyle join, float width, float miterLimit) { // map strokeWidth to freetype. It uses as the radius of the pen not the // diameter width = width / 2.0f; // convert to freetype co-ordinate // IMP: stroker takes radius in 26.6 co-ordinate ftWidth = SW_FT_Fixed(width * (1 << 6)); // IMP: stroker takes meterlimit in 16.16 co-ordinate ftMiterLimit = SW_FT_Fixed(miterLimit * (1 << 16)); // map to freetype capstyle switch (cap) { case CapStyle::Square: ftCap = SW_FT_STROKER_LINECAP_SQUARE; break; case CapStyle::Round: ftCap = SW_FT_STROKER_LINECAP_ROUND; break; default: ftCap = SW_FT_STROKER_LINECAP_BUTT; break; } switch (join) { case JoinStyle::Bevel: ftJoin = SW_FT_STROKER_LINEJOIN_BEVEL; break; case JoinStyle::Round: ftJoin = SW_FT_STROKER_LINEJOIN_ROUND; break; default: ftJoin = SW_FT_STROKER_LINEJOIN_MITER_FIXED; break; } } void FTOutline::moveTo(const VPointF &pt) { assert(ft.n_points <= SHRT_MAX - 1); ft.points[ft.n_points].x = TO_FT_COORD(pt.x()); ft.points[ft.n_points].y = TO_FT_COORD(pt.y()); ft.tags[ft.n_points] = SW_FT_CURVE_TAG_ON; if (ft.n_points) { ft.contours[ft.n_contours] = ft.n_points - 1; ft.n_contours++; } // mark the current contour as open // will be updated if ther is a close tag at the end. ft.contours_flag[ft.n_contours] = 1; ft.n_points++; } void FTOutline::lineTo(const VPointF &pt) { assert(ft.n_points <= SHRT_MAX - 1); ft.points[ft.n_points].x = TO_FT_COORD(pt.x()); ft.points[ft.n_points].y = TO_FT_COORD(pt.y()); ft.tags[ft.n_points] = SW_FT_CURVE_TAG_ON; ft.n_points++; } void FTOutline::cubicTo(const VPointF &cp1, const VPointF &cp2, const VPointF ep) { assert(ft.n_points <= SHRT_MAX - 3); ft.points[ft.n_points].x = TO_FT_COORD(cp1.x()); ft.points[ft.n_points].y = TO_FT_COORD(cp1.y()); ft.tags[ft.n_points] = SW_FT_CURVE_TAG_CUBIC; ft.n_points++; ft.points[ft.n_points].x = TO_FT_COORD(cp2.x()); ft.points[ft.n_points].y = TO_FT_COORD(cp2.y()); ft.tags[ft.n_points] = SW_FT_CURVE_TAG_CUBIC; ft.n_points++; ft.points[ft.n_points].x = TO_FT_COORD(ep.x()); ft.points[ft.n_points].y = TO_FT_COORD(ep.y()); ft.tags[ft.n_points] = SW_FT_CURVE_TAG_ON; ft.n_points++; } void FTOutline::close() { assert(ft.n_points <= SHRT_MAX - 1); // mark the contour as a close path. ft.contours_flag[ft.n_contours] = 0; int index; if (ft.n_contours) { index = ft.contours[ft.n_contours - 1] + 1; } else { index = 0; } // make sure atleast 1 point exists in the segment. if (ft.n_points == index) { closed = false; return; } ft.points[ft.n_points].x = ft.points[index].x; ft.points[ft.n_points].y = ft.points[index].y; ft.tags[ft.n_points] = SW_FT_CURVE_TAG_ON; ft.n_points++; } void FTOutline::end() { assert(ft.n_contours <= SHRT_MAX - 1); if (ft.n_points) { ft.contours[ft.n_contours] = ft.n_points - 1; ft.n_contours++; } } static void rleGenerationCb(int count, const SW_FT_Span *spans, void *user) { VRle *rle = static_cast(user); auto *rleSpan = reinterpret_cast(spans); rle->addSpan(rleSpan, count); } static void bboxCb(int x, int y, int w, int h, void *user) { VRle *rle = static_cast(user); rle->setBoundingRect({x, y, w, h}); } class SharedRle { public: SharedRle() = default; VRle &unsafe() { return _rle; } void notify() { { std::lock_guard lock(_mutex); _ready = true; } _cv.notify_one(); } void wait() { if (!_pending) return; { std::unique_lock lock(_mutex); while (!_ready) _cv.wait(lock); } _pending = false; } VRle &get() { wait(); return _rle; } void reset() { wait(); _ready = false; _pending = true; } private: VRle _rle; std::mutex _mutex; std::condition_variable _cv; bool _ready{true}; bool _pending{false}; }; struct VRleTask { SharedRle mRle; VPath mPath; float mStrokeWidth; float mMiterLimit; VRect mClip; FillRule mFillRule; CapStyle mCap; JoinStyle mJoin; bool mGenerateStroke; VRle &rle() { return mRle.get(); } void update(VPath path, FillRule fillRule, const VRect &clip) { mRle.reset(); mPath = std::move(path); mFillRule = fillRule; mClip = clip; mGenerateStroke = false; } void update(VPath path, CapStyle cap, JoinStyle join, float width, float miterLimit, const VRect &clip) { mRle.reset(); mPath = std::move(path); mCap = cap; mJoin = join; mStrokeWidth = width; mMiterLimit = miterLimit; mClip = clip; mGenerateStroke = true; } void render(FTOutline &outRef) { SW_FT_Raster_Params params; mRle.unsafe().reset(); params.flags = SW_FT_RASTER_FLAG_DIRECT | SW_FT_RASTER_FLAG_AA; params.gray_spans = &rleGenerationCb; params.bbox_cb = &bboxCb; params.user = &mRle.unsafe(); params.source = &outRef.ft; if (!mClip.empty()) { params.flags |= SW_FT_RASTER_FLAG_CLIP; params.clip_box.xMin = mClip.left(); params.clip_box.yMin = mClip.top(); params.clip_box.xMax = mClip.right(); params.clip_box.yMax = mClip.bottom(); } // compute rle sw_ft_grays_raster.raster_render(nullptr, ¶ms); } void operator()(FTOutline &outRef, SW_FT_Stroker &stroker) { if (mPath.points().size() > SHRT_MAX || mPath.points().size() + mPath.segments() > SHRT_MAX) { return; } if (mGenerateStroke) { // Stroke Task outRef.convert(mPath); outRef.convert(mCap, mJoin, mStrokeWidth, mMiterLimit); uint32_t points, contors; SW_FT_Stroker_Set(stroker, outRef.ftWidth, outRef.ftCap, outRef.ftJoin, outRef.ftMiterLimit); SW_FT_Stroker_ParseOutline(stroker, &outRef.ft); SW_FT_Stroker_GetCounts(stroker, &points, &contors); outRef.grow(points, contors); SW_FT_Stroker_Export(stroker, &outRef.ft); } else { // Fill Task outRef.convert(mPath); int fillRuleFlag = SW_FT_OUTLINE_NONE; switch (mFillRule) { case FillRule::EvenOdd: fillRuleFlag = SW_FT_OUTLINE_EVEN_ODD_FILL; break; default: fillRuleFlag = SW_FT_OUTLINE_NONE; break; } outRef.ft.flags = fillRuleFlag; } render(outRef); mPath = VPath(); mRle.notify(); } }; using VTask = std::shared_ptr; #ifdef LOTTIE_THREAD_SUPPORT #include #include "vtaskqueue.h" #ifdef __linux__ #include #include #endif class RleTaskScheduler { const unsigned _count{std::thread::hardware_concurrency()}; std::vector _threads; std::vector> _q{_count}; std::atomic _index{0}; void run(unsigned i) { /* * initalize per thread objects. */ FTOutline outlineRef; SW_FT_Stroker stroker; SW_FT_Stroker_New(&stroker); // Create Thread Name for Debugging (Linux) #ifdef __linux__ std::ostringstream nameStream; nameStream << "lottie-tsk-" << i; pthread_setname_np(pthread_self(), nameStream.str().c_str()); #endif // Task Loop VTask task; while (true) { bool success = false; for (unsigned n = 0; n != _count * 2; ++n) { if (_q[(i + n) % _count].try_pop(task)) { success = true; break; } } if (!success && !_q[i].pop(task)) break; (*task)(outlineRef, stroker); } // cleanup SW_FT_Stroker_Done(stroker); } RleTaskScheduler() { for (unsigned n = 0; n != _count; ++n) { _threads.emplace_back([&, n] { run(n); }); } IsRunning = true; } public: static bool IsRunning; static RleTaskScheduler &instance() { static RleTaskScheduler singleton; return singleton; } ~RleTaskScheduler() { stop(); } void stop() { if (IsRunning) { IsRunning = false; for (auto &e : _q) e.done(); for (auto &e : _threads) e.join(); } } void process(VTask task) { auto i = _index++; for (unsigned n = 0; n != _count; ++n) { if (_q[(i + n) % _count].try_push(std::move(task))) return; } if (_count > 0) { _q[i % _count].push(std::move(task)); } } }; #else class RleTaskScheduler { public: FTOutline outlineRef{}; SW_FT_Stroker stroker; public: static bool IsRunning; static RleTaskScheduler &instance() { static RleTaskScheduler singleton; return singleton; } void stop() {} RleTaskScheduler() { SW_FT_Stroker_New(&stroker); } ~RleTaskScheduler() { SW_FT_Stroker_Done(stroker); } void process(VTask task) { (*task)(outlineRef, stroker); } }; #endif bool RleTaskScheduler::IsRunning{false}; struct VRasterizer::VRasterizerImpl { VRleTask mTask; VRle & rle() { return mTask.rle(); } VRleTask &task() { return mTask; } }; VRle VRasterizer::rle() { if (!d) return VRle(); return d->rle(); } void VRasterizer::init() { if (!d) d = std::make_shared(); } void VRasterizer::updateRequest() { VTask taskObj = VTask(d, &d->task()); RleTaskScheduler::instance().process(std::move(taskObj)); } void VRasterizer::rasterize(VPath path, FillRule fillRule, const VRect &clip) { init(); if (path.empty()) { d->rle().reset(); return; } d->task().update(std::move(path), fillRule, clip); updateRequest(); } void VRasterizer::rasterize(VPath path, CapStyle cap, JoinStyle join, float width, float miterLimit, const VRect &clip) { init(); if (path.empty() || vIsZero(width)) { d->rle().reset(); return; } d->task().update(std::move(path), cap, join, width, miterLimit, clip); updateRequest(); } void lottieShutdownRasterTaskScheduler() { if (RleTaskScheduler::IsRunning) { RleTaskScheduler::instance().stop(); } } V_END_NAMESPACE