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