1 // This file is part of OpenCV project.
2 // It is subject to the license terms in the LICENSE file found in the top-level directory
3 // of this distribution and at http://opencv.org/license.html.
4 
5 //
6 // Not a standalone header, part of parallel.cpp
7 //
8 
9 //==================================================================================================
10 // Dynamic backend implementation
11 
12 #include "opencv2/core/utils/plugin_loader.private.hpp"
13 
14 namespace cv { namespace impl {
15 
16 using namespace cv::parallel;
17 
18 #if OPENCV_HAVE_FILESYSTEM_SUPPORT && defined(PARALLEL_ENABLE_PLUGINS)
19 
20 using namespace cv::plugin::impl;  // plugin_loader.hpp
21 
22 class PluginParallelBackend CV_FINAL: public std::enable_shared_from_this<PluginParallelBackend>
23 {
24 protected:
initPluginAPI()25     void initPluginAPI()
26     {
27         const char* init_name = "opencv_core_parallel_plugin_init_v0";
28         FN_opencv_core_parallel_plugin_init_t fn_init = reinterpret_cast<FN_opencv_core_parallel_plugin_init_t>(lib_->getSymbol(init_name));
29         if (fn_init)
30         {
31             CV_LOG_DEBUG(NULL, "Found entry: '" << init_name << "'");
32             for (int supported_api_version = API_VERSION; supported_api_version >= 0; supported_api_version--)
33             {
34                 plugin_api_ = fn_init(ABI_VERSION, supported_api_version, NULL);
35                 if (plugin_api_)
36                     break;
37             }
38             if (!plugin_api_)
39             {
40                 CV_LOG_INFO(NULL, "core(parallel): plugin is incompatible (can't be initialized): " << lib_->getName());
41                 return;
42             }
43             if (!checkCompatibility(plugin_api_->api_header, ABI_VERSION, API_VERSION, false))
44             {
45                 plugin_api_ = NULL;
46                 return;
47             }
48             CV_LOG_INFO(NULL, "core(parallel): plugin is ready to use '" << plugin_api_->api_header.api_description << "'");
49         }
50         else
51         {
52             CV_LOG_INFO(NULL, "core(parallel): plugin is incompatible, missing init function: '" << init_name << "', file: " << lib_->getName());
53         }
54     }
55 
56 
checkCompatibility(const OpenCV_API_Header & api_header,unsigned int abi_version,unsigned int api_version,bool checkMinorOpenCVVersion)57     bool checkCompatibility(const OpenCV_API_Header& api_header, unsigned int abi_version, unsigned int api_version, bool checkMinorOpenCVVersion)
58     {
59         if (api_header.opencv_version_major != CV_VERSION_MAJOR)
60         {
61             CV_LOG_ERROR(NULL, "core(parallel): wrong OpenCV major version used by plugin '" << api_header.api_description << "': " <<
62                 cv::format("%d.%d, OpenCV version is '" CV_VERSION "'", api_header.opencv_version_major, api_header.opencv_version_minor))
63             return false;
64         }
65         if (!checkMinorOpenCVVersion)
66         {
67             // no checks for OpenCV minor version
68         }
69         else if (api_header.opencv_version_minor != CV_VERSION_MINOR)
70         {
71             CV_LOG_ERROR(NULL, "core(parallel): wrong OpenCV minor version used by plugin '" << api_header.api_description << "': " <<
72                 cv::format("%d.%d, OpenCV version is '" CV_VERSION "'", api_header.opencv_version_major, api_header.opencv_version_minor))
73             return false;
74         }
75         CV_LOG_DEBUG(NULL, "core(parallel): initialized '" << api_header.api_description << "': built with "
76             << cv::format("OpenCV %d.%d (ABI/API = %d/%d)",
77                  api_header.opencv_version_major, api_header.opencv_version_minor,
78                  api_header.min_api_version, api_header.api_version)
79             << ", current OpenCV version is '" CV_VERSION "' (ABI/API = " << abi_version << "/" << api_version << ")"
80         );
81         if (api_header.min_api_version != abi_version)  // future: range can be here
82         {
83             // actually this should never happen due to checks in plugin's init() function
84             CV_LOG_ERROR(NULL, "core(parallel): plugin is not supported due to incompatible ABI = " << api_header.min_api_version);
85             return false;
86         }
87         if (api_header.api_version != api_version)
88         {
89             CV_LOG_INFO(NULL, "core(parallel): NOTE: plugin is supported, but there is API version mismath: "
90                 << cv::format("plugin API level (%d) != OpenCV API level (%d)", api_header.api_version, api_version));
91             if (api_header.api_version < api_version)
92             {
93                 CV_LOG_INFO(NULL, "core(parallel): NOTE: some functionality may be unavailable due to lack of support by plugin implementation");
94             }
95         }
96         return true;
97     }
98 
99 public:
100     std::shared_ptr<cv::plugin::impl::DynamicLib> lib_;
101     const OpenCV_Core_Parallel_Plugin_API* plugin_api_;
102 
PluginParallelBackend(const std::shared_ptr<cv::plugin::impl::DynamicLib> & lib)103     PluginParallelBackend(const std::shared_ptr<cv::plugin::impl::DynamicLib>& lib)
104         : lib_(lib)
105         , plugin_api_(NULL)
106     {
107         initPluginAPI();
108     }
109 
create() const110     std::shared_ptr<cv::parallel::ParallelForAPI> create() const
111     {
112         CV_Assert(plugin_api_);
113 
114         CvPluginParallelBackendAPI instancePtr = NULL;
115 
116         if (plugin_api_->v0.getInstance)
117         {
118             if (CV_ERROR_OK == plugin_api_->v0.getInstance(&instancePtr))
119             {
120                 CV_Assert(instancePtr);
121                 // TODO C++20 "aliasing constructor"
122                 return std::shared_ptr<cv::parallel::ParallelForAPI>(instancePtr, [](cv::parallel::ParallelForAPI*){});  // empty deleter
123             }
124         }
125         return std::shared_ptr<cv::parallel::ParallelForAPI>();
126     }
127 };
128 
129 
130 class PluginParallelBackendFactory CV_FINAL: public IParallelBackendFactory
131 {
132 public:
133     std::string baseName_;
134     std::shared_ptr<PluginParallelBackend> backend;
135     bool initialized;
136 public:
PluginParallelBackendFactory(const std::string & baseName)137     PluginParallelBackendFactory(const std::string& baseName)
138         : baseName_(baseName)
139         , initialized(false)
140     {
141         // nothing, plugins are loaded on demand
142     }
143 
create() const144     std::shared_ptr<cv::parallel::ParallelForAPI> create() const CV_OVERRIDE
145     {
146         if (!initialized)
147         {
148             const_cast<PluginParallelBackendFactory*>(this)->initBackend();
149         }
150         if (backend)
151             return backend->create();
152         return std::shared_ptr<cv::parallel::ParallelForAPI>();
153     }
154 protected:
initBackend()155     void initBackend()
156     {
157         AutoLock lock(getInitializationMutex());
158         try
159         {
160             if (!initialized)
161                 loadPlugin();
162         }
163         catch (...)
164         {
165             CV_LOG_INFO(NULL, "core(parallel): exception during plugin loading: " << baseName_ << ". SKIP");
166         }
167         initialized = true;
168     }
169     void loadPlugin();
170 };
171 
172 static
getPluginCandidates(const std::string & baseName)173 std::vector<FileSystemPath_t> getPluginCandidates(const std::string& baseName)
174 {
175     using namespace cv::utils;
176     using namespace cv::utils::fs;
177     const std::string baseName_l = toLowerCase(baseName);
178     const std::string baseName_u = toUpperCase(baseName);
179     const FileSystemPath_t baseName_l_fs = toFileSystemPath(baseName_l);
180     std::vector<FileSystemPath_t> paths;
181     // TODO OPENCV_PLUGIN_PATH
182     const std::vector<std::string> paths_ = getConfigurationParameterPaths("OPENCV_CORE_PLUGIN_PATH", std::vector<std::string>());
183     if (paths_.size() != 0)
184     {
185         for (size_t i = 0; i < paths_.size(); i++)
186         {
187             paths.push_back(toFileSystemPath(paths_[i]));
188         }
189     }
190     else
191     {
192         FileSystemPath_t binaryLocation;
193         if (getBinLocation(binaryLocation))
194         {
195             binaryLocation = getParent(binaryLocation);
196 #ifndef CV_CORE_PARALLEL_PLUGIN_SUBDIRECTORY
197             paths.push_back(binaryLocation);
198 #else
199             paths.push_back(binaryLocation + toFileSystemPath("/") + toFileSystemPath(CV_CORE_PARALLEL_PLUGIN_SUBDIRECTORY_STR));
200 #endif
201         }
202     }
203     const std::string default_expr = libraryPrefix() + "opencv_core_parallel_" + baseName_l + "*" + librarySuffix();
204     const std::string plugin_expr = getConfigurationParameterString((std::string("OPENCV_CORE_PARALLEL_PLUGIN_") + baseName_u).c_str(), default_expr.c_str());
205     std::vector<FileSystemPath_t> results;
206 #ifdef _WIN32
207     FileSystemPath_t moduleName = toFileSystemPath(libraryPrefix() + "opencv_core_parallel_" + baseName_l + librarySuffix());
208     if (plugin_expr != default_expr)
209     {
210         moduleName = toFileSystemPath(plugin_expr);
211         results.push_back(moduleName);
212     }
213     for (const FileSystemPath_t& path : paths)
214     {
215         results.push_back(path + L"\\" + moduleName);
216     }
217     results.push_back(moduleName);
218 #else
219     CV_LOG_DEBUG(NULL, "core(parallel): " << baseName << " plugin's glob is '" << plugin_expr << "', " << paths.size() << " location(s)");
220     for (const std::string& path : paths)
221     {
222         if (path.empty())
223             continue;
224         std::vector<std::string> candidates;
225         cv::glob(utils::fs::join(path, plugin_expr), candidates);
226         CV_LOG_DEBUG(NULL, "    - " << path << ": " << candidates.size());
227         copy(candidates.begin(), candidates.end(), back_inserter(results));
228     }
229 #endif
230     CV_LOG_DEBUG(NULL, "Found " << results.size() << " plugin(s) for " << baseName);
231     return results;
232 }
233 
loadPlugin()234 void PluginParallelBackendFactory::loadPlugin()
235 {
236     for (const FileSystemPath_t& plugin : getPluginCandidates(baseName_))
237     {
238         auto lib = std::make_shared<cv::plugin::impl::DynamicLib>(plugin);
239         if (!lib->isLoaded())
240         {
241             continue;
242         }
243         try
244         {
245             auto pluginBackend = std::make_shared<PluginParallelBackend>(lib);
246             if (!pluginBackend)
247             {
248                 continue;
249             }
250             if (pluginBackend->plugin_api_ == NULL)
251             {
252                 CV_LOG_ERROR(NULL, "core(parallel): no compatible plugin API for backend: " << baseName_ << " in " << toPrintablePath(plugin));
253                 continue;
254             }
255 #if !defined(_WIN32)
256             // NB: we are going to use parallel backend, so prevent automatic library unloading
257             // (avoid uncontrolled crashes in worker threads of underlying libraries: libgomp, libtbb)
258             // details: https://github.com/opencv/opencv/pull/19470#pullrequestreview-589834777
259             lib->disableAutomaticLibraryUnloading();
260 #endif
261             backend = pluginBackend;
262             return;
263         }
264         catch (...)
265         {
266             CV_LOG_WARNING(NULL, "core(parallel): exception during plugin initialization: " << toPrintablePath(plugin) << ". SKIP");
267         }
268     }
269 }
270 
271 #endif  // OPENCV_HAVE_FILESYSTEM_SUPPORT && defined(PARALLEL_ENABLE_PLUGINS)
272 
273 }  // namespace
274 
275 namespace parallel {
276 
createPluginParallelBackendFactory(const std::string & baseName)277 std::shared_ptr<IParallelBackendFactory> createPluginParallelBackendFactory(const std::string& baseName)
278 {
279 #if OPENCV_HAVE_FILESYSTEM_SUPPORT && defined(PARALLEL_ENABLE_PLUGINS)
280     return std::make_shared<impl::PluginParallelBackendFactory>(baseName);
281 #else
282     CV_UNUSED(baseName);
283     return std::shared_ptr<IParallelBackendFactory>();
284 #endif
285 }
286 
287 }}  // namespace
288