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