fractorium/Source/EmberCL/OpenCLWrapper.cpp
Person 66a597df39 06/04/2017
--User changes
 -Make all fonts be MS Shell Dlg 2. This will require reloading dark.qss if users are already using it.
 -Limit size of the left side of the palette editor.
 -Disable create from image buttons in the palette editor when working on a fixed palette.

--Bug fixes
 -The following variations were wrong: coshq, cothq.
 -During iteration, the color index could become nan if all xform color speeds were negative. This could lead to bad results on the GPU. Fix to check for nan. Minimal speed difference.

--Code changes
 -Make the following variations safer by using Zeps(): sinq, sinhq, secq, sechq, tanq, tanhq, cosq, coshq, cotq, cothq, cscq, cschq, estiq.
 -Always pass -cl-no-signed-zeros -cl-denorms-are-zero to kernel compiles for both single and double.
 -Flush all denormals to zero for all executable programs. This will likely lead to a speedup for badly behaving programs.
2017-06-04 17:37:29 -07:00

1058 lines
36 KiB
C++

#include "EmberCLPch.h"
#include "OpenCLWrapper.h"
namespace EmberCLns
{
/// <summary>
/// Constructor that sets everything to an uninitialized state.
/// No OpenCL setup is done here other than what's done in the
/// global OpenCLInfo object. The caller must explicitly do it.
/// </summary>
OpenCLWrapper::OpenCLWrapper()
{
//Pre-allocate some space to avoid temporary copying.
m_Programs.reserve(4);
m_Buffers.reserve(4);
m_Images.reserve(4);
m_GLImages.reserve(4);
}
/// <summary>
/// Initialize the specified platform and device.
/// This can be shared with OpenGL.
/// </summary>
/// <param name="platform">The index platform of the platform to use</param>
/// <param name="device">The index device of the device to use</param>
/// <param name="shared">True if shared with OpenGL, else false.</param>
/// <returns>True if success, else false.</returns>
bool OpenCLWrapper::Init(size_t platformIndex, size_t deviceIndex, bool shared)
{
cl_int err;
auto& platforms = m_Info->Platforms();
auto& devices = m_Info->Devices();
m_Init = false;
ClearErrorReport();
if (m_Info->Ok())
{
if (platformIndex < platforms.size() && platformIndex < devices.size())
{
cl::Context context;
if (m_Info->CreateContext(platforms[platformIndex], context, shared))//Platform index is within range, now do context.
{
if (deviceIndex < devices[platformIndex].size())//Context is ok, now do device.
{
auto q = cl::CommandQueue(context, devices[platformIndex][deviceIndex], 0, &err);//At least one GPU device is present, so create a command queue.
if (m_Info->CheckCL(err, "cl::CommandQueue()"))//Everything was successful so assign temporaries to members.
{
m_Platform = platforms[platformIndex];
m_Device = devices[platformIndex][deviceIndex];
m_Context = context;
m_Queue = q;
m_PlatformIndex = platformIndex;
m_DeviceIndex = deviceIndex;
m_DeviceVec.clear();
m_DeviceVec.push_back(m_Device);
m_LocalMemSize = size_t(m_Info->GetInfo<cl_ulong>(m_PlatformIndex, m_DeviceIndex, CL_DEVICE_LOCAL_MEM_SIZE));
m_GlobalMemSize = size_t(m_Info->GetInfo<cl_ulong>(m_PlatformIndex, m_DeviceIndex, CL_DEVICE_GLOBAL_MEM_SIZE));
m_MaxAllocSize = size_t(m_Info->GetInfo<cl_ulong>(m_PlatformIndex, m_DeviceIndex, CL_DEVICE_MAX_MEM_ALLOC_SIZE));
m_Shared = shared;
m_Init = true;//Command queue is ok, it's now ok to begin building and running programs.
}
}
}
}
}
return m_Init;
}
/// <summary>
/// Compile and add the program, using the specified entry point.
/// If a program with the same name already exists then it will be replaced.
/// </summary>
/// <param name="name">The name of the program</param>
/// <param name="program">The program source</param>
/// <param name="entryPoint">The name of the entry point kernel function in the program</param>
/// <returns>True if success, else false.</returns>
bool OpenCLWrapper::AddProgram(const string& name, const string& program, const string& entryPoint, bool doublePrecision)
{
Spk spk;
if (CreateSPK(name, program, entryPoint, spk, doublePrecision))
{
for (auto& p : m_Programs)
{
if (name == p.m_Name)
{
p = spk;
return true;
}
}
//Nothing was found, so add.
m_Programs.push_back(spk);
return true;
}
return false;
}
/// <summary>
/// Clear the programs.
/// </summary>
void OpenCLWrapper::ClearPrograms()
{
m_Programs.clear();
}
/// <summary>
/// Add a buffer with the specified size and name.
/// Three possible actions to take:
/// Buffer didn't exist, so create and add.
/// Buffer existed, but was a different size, replace.
/// Buffer existed with the same size, do nothing.
/// </summary>
/// <param name="name">The name of the buffer</param>
/// <param name="size">The size in bytes of the buffer</param>
/// <param name="flags">The buffer flags. Default: CL_MEM_READ_WRITE.</param>
/// <returns>True if success, else false.</returns>
bool OpenCLWrapper::AddBuffer(const string& name, size_t size, cl_mem_flags flags)
{
cl_int err;
if (m_Init)
{
int bufferIndex = FindBufferIndex(name);
if (bufferIndex == -1)//If the buffer didn't exist, create and add.
{
cl::Buffer buff(m_Context, flags, size, nullptr, &err);
if (!m_Info->CheckCL(err, "cl::Buffer()"))
return false;
NamedBuffer nb(buff, name);
m_Buffers.push_back(nb);
}
else if (GetBufferSize(bufferIndex) != size)//If it did exist, only create and add if the sizes were different.
{
m_Buffers[bufferIndex] = NamedBuffer(cl::Buffer(m_Context, flags, size_t(0), nullptr, &err), "emptybuffer");//First clear out the original so the two don't exist in memory at once.
cl::Buffer buff(m_Context, flags, size, nullptr, &err);//Create the new buffer.
if (!m_Info->CheckCL(err, "cl::Buffer()"))
return false;
NamedBuffer nb(buff, name);//Make a named buffer out of the new buffer.
m_Buffers[bufferIndex] = nb;//Finally, assign.
}
//If the buffer existed and the sizes were the same, take no action.
return true;//Either operation succeeded.
}
return false;
}
/// <summary>
/// Add a host side buffer with the specified name, size and host data pointer.
/// Three possible actions to take:
/// Buffer didn't exist, so create and add.
/// Buffer existed, but was a different size or pointer, replace.
/// Buffer existed with the same size and pointer, do nothing.
/// </summary>
/// <param name="name">The name of the buffer</param>
/// <param name="size">The size in bytes of the buffer</param>
/// <param name="data">The pointer to the beginning of the host side data.</param>
/// <returns>True if success, else false.</returns>
bool OpenCLWrapper::AddHostBuffer(const string& name, size_t size, void* data)
{
cl_int err;
if (m_Init)
{
int bufferIndex = FindBufferIndex(name);
if (bufferIndex == -1)//If the buffer didn't exist, create and add.
{
cl::Buffer buff(m_Context, CL_MEM_USE_HOST_PTR, size, data, &err);
if (!m_Info->CheckCL(err, "cl::Buffer()"))
return false;
NamedBuffer nb(buff, name);
m_Buffers.push_back(nb);
}
else
{
if (GetBufferSize(bufferIndex) != size ||//If it did exist, only create and add if the sizes...
data != m_Buffers[bufferIndex].m_Buffer.getInfo<CL_MEM_HOST_PTR>(nullptr))//...or addresses were different.
{
m_Buffers[bufferIndex] = NamedBuffer(cl::Buffer(m_Context, CL_MEM_USE_HOST_PTR, size_t(0), data, &err), "emptybuffer");//First clear out the original so the two don't exist in memory at once.
cl::Buffer buff(m_Context, CL_MEM_USE_HOST_PTR, size, data, &err);//Create the new buffer.
if (!m_Info->CheckCL(err, "cl::Buffer()"))
return false;
NamedBuffer nb(buff, name);//Make a named buffer out of the new buffer.
m_Buffers[bufferIndex] = nb;//Finally, assign.
}
}
//If the buffer existed and the sizes and pointers were the same, take no action.
return true;//Either operation succeeded.
}
return false;
}
/// <summary>
/// Add and/or write a buffer of data with the specified name to the list of buffers.
/// Three possible actions to take:
/// Buffer didn't exist, so create and add.
/// Buffer existed, but was a different size. Replace.
/// Buffer existed with the same size, copy data.
/// </summary>
/// <param name="name">The name of the buffer</param>
/// <param name="data">A pointer to the buffer</param>
/// <param name="size">The size in bytes of the buffer</param>
/// <param name="flags">The buffer flags. Default: CL_MEM_READ_WRITE.</param>
/// <returns>True if success, else false.</returns>
bool OpenCLWrapper::AddAndWriteBuffer(const string& name, void* data, size_t size, cl_mem_flags flags)
{
bool b = false;
if (AddBuffer(name, size, flags))
b = WriteBuffer(name, data, size);
return b;
}
/// <summary>
/// Write data to an existing buffer with the specified name.
/// </summary>
/// <param name="name">The name of the buffer</param>
/// <param name="data">A pointer to the buffer</param>
/// <param name="size">The size in bytes of the buffer</param>
/// <returns>True if success, else false.</returns>
bool OpenCLWrapper::WriteBuffer(const string& name, void* data, size_t size)
{
int bufferIndex = FindBufferIndex(name);
return bufferIndex != -1 ? WriteBuffer(bufferIndex, data, size) : false;
}
/// <summary>
/// Write data to an existing buffer at the specified index.
/// </summary>
/// <param name="bufferIndex">The index of the buffer</param>
/// <param name="data">A pointer to the buffer</param>
/// <param name="size">The size in bytes of the buffer</param>
/// <returns>True if success, else false.</returns>
bool OpenCLWrapper::WriteBuffer(size_t bufferIndex, void* data, size_t size)
{
if (m_Init && (bufferIndex < m_Buffers.size()) && (GetBufferSize(bufferIndex) == size))
{
cl::Event e;
cl_int err = m_Queue.enqueueWriteBuffer(m_Buffers[bufferIndex].m_Buffer, CL_TRUE, 0, size, data, nullptr, &e);
e.wait();
m_Queue.finish();
if (m_Info->CheckCL(err, "cl::CommandQueue::enqueueWriteBuffer()"))
return true;
}
return false;
}
/// <summary>
/// Read data from an existing buffer with the specified name.
/// </summary>
/// <param name="name">The name of the buffer</param>
/// <param name="data">A pointer to a buffer to copy the data to</param>
/// <param name="size">The size in bytes of the buffer</param>
/// <returns>True if success, else false.</returns>
bool OpenCLWrapper::ReadBuffer(const string& name, void* data, size_t size)
{
int bufferIndex = FindBufferIndex(name);
return bufferIndex != -1 ? ReadBuffer(bufferIndex, data, size) : false;
}
/// <summary>
/// Read data from an existing buffer at the specified index.
/// </summary>
/// <param name="bufferIndex">The index of the buffer</param>
/// <param name="data">A pointer to a buffer to copy the data to</param>
/// <param name="size">The size in bytes of the buffer</param>
/// <returns>True if success, else false.</returns>
bool OpenCLWrapper::ReadBuffer(size_t bufferIndex, void* data, size_t size)
{
if (m_Init && (bufferIndex < m_Buffers.size()) && (GetBufferSize(bufferIndex) == size))
{
cl::Event e;
cl_int err = m_Queue.enqueueReadBuffer(m_Buffers[bufferIndex].m_Buffer, CL_TRUE, 0, size, data, nullptr, &e);
e.wait();
m_Queue.finish();
if (m_Info->CheckCL(err, "cl::CommandQueue::enqueueReadBuffer()"))
return true;
}
return false;
}
/// <summary>
/// Find the index of the buffer with the specified name.
/// </summary>
/// <param name="name">The name of the buffer to search for</param>
/// <returns>The index if found, else -1.</returns>
int OpenCLWrapper::FindBufferIndex(const string& name)
{
for (size_t i = 0; i < m_Buffers.size(); i++)
if (m_Buffers[i].m_Name == name)
return int(i);
return -1;
}
/// <summary>
/// Get the size of the buffer with the specified name.
/// </summary>
/// <param name="name">The name of the buffer to search for</param>
/// <returns>The size of the buffer if found, else 0.</returns>
size_t OpenCLWrapper::GetBufferSize(const string& name)
{
int bufferIndex = FindBufferIndex(name);
return bufferIndex != -1 ? GetBufferSize(bufferIndex) : 0;
}
/// <summary>
/// Get the size of the buffer at the specified index.
/// </summary>
/// <param name="name">The index of the buffer to get the size of</param>
/// <returns>The size of the buffer if found, else 0.</returns>
size_t OpenCLWrapper::GetBufferSize(size_t bufferIndex)
{
if (m_Init && (bufferIndex < m_Buffers.size()))
return m_Buffers[bufferIndex].m_Buffer.getInfo<CL_MEM_SIZE>(nullptr);
return 0;
}
/// <summary>
/// Clear all buffers.
/// </summary>
void OpenCLWrapper::ClearBuffers()
{
m_Buffers.clear();
}
/// <summary>
/// Add and/or write a new 2D image.
/// Three possible actions to take:
/// Image didn't exist, so create and add.
/// Image existed, but was a different size. Replace.
/// Image existed with the same size, copy data.
/// </summary>
/// <param name="name">The name of the image to add/replace</param>
/// <param name="flags">The memory flags</param>
/// <param name="format">The image format</param>
/// <param name="width">The width in pixels of the image</param>
/// <param name="height">The height in pixels of the image</param>
/// <param name="row_pitch">The row pitch (usually zero)</param>
/// <param name="data">The image data. Default: NULL.</param>
/// <param name="shared">True if shared with an OpenGL texture, else false. Default: false.</param>
/// <param name="texName">The texture ID of the shared OpenGL texture if shared. Default: 0.</param>
/// <returns>True if success, else false.</returns>
bool OpenCLWrapper::AddAndWriteImage(const string& name, cl_mem_flags flags, const cl::ImageFormat& format, ::size_t width, ::size_t height, ::size_t row_pitch, void* data, bool shared, GLuint texName)
{
cl_int err;
if (m_Init)
{
int imageIndex = FindImageIndex(name, shared);
if (imageIndex == -1)//If the image didn't exist, create and add.
{
if (shared)
{
//::wglMakeCurrent(wglGetCurrentDC(), wglGetCurrentContext());
cl::ImageGL imageGL(m_Context, flags, GL_TEXTURE_2D, 0, texName, &err);
NamedImage2DGL namedImageGL(imageGL, name);
if (m_Info->CheckCL(err, "cl::ImageGL()"))
{
m_GLImages.push_back(namedImageGL);
if (data)
return WriteImage2D(m_GLImages.size() - 1, true, width, height, row_pitch, data);//OpenGL images/textures require a separate write.
else
return true;
}
}
else
{
NamedImage2D namedImage(cl::Image2D(m_Context, flags, format, width, height, row_pitch, data, &err), name);
if (m_Info->CheckCL(err, "cl::Image2D()"))
{
m_Images.push_back(namedImage);
return true;
}
}
}
else//It did exist, so create new if sizes are different. Write if data is not NULL.
{
if (shared)
{
cl::ImageGL imageGL = m_GLImages[imageIndex].m_Image;
if (!CompareImageParams(imageGL, flags, format, width, height, row_pitch))
{
NamedImage2DGL namedImageGL(cl::ImageGL(m_Context, flags, GL_TEXTURE_2D, 0, texName, &err), name);//Sizes are different, so create new.
if (m_Info->CheckCL(err, "cl::ImageGL()"))
{
m_GLImages[imageIndex] = namedImageGL;
}
else
return false;
}
//Write data to new image since OpenGL images/textures require a separate write, must match new size.
if (data)
return WriteImage2D(imageIndex, true, width, height, row_pitch, data);
else
return true;
}
else
{
if (!CompareImageParams(m_Images[imageIndex].m_Image, flags, format, width, height, row_pitch))
{
m_Images[imageIndex] = NamedImage2D();//First clear out the original so the two don't exist in memory at once.
NamedImage2D namedImage(cl::Image2D(m_Context, flags, format, width, height, row_pitch, data, &err), name);
if (m_Info->CheckCL(err, "cl::Image2D()"))
{
m_Images[imageIndex] = namedImage;
return true;
}
}
else if (data)
return WriteImage2D(imageIndex, false, width, height, row_pitch, data);
else//Strange case: images were same dimensions but no data was passed in, so do nothing.
return true;
}
}
}
return false;
}
/// <summary>
/// Write data to an existing 2D image at the specified index.
/// </summary>
/// <param name="index">The index of the image</param>
/// <param name="shared">True if shared with an OpenGL texture, else false.</param>
/// <param name="width">The width in pixels of the image</param>
/// <param name="height">The height in pixels of the image</param>
/// <param name="row_pitch">The row pitch (usually zero)</param>
/// <param name="data">The image data</param>
/// <returns>True if success, else false.</returns>
bool OpenCLWrapper::WriteImage2D(size_t index, bool shared, ::size_t width, ::size_t height, ::size_t row_pitch, void* data)
{
if (m_Init)
{
cl_int err;
cl::Event e;
cl::size_t<3> origin, region;
origin[0] = 0;
origin[1] = 0;
origin[2] = 0;
region[0] = width;
region[1] = height;
region[2] = 1;
if (shared && index < m_GLImages.size())
{
cl::ImageGL imageGL = m_GLImages[index].m_Image;
if (EnqueueAcquireGLObjects(imageGL))
{
err = m_Queue.enqueueWriteImage(imageGL, CL_TRUE, origin, region, row_pitch, 0, data, nullptr, &e);
e.wait();
m_Queue.finish();
bool b = EnqueueReleaseGLObjects(imageGL);
return m_Info->CheckCL(err, "cl::enqueueWriteImage()") && b;
}
}
else if (!shared && index < m_Images.size())
{
err = m_Queue.enqueueWriteImage(m_Images[index].m_Image, CL_TRUE, origin, region, row_pitch, 0, data, nullptr, &e);
e.wait();
m_Queue.finish();
return m_Info->CheckCL(err, "cl::enqueueWriteImage()");
}
}
return false;
}
/// <summary>
/// Read data from an existing 2D image with the specified name.
/// </summary>
/// <param name="name">The name of the image</param>
/// <param name="width">The width in pixels of the image</param>
/// <param name="height">The height in pixels of the image</param>
/// <param name="row_pitch">The row pitch (usually zero)</param>
/// <param name="shared">True if shared with an OpenGL texture, else false.</param>
/// <param name="data">A pointer to a buffer to copy the data to</param>
/// <returns>True if success, else false.</returns>
bool OpenCLWrapper::ReadImage(const string& name, ::size_t width, ::size_t height, ::size_t row_pitch, bool shared, void* data)
{
if (m_Init)
{
int imageIndex = FindImageIndex(name, shared);
if (imageIndex != -1)
return ReadImage(imageIndex, width, height, row_pitch, shared, data);
}
return false;
}
/// <summary>
/// Read data from an existing 2D image at the specified index.
/// </summary>
/// <param name="name">The name of the image</param>
/// <param name="width">The width in pixels of the image</param>
/// <param name="height">The height in pixels of the image</param>
/// <param name="row_pitch">The row pitch (usually zero)</param>
/// <param name="shared">True if shared with an OpenGL texture, else false.</param>
/// <param name="data">A pointer to a buffer to copy the data to</param>
/// <returns>True if success, else false.</returns>
bool OpenCLWrapper::ReadImage(size_t imageIndex, ::size_t width, ::size_t height, ::size_t row_pitch, bool shared, void* data)
{
if (m_Init)
{
cl_int err;
cl::Event e;
cl::size_t<3> origin, region;
origin[0] = 0;
origin[1] = 0;
origin[2] = 0;
region[0] = width;
region[1] = height;
region[2] = 1;
if (shared && imageIndex < m_GLImages.size())
{
cl::ImageGL imageGL = m_GLImages[imageIndex].m_Image;
if (EnqueueAcquireGLObjects(imageGL))
{
err = m_Queue.enqueueReadImage(m_GLImages[imageIndex].m_Image, true, origin, region, row_pitch, 0, data);
bool b = EnqueueReleaseGLObjects(m_GLImages[imageIndex].m_Image);
return m_Info->CheckCL(err, "cl::enqueueReadImage()") && b;
}
}
else if (!shared && imageIndex < m_Images.size())
{
err = m_Queue.enqueueReadImage(m_Images[imageIndex].m_Image, true, origin, region, row_pitch, 0, data);
return m_Info->CheckCL(err, "cl::enqueueReadImage()");
}
}
return false;
}
/// <summary>
/// Find the index of the 2D image with the specified name.
/// </summary>
/// <param name="name">The name of the image to search for</param>
/// <param name="shared">True if shared with an OpenGL texture, else false.</param>
/// <returns>The index if found, else -1.</returns>
int OpenCLWrapper::FindImageIndex(const string& name, bool shared)
{
if (shared)
{
for (size_t i = 0; i < m_GLImages.size(); i++)
if (m_GLImages[i].m_Name == name)
return int(i);
}
else
{
for (size_t i = 0; i < m_Images.size(); i++)
if (m_Images[i].m_Name == name)
return int(i);
}
return -1;
}
/// <summary>
/// Get the size of the 2D image with the specified name.
/// </summary>
/// <param name="name">The name of the image to search for</param>
/// <param name="shared">True if shared with an OpenGL texture, else false.</param>
/// <returns>The size of the 2D image if found, else 0.</returns>
size_t OpenCLWrapper::GetImageSize(const string& name, bool shared)
{
int imageIndex = FindImageIndex(name, shared);
return GetImageSize(imageIndex, shared);
}
/// <summary>
/// Get the size of the 2D image at the specified index.
/// </summary>
/// <param name="imageIndex">Index of the image to search for</param>
/// <param name="shared">True if shared with an OpenGL texture, else false.</param>
/// <returns>The size of the 2D image if found, else 0.</returns>
size_t OpenCLWrapper::GetImageSize(size_t imageIndex, bool shared)
{
size_t size = 0;
if (m_Init)
{
if (shared && imageIndex < m_GLImages.size())
{
vector<cl::Memory> images;
images.push_back(m_GLImages[imageIndex].m_Image);
cl::ImageGL image = m_GLImages[imageIndex].m_Image;
if (EnqueueAcquireGLObjects(&images))
size = image.getImageInfo<CL_IMAGE_WIDTH>(nullptr) * image.getImageInfo<CL_IMAGE_HEIGHT>(nullptr) * image.getImageInfo<CL_IMAGE_ELEMENT_SIZE>(nullptr);//Should pitch be checked here?
EnqueueReleaseGLObjects(&images);
}
else if (!shared && imageIndex < m_Images.size())
{
cl::Image2D image = m_Images[imageIndex].m_Image;
size = image.getImageInfo<CL_IMAGE_WIDTH>(nullptr) * image.getImageInfo<CL_IMAGE_HEIGHT>(nullptr) * image.getImageInfo<CL_IMAGE_ELEMENT_SIZE>(nullptr);//Should pitch be checked here?
}
}
return size;
}
/// <summary>
/// Compare the passed in image with the specified parameters.
/// </summary>
/// <param name="image">The image to compare</param>
/// <param name="flags">The memory flags to compare (ommitted)</param>
/// <param name="format">The format to compare</param>
/// <param name="width">The width to compare</param>
/// <param name="height">The height to compare</param>
/// <param name="row_pitch">The row_pitch to compare (omitted)</param>
/// <returns>True if all parameters matched, else false.</returns>
bool OpenCLWrapper::CompareImageParams(cl::Image& image, cl_mem_flags flags, const cl::ImageFormat& format, ::size_t width, ::size_t height, ::size_t row_pitch)
{
cl_image_format tempFormat = image.getImageInfo<CL_IMAGE_FORMAT>(nullptr);
return (/*image.getImageInfo<CL_MEM_FLAGS>() == flags &&*/
tempFormat.image_channel_data_type == format.image_channel_data_type &&
tempFormat.image_channel_order == format.image_channel_order &&
image.getImageInfo<CL_IMAGE_WIDTH>(nullptr) == width &&
image.getImageInfo<CL_IMAGE_HEIGHT>(nullptr) == height/* &&
image.getImageInfo<CL_IMAGE_ROW_PITCH>() == row_pitch*/);//Pitch will be (width * bytes per pixel) + padding.
}
/// <summary>
/// Clear all images.
/// </summary>
/// <param name="shared">True to clear shared images, else clear regular images.</param>
void OpenCLWrapper::ClearImages(bool shared)
{
if (shared)
m_GLImages.clear();
else
m_Images.clear();
}
/// <summary>
/// Create a 2D image and store in the image passed in.
/// </summary>
/// <param name="image2D">The 2D image to store the newly created image in</param>
/// <param name="flags">The memory flags to use</param>
/// <param name="format">The format to use</param>
/// <param name="width">The width in pixels of the image</param>
/// <param name="height">The height in pixels of the image</param>
/// <param name="row_pitch">The row pitch (usually zero)</param>
/// <param name="data">The image data. Default: NULL.</param>
/// <returns>True if success, else false.</returns>
bool OpenCLWrapper::CreateImage2D(cl::Image2D& image2D, cl_mem_flags flags, cl::ImageFormat format, ::size_t width, ::size_t height, ::size_t row_pitch, void* data)
{
if (m_Init)
{
cl_int err;
image2D = cl::Image2D(m_Context,
flags,
format,
width,
height,
row_pitch,
data,
&err);
return m_Info->CheckCL(err, "cl::Image2D()");
}
return false;
}
/// <summary>
/// Create a 2D image shared with an OpenGL texture and store in the image passed in.
/// </summary>
/// <param name="image2DGL">The 2D image to store the newly created image in</param>
/// <param name="flags">The memory flags to use</param>
/// <param name="target">The target</param>
/// <param name="miplevel">The mip map level</param>
/// <param name="texobj">The texture ID of the shared OpenGL texture</param>
/// <returns>True if success, else false.</returns>
bool OpenCLWrapper::CreateImage2DGL(cl::ImageGL& image2DGL, cl_mem_flags flags, GLenum target, GLint miplevel, GLuint texobj)
{
if (m_Init)
{
cl_int err;
image2DGL = cl::ImageGL(m_Context,
flags,
target,
miplevel,
texobj,
&err);
return m_Info->CheckCL(err, "cl::ImageGL()");
}
return false;
}
/// <summary>
/// Acquire the shared 2D image with the specified name.
/// </summary>
/// <param name="name">The name of the image to acquire</param>
/// <returns>True if success, else false.</returns>
bool OpenCLWrapper::EnqueueAcquireGLObjects(const string& name)
{
int index = FindImageIndex(name, true);
if (index != -1)
return EnqueueAcquireGLObjects(m_GLImages[index].m_Image);
return false;
}
/// <summary>
/// Acquire the shared 2D image.
/// </summary>
/// <param name="image">The image to acquire</param>
/// <returns>True if success, else false.</returns>
bool OpenCLWrapper::EnqueueAcquireGLObjects(cl::ImageGL& image)
{
if (m_Init && m_Shared)
{
vector<cl::Memory> images;
images.push_back(image);
cl_int err = m_Queue.enqueueAcquireGLObjects(&images);
m_Queue.finish();
return m_Info->CheckCL(err, "cl::CommandQueue::enqueueAcquireGLObjects()");
}
return false;
}
/// <summary>
/// Reelease the shared 2D image with the specified name.
/// </summary>
/// <param name="name">The name of the image to release</param>
/// <returns>True if success, else false.</returns>
bool OpenCLWrapper::EnqueueReleaseGLObjects(const string& name)
{
int index = FindImageIndex(name, true);
if (index != -1)
return EnqueueReleaseGLObjects(m_GLImages[index].m_Image);
return false;
}
/// <summary>
/// Release the shared 2D image.
/// </summary>
/// <param name="image">The image to release</param>
/// <returns>True if success, else false.</returns>
bool OpenCLWrapper::EnqueueReleaseGLObjects(cl::ImageGL& image)
{
if (m_Init && m_Shared)
{
vector<cl::Memory> images;
images.push_back(image);
cl_int err = m_Queue.enqueueReleaseGLObjects(&images);
m_Queue.finish();
return m_Info->CheckCL(err, "cl::CommandQueue::enqueueReleaseGLObjects()");
}
return false;
}
/// <summary>
/// Acquire a vector of shared OpenGL memory objects.
/// </summary>
/// <param name="memObjects">The memory objects to acquire</param>
/// <returns>True if success, else false.</returns>
bool OpenCLWrapper::EnqueueAcquireGLObjects(const VECTOR_CLASS<cl::Memory>* memObjects)
{
if (m_Init && m_Shared)
{
cl_int err = m_Queue.enqueueAcquireGLObjects(memObjects);
m_Queue.finish();
return m_Info->CheckCL(err, "cl::CommandQueue::enqueueAcquireGLObjects()");
}
return false;
}
/// <summary>
/// Release a vector of shared OpenGL memory objects.
/// </summary>
/// <param name="memObjects">The memory objects to release</param>
/// <returns>True if success, else false.</returns>
bool OpenCLWrapper::EnqueueReleaseGLObjects(const VECTOR_CLASS<cl::Memory>* memObjects)
{
if (m_Init && m_Shared)
{
cl_int err = m_Queue.enqueueReleaseGLObjects(memObjects);
m_Queue.finish();
return m_Info->CheckCL(err, "cl::CommandQueue::enqueueReleaseGLObjects()");
}
return false;
}
/// <summary>
/// Create a texture sampler.
/// </summary>
/// <param name="sampler">The sampler to store the newly created sampler in</param>
/// <param name="normalizedCoords">True to use normalized coordinates, else don't.</param>
/// <param name="addressingMode">The addressing mode to use</param>
/// <param name="filterMode">The filter mode to use</param>
/// <returns>True if success, else false.</returns>
bool OpenCLWrapper::CreateSampler(cl::Sampler& sampler, cl_bool normalizedCoords, cl_addressing_mode addressingMode, cl_filter_mode filterMode)
{
cl_int err;
sampler = cl::Sampler(m_Context,
normalizedCoords,
addressingMode,
filterMode,
&err);
return m_Info->CheckCL(err, "cl::Sampler()");
}
/// <summary>
/// Set the argument at the specified index for the kernel at the specified index to be
/// the buffer with the specified name.
/// </summary>
/// <param name="kernelIndex">Index of the kernel</param>
/// <param name="argIndex">Index of the argument</param>
/// <param name="name">The name of the buffer</param>
/// <returns>True if success, else false.</returns>
bool OpenCLWrapper::SetBufferArg(size_t kernelIndex, cl_uint argIndex, const string& name)
{
int bufferIndex = OpenCLWrapper::FindBufferIndex(name);
return bufferIndex != -1 ? SetBufferArg(kernelIndex, argIndex, bufferIndex) : false;
}
/// <summary>
/// Set the argument at the specified index for the kernel at the specified index to be
/// the buffer at the specified index.
/// </summary>
/// <param name="kernelIndex">Index of the kernel</param>
/// <param name="argIndex">Index of the argument</param>
/// <param name="bufferIndex">Index of the buffer</param>
/// <returns>True if success, else false.</returns>
bool OpenCLWrapper::SetBufferArg(size_t kernelIndex, cl_uint argIndex, size_t bufferIndex)
{
if (m_Init && bufferIndex < m_Buffers.size())
return SetArg<cl::Buffer>(kernelIndex, argIndex, m_Buffers[bufferIndex].m_Buffer);
return false;
}
/// <summary>
/// Set the argument at the specified index for the kernel at the specified index to be
/// the 2D image with the specified name.
/// </summary>
/// <param name="kernelIndex">Index of the kernel</param>
/// <param name="argIndex">Index of the argument</param>
/// <param name="shared">True if shared with an OpenGL texture, else false</param>
/// <param name="name">The name of the 2D image</param>
/// <returns>True if success, else false.</returns>
bool OpenCLWrapper::SetImageArg(size_t kernelIndex, cl_uint argIndex, bool shared, const string& name)
{
if (m_Init)
{
int imageIndex = FindImageIndex(name, shared);
return SetImageArg(kernelIndex, argIndex, shared, imageIndex);
}
return false;
}
/// <summary>
/// Set the argument at the specified index for the kernel at the specified index to be
/// the 2D image at the specified index.
/// </summary>
/// <param name="kernelIndex">Index of the kernel</param>
/// <param name="argIndex">Index of the argument</param>
/// <param name="shared">True if shared with an OpenGL texture, else false</param>
/// <param name="imageIndex">Index of the 2D image</param>
/// <returns>True if success, else false.</returns>
bool OpenCLWrapper::SetImageArg(size_t kernelIndex, cl_uint argIndex, bool shared, size_t imageIndex)
{
cl_int err;
if (m_Init)
{
if (shared && imageIndex < m_GLImages.size())
{
err = m_Programs[kernelIndex].m_Kernel.setArg(argIndex, m_GLImages[imageIndex].m_Image);
return m_Info->CheckCL(err, "cl::Kernel::setArg()");
}
else if (!shared && imageIndex < m_Images.size())
{
err = m_Programs[kernelIndex].m_Kernel.setArg(argIndex, m_Images[imageIndex].m_Image);
return m_Info->CheckCL(err, "cl::Kernel::setArg()");
}
}
return false;
}
/// <summary>
/// Find the index of the kernel with the specified name.
/// </summary>
/// <param name="name">The name of the kernel to search for</param>
/// <returns>The index if found, else -1.</returns>
int OpenCLWrapper::FindKernelIndex(const string& name)
{
for (size_t i = 0; i < m_Programs.size(); i++)
if (m_Programs[i].m_Name == name)
return int(i);
return -1;
}
/// <summary>
/// Run the kernel at the specified index, using the specified grid and block dimensions.
/// </summary>
/// <param name="kernelIndex">Index of the kernel to run</param>
/// <param name="totalGridWidth">Total width of the grid</param>
/// <param name="totalGridHeight">Total height of the grid</param>
/// <param name="totalGridDepth">The total depth grid</param>
/// <param name="blockWidth">Width of each block</param>
/// <param name="blockHeight">Height of each block</param>
/// <param name="blockDepth">Depth of each block</param>
/// <returns>True if success, else false.</returns>
bool OpenCLWrapper::RunKernel(size_t kernelIndex, size_t totalGridWidth, size_t totalGridHeight, size_t totalGridDepth,
size_t blockWidth, size_t blockHeight, size_t blockDepth)
{
if (m_Init && kernelIndex < m_Programs.size())
{
cl::Event e;
cl_int err = m_Queue.enqueueNDRangeKernel(m_Programs[kernelIndex].m_Kernel,
cl::NullRange,
cl::NDRange(totalGridWidth, totalGridHeight, totalGridDepth),
cl::NDRange(blockWidth, blockHeight, blockDepth),
nullptr,
&e);
e.wait();
m_Queue.finish();
return m_Info->CheckCL(err, "cl::CommandQueue::enqueueNDRangeKernel()");
}
return false;
}
/// <summary>
/// OpenCL properties, getters only.
/// </summary>
bool OpenCLWrapper::Ok() const { return m_Init; }
bool OpenCLWrapper::Shared() const { return m_Shared; }
const cl::Context& OpenCLWrapper::Context() const { return m_Context; }
size_t OpenCLWrapper::PlatformIndex() const { return m_PlatformIndex; }
size_t OpenCLWrapper::DeviceIndex() const { return m_DeviceIndex; }
size_t OpenCLWrapper::TotalDeviceIndex() const { return m_Info->TotalDeviceIndex(m_PlatformIndex, m_DeviceIndex); }
const string& OpenCLWrapper::DeviceName() const { return m_Info->DeviceName(m_PlatformIndex, m_DeviceIndex); }
size_t OpenCLWrapper::LocalMemSize() const { return m_LocalMemSize; }
size_t OpenCLWrapper::GlobalMemSize() const { return m_GlobalMemSize; }
size_t OpenCLWrapper::MaxAllocSize() const { return m_MaxAllocSize; }
/// <summary>
/// Make even grid dimensions.
/// The size of the blocks in terms of threads must divide evenly into the total number of threads in the grid.
/// In the case of a remainder, expand the width and height of the grid to the next highest evenly divisible value.
/// Ex:
/// blockW = 5, blockH = 5
/// gridW = 18, gridH = 27
///
/// To make these even:
/// gridW = 20, gridH = 30
/// </summary>
/// <param name="blockW">The width of each block in terms of threads.</param>
/// <param name="blockH">The height of each block in terms of threads.</param>
/// <param name="gridW">The width of the entire grid in terms of threads.</param>
/// <param name="gridH">The width of the entire grid in terms of threads.</param>
void OpenCLWrapper::MakeEvenGridDims(size_t blockW, size_t blockH, size_t& gridW, size_t& gridH)
{
if (gridW % blockW != 0)
gridW += (blockW - (gridW % blockW));
if (gridH % blockH != 0)
gridH += (blockH - (gridH % blockH));
}
/// <summary>
/// Create an Spk object created by compiling the program arguments passed in.
/// </summary>
/// <param name="name">The name of the program</param>
/// <param name="program">The source of the program</param>
/// <param name="entryPoint">The name of the entry point kernel function in the program</param>
/// <param name="spk">The Spk object to store the resulting compiled program in</param>
/// <returns>True if success, else false.</returns>
bool OpenCLWrapper::CreateSPK(const string& name, const string& program, const string& entryPoint, Spk& spk, bool doublePrecision)
{
if (m_Init)
{
cl_int err;
spk.m_Name = name;
spk.m_Source = cl::Program::Sources(1, std::make_pair(program.c_str(), program.length() + 1));
spk.m_Program = cl::Program(m_Context, spk.m_Source);
if (doublePrecision)
err = spk.m_Program.build(m_DeviceVec, "-cl-mad-enable -cl-no-signed-zeros -cl-denorms-are-zero");//Tinker with other options later.
else
err = spk.m_Program.build(m_DeviceVec, "-cl-mad-enable -cl-no-signed-zeros -cl-single-precision-constant -cl-denorms-are-zero");
//err = spk.m_Program.build(m_DeviceVec, "-cl-single-precision-constant");
//err = spk.m_Program.build(m_DeviceVec, "-cl-mad-enable -cl-single-precision-constant");
//err = spk.m_Program.build(m_DeviceVec, "-cl-mad-enable -cl-no-signed-zeros -cl-fast-relaxed-math -cl-single-precision-constant");//This can cause some rounding.
//err = spk.m_Program.build(m_DeviceVec, "-cl-mad-enable -cl-no-signed-zeros -cl-single-precision-constant -cl-denorms-are-zero");
//err = spk.m_Program.build(m_DeviceVec, "-cl-mad-enable -cl-single-precision-constant");
if (m_Info->CheckCL(err, "cl::Program::build()"))
{
//Building of program is ok, now create kernel with the specified entry point.
spk.m_Kernel = cl::Kernel(spk.m_Program, entryPoint.c_str(), &err);
if (m_Info->CheckCL(err, "cl::Kernel()"))
return true;//Everything is ok.
}
else
{
for (auto& i : m_DeviceVec)
AddToReport(spk.m_Program.getBuildInfo<CL_PROGRAM_BUILD_LOG>(i, nullptr));
}
}
return false;
}
}