//+------------------------------------------------------------------+
//|                                                       OpenCL.mqh |
//|                             Copyright 2000-2024, MetaQuotes Ltd. |
//|                                             https://www.mql5.com |
//+------------------------------------------------------------------+

//+------------------------------------------------------------------+
//| Class for working with OpenCL                                    |
//+------------------------------------------------------------------+
class COpenCL
  {
protected:
   int               m_context;
   int               m_program;
   //--- kernel
   string            m_kernel_names[];
   int               m_kernels[];
   int               m_kernels_total;
   //--- buffers
   int               m_buffers[];
   int               m_buffers_total;
   string            m_device_extensions;
   bool              m_support_cl_khr_fp64;
public:
   //--- constructor/destructor
                     COpenCL();
                    ~COpenCL();
   //--- get handles
   int               GetContext(void) const { return(m_context); }
   int               GetProgram(void) const { return(m_program); }
   int               GetKernel(const int kernel_index) const;
   string            GetKernelName(const int kernel_index) const;
   //--- global and local memory size
   bool              GetGlobalMemorySize(long &global_memory_size);
   bool              GetLocalMemorySize(long &local_memory_size);
   //--- maximal workgroup size
   bool              GetMaxWorkgroupSize(long &max_workgroup_size);
   //--- check support working with double
   bool              SupportDouble(void) const { return(m_support_cl_khr_fp64); }
   //--- initialization and shutdown
   bool              Initialize(const string program,const bool show_log=true);
   bool              ContextCreate(const int device=CL_USE_ANY);
   bool              ProgramCreate(const string program,const bool show_log=true);
   void              ContextClean(void);
   void              Shutdown(void);
   //--- set buffers/kernels count
   bool              SetBuffersCount(const int total_buffers);
   bool              SetKernelsCount(const int total_kernels);
   //--- kernel operations
   bool              KernelCreate(const int kernel_index,const string kernel_name);
   bool              KernelFree(const int kernel_index);
   //--- device and kernel info
   long              GetDeviceInfo(const int prop);
   long              GetDeviceInfoInteger(ENUM_OPENCL_PROPERTY_INTEGER prop);
   long              GetKernelInfoInteger(const int kernel_index,ENUM_OPENCL_PROPERTY_INTEGER prop);
   //--- buffer operations
   bool              BufferCreate(const int buffer_index,const uint size_in_bytes,const uint flags);
   bool              BufferFree(const int buffer_index);
   template<typename T>
   bool              BufferFromArray(const int buffer_index,T &data[],const uint data_array_offset,const uint data_array_count,const uint flags);
   template<typename T>
   bool              BufferFromMatrix(const int buffer_index,matrix<T> &data,const uint flags);
   template<typename T>
   bool              BufferFromVector(const int buffer_index,vector<T> &data,const uint flags);
   template<typename T>
   bool              BufferToMatrix(const int buffer_index,matrix<T> &data,const ulong rows=-1,const ulong cols=-1);
   template<typename T>
   bool              BufferToVector(const int buffer_index,vector<T> &data,const ulong size=-1);
   template<typename T>
   bool              BufferRead(const int buffer_index,T &data[],const uint cl_buffer_offset,const uint data_array_offset,const uint data_array_count);
   template<typename T>
   bool              BufferWrite(const int buffer_index,T &data[],const uint cl_buffer_offset,const uint data_array_offset,const uint data_array_count);
   //--- set kernel arguments
   template<typename T>
   bool              SetArgument(const int kernel_index,const int arg_index,T value);
   bool              SetArgumentBuffer(const int kernel_index,const int arg_index,const int buffer_index);
   bool              SetArgumentLocalMemory(const int kernel_index,const int arg_index,const int local_memory_size);
   //--- kernel execution
   bool              Execute(const int kernel_index,const int work_dim,const uint &work_offset[],const uint &work_size[]);
   bool              Execute(const int kernel_index,const int work_dim,const uint &work_offset[],const uint &work_size[],const uint &local_work_size[]);
  };
