#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(1), nullptr, &err), "emptybuffer");//First clear out the original so the two don't exist in memory at once.
			char ch = 0;

			if (!WriteBuffer("emptybuffer", &ch, 1))//Write the dummy buffer at least once because OpenCL seems to do a lazy instantiation of buffers.
				return false;

			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(1), data, &err), "emptybuffer");//First clear out the original so the two don't exist in memory at once.
				char ch = 0;

				if (!WriteBuffer("emptybuffer", &ch, 1))//Write the dummy buffer at least once because OpenCL seems to do a lazy instantiation of buffers.
					return false;

				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;
		const auto 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;
		const auto 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)
			{
				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);
					imageIndex = int(m_GLImages.size()) - 1;
				}
				else
					return false;
			}
			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);
					imageIndex = int(m_Images.size()) - 1;
				}
				else
					return false;
			}
		}
		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;
				}
			}
			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;
					else
						return false;
				}
			}
		}

		if (data)
			return WriteImage2D(imageIndex, shared, 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);
		const auto 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);
		const auto 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)
	{
		const auto 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)
	{
		const auto 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;
		const auto 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>
/// Clear the error report for this class as well as the global OpenCLInfo instance.
/// </summary>
void OpenCLWrapper::ClearErrorReport()
{
	EmberReport::ClearErrorReport();
	m_Info->ClearErrorReport();
}

/// <summary>
/// Concatenate and return the error report for this class and the
/// global OpenCLInfo instance as a single string.
/// </summary>
/// <returns>The concatenated error report string</returns>
string OpenCLWrapper::ErrorReportString()
{
	auto s = EmberReport::ErrorReportString();
	return s + m_Info->ErrorReportString();
}

/// <summary>
/// Concatenate and return the error report for this class and the
/// global OpenCLInfo instance as a vector of strings.
/// </summary>
/// <returns>The concatenated error report vector of strings</returns>
vector<string> OpenCLWrapper::ErrorReport()
{
	auto ours = EmberReport::ErrorReport();
	auto s = m_Info->ErrorReport();
	ours.insert(ours.end(), s.begin(), s.end());
	return ours;
}

/// <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-denorms-are-zero -cl-single-precision-constant");

		//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;
}
}
