You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

655 lines
22 KiB

#ifndef OPENCLHELPER_H
#define OPENCLHELPER_H
#define CL_HPP_MINIMUM_OPENCL_VERSION 120
#define CL_HPP_TARGET_OPENCL_VERSION 120
#include <QObject>
#include <QFile>
#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<cl::Platform> getAllAvailablePlatform();
inline std::vector<cl::Platform> getAmdAndNvidiaPlatforms();
inline std::vector<cl::Platform> filterNoneAmdAndNoneNvidiaPlatforms(
std::vector<cl::Platform> platforms);
inline void ThrowIfNoPlatformAvailable(const std::vector<cl::Platform>& platforms);
inline std::vector<cl::Device> getAllAvailableDevicesForValidPlatforms(
const std::vector<cl::Platform>& platforms);
inline std::vector<cl::Device> getAllAvailableDevicesForGivenPlatforms(
const cl::Platform& platform);
inline void ThrowIfNoDeviceAvailable(const std::vector<cl::Device>& 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<quint64> dimensions, bool read);
inline cl::CommandQueue createCommandQueue(const cl::Context& context,
const cl_command_queue_properties properties);
template<typename ... Ts>
inline cl::KernelFunctor<Ts...> createKernelFunctor(const cl::Kernel& kernel);
template<typename ... Ts>
inline void runKernelFunctor(cl::KernelFunctor<Ts...>& 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<cl::Platform>& 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<cl::Device>& 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<cl::Platform> OpenClHelper::getAllAvailablePlatform()
{
std::vector<cl::Platform> 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<cl::Platform> OpenClHelper::filterNoneAmdAndNoneNvidiaPlatforms(
std::vector<cl::Platform> platforms)
{
std::vector<cl::Platform> AmdNvidiaPlatforms;
for(auto it = platforms.begin(); it != platforms.end(); it++)
{
cl::string currentPlatformName = it->getInfo<CL_PLATFORM_NAME>();
Logger::log(ESeverityLevel::Debug, ELogID::CLPlatformInfo,
"Platform Name: " + QString::fromStdString(currentPlatformName),
this->metaObject()->className());
if(!(it->getInfo<CL_PLATFORM_NAME>().find("NVIDIA") == cl::string::npos) ||
!(it->getInfo<CL_PLATFORM_NAME>().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<cl::Platform> 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<cl::Device> OpenClHelper::getAllAvailableDevicesForValidPlatforms(
const std::vector<cl::Platform>& platforms)
{
std::vector<cl::Device> 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<CL_DEVICE_NAME>();
Logger::log(ESeverityLevel::Debug, ELogID::CLDeviceInfo,
"Device Name: " + QString::fromStdString(currentDeviceName),
this->metaObject()->className());
}
}
return availableDevices;
}
std::vector<cl::Device> OpenClHelper::getAllAvailableDevicesForGivenPlatforms(
const cl::Platform& platform)
{
std::vector<cl::Device> 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<quint64> 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<typename ... Ts>
cl::KernelFunctor<Ts...> OpenClHelper::createKernelFunctor(const cl::Kernel& kernel)
{
auto kernelName = QString::fromStdString(kernel.getInfo<CL_KERNEL_FUNCTION_NAME>());
cl_int err;
cl::KernelFunctor<Ts...> functor(kernel.getInfo<CL_KERNEL_PROGRAM>(), 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<cl::Device> devices;
auto err = context.getInfo<std::vector<cl::Device> >(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<typename ... Ts>
void OpenClHelper::runKernelFunctor(cl::KernelFunctor<Ts...>& 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