diff --git a/meson.build b/meson.build index 7ec44ce0..9dece91d 100644 --- a/meson.build +++ b/meson.build @@ -13,6 +13,11 @@ add_project_arguments('-DEXAMPLE_DIR="@0@/src/examples/resources"'.format(src_di config_h.set_quoted('THORVG_VERSION_STRING', meson.project_version()) +#Multi-Tasking +if get_option('threads') == true + config_h.set10('THORVG_THREAD_SUPPORT', true) +endif + #Engines if get_option('engines').contains('sw') == true config_h.set10('THORVG_SW_RASTER_SUPPORT', true) @@ -132,32 +137,34 @@ Summary: ThorVG version: @0@ Build Type: @1@ Prefix: @2@ - SIMD Instruction: @3@ - Raster Engine (SW): @4@ - Raster Engine (GL): @5@ - Raster Engine (WG): @6@ - Loader (TVG): @7@ - Loader (SVG): @8@ - Loader (TTF): @9@ - Loader (LOTTIE): @10@ - Loader (PNG): @11@ - Loader (JPG): @12@ - Loader (WEBP_BETA): @13@ - Saver (TVG): @14@ - Saver (GIF): @15@ - Binding (CAPI): @16@ - Binding (WASM_BETA): @17@ - Log Message: @18@ - Tests: @19@ - Examples: @20@ - Tool (Svg2Tvg): @21@ - Tool (Svg2Png): @22@ - Tool (Lottie2Gif): @23@ + Multi-Tasking: @3@ + SIMD Instruction: @4@ + Raster Engine (SW): @5@ + Raster Engine (GL): @6@ + Raster Engine (WG): @7@ + Loader (TVG): @8@ + Loader (SVG): @9@ + Loader (TTF): @10@ + Loader (LOTTIE): @11@ + Loader (PNG): @12@ + Loader (JPG): @13@ + Loader (WEBP_BETA): @14@ + Saver (TVG): @15@ + Saver (GIF): @16@ + Binding (CAPI): @17@ + Binding (WASM_BETA): @18@ + Log Message: @19@ + Tests: @20@ + Examples: @21@ + Tool (Svg2Tvg): @22@ + Tool (Svg2Png): @23@ + Tool (Lottie2Gif): @24@ '''.format( meson.project_version(), get_option('buildtype'), get_option('prefix'), + get_option('threads'), simd_type, get_option('engines').contains('sw'), get_option('engines').contains('gl_beta'), diff --git a/meson_options.txt b/meson_options.txt index d9db5aea..c9e6c89a 100644 --- a/meson_options.txt +++ b/meson_options.txt @@ -16,6 +16,11 @@ option('savers', value: [''], description: 'Enable File Savers in thorvg') +option('threads', + type: 'boolean', + value: true, + description: 'Enable the multi-threading task scheduler in thorvg') + option('vector', type: 'boolean', value: false, diff --git a/src/common/meson.build b/src/common/meson.build index 362d7f7a..41193656 100644 --- a/src/common/meson.build +++ b/src/common/meson.build @@ -1,9 +1,10 @@ source_file = [ 'tvgArray.h', 'tvgBezier.h', - 'tvgFormat.h', 'tvgCompressor.h', + 'tvgFormat.h', 'tvgInlist.h', + 'tvgLock.h', 'tvgMath.h', 'tvgStr.h', 'tvgBezier.cpp', diff --git a/src/common/tvgLock.h b/src/common/tvgLock.h new file mode 100644 index 00000000..ea78e046 --- /dev/null +++ b/src/common/tvgLock.h @@ -0,0 +1,70 @@ +/* + * 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. + */ + +#ifndef _TVG_LOCK_H_ +#define _TVG_LOCK_H_ + +#ifdef THORVG_THREAD_SUPPORT + +#include + +namespace tvg { + + struct Key + { + std::mutex mtx; + }; + + struct ScopedLock + { + Key* key = nullptr; + + ScopedLock(Key& key) + { + key.mtx.lock(); + this->key = &key; + } + + ~ScopedLock() + { + key->mtx.unlock(); + } + }; + +} + +#else //THORVG_THREAD_SUPPORT + +namespace tvg { + + struct Key {}; + + struct ScopedLock + { + ScopedLock(Key& key) {} + }; + +} + +#endif //THORVG_THREAD_SUPPORT + +#endif //_TVG_LOCK_H_ \ No newline at end of file diff --git a/src/meson.build b/src/meson.build index ad218c3c..1385a7ef 100644 --- a/src/meson.build +++ b/src/meson.build @@ -42,7 +42,8 @@ subdir('loaders') subdir('savers') thorvg_lib_dep = [common_dep, utils_dep, loader_dep, saver_dep] -if host_machine.system() != 'windows' and host_machine.system() != 'android' + +if get_option('threads') == true and host_machine.system() != 'windows' and host_machine.system() != 'android' thread_dep = meson.get_compiler('cpp').find_library('pthread') thorvg_lib_dep += [thread_dep] endif diff --git a/src/renderer/sw_engine/tvgSwRaster.cpp b/src/renderer/sw_engine/tvgSwRaster.cpp index 9a6dc459..fd45ecb9 100644 --- a/src/renderer/sw_engine/tvgSwRaster.cpp +++ b/src/renderer/sw_engine/tvgSwRaster.cpp @@ -1855,7 +1855,7 @@ void rasterUnpremultiply(Surface* surface) void rasterPremultiply(Surface* surface) { - unique_lock lock{surface->mtx}; + ScopedLock lock(surface->key); if (surface->premultiplied || (surface->channelSize != sizeof(uint32_t))) return; surface->premultiplied = true; @@ -1936,7 +1936,7 @@ bool rasterImage(SwSurface* surface, SwImage* image, const RenderMesh* mesh, con bool rasterConvertCS(Surface* surface, ColorSpace to) { - unique_lock lock{surface->mtx}; + ScopedLock lock(surface->key); if (surface->cs == to) return true; //TOOD: Support SIMD accelerations diff --git a/src/renderer/tvgLoader.cpp b/src/renderer/tvgLoader.cpp index d1f67cd6..e2ee0e1e 100644 --- a/src/renderer/tvgLoader.cpp +++ b/src/renderer/tvgLoader.cpp @@ -24,6 +24,7 @@ #include "tvgInlist.h" #include "tvgLoader.h" +#include "tvgLock.h" #ifdef THORVG_SVG_LOADER_SUPPORT #include "tvgSvgLoader.h" @@ -65,7 +66,7 @@ uint64_t HASH_KEY(const char* data, uint64_t size) /* Internal Class Implementation */ /************************************************************************/ -static mutex mtx; +static Key key; static Inlist _activeLoaders; @@ -211,7 +212,7 @@ static LoadModule* _findByType(const string& mimeType) static LoadModule* _findFromCache(const string& path) { - unique_lock lock{mtx}; + ScopedLock lock(key); auto loader = _activeLoaders.head; @@ -231,7 +232,7 @@ static LoadModule* _findFromCache(const char* data, uint32_t size, const string& auto type = _convert(mimeType); if (type == FileType::Unknown) return nullptr; - unique_lock lock{mtx}; + ScopedLock lock(key); auto loader = _activeLoaders.head; auto key = HASH_KEY(data, size); @@ -279,7 +280,7 @@ bool LoaderMgr::retrieve(LoadModule* loader) if (!loader) return false; if (loader->close()) { { - unique_lock lock{mtx}; + ScopedLock lock(key); _activeLoaders.remove(loader); } delete(loader); @@ -298,7 +299,7 @@ LoadModule* LoaderMgr::loader(const string& path, bool* invalid) if (loader->open(path)) { loader->hashpath = strdup(path.c_str()); { - unique_lock lock{mtx}; + ScopedLock lock(key); _activeLoaders.back(loader); } return loader; @@ -340,7 +341,7 @@ LoadModule* LoaderMgr::loader(const char* data, uint32_t size, const string& mim if (auto loader = _findByType(mimeType)) { if (loader->open(data, size, rpath, copy)) { loader->hashkey = HASH_KEY(data, size); - unique_lock lock{mtx}; + ScopedLock lock(key); _activeLoaders.back(loader); return loader; } else { @@ -356,7 +357,7 @@ LoadModule* LoaderMgr::loader(const char* data, uint32_t size, const string& mim if (loader->open(data, size, rpath, copy)) { loader->hashkey = HASH_KEY(data, size); { - unique_lock lock{mtx}; + ScopedLock lock(key); _activeLoaders.back(loader); } return loader; @@ -379,7 +380,7 @@ LoadModule* LoaderMgr::loader(const uint32_t *data, uint32_t w, uint32_t h, bool if (loader->open(data, w, h, premultiplied, copy)) { loader->hashkey = HASH_KEY((const char*)data, w * h); { - unique_lock lock{mtx}; + ScopedLock lock(key); _activeLoaders.back(loader); } return loader; diff --git a/src/renderer/tvgRender.h b/src/renderer/tvgRender.h index bc6ed244..cdc4307f 100644 --- a/src/renderer/tvgRender.h +++ b/src/renderer/tvgRender.h @@ -23,9 +23,9 @@ #ifndef _TVG_RENDER_H_ #define _TVG_RENDER_H_ -#include #include "tvgCommon.h" #include "tvgArray.h" +#include "tvgLock.h" namespace tvg { @@ -54,7 +54,7 @@ struct Surface uint32_t* buf32; //for explicit 32bits channels uint8_t* buf8; //for explicit 8bits grayscale }; - mutex mtx; //reserved for the thread safety + Key key; //a reserved lock for the thread safety uint32_t stride = 0; uint32_t w = 0, h = 0; ColorSpace cs = ColorSpace::Unsupported; diff --git a/src/renderer/tvgTaskScheduler.cpp b/src/renderer/tvgTaskScheduler.cpp index 20ece636..ac683b4e 100644 --- a/src/renderer/tvgTaskScheduler.cpp +++ b/src/renderer/tvgTaskScheduler.cpp @@ -20,19 +20,28 @@ * SOFTWARE. */ -#include -#include -#include #include "tvgArray.h" #include "tvgInlist.h" #include "tvgTaskScheduler.h" +#ifdef THORVG_THREAD_SUPPORT + #include + #include +#endif + /************************************************************************/ /* Internal Class Implementation */ /************************************************************************/ namespace tvg { +struct TaskSchedulerImpl; +static TaskSchedulerImpl* inst = nullptr; + +#ifdef THORVG_THREAD_SUPPORT + +static thread_local bool _async = true; + struct TaskQueue { Inlist taskDeque; mutex mtx; @@ -61,7 +70,7 @@ struct TaskQueue { void complete() { { - unique_lock lock{mtx}; + lock_guard lock{mtx}; done = true; } ready.notify_all(); @@ -84,7 +93,7 @@ struct TaskQueue { void push(Task* task) { { - unique_lock lock{mtx}; + lock_guard lock{mtx}; taskDeque.back(task); } ready.notify_one(); @@ -92,25 +101,22 @@ struct TaskQueue { }; -static thread_local bool _async = true; //toggle async tasking for each thread on/off - - struct TaskSchedulerImpl { Array threads; Array taskQueues; atomic idx{0}; - TaskSchedulerImpl(unsigned threadCnt) + TaskSchedulerImpl(uint32_t threadCnt) { threads.reserve(threadCnt); taskQueues.reserve(threadCnt); - for (unsigned i = 0; i < threadCnt; ++i) { + for (uint32_t i = 0; i < threadCnt; ++i) { taskQueues.push(new TaskQueue); threads.push(new thread); } - for (unsigned i = 0; i < threadCnt; ++i) { + for (uint32_t i = 0; i < threadCnt; ++i) { *threads.data[i] = thread([&, i] { run(i); }); } } @@ -136,7 +142,7 @@ struct TaskSchedulerImpl //Thread Loop while (true) { auto success = false; - for (unsigned x = 0; x < threads.count * 2; ++x) { + for (uint32_t x = 0; x < threads.count * 2; ++x) { if (taskQueues[(i + x) % threads.count]->tryPop(&task)) { success = true; break; @@ -154,7 +160,7 @@ struct TaskSchedulerImpl if (threads.count > 0 && _async) { task->prepare(); auto i = idx++; - for (unsigned n = 0; n < threads.count; ++n) { + for (uint32_t n = 0; n < threads.count; ++n) { if (taskQueues[(i + n) % threads.count]->tryPush(task)) return; } taskQueues[i % threads.count]->push(task); @@ -163,17 +169,33 @@ struct TaskSchedulerImpl task->run(0); } } + + uint32_t threadCnt() + { + return threads.count; + } }; -} +#else //THORVG_THREAD_SUPPORT -static TaskSchedulerImpl* inst = nullptr; +static bool _async = true; + +struct TaskSchedulerImpl +{ + TaskSchedulerImpl(TVG_UNUSED uint32_t threadCnt) {} + void request(Task* task) { task->run(0); } + uint32_t threadCnt() { return 0; } +}; + +#endif //THORVG_THREAD_SUPPORT + +} //namespace /************************************************************************/ /* External Class Implementation */ /************************************************************************/ -void TaskScheduler::init(unsigned threads) +void TaskScheduler::init(uint32_t threads) { if (inst) return; inst = new TaskSchedulerImpl(threads); @@ -182,7 +204,6 @@ void TaskScheduler::init(unsigned threads) void TaskScheduler::term() { - if (!inst) return; delete(inst); inst = nullptr; } @@ -194,14 +215,15 @@ void TaskScheduler::request(Task* task) } -unsigned TaskScheduler::threads() +uint32_t TaskScheduler::threads() { - if (inst) return inst->threads.count; + if (inst) return inst->threadCnt(); return 0; } void TaskScheduler::async(bool on) { + //toggle async tasking for each thread on/off _async = on; } \ No newline at end of file diff --git a/src/renderer/tvgTaskScheduler.h b/src/renderer/tvgTaskScheduler.h index 4cdefab4..fb9de21c 100644 --- a/src/renderer/tvgTaskScheduler.h +++ b/src/renderer/tvgTaskScheduler.h @@ -25,22 +25,13 @@ #include #include + #include "tvgCommon.h" #include "tvgInlist.h" -namespace tvg -{ +namespace tvg { -struct Task; - -struct TaskScheduler -{ - static unsigned threads(); - static void init(unsigned threads); - static void term(); - static void request(Task* task); - static void async(bool on); -}; +#ifdef THORVG_THREAD_SUPPORT struct Task { @@ -86,7 +77,36 @@ private: friend struct TaskSchedulerImpl; }; -} +#else //THORVG_THREAD_SUPPORT + +struct Task +{ +public: + INLIST_ITEM(Task); + + virtual ~Task() = default; + void done() {} + +protected: + virtual void run(unsigned tid) = 0; + +private: + friend struct TaskSchedulerImpl; +}; + +#endif //THORVG_THREAD_SUPPORT + + +struct TaskScheduler +{ + static uint32_t threads(); + static void init(uint32_t threads); + static void term(); + static void request(Task* task); + static void async(bool on); +}; + +} //namespace #endif //_TVG_TASK_SCHEDULER_H_ \ No newline at end of file diff --git a/wasm_build.sh b/wasm_build.sh index 1bc36dab..5714dc31 100755 --- a/wasm_build.sh +++ b/wasm_build.sh @@ -4,7 +4,7 @@ if [ ! -d "./build_wasm" ]; then sed "s|EMSDK:|$1|g" ./cross/wasm_x86_i686.txt > /tmp/.wasm_cross.txt - meson -Db_lto=true -Ddefault_library=static -Dstatic=true -Dloaders="all" -Dsavers="all" -Dbindings="wasm_beta" --cross-file /tmp/.wasm_cross.txt build_wasm + meson -Db_lto=true -Ddefault_library=static -Dstatic=true -Dloaders="all" -Dsavers="all" -Dthreads=false -Dbindings="wasm_beta" --cross-file /tmp/.wasm_cross.txt build_wasm fi ninja -C build_wasm/