//+------------------------------------------------------------------+
//| COpenCL class constructor                                        |
//+------------------------------------------------------------------+
COpenCL::COpenCL()
  {
   m_context=INVALID_HANDLE;
   m_program=INVALID_HANDLE;
   m_buffers_total=0;
   m_kernels_total=0;
   m_device_extensions="";
   m_support_cl_khr_fp64=false;
  }
//+------------------------------------------------------------------+
//| COpenCL class destructor                                         |
//+------------------------------------------------------------------+
COpenCL::~COpenCL()
  {
   Shutdown();
  }
//+------------------------------------------------------------------+
//| GetKernel                                                        |
//+------------------------------------------------------------------+
int COpenCL::GetKernel(const int kernel_index) const
  {
   if(m_kernels_total<=0 || kernel_index<0 || kernel_index>=m_kernels_total)
      return(INVALID_HANDLE);
//---
   return m_kernels[kernel_index];
  }
//+------------------------------------------------------------------+
//| GetKernelName                                                    |
//+------------------------------------------------------------------+
string COpenCL::GetKernelName(const int kernel_index) const
  {
   if(m_kernels_total<=0 || kernel_index<0 || kernel_index>=m_kernels_total)
      return("");
//---
   return m_kernel_names[kernel_index];
  }
//+------------------------------------------------------------------+
//| GetGlobalMemorySize                                              |
//+------------------------------------------------------------------+
bool COpenCL::GetGlobalMemorySize(long &global_memory_size)
  {
   if(m_context==INVALID_HANDLE)
      return(false);

//--- get global memory size
   global_memory_size=CLGetInfoInteger(m_context,CL_DEVICE_GLOBAL_MEM_SIZE);
   if(global_memory_size==-1)
      return(false);
//---
   return(true);
  }
//+------------------------------------------------------------------+
//| GetLocalMemorySize                                               |
//+------------------------------------------------------------------+
bool COpenCL::GetLocalMemorySize(long &local_memory_size)
  {
   if(m_context==INVALID_HANDLE)
      return(false);

//--- get local memory size
   local_memory_size=CLGetInfoInteger(m_context,CL_DEVICE_LOCAL_MEM_SIZE);
   if(local_memory_size==-1)
      return(false);
//---
   return(true);
  }
//+------------------------------------------------------------------+
//| GetMaxWorkgroupSize                                              |
//+------------------------------------------------------------------+
bool COpenCL::GetMaxWorkgroupSize(long &max_workgroup_size)
  {
   if(m_context==INVALID_HANDLE)
      return(false);

//--- get maximal workgroup size
   max_workgroup_size=CLGetInfoInteger(m_context,CL_DEVICE_MAX_WORK_GROUP_SIZE);
   if(max_workgroup_size==-1)
      return(false);
//---
   return(true);
  }
//+------------------------------------------------------------------+
//| Initialize                                                       |
//+------------------------------------------------------------------+
bool COpenCL::Initialize(const string program,const bool show_log)
  {
   if(!ContextCreate(CL_USE_ANY))
      return(false);
   return(ProgramCreate(program,show_log));
  }
//+------------------------------------------------------------------+
//| ContextCreate                                                    |
//+------------------------------------------------------------------+
bool COpenCL::ContextCreate(const int device)
  {
   if((m_context=CLContextCreate(device))==INVALID_HANDLE)
     {
      Print("OpenCL not found. Error code=",GetLastError());
      return(false);
     }
//--- check support working with doubles (cl_khr_fp64)
   if(CLGetInfoString(m_context,CL_DEVICE_EXTENSIONS,m_device_extensions))
     {
      string extenstions[];
      StringSplit(m_device_extensions,' ',extenstions);
      m_support_cl_khr_fp64=false;
      int size=ArraySize(extenstions);
      for(int i=0; i<size; i++)
        {
         if(extenstions[i]=="cl_khr_fp64")
            m_support_cl_khr_fp64=true;
        }
     }
//---
   return(true);
  }
