From 86b60c6c638c7a3165d2e910296a92c561aef395 Mon Sep 17 00:00:00 2001 From: Alessandro Pasotti Date: Sat, 21 Jul 2018 19:20:00 +0200 Subject: [PATCH] [opencl] Class docs improvements Also try hard to find a device before giving up --- src/core/qgsopenclutils.cpp | 98 +++++++++++++++++++++---------- src/core/qgsopenclutils.h | 114 ++++++++++++++++++++++++------------ 2 files changed, 142 insertions(+), 70 deletions(-) diff --git a/src/core/qgsopenclutils.cpp b/src/core/qgsopenclutils.cpp index e3bcc589fb2..fae95bcac12 100644 --- a/src/core/qgsopenclutils.cpp +++ b/src/core/qgsopenclutils.cpp @@ -32,12 +32,10 @@ cl::Device QgsOpenClUtils::sActiveDevice = cl::Device(); QString QgsOpenClUtils::sSourcePath = QString(); -std::vector QgsOpenClUtils::devices() +const std::vector QgsOpenClUtils::devices() { std::vector platforms; cl::Platform::get( &platforms ); - cl::Platform plat; - cl::Device dev; std::vector existingDevices; for ( auto &p : platforms ) { @@ -91,7 +89,7 @@ void QgsOpenClUtils::init() QString QgsOpenClUtils::sourcePath() { - return sSourcePath.isEmpty() ? QStringLiteral( "./" ) : sSourcePath; + return sSourcePath; } void QgsOpenClUtils::setSourcePath( const QString &value ) @@ -99,7 +97,7 @@ void QgsOpenClUtils::setSourcePath( const QString &value ) sSourcePath = value; } -QString QgsOpenClUtils::defaultDeviceInfo( const QgsOpenClUtils::Info infoType ) +QString QgsOpenClUtils::activeDeviceInfo( const QgsOpenClUtils::Info infoType ) { return deviceInfo( infoType, activeDevice( ) ); } @@ -126,7 +124,7 @@ QString QgsOpenClUtils::deviceInfo( const Info infoType, cl::Device device ) return QString::number( device.getInfo() ); case Info::Type: { - int type( device.getInfo() ); + unsigned long type( device.getInfo() ); int mappedType; switch ( type ) { @@ -143,7 +141,6 @@ QString QgsOpenClUtils::deviceInfo( const Info infoType, cl::Device device ) return metaEnum.valueToKey( mappedType ); } case Info::Name: - default: return QString::fromStdString( device.getInfo() ); } } @@ -168,11 +165,6 @@ cl::Device QgsOpenClUtils::activeDevice() return sActiveDevice; } -void QgsOpenClUtils::setActiveDevice( const cl::Device device ) -{ - sActiveDevice = device; -} - void QgsOpenClUtils::storePreferredDevice( const QString deviceId ) { QgsSettings().setValue( SETTINGS_DEFAULT_DEVICE_KEY, deviceId, QgsSettings::Section::Core ); @@ -192,7 +184,7 @@ QString QgsOpenClUtils::deviceId( const cl::Device device ) .arg( deviceInfo( QgsOpenClUtils::Info::Type, device ) ); } -bool QgsOpenClUtils::activate( const QString preferredDeviceId ) +bool QgsOpenClUtils::activate( const QString &preferredDeviceId ) { if ( deviceId( activeDevice() ) == preferredDeviceId ) { @@ -204,8 +196,11 @@ bool QgsOpenClUtils::activate( const QString preferredDeviceId ) cl::Platform::get( &platforms ); cl::Platform plat; cl::Device dev; + bool deviceFound = false; for ( auto &p : platforms ) { + if ( deviceFound ) + break; std::string platver = p.getInfo(); QgsDebugMsg( QStringLiteral( "Found OpenCL platform %1: %2" ).arg( QString::fromStdString( platver ), QString::fromStdString( p.getInfo() ) ) ); if ( platver.find( "OpenCL 1." ) != std::string::npos ) @@ -215,30 +210,69 @@ bool QgsOpenClUtils::activate( const QString preferredDeviceId ) try { p.getDevices( CL_DEVICE_TYPE_ALL, &devices ); + // First search for the preferred device + if ( ! preferredDeviceId.isEmpty() ) + { + for ( const auto &_dev : devices ) + { + if ( preferredDeviceId == deviceId( _dev ) ) + { + // Got one! + plat = p; + dev = _dev; + deviceFound = true; + break; + } + } + } + // Not found or preferred device id not set: get the first GPU + if ( ! deviceFound ) + { + for ( const auto &_dev : devices ) + { + if ( preferredDeviceId.isEmpty() && _dev.getInfo() == CL_DEVICE_TYPE_GPU ) + { + // Got one! + plat = p; + dev = _dev; + deviceFound = true; + break; + } + } + } + // Still nothing? Get the first device + if ( ! deviceFound ) + { + for ( const auto &_dev : devices ) + { + if ( preferredDeviceId.isEmpty() && _dev.getInfo() == CL_DEVICE_TYPE_GPU ) + { + // Got one! + plat = p; + dev = _dev; + deviceFound = true; + break; + } + } + } + if ( ! deviceFound ) + { + QgsMessageLog::logMessage( QObject::tr( "No OpenCL 1.x device could be found." ), LOGMESSAGE_TAG, Qgis::Warning ); + } } catch ( cl::Error &e ) { - QgsDebugMsgLevel( QStringLiteral( "Error %1 on platform %3 searching for OpenCL device: %2" ) - .arg( errorText( e.err() ), - QString::fromStdString( e.what() ), - QString::fromStdString( p.getInfo() ) ), 2 ); - } - for ( const auto &_dev : devices ) - { - if ( ( preferredDeviceId.isEmpty() && _dev.getInfo() == CL_DEVICE_TYPE_GPU ) || - ( preferredDeviceId == deviceId( _dev ) ) ) - { - // Got one! - plat = p; - dev = _dev; - break; - } + QgsDebugMsg( QStringLiteral( "Error %1 on platform %3 searching for OpenCL device: %2" ) + .arg( errorText( e.err() ), + QString::fromStdString( e.what() ), + QString::fromStdString( p.getInfo() ) ) ); } + } } - if ( plat() == 0 ) + if ( ! plat() ) { - QgsMessageLog::logMessage( QObject::tr( "No OpenCL 1.x platform with GPU found." ), LOGMESSAGE_TAG, Qgis::Warning ); + QgsMessageLog::logMessage( QObject::tr( "No OpenCL 1.x platform found." ), LOGMESSAGE_TAG, Qgis::Warning ); sAvailable = false; } else @@ -253,7 +287,7 @@ bool QgsOpenClUtils::activate( const QString preferredDeviceId ) else { cl::Device::setDefault( dev ); - QgsMessageLog::logMessage( QObject::tr( "Activated OpenCL device %1" ) + QgsMessageLog::logMessage( QObject::tr( "Active OpenCL device: %1" ) .arg( QString::fromStdString( dev.getInfo() ) ), LOGMESSAGE_TAG, Qgis::Success ); sAvailable = true; @@ -487,7 +521,7 @@ cl::Program QgsOpenClUtils::buildProgram( const cl::Context &context, const QStr } catch ( cl::Error &e ) { - QString err = QObject::tr( "Error %1 running OpenCL program in %2" ) + QString err = QObject::tr( "Error %1 building OpenCL program in %2" ) .arg( errorText( e.err() ), QString::fromStdString( e.what() ) ); QgsMessageLog::logMessage( err, LOGMESSAGE_TAG, Qgis::Critical ); throw e; diff --git a/src/core/qgsopenclutils.h b/src/core/qgsopenclutils.h index 4ae7580177f..b35926d1a48 100644 --- a/src/core/qgsopenclutils.h +++ b/src/core/qgsopenclutils.h @@ -34,12 +34,30 @@ * \brief The QgsOpenClUtils class is responsible for common OpenCL operations such as * - enable/disable opencl * - store and retrieve preferences for the default device - * - check opencl device availability and automatically choose the first GPU (TODO: let the user choose & override!) - * - creating contexts + * - check opencl device availability and automatically choose the first GPU device + * - creating the default context * - loading program sources from standard locations * - build programs and log errors - * \since QGIS 3.4 + * + * Usage: + * + * \code{.cpp} + // This will check if OpenCL is enabled in user options and if there is a suitable + // device, if a device is found it is initialized. + if ( QgsOpenClUtils::enabled() && QgsOpenClUtils::available() ) + { + // Use the default context + cl::Context ctx = QgsOpenClUtils::context(); + cl::CommandQueue queue( ctx ); + // Load the program from a standard location and build it + cl::Program program = QgsOpenClUtils::buildProgram( ctx, QgsOpenClUtils::sourceFromBaseName( QStringLiteral ( "hillshade" ) ) ); + // Continue with the usual OpenCL buffer, kernel and execution + ... + } + * \endcode + * * \note not available in Python bindings + * \since QGIS 3.4 */ class CORE_EXPORT QgsOpenClUtils { @@ -52,8 +70,8 @@ class CORE_EXPORT QgsOpenClUtils */ enum ExceptionBehavior { - Catch, // Write errors in the message log and silently fail - Throw // Write errors in the message log and re-throw exceptions + Catch, //! Write errors in the message log and silently fail + Throw //! Write errors in the message log and re-throw exceptions }; /** @@ -86,18 +104,32 @@ class CORE_EXPORT QgsOpenClUtils Type = CL_DEVICE_TYPE // CPU/GPU etc. }; - //! Return true if OpenCL is enabled in the user settings + /** + * Checks whether a suitable OpenCL platform and device is available on this system + * and initialize the QGIS OpenCL system by activating the preferred device + * if specified in the user the settings, if no preferred device was set or + * the preferred device could not be found the first GPU device is activated, + * the first CPU device acts as a fallback if none of the previous could be found. + * + * This function must always be called before using QGIS OpenCL utils + */ + static bool available(); + + //! Returns true if OpenCL is enabled in the user settings static bool enabled(); - //! Return a list of OpenCL devices found on this sysytem - static std::vector devices(); + //! Returns a list of OpenCL devices found on this sysytem + static const std::vector devices(); - //! Return the active device + /** + * Returns the active device. + * + * The active device is set as the default device for all OpenCL operations, + * once it is set it cannot be changed until QGIS is restarted (this is + * due to the way the underlying OpenCL library is built). + */ static cl::Device activeDevice( ); - //! Set the active \a device - static void setActiveDevice( const cl::Device device ); - //! Store in the settings the preferred \a deviceId device identifier static void storePreferredDevice( const QString deviceId ); @@ -107,15 +139,6 @@ class CORE_EXPORT QgsOpenClUtils //! Create a string identifier from a \a device static QString deviceId( const cl::Device device ); - /** - * Activate a device identified by its \a preferredDeviceId by making it the default device - * if the device does not exists or deviceId is empty, the first GPU device will be - * activated - * \return true if the device could be found and activated. Return false if the device was already - * the active one or if it could not be found. - */ - static bool activate( const QString preferredDeviceId = QString() ); - /** * Returns a formatted description for the \a device */ @@ -126,12 +149,6 @@ class CORE_EXPORT QgsOpenClUtils */ static QString deviceDescription( const QString deviceId ); - /** - * Checks whether a suitable OpenCL platform and device is found on this system and initialize the QGIS OpenCL system - * This function must be called before using QGIS OpenCL utils - */ - static bool available(); - //! Set the OpenCL user setting to \a enabled static void setEnabled( bool enabled ); @@ -141,17 +158,17 @@ class CORE_EXPORT QgsOpenClUtils //! Read an OpenCL source file from \a path static QString sourceFromPath( const QString &path ); - //! Return the full path to a an OpenCL source file from the \a baseName ('.cl' extension is automatically appended) + //! Returns the full path to a an OpenCL source file from the \a baseName ('.cl' extension is automatically appended) static QString sourceFromBaseName( const QString &baseName ); //! OpenCL string for message logs static QLatin1String LOGMESSAGE_TAG; - //! Return a string representation from an OpenCL \a errorCode + //! Returns a string representation from an OpenCL \a errorCode static QString errorText( const int errorCode ); /** - * Build the program from \a source in the given \a context and depending on \a exeptionBehavior + * Build the program from \a source in the given \a context and depending on \a exceptionBehavior * can throw or catch the exceptions * \return the built program */ @@ -166,16 +183,16 @@ class CORE_EXPORT QgsOpenClUtils */ static cl::Context context(); - //! Return the base path to OpenCL program directory + //! Returns the base path to OpenCL program directory static QString sourcePath(); //! Set the base path to OpenCL program directory static void setSourcePath( const QString &value ); - //! Return \a infoType information about the default device - static QString defaultDeviceInfo( const Info infoType = Info::Name ); + //! Returns \a infoType information about the active (default) device + static QString activeDeviceInfo( const Info infoType = Info::Name ); - //! Return \a infoType information about the \a device + //! Returns \a infoType information about the \a device static QString deviceInfo( const Info infoType, cl::Device device ); /** @@ -188,23 +205,23 @@ class CORE_EXPORT QgsOpenClUtils public: - explicit CPLAllocator( unsigned long size ): mMem( ( T * )CPLMalloc( sizeof( T ) * size ) ) { } + explicit CPLAllocator( unsigned long size ): mMem( static_cast( CPLMalloc( sizeof( T ) * size ) ) ) { } ~CPLAllocator() { - CPLFree( ( void * )mMem ); + CPLFree( static_cast( mMem ) ); } void reset( T *newData ) { if ( mMem ) - CPLFree( ( void * )mMem ); + CPLFree( static_cast( mMem ) ); mMem = newData; } void reset( unsigned long size ) { - reset( ( T * )CPLMalloc( sizeof( T ) *size ) ); + reset( static_cast( CPLMalloc( sizeof( T ) *size ) ) ); } T &operator* () @@ -236,8 +253,29 @@ class CORE_EXPORT QgsOpenClUtils private: + QgsOpenClUtils(); + + /** + * Activate a device identified by its \a preferredDeviceId by making it the default device + * if the device does not exists or deviceId is empty, the first GPU device will be + * activated, if a GPU device is not found, the first CPU device will be chosen instead. + * + * Called once by init() when OpenCL is used for the first time in a QGIS working session. + * + * \return true if the device could be found and activated. Return false if the device was already + * the active one or if a device could not be activated. + * + * \see init() + * \see available() + */ + static bool activate( const QString &preferredDeviceId = QString() ); + + /** + * Initialize the OpenCL system by setting and activating the default device. + */ static void init(); + static bool sAvailable; static cl::Device sActiveDevice; static cl::Platform sDefaultPlatform;