diff --git a/modules/core/include/opencv2/core/ocl.hpp b/modules/core/include/opencv2/core/ocl.hpp index 5f13e04ea9..540e2f80ea 100644 --- a/modules/core/include/opencv2/core/ocl.hpp +++ b/modules/core/include/opencv2/core/ocl.hpp @@ -261,6 +261,8 @@ public: void setUseSVM(bool enabled); struct Impl; + inline Impl* getImpl() const { return (Impl*)p; } +//protected: Impl* p; }; diff --git a/modules/core/include/opencv2/core/utils/filesystem.hpp b/modules/core/include/opencv2/core/utils/filesystem.hpp new file mode 100644 index 0000000000..e8ffa4b399 --- /dev/null +++ b/modules/core/include/opencv2/core/utils/filesystem.hpp @@ -0,0 +1,38 @@ +// This file is part of OpenCV project. +// It is subject to the license terms in the LICENSE file found in the top-level directory +// of this distribution and at http://opencv.org/license.html. + +#ifndef OPENCV_UTILS_FILESYSTEM_HPP +#define OPENCV_UTILS_FILESYSTEM_HPP + +namespace cv { namespace utils { namespace fs { + + +CV_EXPORTS bool exists(const cv::String& path); +CV_EXPORTS bool isDirectory(const cv::String& path); + + +CV_EXPORTS cv::String getcwd(); + + +CV_EXPORTS bool createDirectory(const cv::String& path); +CV_EXPORTS bool createDirectories(const cv::String& path); + +#ifdef __OPENCV_BUILD +// TODO +//CV_EXPORTS cv::String getTempDirectory(); + +/** + * @brief Returns directory to store OpenCV cache files + * Create sub-directory in common OpenCV cache directory if it doesn't exist. + * @param sub_directory_name name of sub-directory. NULL or "" value asks to return root cache directory. + * @param configuration_name optional name of configuration parameter name which overrides default behavior. + * @return Path to cache directory. Returns empty string if cache directories support is not available. Returns "disabled" if cache disabled by user. + */ +CV_EXPORTS cv::String getCacheDirectory(const char* sub_directory_name, const char* configuration_name = NULL); + +#endif + +}}} // namespace + +#endif // OPENCV_UTILS_FILESYSTEM_HPP diff --git a/modules/core/include/opencv2/core/utils/filesystem.private.hpp b/modules/core/include/opencv2/core/utils/filesystem.private.hpp new file mode 100644 index 0000000000..28b174742f --- /dev/null +++ b/modules/core/include/opencv2/core/utils/filesystem.private.hpp @@ -0,0 +1,64 @@ +// This file is part of OpenCV project. +// It is subject to the license terms in the LICENSE file found in the top-level directory +// of this distribution and at http://opencv.org/license.html. + +#ifndef OPENCV_UTILS_FILESYSTEM_PRIVATE_HPP +#define OPENCV_UTILS_FILESYSTEM_PRIVATE_HPP + +// TODO Move to CMake? +#ifndef OPENCV_HAVE_FILESYSTEM_SUPPORT +# if defined(__EMSCRIPTEN__) || defined(__native_client__) + /* no support */ +# elif defined __ANDROID__ || defined __linux__ || defined _WIN32 || \ + defined __FreeBSD__ || defined __bsdi__ +# define OPENCV_HAVE_FILESYSTEM_SUPPORT 1 +# elif defined(__APPLE__) +# include +# if (defined(TARGET_OS_OSX) && TARGET_OS_OSX) || (!defined(TARGET_OS_OSX) && !TARGET_OS_IPHONE) +# define OPENCV_HAVE_FILESYSTEM_SUPPORT 1 // OSX only +# endif +# else + /* unknown */ +# endif +# ifndef OPENCV_HAVE_FILESYSTEM_SUPPORT +# define OPENCV_HAVE_FILESYSTEM_SUPPORT 0 +# endif +#endif + +#if OPENCV_HAVE_FILESYSTEM_SUPPORT +namespace cv { namespace utils { namespace fs { + +/** + * File-based lock object. + * + * Provides interprocess synchronization mechanism. + * Platform dependent. + * + * Supports multiple readers / single writer access pattern (RW / readers–writer / shared-exclusive lock). + * + * File must exist. + * File can't be re-used (for example, I/O operations via std::fstream is not safe) + */ +class CV_EXPORTS FileLock { +public: + explicit FileLock(const char* fname); + ~FileLock(); + + void lock(); //< acquire exclusive (writer) lock + void unlock(); //< release exclusive (writer) lock + + void lock_shared(); //< acquire sharable (reader) lock + void unlock_shared(); //< release sharable (reader) lock + + struct Impl; +protected: + Impl* pImpl; + +private: + FileLock(const FileLock&); // disabled + FileLock& operator=(const FileLock&); // disabled +}; + +}}} // namespace +#endif +#endif // OPENCV_UTILS_FILESYSTEM_PRIVATE_HPP diff --git a/modules/core/include/opencv2/core/utils/lock.private.hpp b/modules/core/include/opencv2/core/utils/lock.private.hpp new file mode 100644 index 0000000000..7148a39180 --- /dev/null +++ b/modules/core/include/opencv2/core/utils/lock.private.hpp @@ -0,0 +1,119 @@ +// This file is part of OpenCV project. +// It is subject to the license terms in the LICENSE file found in the top-level directory +// of this distribution and at http://opencv.org/license.html. + +#ifndef OPENCV_UTILS_LOCK_HPP +#define OPENCV_UTILS_LOCK_HPP + +namespace cv { namespace utils { + + +/** @brief A simple scoped lock (RAII-style locking for exclusive/write access). + * + * Emulate std::lock_guard (C++11), partially std::unique_lock (C++11), + */ +template +class lock_guard { +public: + typedef _Mutex Mutex; + + explicit inline lock_guard(Mutex &m) : mutex_(&m) { mutex_->lock(); } + + inline ~lock_guard() { if (mutex_) mutex_->unlock(); } + + inline void release() + { + CV_DbgAssert(mutex_); + mutex_->unlock(); + mutex_ = NULL; + } + +private: + Mutex* mutex_; + +private: + lock_guard(const lock_guard&); // disabled + lock_guard& operator=(const lock_guard&); // disabled +}; + + +/** @brief A shared scoped lock (RAII-style locking for shared/reader access). + * + * Emulate boost::shared_lock_guard, subset of std::shared_lock (C++14), + */ +template +class shared_lock_guard { +public: + typedef _Mutex Mutex; + + explicit inline shared_lock_guard(Mutex &m) : mutex_(&m) { mutex_->lock_shared(); } + + inline ~shared_lock_guard() { if (mutex_) mutex_->unlock_shared(); } + + inline void release() + { + CV_DbgAssert(mutex_); + mutex_->unlock_shared(); + mutex_ = NULL; + } + +protected: + Mutex* mutex_; + +private: + shared_lock_guard(const shared_lock_guard&); // disabled + shared_lock_guard& operator=(const shared_lock_guard&); // disabled +}; + + +/** @brief An optional simple scoped lock (RAII-style locking for exclusive/write access). + * + * Doesn't lock if mutex pointer is NULL. + * + * @sa lock_guard + */ +template +class optional_lock_guard { +public: + typedef _Mutex Mutex; + + explicit inline optional_lock_guard(Mutex* m) : mutex_(m) { if (mutex_) mutex_->lock(); } + + inline ~optional_lock_guard() { if (mutex_) mutex_->unlock(); } + +private: + Mutex* mutex_; + +private: + optional_lock_guard(const optional_lock_guard&); // disabled + optional_lock_guard& operator=(const optional_lock_guard&); // disabled +}; + + +/** @brief An optional shared scoped lock (RAII-style locking for shared/reader access). + * + * Doesn't lock if mutex pointer is NULL. + * + * @sa shared_lock_guard + */ +template +class optional_shared_lock_guard { +public: + typedef _Mutex Mutex; + + explicit inline optional_shared_lock_guard(Mutex* m) : mutex_(m) { if (mutex_) mutex_->lock_shared(); } + + inline ~optional_shared_lock_guard() { if (mutex_) mutex_->unlock_shared(); } + +protected: + Mutex* mutex_; + +private: + optional_shared_lock_guard(const optional_shared_lock_guard&); // disabled + optional_shared_lock_guard& operator=(const optional_shared_lock_guard&); // disabled +}; + + +}} // namespace + +#endif // OPENCV_UTILS_LOCK_HPP diff --git a/modules/core/src/glob.cpp b/modules/core/src/glob.cpp index 60a220d130..844e64620e 100644 --- a/modules/core/src/glob.cpp +++ b/modules/core/src/glob.cpp @@ -42,6 +42,8 @@ #include "precomp.hpp" +#include "opencv2/core/utils/filesystem.hpp" + #if defined _WIN32 || defined WINCE # include const char dir_separators[] = "/\\"; @@ -169,6 +171,12 @@ static bool isDir(const cv::String& path, DIR* dir) #endif } +bool cv::utils::fs::isDirectory(const cv::String& path) +{ + CV_INSTRUMENT_REGION() + return isDir(path, NULL); +} + static bool wildcmp(const char *string, const char *wild) { // Based on wildcmp written by Jack Handy - jakkhandy@hotmail.com diff --git a/modules/core/src/ocl.cpp b/modules/core/src/ocl.cpp index 5d7981aeb4..3d596ef4f3 100644 --- a/modules/core/src/ocl.cpp +++ b/modules/core/src/ocl.cpp @@ -47,22 +47,31 @@ #include #include #include // std::cerr +#include #if !(defined _MSC_VER) || (defined _MSC_VER && _MSC_VER > 1700) #include #endif #include +#include + #include "opencv2/core/ocl_genbase.hpp" #include "opencl_kernels_core.hpp" -#define CV_OPENCL_ALWAYS_SHOW_BUILD_LOG 0 +#include "opencv2/core/utils/lock.private.hpp" +#include "opencv2/core/utils/filesystem.hpp" +#include "opencv2/core/utils/filesystem.private.hpp" -#define CV_OPENCL_SHOW_RUN_KERNELS 0 -#define CV_OPENCL_TRACE_CHECK 0 +#define CV_OPENCL_ALWAYS_SHOW_BUILD_LOG 0 -#define CV_OPENCL_SHOW_SVM_ERROR_LOG 1 -#define CV_OPENCL_SHOW_SVM_LOG 0 +#define CV_OPENCL_SHOW_RUN_KERNELS 0 +#define CV_OPENCL_TRACE_CHECK 0 + +#define CV_OPENCL_VALIDATE_BINARY_PROGRAMS 1 + +#define CV_OPENCL_SHOW_SVM_ERROR_LOG 1 +#define CV_OPENCL_SHOW_SVM_LOG 0 #include "opencv2/core/bufferpool.hpp" #ifndef LOG_BUFFER_POOL @@ -167,6 +176,16 @@ void traceOpenCLCheck(cl_int status, const char* message) #define CV_OCL_DBG_CHECK(expr) do { cl_int __cl_result = (expr); CV_OCL_DBG_CHECK_RESULT(__cl_result, #expr); } while (0) #endif + +static const bool CV_OPENCL_CACHE_ENABLE = utils::getConfigurationParameterBool("OPENCV_OPENCL_CACHE_ENABLE", true); +static const bool CV_OPENCL_CACHE_WRITE = utils::getConfigurationParameterBool("OPENCV_OPENCL_CACHE_WRITE", true); +static const bool CV_OPENCL_CACHE_LOCK_ENABLE = utils::getConfigurationParameterBool("OPENCV_OPENCL_CACHE_LOCK_ENABLE", true); + +#if CV_OPENCL_VALIDATE_BINARY_PROGRAMS +static const bool CV_OPENCL_VALIDATE_BINARY_PROGRAMS_VALUE = utils::getConfigurationParameterBool("OPENCV_OPENCL_VALIDATE_BINARY_PROGRAMS", false); +#endif + + struct UMat2D { UMat2D(const UMat& m) @@ -226,6 +245,486 @@ static uint64 crc64( const uchar* data, size_t size, uint64 crc0=0 ) return ~crc; } +#if OPENCV_HAVE_FILESYSTEM_SUPPORT +struct OpenCLBinaryCacheConfigurator +{ + cv::String cache_path_; + cv::String cache_lock_filename_; + cv::Ptr cache_lock_; + + typedef std::map ContextCacheType; + ContextCacheType prepared_contexts_; + + OpenCLBinaryCacheConfigurator() + { + CV_LOG_DEBUG(NULL, "Initializing OpenCL cache configuration..."); + if (!CV_OPENCL_CACHE_ENABLE) + { + CV_LOG_INFO(NULL, "OpenCL cache is disabled"); + return; + } + cache_path_ = utils::fs::getCacheDirectory("opencl_cache", "OPENCV_OPENCL_CACHE_DIR"); + if (cache_path_.empty()) + { + CV_LOG_INFO(NULL, "Specify OPENCV_OPENCL_CACHE_DIR configuration parameter to enable OpenCL cache"); + } + do + { + try + { + if (cache_path_.empty()) + break; + if (cache_path_ == "disabled") + break; + if (!utils::fs::createDirectories(cache_path_)) + { + CV_LOG_DEBUG(NULL, "Can't use OpenCL cache directory: " << cache_path_); + clear(); + break; + } + + if (CV_OPENCL_CACHE_LOCK_ENABLE) + { + cache_lock_filename_ = cache_path_ + ".lock"; + if (!utils::fs::exists(cache_lock_filename_)) + { + CV_LOG_DEBUG(NULL, "Creating lock file... (" << cache_lock_filename_ << ")"); + std::ofstream lock_filename(cache_lock_filename_.c_str(), std::ios::out); + if (!lock_filename.is_open()) + { + CV_LOG_WARNING(NULL, "Can't create lock file for OpenCL program cache: " << cache_lock_filename_); + break; + } + } + + try + { + cache_lock_ = makePtr(cache_lock_filename_.c_str()); + CV_LOG_VERBOSE(NULL, 0, "Checking cache lock... (" << cache_lock_filename_ << ")"); + { + utils::shared_lock_guard lock(*cache_lock_); + } + CV_LOG_VERBOSE(NULL, 0, "Checking cache lock... Done!"); + } + catch (const cv::Exception& e) + { + CV_LOG_WARNING(NULL, "Can't create OpenCL program cache lock: " << cache_lock_filename_ << std::endl << e.what()); + } + catch (...) + { + CV_LOG_WARNING(NULL, "Can't create OpenCL program cache lock: " << cache_lock_filename_); + } + } + else + { + if (CV_OPENCL_CACHE_WRITE) + { + CV_LOG_WARNING(NULL, "OpenCL cache lock is disabled while cache write is allowed " + "(not safe for multiprocess environment)"); + } + else + { + CV_LOG_INFO(NULL, "OpenCL cache lock is disabled"); + } + } + } + catch (const cv::Exception& e) + { + CV_LOG_WARNING(NULL, "Can't prepare OpenCL program cache: " << cache_path_ << std::endl << e.what()); + clear(); + } + } while (0); + if (!cache_path_.empty()) + { + if (cache_lock_.empty() && CV_OPENCL_CACHE_LOCK_ENABLE) + { + CV_LOG_WARNING(NULL, "Initialized OpenCL cache directory, but interprocess synchronization lock is not available. " + "Consider to disable OpenCL cache: OPENCV_OPENCL_CACHE_DIR=disabled"); + } + else + { + CV_LOG_INFO(NULL, "Successfully initialized OpenCL cache directory: " << cache_path_); + } + } + } + + void clear() + { + cache_path_.clear(); + cache_lock_filename_.clear(); + cache_lock_.release(); + } + + std::string prepareCacheDirectoryForContext(const std::string& ctx_prefix) + { + if (cache_path_.empty()) + return std::string(); + + ContextCacheType::iterator i = prepared_contexts_.find(ctx_prefix); + if (i != prepared_contexts_.end()) + return i->second; + + CV_LOG_INFO(NULL, "Preparing OpenCL cache configuration for context: " << ctx_prefix); + + std::string target_directory = cache_path_ + ctx_prefix + "/"; + bool result = utils::fs::isDirectory(target_directory); + if (!result) + { + try + { + CV_LOG_VERBOSE(NULL, 0, "Creating directory: " << target_directory); + if (utils::fs::createDirectories(target_directory)) + { + result = true; + } + else + { + CV_LOG_WARNING(NULL, "Can't create directory: " << target_directory); + } + } + catch (const cv::Exception& e) + { + CV_LOG_ERROR(NULL, "Can't create OpenCL program cache directory for context: " << target_directory << std::endl << e.what()); + } + } + target_directory = result ? target_directory : std::string(); + prepared_contexts_.insert(std::pair(ctx_prefix, target_directory)); + + CV_LOG_VERBOSE(NULL, 1, " Result: " << (target_directory.empty() ? std::string("Failed") : target_directory)); + + return target_directory; + } + + static OpenCLBinaryCacheConfigurator& getSingletonInstance() + { + CV_SINGLETON_LAZY_INIT_REF(OpenCLBinaryCacheConfigurator, new OpenCLBinaryCacheConfigurator()); + } +}; +class BinaryProgramFile +{ + enum { MAX_ENTRIES = 64 }; + + typedef unsigned int uint32_t; + + struct CV_DECL_ALIGNED(4) FileHeader + { + uint32_t sourceSignatureSize; + //char sourceSignature[]; + }; + + struct CV_DECL_ALIGNED(4) FileTable + { + uint32_t numberOfEntries; + //uint32_t firstEntryOffset[]; + }; + + struct CV_DECL_ALIGNED(4) FileEntry + { + uint32_t nextEntryFileOffset; // 0 for the last entry in chain + uint32_t keySize; + uint32_t dataSize; + //char key[]; + //char data[]; + }; + + const std::string fileName_; + const char* const sourceSignature_; + const size_t sourceSignatureSize_; + + std::fstream f; + + uint32_t entryOffsets[MAX_ENTRIES]; + + uint32_t getHash(const std::string& options) + { + uint64 hash = crc64((const uchar*)options.c_str(), options.size(), 0); + return hash & (MAX_ENTRIES - 1); + } + + inline size_t getFileSize() + { + size_t pos = (size_t)f.tellg(); + f.seekg(0, std::fstream::end); + size_t fileSize = (size_t)f.tellg(); + f.seekg(pos, std::fstream::beg); + return fileSize; + } + inline uint32_t readUInt32() + { + uint32_t res = 0; + f.read((char*)&res, sizeof(uint32_t)); + CV_Assert(!f.fail()); + return res; + } + inline void writeUInt32(const uint32_t value) + { + uint32_t v = value; + f.write((char*)&v, sizeof(uint32_t)); + CV_Assert(!f.fail()); + } + + inline void seekReadAbsolute(size_t pos) + { + f.seekg(pos, std::fstream::beg); + CV_Assert(!f.fail()); + } + inline void seekReadRelative(size_t pos) + { + f.seekg(pos, std::fstream::cur); + CV_Assert(!f.fail()); + } + + inline void seekWriteAbsolute(size_t pos) + { + f.seekp(pos, std::fstream::beg); + CV_Assert(!f.fail()); + } + + void clearFile() + { + f.close(); + if (0 != remove(fileName_.c_str())) + CV_LOG_ERROR(NULL, "Can't remove: " << fileName_); + return; + } + +public: + BinaryProgramFile(const std::string& fileName, const char* sourceSignature) + : fileName_(fileName), sourceSignature_(sourceSignature), sourceSignatureSize_(sourceSignature_ ? strlen(sourceSignature_) : 0) + { + CV_StaticAssert(sizeof(uint32_t) == 4, ""); + CV_Assert(sourceSignature_ != NULL); + CV_Assert(sourceSignatureSize_ > 0); + memset(entryOffsets, 0, sizeof(entryOffsets)); + + f.rdbuf()->pubsetbuf(0, 0); // disable buffering + f.open(fileName_.c_str(), std::ios::in|std::ios::out|std::ios::binary); + if(f.is_open() && getFileSize() > 0) + { + bool isValid = false; + try + { + uint32_t fileSourceSignatureSize = readUInt32(); + if (fileSourceSignatureSize == sourceSignatureSize_) + { + cv::AutoBuffer fileSourceSignature(fileSourceSignatureSize + 1); + f.read((char*)fileSourceSignature, fileSourceSignatureSize); + if (f.eof()) + { + CV_LOG_ERROR(NULL, "Unexpected EOF"); + } + else if (memcmp(sourceSignature, (const char*)fileSourceSignature, fileSourceSignatureSize) == 0) + { + isValid = true; + } + } + if (!isValid) + { + CV_LOG_ERROR(NULL, "Source code signature/hash mismatch (program source code has been changed/updated)"); + } + } + catch (const cv::Exception& e) + { + CV_LOG_ERROR(NULL, "Can't open binary program file: " << fileName << " : " << e.what()); + } + catch (...) + { + CV_LOG_ERROR(NULL, "Can't open binary program file: " << fileName << " : Unknown error"); + } + if (!isValid) + { + clearFile(); + } + else + { + seekReadAbsolute(0); + } + } + } + + bool read(const std::string& key, std::vector& buf) + { + if (!f.is_open()) + return false; + + size_t fileSize = getFileSize(); + if (fileSize == 0) + { + CV_LOG_ERROR(NULL, "Invalid file (empty): " << fileName_); + clearFile(); + return false; + } + seekReadAbsolute(0); + + // bypass FileHeader + uint32_t fileSourceSignatureSize = readUInt32(); + CV_Assert(fileSourceSignatureSize > 0); + seekReadRelative(fileSourceSignatureSize); + + uint32_t numberOfEntries = readUInt32(); + CV_Assert(numberOfEntries > 0); + if (numberOfEntries != MAX_ENTRIES) + { + CV_LOG_ERROR(NULL, "Invalid file: " << fileName_); + clearFile(); + return false; + } + f.read((char*)&entryOffsets[0], sizeof(entryOffsets)); + CV_Assert(!f.fail()); + + uint32_t entryNum = getHash(key); + + uint32_t entryOffset = entryOffsets[entryNum]; + FileEntry entry; + while (entryOffset > 0) + { + seekReadAbsolute(entryOffset); + //CV_StaticAssert(sizeof(entry) == sizeof(uint32_t) * 3, ""); + f.read((char*)&entry, sizeof(entry)); + CV_Assert(!f.fail()); + cv::AutoBuffer fileKey(entry.keySize + 1); + if (key.size() == entry.keySize) + { + if (entry.keySize > 0) + { + f.read((char*)fileKey, entry.keySize); + CV_Assert(!f.fail()); + } + if (memcmp((const char*)fileKey, key.c_str(), entry.keySize) == 0) + { + buf.resize(entry.dataSize); + f.read(&buf[0], entry.dataSize); + CV_Assert(!f.fail()); + seekReadAbsolute(0); + CV_LOG_VERBOSE(NULL, 0, "Read..."); + return true; + } + } + if (entry.nextEntryFileOffset == 0) + break; + entryOffset = entry.nextEntryFileOffset; + } + return false; + } + + bool write(const std::string& key, std::vector& buf) + { + if (!f.is_open()) + { + f.open(fileName_.c_str(), std::ios::in|std::ios::out|std::ios::binary); + if (!f.is_open()) + { + f.open(fileName_.c_str(), std::ios::out|std::ios::binary); + if (!f.is_open()) + { + CV_LOG_ERROR(NULL, "Can't create file: " << fileName_); + return false; + } + } + } + + size_t fileSize = getFileSize(); + if (fileSize == 0) + { + // Write header + seekWriteAbsolute(0); + writeUInt32((uint32_t)sourceSignatureSize_); + f.write(sourceSignature_, sourceSignatureSize_); + CV_Assert(!f.fail()); + + writeUInt32(MAX_ENTRIES); + memset(entryOffsets, 0, sizeof(entryOffsets)); + f.write((char*)entryOffsets, sizeof(entryOffsets)); + CV_Assert(!f.fail()); + f.flush(); + CV_Assert(!f.fail()); + f.close(); + f.open(fileName_.c_str(), std::ios::in|std::ios::out|std::ios::binary); + CV_Assert(f.is_open()); + fileSize = getFileSize(); + } + seekReadAbsolute(0); + + // bypass FileHeader + uint32_t fileSourceSignatureSize = readUInt32(); + CV_Assert(fileSourceSignatureSize == sourceSignatureSize_); + seekReadRelative(fileSourceSignatureSize); + + uint32_t numberOfEntries = readUInt32(); + CV_Assert(numberOfEntries > 0); + if (numberOfEntries != MAX_ENTRIES) + { + CV_LOG_ERROR(NULL, "Invalid file: " << fileName_); + clearFile(); + return false; + } + size_t tableEntriesOffset = (size_t)f.tellg(); + f.read((char*)&entryOffsets[0], sizeof(entryOffsets)); + CV_Assert(!f.fail()); + + uint32_t entryNum = getHash(key); + + uint32_t entryOffset = entryOffsets[entryNum]; + FileEntry entry; + while (entryOffset > 0) + { + seekReadAbsolute(entryOffset); + //CV_StaticAssert(sizeof(entry) == sizeof(uint32_t) * 3, ""); + f.read((char*)&entry, sizeof(entry)); + CV_Assert(!f.fail()); + cv::AutoBuffer fileKey(entry.keySize + 1); + if (key.size() == entry.keySize) + { + if (entry.keySize > 0) + { + f.read((char*)fileKey, entry.keySize); + CV_Assert(!f.fail()); + } + if (0 == memcmp((const char*)fileKey, key.c_str(), entry.keySize)) + { + // duplicate + CV_LOG_VERBOSE(NULL, 0, "Duplicate key ignored: " << fileName_); + return false; + } + } + if (entry.nextEntryFileOffset == 0) + break; + entryOffset = entry.nextEntryFileOffset; + } + seekReadAbsolute(0); + if (entryOffset > 0) + { + seekWriteAbsolute(entryOffset); + entry.nextEntryFileOffset = (uint32_t)fileSize; + f.write((char*)&entry, sizeof(entry)); + CV_Assert(!f.fail()); + } + else + { + entryOffsets[entryNum] = (uint32_t)fileSize; + seekWriteAbsolute(tableEntriesOffset); + f.write((char*)entryOffsets, sizeof(entryOffsets)); + CV_Assert(!f.fail()); + } + seekWriteAbsolute(fileSize); + entry.nextEntryFileOffset = 0; + entry.dataSize = (uint32_t)buf.size(); + entry.keySize = (uint32_t)key.size(); + f.write((char*)&entry, sizeof(entry)); + CV_Assert(!f.fail()); + f.write(key.c_str(), entry.keySize); + CV_Assert(!f.fail()); + f.write(&buf[0], entry.dataSize); + CV_Assert(!f.fail()); + f.flush(); + CV_Assert(!f.fail()); + CV_LOG_VERBOSE(NULL, 0, "Write... (" << buf.size() << " bytes)"); + return true; + } +}; +#endif // OPENCV_HAVE_FILESYSTEM_SUPPORT + + bool haveOpenCL() { #ifdef HAVE_OPENCL @@ -1470,11 +1969,32 @@ struct Context::Impl } } + std::string getPrefixString() + { + if (prefix.empty()) + { + const Device& d = devices[0]; + prefix = d.vendorName() + "--" + d.name() + "--" + d.driverVersion(); + // sanitize chars + for (size_t i = 0; i < prefix.size(); i++) + { + char c = prefix[i]; + if (!((c >= '0' && c <= '9') || (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || c == '_' || c == '-')) + { + prefix[i] = '_'; + } + } + } + return prefix; + } + IMPLEMENT_REFCOUNTABLE(); cl_context handle; std::vector devices; + std::string prefix; + cv::Mutex program_cache_mutex; typedef std::map phash_t; phash_t phash; @@ -2700,13 +3220,141 @@ struct Program::Impl handle(NULL) { refcount = 1; - compile(Context::getDefault(), errmsg); + const Context ctx = Context::getDefault(); + Device device = ctx.device(0); + if (device.isAMD()) + buildflags += " -D AMD_DEVICE"; + else if (device.isIntel()) + buildflags += " -D INTEL_DEVICE"; + compile(ctx, errmsg); } bool compile(const Context& ctx, String& errmsg) { +#if OPENCV_HAVE_FILESYSTEM_SUPPORT + OpenCLBinaryCacheConfigurator& config = OpenCLBinaryCacheConfigurator::getSingletonInstance(); + const std::string base_dir = config.prepareCacheDirectoryForContext(ctx.getImpl()->getPrefixString()); + const std::string fname = base_dir.empty() ? std::string() : + std::string(base_dir + src.getImpl()->module_.c_str() + "--" + src.getImpl()->name_ + "_" + src.getImpl()->codeHash_ + ".bin"); + const cv::Ptr fileLock = config.cache_lock_; // can be empty + const String& hash_str = src.getImpl()->codeHash_; + if (!fname.empty() && CV_OPENCL_CACHE_ENABLE) + { + try + { + std::vector binaryBuf; + bool res = false; + { + cv::utils::optional_shared_lock_guard lock_fs(fileLock.get()); + BinaryProgramFile file(fname, hash_str.c_str()); + res = file.read(buildflags, binaryBuf); + } + if (res) + { + CV_Assert(!binaryBuf.empty()); + bool isLoaded = createFromBinary(ctx, binaryBuf, errmsg); + if (isLoaded) + return true; + } + } + catch (const cv::Exception& e) + { + CV_UNUSED(e); + CV_LOG_VERBOSE(NULL, 0, "Can't load OpenCL binary: " + fname << std::endl << e.what()); + } + catch (...) + { + CV_LOG_VERBOSE(NULL, 0, "Can't load OpenCL binary: " + fname); + } + } +#endif // OPENCV_HAVE_FILESYSTEM_SUPPORT CV_Assert(handle == NULL); - CV_INSTRUMENT_REGION_OPENCL_COMPILE(cv::format("Compile: %" PRIx64 " options: %s", src.hash(), buildflags.c_str()).c_str()); + if (!buildFromSources(ctx, errmsg)) + { + return true; + } + CV_Assert(handle != NULL); +#if OPENCV_HAVE_FILESYSTEM_SUPPORT + if (!fname.empty() && CV_OPENCL_CACHE_WRITE) + { + try + { + std::vector binaryBuf; + getProgramBinary(binaryBuf); + { + cv::utils::optional_lock_guard lock_fs(fileLock.get()); + BinaryProgramFile file(fname, hash_str.c_str()); + file.write(buildflags, binaryBuf); + } + } + catch (const cv::Exception& e) + { + CV_LOG_WARNING(NULL, "Can't save OpenCL binary into cache: " + fname << std::endl << e.what()); + } + catch (...) + { + CV_LOG_WARNING(NULL, "Can't save OpenCL binary into cache: " + fname); + } + } +#endif // OPENCV_HAVE_FILESYSTEM_SUPPORT +#if CV_OPENCL_VALIDATE_BINARY_PROGRAMS + if (CV_OPENCL_VALIDATE_BINARY_PROGRAMS_VALUE) + { + std::vector binaryBuf; + getProgramBinary(binaryBuf); + if (!binaryBuf.empty()) + { + CV_OCL_DBG_CHECK(clReleaseProgram(handle)); + handle = NULL; + createFromBinary(ctx, binaryBuf, errmsg); + } + } +#endif + return handle != NULL; + } + + void dumpBuildLog_(cl_int result, const cl_device_id* deviceList, String& errmsg) + { + AutoBuffer buffer; buffer[0] = 0; + + size_t retsz = 0; + cl_int log_retval = clGetProgramBuildInfo(handle, deviceList[0], + CL_PROGRAM_BUILD_LOG, 0, 0, &retsz); + if (log_retval == CL_SUCCESS && retsz > 1) + { + buffer.resize(retsz + 16); + log_retval = clGetProgramBuildInfo(handle, deviceList[0], + CL_PROGRAM_BUILD_LOG, retsz+1, (char*)buffer, &retsz); + if (log_retval == CL_SUCCESS) + { + if (retsz < buffer.size()) + buffer[retsz] = 0; + else + buffer[buffer.size() - 1] = 0; + } + else + { + buffer[0] = 0; + } + } + + errmsg = String(buffer); + printf("OpenCL program build log: %s/%s\nStatus %d: %s\n%s\n%s\n", + src.getImpl()->module_.c_str(), src.getImpl()->name_.c_str(), + result, getOpenCLErrorString(result), + buildflags.c_str(), errmsg.c_str()); + fflush(stdout); + } + + bool buildFromSources(const Context& ctx, String& errmsg) + { + CV_Assert(handle == NULL); + CV_INSTRUMENT_REGION_OPENCL_COMPILE(cv::format("Build OpenCL program: %s/%s %" PRIx64 " options: %s", + src.getImpl()->module_.c_str(), src.getImpl()->name_.c_str(), + src.hash(), buildflags.c_str()).c_str()); + + CV_LOG_VERBOSE(NULL, 0, "Compile... " << src.getImpl()->module_.c_str() << "/" << src.getImpl()->name_.c_str()); + const String& srcstr = src.source(); const char* srcptr = srcstr.c_str(); size_t srclen = srcstr.size(); @@ -2717,54 +3365,20 @@ struct Program::Impl CV_Assert(handle || retval != CL_SUCCESS); if (handle && retval == CL_SUCCESS) { - int i, n = (int)ctx.ndevices(); - AutoBuffer deviceListBuf(n+1); - void** deviceList = deviceListBuf; - for( i = 0; i < n; i++ ) - deviceList[i] = ctx.device(i).ptr(); + size_t n = ctx.ndevices(); + AutoBuffer deviceListBuf(n + 1); + cl_device_id* deviceList = deviceListBuf; + for (size_t i = 0; i < n; i++) + { + deviceList[i] = (cl_device_id)(ctx.device(i).ptr()); + } - Device device = Device::getDefault(); - if (device.isAMD()) - buildflags += " -D AMD_DEVICE"; - else if (device.isIntel()) - buildflags += " -D INTEL_DEVICE"; - - retval = clBuildProgram(handle, n, - (const cl_device_id*)deviceList, - buildflags.c_str(), 0, 0); + retval = clBuildProgram(handle, (cl_uint)n, deviceList, buildflags.c_str(), 0, 0); #if !CV_OPENCL_ALWAYS_SHOW_BUILD_LOG if (retval != CL_SUCCESS) #endif { - AutoBuffer buffer; buffer[0] = 0; - - size_t retsz = 0; - cl_int log_retval = clGetProgramBuildInfo(handle, (cl_device_id)deviceList[0], - CL_PROGRAM_BUILD_LOG, 0, 0, &retsz); - if (log_retval == CL_SUCCESS && retsz > 1) - { - buffer.resize(retsz + 16); - log_retval = clGetProgramBuildInfo(handle, (cl_device_id)deviceList[0], - CL_PROGRAM_BUILD_LOG, retsz+1, (char*)buffer, &retsz); - if (log_retval == CL_SUCCESS) - { - if (retsz < buffer.size()) - buffer[retsz] = 0; - else - buffer[buffer.size() - 1] = 0; - } - else - { - buffer[0] = 0; - } - } - - errmsg = String(buffer); - printf("OpenCL program build log: %s (%s)\nStatus %d: %s\n%s\n%s\n", - src.getImpl()->name_.c_str(), src.getImpl()->module_.c_str(), - retval, getOpenCLErrorString(retval), - buildflags.c_str(), errmsg.c_str()); - fflush(stdout); + dumpBuildLog_(retval, deviceList, errmsg); // don't remove "retval != CL_SUCCESS" condition here: // it would break CV_OPENCL_ALWAYS_SHOW_BUILD_LOG mode @@ -2774,6 +3388,7 @@ struct Program::Impl handle = NULL; } } + } return handle != NULL; } @@ -2830,6 +3445,135 @@ struct Program::Impl return String((const char*)(uchar*)bufbuf, prefixlen + progsz); } + void getProgramBinary(std::vector& buf) + { + CV_Assert(handle); + size_t sz = 0; + CV_OCL_CHECK(clGetProgramInfo(handle, CL_PROGRAM_BINARY_SIZES, sizeof(sz), &sz, NULL)); + buf.resize(sz); + uchar* ptr = (uchar*)&buf[0]; + CV_OCL_CHECK(clGetProgramInfo(handle, CL_PROGRAM_BINARIES, sizeof(ptr), &ptr, NULL)); +#if CV_OPENCL_VALIDATE_BINARY_PROGRAMS + if (CV_OPENCL_VALIDATE_BINARY_PROGRAMS_VALUE) + { + CV_LOG_INFO(NULL, "OpenCL: query kernel names (compiled)..."); + size_t retsz = 0; + char kernels_buffer[4096] = {0}; + cl_int result = clGetProgramInfo(handle, CL_PROGRAM_KERNEL_NAMES, sizeof(kernels_buffer), &kernels_buffer[0], &retsz); + if (retsz < sizeof(kernels_buffer)) + kernels_buffer[retsz] = 0; + else + kernels_buffer[0] = 0; + CV_LOG_INFO(NULL, result << ": Kernels='" << kernels_buffer << "'"); + } +#endif + } + + bool createFromBinary(const Context& ctx, const std::vector& buf, String& errmsg) + { + CV_Assert(handle == NULL); + CV_INSTRUMENT_REGION_OPENCL_COMPILE("Load OpenCL program"); + CV_LOG_VERBOSE(NULL, 0, "Load from binary... " << src.getImpl()->module_.c_str() << "/" << src.getImpl()->name_.c_str()); + + const uchar* binaryPtr = (uchar*)&buf[0]; + size_t binarySize = buf.size(); + CV_Assert(binarySize > 0); + + size_t ndevices = (int)ctx.ndevices(); + AutoBuffer devices_(ndevices); + AutoBuffer binaryPtrs_(ndevices); + AutoBuffer binarySizes_(ndevices); + + cl_device_id* devices = devices_; + const uchar** binaryPtrs = binaryPtrs_; + size_t* binarySizes = binarySizes_; + for (size_t i = 0; i < ndevices; i++) + { + devices[i] = (cl_device_id)ctx.device(i).ptr(); + binaryPtrs[i] = binaryPtr; + binarySizes[i] = binarySize; + } + + cl_int result = 0; + handle = clCreateProgramWithBinary((cl_context)ctx.ptr(), (cl_uint)ndevices, (cl_device_id*)devices_, + binarySizes, binaryPtrs, NULL, &result); + if (result != CL_SUCCESS) + { + CV_LOG_ERROR(NULL, CV_OCL_API_ERROR_MSG(result, "clCreateProgramWithBinary")); + if (handle) + { + CV_OCL_DBG_CHECK(clReleaseProgram(handle)); + handle = NULL; + } + } + if (!handle) + return false; + cl_build_status build_status = CL_BUILD_NONE; + size_t retsz = 0; + CV_OCL_DBG_CHECK(result = clGetProgramBuildInfo(handle, devices[0], CL_PROGRAM_BUILD_STATUS, + sizeof(build_status), &build_status, &retsz)); + if (result == CL_SUCCESS && build_status == CL_BUILD_SUCCESS) + { + CV_LOG_VERBOSE(NULL, 0, "clGetProgramBuildInfo() pre-check returns CL_BUILD_SUCCESS. Skip clBuildProgram() call"); + } + else + { + result = clBuildProgram(handle, (cl_uint)ndevices, (cl_device_id*)devices_, buildflags.c_str(), 0, 0); + CV_OCL_DBG_CHECK_RESULT(result, cv::format("clBuildProgram(binary: %s/%s)", src.getImpl()->module_.c_str(), src.getImpl()->name_.c_str()).c_str()); + if (result != CL_SUCCESS) + { + dumpBuildLog_(result, devices, errmsg); + if (handle) + { + CV_OCL_DBG_CHECK(clReleaseProgram(handle)); + handle = NULL; + } + return false; + } + } + if (build_status != CL_BUILD_SUCCESS) + { + CV_OCL_DBG_CHECK(result = clGetProgramBuildInfo(handle, devices[0], CL_PROGRAM_BUILD_STATUS, + sizeof(build_status), &build_status, &retsz)); + if (result == CL_SUCCESS) + { + if (build_status == CL_BUILD_SUCCESS) + { + return true; + } + else + { + CV_LOG_WARNING(NULL, "clGetProgramBuildInfo() returns " << build_status); + return false; + } + } + else + { + CV_LOG_ERROR(NULL, CV_OCL_API_ERROR_MSG(result, "clGetProgramBuildInfo()")); + if (handle) + { + CV_OCL_DBG_CHECK(clReleaseProgram(handle)); + handle = NULL; + } + } + } +#if CV_OPENCL_VALIDATE_BINARY_PROGRAMS + if (handle && CV_OPENCL_VALIDATE_BINARY_PROGRAMS_VALUE) + { + CV_LOG_INFO(NULL, "OpenCL: query kernel names (binary)..."); + retsz = 0; + char kernels_buffer[4096] = {0}; + result = clGetProgramInfo(handle, CL_PROGRAM_KERNEL_NAMES, sizeof(kernels_buffer), &kernels_buffer[0], &retsz); + if (retsz < sizeof(kernels_buffer)) + kernels_buffer[retsz] = 0; + else + kernels_buffer[0] = 0; + CV_LOG_INFO(NULL, result << ": Kernels='" << kernels_buffer << "'"); + } +#endif + return handle != NULL; + } + ~Impl() { if( handle ) diff --git a/modules/core/src/utils/filesystem.cpp b/modules/core/src/utils/filesystem.cpp new file mode 100644 index 0000000000..aea7a9570e --- /dev/null +++ b/modules/core/src/utils/filesystem.cpp @@ -0,0 +1,445 @@ +// This file is part of OpenCV project. +// It is subject to the license terms in the LICENSE file found in the top-level directory +// of this distribution and at http://opencv.org/license.html. + +#include "precomp.hpp" + +#include + +#include + +#include "opencv2/core/utils/filesystem.private.hpp" +#include "opencv2/core/utils/filesystem.hpp" + +//#define DEBUG_FS_UTILS + +#ifdef DEBUG_FS_UTILS +#include +#define DBG(...) __VA_ARGS__ +#else +#define DBG(...) +#endif + + +#if OPENCV_HAVE_FILESYSTEM_SUPPORT + +#ifdef _WIN32 +#define WIN32_LEAN_AND_MEAN +#define NOMINMAX +#include +#include +#include +#include +#include +#elif defined __linux__ || defined __APPLE__ +#include +#include +#include +#include +#include +#endif + +namespace cv { namespace utils { namespace fs { + +static inline +bool isPathSeparator(char c) +{ + return c == '/' || c == '\\'; +} + +bool exists(const cv::String& path) +{ + CV_INSTRUMENT_REGION() + +#if defined _WIN32 || defined WINCE + BOOL status = TRUE; + { + WIN32_FILE_ATTRIBUTE_DATA all_attrs; +#ifdef WINRT + wchar_t wpath[MAX_PATH]; + size_t copied = mbstowcs(wpath, path.c_str(), MAX_PATH); + CV_Assert((copied != MAX_PATH) && (copied != (size_t)-1)); + status = ::GetFileAttributesExW(wpath, GetFileExInfoStandard, &all_attrs); +#else + status = ::GetFileAttributesExA(path.c_str(), GetFileExInfoStandard, &all_attrs); +#endif + } + + return !!status; +#else + struct stat stat_buf; + return (0 == stat(path.c_str(), &stat_buf)); +#endif +} + +cv::String getcwd() +{ + CV_INSTRUMENT_REGION() + cv::AutoBuffer buf; +#if defined WIN32 || defined _WIN32 || defined WINCE +#ifdef WINRT + return cv::String(); +#else + DWORD sz = GetCurrentDirectoryA(0, NULL); + buf.allocate((size_t)sz); + sz = GetCurrentDirectoryA((DWORD)buf.size(), (char*)buf); + return cv::String((char*)buf, (size_t)sz); +#endif +#elif defined __linux__ || defined __APPLE__ + for(;;) + { + char* p = ::getcwd((char*)buf, buf.size()); + if (p == NULL) + { + if (errno == ERANGE) + { + buf.allocate(buf.size() * 2); + continue; + } + return cv::String(); + } + break; + } + return cv::String((char*)buf, (size_t)strlen((char*)buf)); +#else + return cv::String(); +#endif +} + + +bool createDirectory(const cv::String& path) +{ + CV_INSTRUMENT_REGION() +#if defined WIN32 || defined _WIN32 || defined WINCE +#ifdef WINRT + wchar_t wpath[MAX_PATH]; + size_t copied = mbstowcs(wpath, path.c_str(), MAX_PATH); + CV_Assert((copied != MAX_PATH) && (copied != (size_t)-1)); + int result = CreateDirectoryA(wpath, NULL) ? 0 : -1; +#else + int result = _mkdir(path.c_str()); +#endif +#elif defined __linux__ || defined __APPLE__ + int result = mkdir(path.c_str(), 0777); +#else + int result = -1; +#endif + + if (result == -1) + { + return isDirectory(path); + } + return true; +} + +bool createDirectories(const cv::String& path_) +{ + cv::String path = path_; + for (;;) + { + char last_char = path.empty() ? 0 : path[path.length() - 1]; + if (last_char == '/' || last_char == '\\') + { + path = path.substr(0, path.length() - 1); + continue; + } + break; + } + + if (path.empty() || path == "./" || path == ".\\" || path == ".") + return true; + if (isDirectory(path)) + return true; + + size_t pos = path.rfind('/'); + if (pos == cv::String::npos) + pos = path.rfind('\\'); + if (pos != cv::String::npos) + { + cv::String parent_directory = path.substr(0, pos); + if (!parent_directory.empty()) + { + if (!createDirectories(parent_directory)) + return false; + } + } + + return createDirectory(path); +} + +#ifdef _WIN32 + +struct FileLock::Impl +{ + Impl(const char* fname) + { + // http://support.microsoft.com/kb/316609 + int numRetries = 5; + do + { + handle = ::CreateFileA(fname, GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, + OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); + if (INVALID_HANDLE_VALUE == handle) + { + if (ERROR_SHARING_VIOLATION == GetLastError()) + { + numRetries--; + Sleep(250); + continue; + } + else + { + CV_ErrorNoReturn_(Error::StsAssert, ("Can't open lock file: %s", fname)); + } + } + break; + } while (numRetries > 0); + } + ~Impl() + { + if (INVALID_HANDLE_VALUE != handle) + { + ::CloseHandle(handle); + } + } + + bool lock() + { + OVERLAPPED overlapped; + std::memset(&overlapped, 0, sizeof(overlapped)); + return !!::LockFileEx(handle, LOCKFILE_EXCLUSIVE_LOCK, 0, MAXDWORD, MAXDWORD, &overlapped); + } + bool unlock() + { + OVERLAPPED overlapped; + std::memset(&overlapped, 0, sizeof(overlapped)); + return !!::UnlockFileEx(handle, 0, MAXDWORD, MAXDWORD, &overlapped); + } + + bool lock_shared() + { + OVERLAPPED overlapped; + std::memset(&overlapped, 0, sizeof(overlapped)); + return !!::LockFileEx(handle, 0, 0, MAXDWORD, MAXDWORD, &overlapped); + } + bool unlock_shared() + { + return unlock(); + } + + HANDLE handle; + +private: + Impl(const Impl&); // disabled + Impl& operator=(const Impl&); // disabled +}; + +#elif defined __linux__ || defined __APPLE__ + +struct FileLock::Impl +{ + Impl(const char* fname) + { + handle = ::open(fname, O_RDWR); + CV_Assert(handle != -1); + } + ~Impl() + { + if (handle >= 0) + ::close(handle); + } + + bool lock() + { + struct ::flock l; + std::memset(&l, 0, sizeof(l)); + l.l_type = F_WRLCK; + l.l_whence = SEEK_SET; + l.l_start = 0; + l.l_len = 0; + DBG(std::cout << "Lock..." << std::endl); + bool res = -1 != ::fcntl(handle, F_SETLKW, &l); + return res; + } + bool unlock() + { + struct ::flock l; + std::memset(&l, 0, sizeof(l)); + l.l_type = F_UNLCK; + l.l_whence = SEEK_SET; + l.l_start = 0; + l.l_len = 0; + DBG(std::cout << "Unlock..." << std::endl); + bool res = -1 != ::fcntl(handle, F_SETLK, &l); + return res; + } + + bool lock_shared() + { + struct ::flock l; + std::memset(&l, 0, sizeof(l)); + l.l_type = F_RDLCK; + l.l_whence = SEEK_SET; + l.l_start = 0; + l.l_len = 0; + DBG(std::cout << "Lock read..." << std::endl); + bool res = -1 != ::fcntl(handle, F_SETLKW, &l); + return res; + } + bool unlock_shared() + { + return unlock(); + } + + int handle; + +private: + Impl(const Impl&); // disabled + Impl& operator=(const Impl&); // disabled +}; + +#endif + +FileLock::FileLock(const char* fname) + : pImpl(new Impl(fname)) +{ + // nothing +} +FileLock::~FileLock() +{ + delete pImpl; + pImpl = NULL; +} + +void FileLock::lock() { CV_Assert(pImpl->lock()); } +void FileLock::unlock() { CV_Assert(pImpl->unlock()); } +void FileLock::lock_shared() { CV_Assert(pImpl->lock_shared()); } +void FileLock::unlock_shared() { CV_Assert(pImpl->unlock_shared()); } + + + +cv::String getCacheDirectory(const char* sub_directory_name, const char* configuration_name) +{ + String cache_path; + if (configuration_name) + { + cache_path = utils::getConfigurationParameterString(configuration_name, ""); + } + if (cache_path.empty()) + { + cv::String default_cache_path; +#ifdef _WIN32 + char tmp_path_buf[MAX_PATH+1] = {0}; + DWORD res = GetTempPath(MAX_PATH, tmp_path_buf); + if (res > 0 && res <= MAX_PATH) + { + default_cache_path = tmp_path_buf; + } +#elif defined __ANDROID__ + // no defaults +#elif defined __APPLE__ + const char* tmpdir_env = getenv("TMPDIR"); + if (tmpdir_env && utils::fs::isDirectory(tmpdir_env)) + { + default_cache_path = tmpdir_env; + } + else + { + default_cache_path = "/tmp/"; + CV_LOG_WARNING(NULL, "Using world accessible cache directory. This may be not secure: " << default_cache_path); + } +#elif defined __linux__ + // https://specifications.freedesktop.org/basedir-spec/basedir-spec-latest.html + if (default_cache_path.empty()) + { + const char* xdg_cache_env = getenv("XDG_CACHE_HOME"); + if (xdg_cache_env && xdg_cache_env[0] && utils::fs::isDirectory(xdg_cache_env)) + { + default_cache_path = xdg_cache_env; + } + } + if (default_cache_path.empty()) + { + const char* home_env = getenv("HOME"); + if (home_env && home_env[0] && utils::fs::isDirectory(home_env)) + { + cv::String home_path = home_env; + cv::String home_cache_path = home_path + "/.cache/"; + if (utils::fs::isDirectory(home_cache_path)) + { + default_cache_path = home_cache_path; + } + } + } + if (default_cache_path.empty()) + { + const char* temp_path = "/var/tmp/"; + if (utils::fs::isDirectory(temp_path)) + { + default_cache_path = temp_path; + CV_LOG_WARNING(NULL, "Using world accessible cache directory. This may be not secure: " << default_cache_path); + } + } + if (default_cache_path.empty()) + { + default_cache_path = "/tmp/"; + CV_LOG_WARNING(NULL, "Using world accessible cache directory. This may be not secure: " << default_cache_path); + } +#else + // no defaults +#endif + CV_LOG_VERBOSE(NULL, 0, "default_cache_path = " << default_cache_path); + if (!default_cache_path.empty()) + { + if (utils::fs::isDirectory(default_cache_path)) + { + default_cache_path += "/opencv/" CV_VERSION "/"; + if (sub_directory_name && sub_directory_name[0] != '\0') + default_cache_path += cv::String(sub_directory_name) + "/"; + if (!utils::fs::createDirectories(default_cache_path)) + { + CV_LOG_DEBUG(NULL, "Can't create OpenCV cache sub-directory: " << default_cache_path); + } + else + { + cache_path = default_cache_path; + } + } + else + { + CV_LOG_INFO(NULL, "Can't find default cache directory (does it exist?): " << default_cache_path); + } + } + else + { + CV_LOG_DEBUG(NULL, "OpenCV has no support to discover default cache directory on the current platform"); + } + } + else + { + if (cache_path == "disabled") + return cache_path; + if (!isDirectory(cache_path)) + { + CV_LOG_WARNING(NULL, "Specified non-existed directory, creating OpenCV sub-directory for caching purposes: " << cache_path); + if (!createDirectories(cache_path)) + { + CV_LOG_ERROR(NULL, "Can't create OpenCV cache sub-directory: " << cache_path); + cache_path.clear(); + } + } + } + CV_Assert(cache_path.empty() || utils::fs::isDirectory(cache_path)); + if (!cache_path.empty()) + { + if (!isPathSeparator(cache_path[cache_path.size() - 1])) + { + cache_path += '/'; + } + } + return cache_path; +} + +}}} // namespace + +#endif // OPENCV_HAVE_FILESYSTEM_SUPPORT