//+------------------------------------------------------------------+
//| ProgramCreate                                                    |
//+------------------------------------------------------------------+
bool COpenCL::ProgramCreate(const string program,const bool show_log)
  {
//--- compile the program
   string build_error_log;
   if((m_program=CLProgramCreate(m_context,program,build_error_log))==INVALID_HANDLE)
     {
      if(show_log)
        {
         string loglines[];
         StringSplit(build_error_log,'\n',loglines);
         int lines_count=ArraySize(loglines);
         for(int i=0; i<lines_count; i++)
            Print(loglines[i]);
        }
      CLContextFree(m_context);
      Print("OpenCL program create failed. Error code=",GetLastError());
      return(false);
     }
//---
   return(true);
  }
//+------------------------------------------------------------------+
//| ContextClean                                                     |
//+------------------------------------------------------------------+
void COpenCL::ContextClean()
  {
//--- remove buffers
   if(m_buffers_total>0)
     {
      for(int i=0; i<m_buffers_total; i++)
         BufferFree(i);
      m_buffers_total=0;
     }
//--- remove buffers
   if(m_kernels_total>0)
     {
      for(int i=0; i<m_kernels_total; i++)
         KernelFree(i);
      m_kernels_total=0;
     }
//--- remove program
   if(m_program!=INVALID_HANDLE)
     {
      CLProgramFree(m_program);
      m_program=INVALID_HANDLE;
     }
  }
//+------------------------------------------------------------------+
//| Shutdown                                                         |
//+------------------------------------------------------------------+
void COpenCL::Shutdown()
  {
   ContextClean();
//--- remove context
   if(m_context!=INVALID_HANDLE)
     {
      CLContextFree(m_context);
      m_context=INVALID_HANDLE;
     }
  }
//+------------------------------------------------------------------+
//| SetBuffersCount                                                  |
//+------------------------------------------------------------------+
bool COpenCL::SetBuffersCount(const int total_buffers)
  {
   if(total_buffers<=0)
      return(false);
//---
   m_buffers_total=total_buffers;
   if(ArraySize(m_buffers)<m_buffers_total)
      ArrayResize(m_buffers,m_buffers_total);
   for(int i=0; i<m_buffers_total; i++)
      m_buffers[i]=INVALID_HANDLE;
//---
   return(true);
  }
//+------------------------------------------------------------------+
//| SetKernelsCount                                                  |
//+------------------------------------------------------------------+
bool COpenCL::SetKernelsCount(const int total_kernels)
  {
   if(total_kernels<=0)
      return(false);
//---
   m_kernels_total=total_kernels;
   if(ArraySize(m_kernels)<m_kernels_total)
      ArrayResize(m_kernels,m_kernels_total);
   if(ArraySize(m_kernel_names)<m_kernels_total)
      ArrayResize(m_kernel_names,m_kernels_total);
//---
   for(int i=0; i<m_kernels_total; i++)
     {
      m_kernel_names[i]="";
      m_kernels[i]=INVALID_HANDLE;
     }
//---
   return(true);
  }
//+------------------------------------------------------------------+
//| KernelCreate                                                     |
//+------------------------------------------------------------------+
bool COpenCL::KernelCreate(const int kernel_index,const string kernel_name)
  {
   if(m_context==INVALID_HANDLE || m_program==INVALID_HANDLE)
      return(false);
//--- check parameters
   if(kernel_index<0 || kernel_index>=m_kernels_total)
      return(false);
//---
   int kernel_handle=m_kernels[kernel_index];
   if(kernel_handle==INVALID_HANDLE || m_kernel_names[kernel_index]!=kernel_name)
     {
      //--- create kernel
      if((kernel_handle=CLKernelCreate(m_program,kernel_name))==INVALID_HANDLE)
        {
         CLProgramFree(m_program);
         CLContextFree(m_context);
         Print("OpenCL kernel create failed. Error code=",GetLastError());
         return(false);
        }
      else
        {
         m_kernels[kernel_index]=kernel_handle;
         m_kernel_names[kernel_index]=kernel_name;
        }
     }
   return(true);
  }
