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