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