//+------------------------------------------------------------------+
//| KernelFree                                                       |
//+------------------------------------------------------------------+
bool COpenCL::KernelFree(const int kernel_index)
  {
//--- check kernel index
   if(kernel_index<0 || kernel_index>=m_kernels_total)
      return(false);
   if(m_kernels[kernel_index]==INVALID_HANDLE)
      return(false);
//--- free kernel handle
   CLKernelFree(m_kernels[kernel_index]);
   m_kernels[kernel_index]=INVALID_HANDLE;
//---
   return(true);
  }
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
long COpenCL::GetDeviceInfo(const int prop)
  {
   if(m_context==INVALID_HANDLE)
      return(-1);

   uchar data[];
   uint  size;
   if(!CLGetDeviceInfo(m_context,prop,data,size))
      return(-1);
   if(size<4)
      return(-1);

   union res_data
     {
      uchar cdata[8];
      long  ldata;
     } res;
   if(size<=8)
     {
      ZeroMemory(res);
      ArrayCopy(res.cdata,data);
     }
   else
      ArrayCopy(res.cdata,data,0,8);

   return(res.ldata);
  }
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
long COpenCL::GetDeviceInfoInteger(ENUM_OPENCL_PROPERTY_INTEGER prop)
  {
   if(m_context==INVALID_HANDLE)
      return(-1);

   return(CLGetInfoInteger(m_context,prop));
  }
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
long COpenCL::GetKernelInfoInteger(const int kernel_index,ENUM_OPENCL_PROPERTY_INTEGER prop)
  {
   if(kernel_index<0 || kernel_index>=m_kernels_total)
      return(-1);

   return(CLGetInfoInteger(m_kernels[kernel_index],prop));
  }
//+------------------------------------------------------------------+
//| BufferCreate                                                     |
//+------------------------------------------------------------------+
bool COpenCL::BufferCreate(const int buffer_index,const uint size_in_bytes,const uint flags)
  {
//--- check parameters
   if(buffer_index<0 || buffer_index>=m_buffers_total)
      return(false);

   if(m_context==INVALID_HANDLE)
      return(false);
//---
   int buffer_handle=CLBufferCreate(m_context,size_in_bytes,flags);
   if(buffer_handle!=INVALID_HANDLE)
     {
      m_buffers[buffer_index]=buffer_handle;
      return(true);
     }
   else
      return(false);
  }
//+------------------------------------------------------------------+
//| BufferFree                                                       |
//+------------------------------------------------------------------+
bool COpenCL::BufferFree(const int buffer_index)
  {
//--- check buffer index
   if(buffer_index<0 || buffer_index>=m_buffers_total)
      return(false);
   if(m_buffers[buffer_index]==INVALID_HANDLE)
      return(false);
//--- free buffer handle
   CLBufferFree(m_buffers[buffer_index]);
   m_buffers[buffer_index]=INVALID_HANDLE;
//---
   return(true);
  }
//+------------------------------------------------------------------+
//| BufferFromArray                                                  |
//+------------------------------------------------------------------+
template<typename T>
bool COpenCL::BufferFromArray(const int buffer_index,T &data[],const uint data_array_offset,const uint data_array_count,const uint flags)
  {
//--- check parameters
   if(m_context==INVALID_HANDLE)
      return(false);
   if(buffer_index<0 || buffer_index>=m_buffers_total || data_array_count<=0)
      return(false);

//--- buffer does not exists, create it
   if(m_buffers[buffer_index]==INVALID_HANDLE)
     {
      uint size_in_bytes=data_array_count*sizeof(T);
      int buffer_handle=CLBufferCreate(m_context,size_in_bytes,flags);
      if(buffer_handle!=INVALID_HANDLE)
        {
         m_buffers[buffer_index]=buffer_handle;
        }
      else
         return(false);
     }
//--- write data to OpenCL buffer
   uint data_written=CLBufferWrite(m_buffers[buffer_index],data,0,data_array_offset,data_array_count);
   if(data_written!=data_array_count)
      return(false);
//---
   return(true);
  }
