1 /*************************************************************************** 2 qgsopenclutils.h - QgsOpenClUtils 3 4 --------------------- 5 begin : 11.4.2018 6 copyright : (C) 2018 by Alessandro Pasotti 7 email : elpaso at itopen dot it 8 *************************************************************************** 9 * * 10 * This program is free software; you can redistribute it and/or modify * 11 * it under the terms of the GNU General Public License as published by * 12 * the Free Software Foundation; either version 2 of the License, or * 13 * (at your option) any later version. * 14 * * 15 ***************************************************************************/ 16 #ifndef QGSOPENCLUTILS_H 17 #define QGSOPENCLUTILS_H 18 19 #define SIP_NO_FILE 20 21 #define CL_HPP_ENABLE_EXCEPTIONS 22 23 #include <QtGlobal> 24 #ifdef Q_OS_MAC 25 #define CL_HPP_MINIMUM_OPENCL_VERSION 120 26 #define CL_HPP_TARGET_OPENCL_VERSION 120 27 #define CL_TARGET_OPENCL_VERSION 120 28 #else 29 #define CL_USE_DEPRECATED_OPENCL_1_1_APIS 30 #define CL_HPP_TARGET_OPENCL_VERSION 200 31 #define CL_TARGET_OPENCL_VERSION 200 32 #endif 33 34 #include "qgsconfig.h" 35 36 #ifdef OPENCL_USE_NEW_HEADER 37 #include <CL/opencl.hpp> 38 #else 39 #include <CL/cl2.hpp> 40 #endif 41 42 #include "qgis_core.h" 43 #include "qgis.h" 44 45 #include "cpl_conv.h" 46 47 /** 48 * \ingroup core 49 * \class QgsOpenClUtils 50 * \brief The QgsOpenClUtils class is responsible for common OpenCL operations such as 51 * 52 * - enable/disable opencl 53 * - store and retrieve preferences for the default device 54 * - check opencl device availability and automatically choose the first GPU device 55 * - creating the default context 56 * - loading program sources from standard locations 57 * - build programs and log errors 58 * 59 * Usage: 60 * 61 * \code{.cpp} 62 * // This will check if OpenCL is enabled in user options and if there is a suitable 63 * // device, if a device is found it is initialized. 64 * if ( QgsOpenClUtils::enabled() && QgsOpenClUtils::available() ) 65 * { 66 * // Use the default context 67 * cl::Context ctx = QgsOpenClUtils::context(); 68 * cl::CommandQueue queue( ctx ); 69 * // Load the program from a standard location and build it 70 * cl::Program program = QgsOpenClUtils::buildProgram( ctx, QgsOpenClUtils::sourceFromBaseName( QStringLiteral ( "hillshade" ) ) ); 71 * // Continue with the usual OpenCL buffer, kernel and execution 72 * ... 73 * } 74 * \endcode 75 * 76 * \note not available in Python bindings 77 * \since QGIS 3.4 78 */ 79 class CORE_EXPORT QgsOpenClUtils 80 { 81 Q_GADGET 82 83 public: 84 85 /** 86 * The ExceptionBehavior enum define how exceptions generated by OpenCL should be treated 87 */ 88 enum ExceptionBehavior 89 { 90 Catch, //!< Write errors in the message log and silently fail 91 Throw //!< Write errors in the message log and re-throw exceptions 92 }; 93 94 /** 95 * The Type enum represent OpenCL device type 96 */ 97 enum HardwareType 98 { 99 CPU, 100 GPU, 101 Other 102 }; 103 104 Q_ENUM( HardwareType ) 105 106 /** 107 * The Info enum maps to OpenCL info constants 108 * 109 * \see deviceInfo() 110 */ 111 enum Info 112 { 113 Name = CL_DEVICE_NAME, 114 Vendor = CL_DEVICE_VENDOR, 115 Version = CL_DEVICE_VERSION, 116 Profile = CL_DEVICE_PROFILE, 117 ImageSupport = CL_DEVICE_IMAGE_SUPPORT, 118 Image2dMaxWidth = CL_DEVICE_IMAGE2D_MAX_WIDTH, 119 Image2dMaxHeight = CL_DEVICE_IMAGE2D_MAX_HEIGHT, 120 MaxMemAllocSize = CL_DEVICE_MAX_MEM_ALLOC_SIZE, 121 Type = CL_DEVICE_TYPE // CPU/GPU etc. 122 }; 123 124 /** 125 * Checks whether a suitable OpenCL platform and device is available on this system 126 * and initialize the QGIS OpenCL system by activating the preferred device 127 * if specified in the user the settings, if no preferred device was set or 128 * the preferred device could not be found the first GPU device is activated, 129 * the first CPU device acts as a fallback if none of the previous could be found. 130 * 131 * This function must always be called before using QGIS OpenCL utils 132 */ 133 static bool available(); 134 135 //! Returns TRUE if OpenCL is enabled in the user settings 136 static bool enabled(); 137 138 //! Returns a list of OpenCL devices found on this sysytem 139 static const std::vector<cl::Device> devices(); 140 141 /** 142 * Returns the active device. 143 * 144 * The active device is set as the default device for all OpenCL operations, 145 * once it is set it cannot be changed until QGIS is restarted (this is 146 * due to the way the underlying OpenCL library is built). 147 */ 148 static cl::Device activeDevice( ); 149 150 /** 151 * Returns the active platform OpenCL version string (e.g. 1.1, 2.0 etc.) 152 * or a blank string if there is no active platform. 153 * \since QGIS 3.6 154 */ 155 static QString activePlatformVersion( ); 156 157 //! Store in the settings the preferred \a deviceId device identifier 158 static void storePreferredDevice( const QString deviceId ); 159 160 //! Read from the settings the preferred device identifier 161 static QString preferredDevice( ); 162 163 //! Create a string identifier from a \a device 164 static QString deviceId( const cl::Device device ); 165 166 /** 167 * Returns a formatted description for the \a device 168 */ 169 static QString deviceDescription( const cl::Device device ); 170 171 /** 172 * Returns a formatted description for the device identified by \a deviceId 173 */ 174 static QString deviceDescription( const QString deviceId ); 175 176 //! Set the OpenCL user setting to \a enabled 177 static void setEnabled( bool enabled ); 178 179 //! Extract and return the build log error from \a error 180 static QString buildLog( cl::BuildError &error ); 181 182 //! Read an OpenCL source file from \a path 183 static QString sourceFromPath( const QString &path ); 184 185 //! Returns the full path to a an OpenCL source file from the \a baseName ('.cl' extension is automatically appended) 186 static QString sourceFromBaseName( const QString &baseName ); 187 188 //! OpenCL string for message logs 189 static QLatin1String LOGMESSAGE_TAG; 190 191 //! Returns a string representation from an OpenCL \a errorCode 192 static QString errorText( const int errorCode ); 193 194 /** 195 * Create an OpenCL command queue from the default context. 196 * 197 * This wrapper is required in order to prevent a crash when 198 * running on OpenCL platforms < 2 199 */ 200 static cl::CommandQueue commandQueue(); 201 202 /** 203 * Build the program from \a source in the given \a context and depending on \a exceptionBehavior 204 * can throw or catch the exceptions 205 * \return the built program 206 * \deprecated since QGIS 3.6 207 */ 208 Q_DECL_DEPRECATED static cl::Program buildProgram( const cl::Context &context, const QString &source, ExceptionBehavior exceptionBehavior = Catch ); 209 210 /** 211 * Build the program from \a source, depending on \a exceptionBehavior can throw or catch the exceptions 212 * \return the built program 213 */ 214 static cl::Program buildProgram( const QString &source, ExceptionBehavior exceptionBehavior = Catch ); 215 216 217 /** 218 * Context factory 219 * 220 * \return a new context for the default device or an invalid context if 221 * no device were identified or OpenCL support is not available 222 * and enabled 223 */ 224 static cl::Context context(); 225 226 //! Returns the base path to OpenCL program directory 227 static QString sourcePath(); 228 229 //! Set the base path to OpenCL program directory 230 static void setSourcePath( const QString &value ); 231 232 //! Returns \a infoType information about the active (default) device 233 static QString activeDeviceInfo( const Info infoType = Info::Name ); 234 235 //! Returns \a infoType information about the \a device 236 static QString deviceInfo( const Info infoType, cl::Device device ); 237 238 /** 239 * Tiny smart-pointer-like wrapper around CPLMalloc and CPLFree: this is needed because 240 * OpenCL C++ API may throw exceptions 241 */ 242 template <typename T> 243 struct CPLAllocator 244 { 245 246 public: 247 CPLAllocatorCPLAllocator248 explicit CPLAllocator( unsigned long size ): mMem( static_cast<T *>( CPLMalloc( sizeof( T ) * size ) ) ) { } 249 ~CPLAllocatorCPLAllocator250 ~CPLAllocator() 251 { 252 CPLFree( static_cast<void *>( mMem ) ); 253 } 254 resetCPLAllocator255 void reset( T *newData ) 256 { 257 if ( mMem ) 258 CPLFree( static_cast<void *>( mMem ) ); 259 mMem = newData; 260 } 261 resetCPLAllocator262 void reset( unsigned long size ) 263 { 264 reset( static_cast<T *>( CPLMalloc( sizeof( T ) *size ) ) ); 265 } 266 267 T &operator* () 268 { 269 return &mMem[0]; 270 } 271 releaseCPLAllocator272 T *release() 273 { 274 T *tmpMem = mMem; 275 mMem = nullptr; 276 return tmpMem; 277 } 278 279 T &operator[]( const int index ) 280 { 281 return mMem[index]; 282 } 283 getCPLAllocator284 T *get() 285 { 286 return mMem; 287 } 288 289 private: 290 291 T *mMem = nullptr; 292 }; 293 294 295 private: 296 297 QgsOpenClUtils(); 298 299 /** 300 * Activate a device identified by its \a preferredDeviceId by making it the default device 301 * if the device does not exists or deviceId is empty, the first GPU device will be 302 * activated, if a GPU device is not found, the first CPU device will be chosen instead. 303 * 304 * Called once by init() when OpenCL is used for the first time in a QGIS working session. 305 * 306 * \return TRUE if the device could be found and activated. Return FALSE if the device was already 307 * the active one or if a device could not be activated. 308 * 309 * \see init() 310 * \see available() 311 */ 312 static bool activate( const QString &preferredDeviceId = QString() ); 313 314 /** 315 * Initialize the OpenCL system by setting and activating the default device. 316 */ 317 static void init(); 318 319 static bool sAvailable; 320 static QLatin1String SETTINGS_GLOBAL_ENABLED_KEY; 321 static QLatin1String SETTINGS_DEFAULT_DEVICE_KEY; 322 }; 323 324 325 326 #endif // QGSOPENCLUTILS_H 327