#ifndef OPENCLHELPER_H #define OPENCLHELPER_H #define CL_HPP_MINIMUM_OPENCL_VERSION 120 #define CL_HPP_TARGET_OPENCL_VERSION 120 #include #include #include "logger/Logger.h" #include "lib/CL/cl2.hpp" #define caseReturnString(x) case x: \ return #x /*************************************************************************************************/ /** * @brief The OpenCLHelper class * @note This class can not be broken to .h and .cpp files, because of the way opencl works, * if we include it in 2 places it complains about duplicated defenition * @author Mohsen Emami revised by Hessamoddin Hediyehloo(H-4nd-H) * @date 2019/8/5(1398/5/14) */ /*************************************************************************************************/ class OpenClHelper : public QObject { Q_OBJECT private: inline std::vector getAllAvailablePlatform(); inline std::vector getAmdAndNvidiaPlatforms(); inline std::vector filterNoneAmdAndNoneNvidiaPlatforms( std::vector platforms); inline void ThrowIfNoPlatformAvailable(const std::vector& platforms); inline std::vector getAllAvailableDevicesForValidPlatforms( const std::vector& platforms); inline std::vector getAllAvailableDevicesForGivenPlatforms( const cl::Platform& platform); inline void ThrowIfNoDeviceAvailable(const std::vector& devices); inline QString getErrorString(int error); inline QString getKernelFullPath(QString kernelPath, QString kernelName); inline std::string readKernelSourceCode(QString kernelPath); inline cl::Program createProgramFromKernelCode(const cl::Context& context, std::string kernelSource); inline cl::Kernel createKernelFromProgram(const cl::Program& program, QString kernelName); public: inline cl::Context& getContext(); inline cl::Device getFirstDeviceByContext(const cl::Context& context); inline cl::Kernel createKernel(const cl::Context& context, const QString kernelPath, const QString KernelName); inline cl::Buffer* allocateClBuffer(const cl::Context& context, quint64 size, bool read); inline cl::Image* allocateClImage(const cl::Context& context, cl::ImageFormat clFrameFormat, QList dimensions, bool read); inline cl::CommandQueue createCommandQueue(const cl::Context& context, const cl_command_queue_properties properties); template inline cl::KernelFunctor createKernelFunctor(const cl::Kernel& kernel); template inline void runKernelFunctor(cl::KernelFunctor& kernelFunctor, cl::EnqueueArgs args, Ts... ts); }; /*************************************************************************************************/ /** * @brief Returns the name of error * @param error The error to get th ename of * @return The error name */ /*************************************************************************************************/ QString OpenClHelper::getErrorString(int error) { switch(error) { caseReturnString(CL_SUCCESS); caseReturnString(CL_DEVICE_NOT_FOUND); caseReturnString(CL_DEVICE_NOT_AVAILABLE); caseReturnString(CL_COMPILER_NOT_AVAILABLE); caseReturnString(CL_MEM_OBJECT_ALLOCATION_FAILURE); caseReturnString(CL_OUT_OF_RESOURCES); caseReturnString(CL_OUT_OF_HOST_MEMORY); caseReturnString(CL_PROFILING_INFO_NOT_AVAILABLE); caseReturnString(CL_MEM_COPY_OVERLAP); caseReturnString(CL_IMAGE_FORMAT_MISMATCH); caseReturnString(CL_IMAGE_FORMAT_NOT_SUPPORTED); caseReturnString(CL_BUILD_PROGRAM_FAILURE); caseReturnString(CL_MAP_FAILURE); caseReturnString(CL_MISALIGNED_SUB_BUFFER_OFFSET); caseReturnString(CL_COMPILE_PROGRAM_FAILURE); caseReturnString(CL_LINKER_NOT_AVAILABLE); caseReturnString(CL_LINK_PROGRAM_FAILURE); caseReturnString(CL_DEVICE_PARTITION_FAILED); caseReturnString(CL_KERNEL_ARG_INFO_NOT_AVAILABLE); caseReturnString(CL_INVALID_VALUE); caseReturnString(CL_INVALID_DEVICE_TYPE); caseReturnString(CL_INVALID_PLATFORM); caseReturnString(CL_INVALID_DEVICE); caseReturnString(CL_INVALID_CONTEXT); caseReturnString(CL_INVALID_QUEUE_PROPERTIES); caseReturnString(CL_INVALID_COMMAND_QUEUE); caseReturnString(CL_INVALID_HOST_PTR); caseReturnString(CL_INVALID_MEM_OBJECT); caseReturnString(CL_INVALID_IMAGE_FORMAT_DESCRIPTOR); caseReturnString(CL_INVALID_IMAGE_SIZE); caseReturnString(CL_INVALID_SAMPLER); caseReturnString(CL_INVALID_BINARY); caseReturnString(CL_INVALID_BUILD_OPTIONS); caseReturnString(CL_INVALID_PROGRAM); caseReturnString(CL_INVALID_PROGRAM_EXECUTABLE); caseReturnString(CL_INVALID_KERNEL_NAME); caseReturnString(CL_INVALID_KERNEL_DEFINITION); caseReturnString(CL_INVALID_KERNEL); caseReturnString(CL_INVALID_ARG_INDEX); caseReturnString(CL_INVALID_ARG_VALUE); caseReturnString(CL_INVALID_ARG_SIZE); caseReturnString(CL_INVALID_KERNEL_ARGS); caseReturnString(CL_INVALID_WORK_DIMENSION); caseReturnString(CL_INVALID_WORK_GROUP_SIZE); caseReturnString(CL_INVALID_WORK_ITEM_SIZE); caseReturnString(CL_INVALID_GLOBAL_OFFSET); caseReturnString(CL_INVALID_EVENT_WAIT_LIST); caseReturnString(CL_INVALID_EVENT); caseReturnString(CL_INVALID_OPERATION); caseReturnString(CL_INVALID_GL_OBJECT); caseReturnString(CL_INVALID_BUFFER_SIZE); caseReturnString(CL_INVALID_MIP_LEVEL); caseReturnString(CL_INVALID_GLOBAL_WORK_SIZE); caseReturnString(CL_INVALID_PROPERTY); caseReturnString(CL_INVALID_IMAGE_DESCRIPTOR); caseReturnString(CL_INVALID_COMPILER_OPTIONS); caseReturnString(CL_INVALID_LINKER_OPTIONS); caseReturnString(CL_INVALID_DEVICE_PARTITION_COUNT); default: return "Unknown OpenCL error code"; } } /*************************************************************************************************/ /** * @brief Return the full file path for the kernel * @param kernelPath Directory of kernel * @param kernelName Kernel name * @return Full kernel path */ /*************************************************************************************************/ QString OpenClHelper::getKernelFullPath(QString kernelPath, QString kernelName) { return QDir(kernelPath).filePath(kernelName + ".cl"); } /*************************************************************************************************/ /** * @brief Read kernel source file from the given path * @param kernelPath Kernel file path * @return Kernel source code */ /*************************************************************************************************/ std::string OpenClHelper::readKernelSourceCode(QString kernelPath) { QFile kernelFile(kernelPath); if(!kernelFile.open(QIODevice::ReadOnly | QIODevice::Text)) { throw LoggerException(ESeverityLevel::Alert, ELogID::CLKernelError, "Can not open kernel file @ " + kernelPath, this->metaObject()->className()); } auto kernelSource = kernelFile.readAll().toStdString(); kernelFile.close(); return kernelSource; } /*************************************************************************************************/ /** * @brief Compile the rkernel code and create a ne wprogram object * @param context OpenCL object * @param kernelSource Kernel source code * @return Created program object */ /*************************************************************************************************/ cl::Program OpenClHelper::createProgramFromKernelCode(const cl::Context& context, std::string kernelSource) { cl_int err = 0; cl::Program program(context, kernelSource, true, &err); if(err != CL_SUCCESS) { throw LoggerException(ESeverityLevel::Alert, ELogID::CLKernelError, getErrorString(err), this->metaObject()->className()); } return program; } /*************************************************************************************************/ /** * @brief Create kernel object from openCl program object * @param program OpenCL program object * @param kernelName Name of the kernel * @return OpenCl kernel */ /*************************************************************************************************/ cl::Kernel OpenClHelper::createKernelFromProgram(const cl::Program& program, QString kernelName) { cl_int err; cl::Kernel kernel(program, kernelName.toStdString().c_str(), &err); if(err != CL_SUCCESS) { throw LoggerException(ESeverityLevel::Alert, ELogID::CLKernelError, getErrorString(err), this->metaObject()->className()); } return kernel; } /*************************************************************************************************/ /** * @brief Checks to see if platform list is empty or not, throws exception if empty * @param platforms List of platforms */ /*************************************************************************************************/ void OpenClHelper::ThrowIfNoPlatformAvailable(const std::vector& platforms) { if(platforms.size() > 0) { return; } throw LoggerException(ESeverityLevel::Alert, ELogID::CLPlatformError, "No valid platform was found", this->metaObject()->className()); } /*************************************************************************************************/ /** * @brief Checks to see if device list is empty or not, throws exception if empty * @param devices List of devices */ /*************************************************************************************************/ void OpenClHelper::ThrowIfNoDeviceAvailable(const std::vector& devices) { if(devices.size() > 0) { return; } throw LoggerException(ESeverityLevel::Alert, ELogID::CLDeviceError, "No valid device was found", this->metaObject()->className()); } /*************************************************************************************************/ /** * @brief Return all available opencl platforms * @return Availaable opencl platforms */ /*************************************************************************************************/ std::vector OpenClHelper::getAllAvailablePlatform() { std::vector availablePlatforms; cl_int err = cl::Platform::get(&availablePlatforms); if(err != CL_SUCCESS) { throw LoggerException(ESeverityLevel::Alert, ELogID::CLPlatformError, getErrorString(err), this->metaObject()->className()); } return availablePlatforms; } /*************************************************************************************************/ /** * @brief Remove none amd and none nvidia paltforsm from a given list of platforms * @param platforms List of available platforms * @return Platform list containing only nvidia and amd */ /*************************************************************************************************/ std::vector OpenClHelper::filterNoneAmdAndNoneNvidiaPlatforms( std::vector platforms) { std::vector AmdNvidiaPlatforms; for(auto it = platforms.begin(); it != platforms.end(); it++) { cl::string currentPlatformName = it->getInfo(); Logger::log(ESeverityLevel::Debug, ELogID::CLPlatformInfo, "Platform Name: " + QString::fromStdString(currentPlatformName), this->metaObject()->className()); if(!(it->getInfo().find("NVIDIA") == cl::string::npos) || !(it->getInfo().find("AMD") == cl::string::npos)) { AmdNvidiaPlatforms.push_back(*it); } } return AmdNvidiaPlatforms; } /*************************************************************************************************/ /** * @brief gets available nvidia or amd platforms on host machine * @return platforms The list of available nvidia nd amd platforms */ /*************************************************************************************************/ std::vector OpenClHelper::getAmdAndNvidiaPlatforms() { auto availablePlatform = getAllAvailablePlatform(); availablePlatform = filterNoneAmdAndNoneNvidiaPlatforms(availablePlatform); ThrowIfNoPlatformAvailable(availablePlatform); return availablePlatform; } /*************************************************************************************************/ /** * @brief get the opencl devices for all provided platforms * @param platforms The list of nvidia and amd platforms \sa getPlatforms * @return devices List of available devices for provided platforms */ /*************************************************************************************************/ std::vector OpenClHelper::getAllAvailableDevicesForValidPlatforms( const std::vector& platforms) { std::vector availableDevices; for(auto platform = platforms.begin(); platform != platforms.end(); platform++) { auto temp = getAllAvailableDevicesForGivenPlatforms(*platform); for(auto device = temp.begin(); device != temp.end(); device++) { availableDevices.push_back(*device); cl::string currentDeviceName = device->getInfo(); Logger::log(ESeverityLevel::Debug, ELogID::CLDeviceInfo, "Device Name: " + QString::fromStdString(currentDeviceName), this->metaObject()->className()); } } return availableDevices; } std::vector OpenClHelper::getAllAvailableDevicesForGivenPlatforms( const cl::Platform& platform) { std::vector availableDevices; cl_int err = platform.getDevices(CL_DEVICE_TYPE_GPU, &availableDevices); if(err != CL_SUCCESS) { throw LoggerException(ESeverityLevel::Alert, ELogID::CLDeviceError, getErrorString(err), this->metaObject()->className()); } return availableDevices; } /*************************************************************************************************/ /** * @brief get context of opencl * @return context Actual opencl context */ /*************************************************************************************************/ cl::Context& OpenClHelper::getContext() { static cl::Context* internalContext = nullptr; if(internalContext) { return *internalContext; } auto platforms = getAmdAndNvidiaPlatforms(); auto devices = getAllAvailableDevicesForValidPlatforms(platforms); cl_int err; internalContext = new cl::Context(devices, Q_NULLPTR, Q_NULLPTR, Q_NULLPTR, &err); if(err != CL_SUCCESS) { throw LoggerException(ESeverityLevel::Alert, ELogID::CLContextError, getErrorString(err), this->metaObject()->className()); } return *internalContext; } /*************************************************************************************************/ /** * @brief Creates and compiles the given kernel * @param context OpenCl contex \sa getContext * @param kernelPath The folder directory for kernel * @param kernelName The kernel file name without .cl extension * @return kernel The newly created kernel instance */ /*************************************************************************************************/ cl::Kernel OpenClHelper::createKernel(const cl::Context& context, const QString kernelPath, const QString kernelName) { auto kernelFullPath = getKernelFullPath(kernelPath, kernelName); auto kernelSource = readKernelSourceCode(kernelFullPath); auto program = createProgramFromKernelCode(context, kernelSource); return createKernelFromProgram(program, kernelName); } /*************************************************************************************************/ /** * @brief allocate a cl buffer * @param context OpenCL context \sa getContext * @param size Number of bytes to allocate * @param read Should the buffer be allocated with read access or write access * @return buffer The allocated buffer pointer */ /*************************************************************************************************/ cl::Buffer* OpenClHelper::allocateClBuffer(const cl::Context& context, quint64 size, bool read = true) { cl_mem_flags clMemFlags = read ? CL_MEM_READ_ONLY : CL_MEM_WRITE_ONLY; cl_int err; auto buffer = new cl::Buffer(context, clMemFlags, size, Q_NULLPTR, &err); if(err != CL_SUCCESS) { throw LoggerException(ESeverityLevel::Alert, ELogID::CLBufferError, getErrorString(err), this->metaObject()->className()); } return buffer; } /*************************************************************************************************/ /** * @brief Allocate proper CL image based on input * @param context OpenCL context \sa getContext * @param clFrameFormat Frame format for image * @param dimensions List containing size for each image dimension, can be 1D, 2D or 3D * @return image Actual allocated image */ /*************************************************************************************************/ cl::Image* OpenClHelper::allocateClImage(const cl::Context& context, cl::ImageFormat clFrameFormat, QList dimensions, bool read) { cl_mem_flags clMemFlags = CL_MEM_READ_WRITE; cl::Image* image = Q_NULLPTR; cl_int err = 0; switch(dimensions.size()) { case 1: image = new cl::Image1D(context, clMemFlags, clFrameFormat, dimensions[0], &err); break; case 2: image = new cl::Image2D(context, clMemFlags, clFrameFormat, dimensions[0], dimensions[1], 0, &err); break; case 3: image = new cl::Image3D(context, clMemFlags, clFrameFormat, dimensions[0], dimensions[1], dimensions[2], 0, 0, &err); break; default: throw LoggerException(ESeverityLevel::Alert, ELogID::CLFrameError, "Invalid dimension size", this->metaObject()->className()); } if(err != CL_SUCCESS) { throw LoggerException(ESeverityLevel::Alert, ELogID::CLFrameError, getErrorString(err), this->metaObject()->className()); } return image; } /*************************************************************************************************/ /** * @brief Creates a functor for given kernel * @param kernel The kernel to build functor for * @return functor The created functor */ /*************************************************************************************************/ template cl::KernelFunctor OpenClHelper::createKernelFunctor(const cl::Kernel& kernel) { auto kernelName = QString::fromStdString(kernel.getInfo()); cl_int err; cl::KernelFunctor functor(kernel.getInfo(), kernelName, &err); if(err != CL_SUCCESS) { throw LoggerException(ESeverityLevel::Alert, ELogID::CLKernelFunctorError, getErrorString(err), this->metaObject()->className()); } return functor; } /*************************************************************************************************/ /** * @brief Create an OpenCl commandQueue * @param context OpenCL context \sa getContext * @param properties Properties to build queue upon them * @return queue The newly created queue */ /*************************************************************************************************/ cl::CommandQueue OpenClHelper::createCommandQueue(const cl::Context& context, const cl_command_queue_properties properties) { cl_int err; cl::CommandQueue commandQueue(context, properties, &err); if(err != CL_SUCCESS) { throw LoggerException(ESeverityLevel::Alert, ELogID::CLCommandQueueCreationError, getErrorString(err), this->metaObject()->className()); } return commandQueue; } /*************************************************************************************************/ /** * @brief Returns the first device of context * @param context OpenCl context \sa getContext * @param device First device of context * @return success or failure */ /*************************************************************************************************/ cl::Device OpenClHelper::getFirstDeviceByContext(const cl::Context& context) { std::vector devices; auto err = context.getInfo >(CL_CONTEXT_DEVICES, &devices); if(err != CL_SUCCESS) { throw LoggerException(ESeverityLevel::Alert, ELogID::CLDeviceError, getErrorString(err), this->metaObject()->className()); } return devices[0]; } /*************************************************************************************************/ /** * @brief Run the kernel functor \sa createKernelFunctor * @param kernelFunctor Functor to run * @param args Arg structure for functor * @param ts actual arg for functor * @return success or failure */ /*************************************************************************************************/ template void OpenClHelper::runKernelFunctor(cl::KernelFunctor& kernelFunctor, cl::EnqueueArgs args, Ts... ts) { cl_int err; kernelFunctor(args, ts ..., err); if(err != CL_SUCCESS) { throw LoggerException(ESeverityLevel::Alert, ELogID::CLKernelRunError, getErrorString(err), this->metaObject()->className()); } } #endif