//+------------------------------------------------------------------+
//| BufferWriteFromMatrix                                            |
//+------------------------------------------------------------------+
template<typename T>
bool COpenCL::BufferFromMatrix(const int buffer_index,matrix<T> &data,const uint flags)
  {
//--- check parameters
   if(m_context==INVALID_HANDLE)
      return(false);
   if(buffer_index<0 || buffer_index>=m_buffers_total || data.Rows()==0 || data.Cols()==0)
      return(false);

   uint matrix_size=uint(data.Rows()*data.Cols());
//--- buffer does not exists, create it
   if(m_buffers[buffer_index]==INVALID_HANDLE)
     {
      uint size_in_bytes=matrix_size*sizeof(T);
      int buffer_handle=CLBufferCreate(m_context,size_in_bytes,flags);
      if(buffer_handle!=INVALID_HANDLE)
         m_buffers[buffer_index]=buffer_handle;
      else
         return(false);
     }
//--- write data to OpenCL buffer
   return(CLBufferWrite(m_buffers[buffer_index],0,data));
  }
//+------------------------------------------------------------------+
//| BufferWriteFromVector                                            |
//+------------------------------------------------------------------+
template<typename T>
bool COpenCL::BufferFromVector(const int buffer_index,vector<T> &data,const uint flags)
  {
//--- check parameters
   if(m_context==INVALID_HANDLE)
      return(false);
   if(buffer_index<0 || buffer_index>=m_buffers_total || data.Size()==0)
      return(false);

//--- buffer does not exists, create it
   if(m_buffers[buffer_index]==INVALID_HANDLE)
     {
      uint size_in_bytes=(uint)data.Size()*sizeof(T);
      int buffer_handle=CLBufferCreate(m_context,size_in_bytes,flags);
      if(buffer_handle!=INVALID_HANDLE)
         m_buffers[buffer_index]=buffer_handle;
      else
         return(false);
     }
//--- write data to OpenCL buffer
   return(CLBufferWrite(m_buffers[buffer_index],0,data));
  }
//+------------------------------------------------------------------+
//| BufferReadToMatrix                                               |
//+------------------------------------------------------------------+
template<typename T>
bool COpenCL::BufferToMatrix(const int buffer_index,matrix<T> &data,const ulong rows,const ulong cols)
  {
//--- check parameters
   if(buffer_index<0 || buffer_index>=m_buffers_total)
      return(false);
   if(m_buffers[buffer_index]==INVALID_HANDLE)
      return(false);
   if(m_context==INVALID_HANDLE || m_program==INVALID_HANDLE)
      return(false);
//--- read data from OpenCL buffer
   return(CLBufferRead(m_buffers[buffer_index],0,data,rows,cols));
  }
//+------------------------------------------------------------------+
//| BufferReadToVector                                               |
//+------------------------------------------------------------------+
template<typename T>
bool COpenCL::BufferToVector(const int buffer_index,vector<T> &data,const ulong size)
  {
//--- check parameters
   if(buffer_index<0 || buffer_index>=m_buffers_total)
      return(false);
   if(m_buffers[buffer_index]==INVALID_HANDLE)
      return(false);
   if(m_context==INVALID_HANDLE || m_program==INVALID_HANDLE)
      return(false);
//--- read data from OpenCL buffer
   return(CLBufferRead(m_buffers[buffer_index],0,data,size));
  }
//+------------------------------------------------------------------+
//| BufferRead                                                       |
//+------------------------------------------------------------------+
template<typename T>
bool COpenCL::BufferRead(const int buffer_index,T &data[],const uint cl_buffer_offset,const uint data_array_offset,const uint data_array_count)
  {
//--- check parameters
   if(buffer_index<0 || buffer_index>=m_buffers_total || data_array_count<=0)
      return(false);
   if(m_buffers[buffer_index]==INVALID_HANDLE)
      return(false);
   if(m_context==INVALID_HANDLE || m_program==INVALID_HANDLE)
      return(false);
//--- read data from OpenCL buffer
   uint data_read=CLBufferRead(m_buffers[buffer_index],data,cl_buffer_offset,data_array_offset,data_array_count);
   if(data_read!=data_array_count)
      return(false);
//---
   return(true);
  }
//+------------------------------------------------------------------+
//| BufferWrite                                                      |
//+------------------------------------------------------------------+
template<typename T>
bool COpenCL::BufferWrite(const int buffer_index,T &data[],const uint cl_buffer_offset,const uint data_array_offset,const uint data_array_count)
  {
//--- check parameters
   if(buffer_index<0 || buffer_index>=m_buffers_total || data_array_count<=0)
      return(false);
   if(m_buffers[buffer_index]==INVALID_HANDLE)
      return(false);
   if(m_context==INVALID_HANDLE)
      return(false);
//--- write data to OpenCL buffer
   uint data_written=CLBufferWrite(m_buffers[buffer_index],data,cl_buffer_offset,data_array_offset,data_array_count);
   if(data_written!=data_array_count)
      return(false);
//---
   return(true);
  }
//+------------------------------------------------------------------+
//| SetArgument                                                      |
//+------------------------------------------------------------------+
template<typename T>
bool COpenCL::SetArgument(const int kernel_index,const int arg_index,T value)
  {
   if(kernel_index<0 || kernel_index>=m_kernels_total)
      return(false);

   int kernel_handle=m_kernels[kernel_index];
   if(kernel_handle==INVALID_HANDLE)
      return(false);
//---
   return CLSetKernelArg(kernel_handle,arg_index,value);
  }
//+------------------------------------------------------------------+
//| SetArgumentBuffer                                                |
//+------------------------------------------------------------------+
bool COpenCL::SetArgumentBuffer(const int kernel_index,const int arg_index,const int buffer_index)
  {
   if(m_context==INVALID_HANDLE || m_program==INVALID_HANDLE)
      return(false);
   if(kernel_index<0 || kernel_index>=m_kernels_total)
      return(false);
   if(buffer_index<0 || buffer_index>=m_buffers_total)
      return(false);
   if(m_buffers[buffer_index]==INVALID_HANDLE)
      return(false);
//---
   return CLSetKernelArgMem(m_kernels[kernel_index],arg_index,m_buffers[buffer_index]);
  }
//+------------------------------------------------------------------+
//| SetArgumentLocalMemory                                           |
//+------------------------------------------------------------------+
bool COpenCL::SetArgumentLocalMemory(const int kernel_index,const int arg_index,const int local_memory_size)
  {
   if(m_context==INVALID_HANDLE || m_program==INVALID_HANDLE)
      return(false);
   if(kernel_index<0 || kernel_index>=m_kernels_total)
      return(false);
//--- check device local memory size 
   long device_local_memory_size=CLGetInfoInteger(m_context,CL_DEVICE_LOCAL_MEM_SIZE);
   if(local_memory_size>device_local_memory_size)
      return(false);
//---
   return CLSetKernelArgMemLocal(m_kernels[kernel_index],arg_index,local_memory_size);
  }
//+------------------------------------------------------------------+
//| Execute                                                          |
//+------------------------------------------------------------------+
bool COpenCL::Execute(const int kernel_index,const int work_dim,const uint &work_offset[],const uint &work_size[])
  {
   if(kernel_index<0 || kernel_index>=m_kernels_total)
      return(false);
   int kernel_handle=m_kernels[kernel_index];
   if(kernel_handle==INVALID_HANDLE)
      return(false);
//---
   return CLExecute(kernel_handle,work_dim,work_offset,work_size);
  }
//+------------------------------------------------------------------+
//| Execute                                                          |
//+------------------------------------------------------------------+
bool COpenCL::Execute(const int kernel_index,const int work_dim,const uint &work_offset[],const uint &work_size[],const uint &local_work_size[])
  {
   if(kernel_index<0 || kernel_index>=m_kernels_total)
      return(false);
//---
   return CLExecute(m_kernels[kernel_index],work_dim,work_offset,work_size,local_work_size);
  }
//+------------------------------------------------------------------+
