#include "EmberCLPch.h"
#include "OpenCLInfo.h"
namespace EmberCLns
{
/// 
/// Initialize the all platforms and devices and keep information about them in lists.
/// 
OpenCLInfo::OpenCLInfo()
{
	cl_int err;
	vector platforms;
	vector> devices;
	intmax_t workingPlatformIndex = -1;
	m_Init = false;
	cl::Platform::get(&platforms);
	devices.resize(platforms.size());
	m_Platforms.reserve(platforms.size());
	m_Devices.reserve(platforms.size());
	m_DeviceNames.reserve(platforms.size());
	m_AllDeviceNames.reserve(platforms.size());
	m_DeviceIndices.reserve(platforms.size());
	for (size_t i = 0; i < platforms.size(); i++)
		platforms[i].getDevices(CL_DEVICE_TYPE_ALL, &devices[i]);
	for (size_t platform = 0; platform < platforms.size(); platform++)
	{
		bool platformOk = false;
		bool deviceOk = false;
		cl::Context context;
		if (CreateContext(platforms[platform], context, false))//Platform is ok, now do context. Unshared by default.
		{
			size_t workingDeviceIndex = 0;
			for (size_t device = 0; device < devices[platform].size(); device++)//Context is ok, now do devices.
			{
				auto q = cl::CommandQueue(context, devices[platform][device], 0, &err);//At least one GPU device is present, so create a command queue.
				if (CheckCL(err, "cl::CommandQueue()"))
				{
					if (!platformOk)
					{
						m_Platforms.push_back(platforms[platform]);
						m_PlatformNames.push_back(platforms[platform].getInfo(nullptr) + " " + platforms[platform].getInfo(nullptr) + " " + platforms[platform].getInfo(nullptr));
						workingPlatformIndex++;
						platformOk = true;
					}
					if (!deviceOk)
					{
						m_Devices.push_back(vector());
						m_DeviceNames.push_back(vector());
						m_Devices.back().reserve(devices[platform].size());
						m_DeviceNames.back().reserve(devices[platform].size());
						deviceOk = true;
					}
					m_Devices.back().push_back(devices[platform][device]);
					m_DeviceNames.back().push_back(devices[platform][device].getInfo(nullptr) + " " + devices[platform][device].getInfo(nullptr));// + " " + devices[platform][device].getInfo());
					m_AllDeviceNames.push_back(m_DeviceNames.back().back());
					m_DeviceIndices.push_back(pair(workingPlatformIndex, workingDeviceIndex++));
					m_Init = true;//If at least one platform and device succeeded, OpenCL is ok. It's now ok to begin building and running programs.
				}
			}
		}
	}
}
SINGLETON_INSTANCE_IMPL(OpenCLInfo)
/// 
/// Get a const reference to the vector of available platforms.
/// 
/// A const reference to the vector of available platforms
const vector& OpenCLInfo::Platforms() const
{
	return m_Platforms;
}
/// 
/// Get a const reference to the platform name at the specified index.
/// 
/// The platform index to get the name of
/// The platform name if found, else empty string
const string& OpenCLInfo::PlatformName(size_t platform) const
{
	static string s;
	return platform < m_PlatformNames.size() ? m_PlatformNames[platform] : s;
}
/// 
/// Get a const reference to a vector of all available platform names on the system as a vector of strings.
/// 
/// All available platform names on the system as a vector of strings
const vector& OpenCLInfo::PlatformNames() const
{
	return m_PlatformNames;
}
/// 
/// Get a const reference to a vector of vectors of all available devices on the system.
/// Each outer vector is a different platform.
/// 
/// All available devices on the system, grouped by platform.
const vector>& OpenCLInfo::Devices() const
{
	return m_Devices;
}
/// 
/// Get a const reference to the device name at the specified index on the platform
/// at the specified index.
/// 
/// The platform index of the device
/// The device index
/// The name of the device if found, else empty string
const string& OpenCLInfo::DeviceName(size_t platform, size_t device) const
{
	static string s;
	if (platform < m_Platforms.size() && platform < m_Devices.size())
		if (device < m_Devices[platform].size())
			return m_DeviceNames[platform][device];
	return s;
}
/// 
/// Get a const reference to a vector of pairs of uints which contain the platform,device
/// indices of all available devices on the system.
/// 
/// All available devices on the system as platform,device index pairs
const vector>& OpenCLInfo::DeviceIndices() const
{
	return m_DeviceIndices;
}
/// 
/// Get a const reference to a vector of all available device names on the system as a vector of strings.
/// 
/// All available device names on the system as a vector of strings
const vector& OpenCLInfo::AllDeviceNames() const
{
	return m_AllDeviceNames;
}
/// 
/// Get a const reference to a vector of all available device names on the platform
/// at the specified index as a vector of strings.
/// 
/// The platform index whose devices names will be returned
/// All available device names on the platform at the specified index as a vector of strings if within range, else empty vector.
const vector& OpenCLInfo::DeviceNames(size_t platform) const
{
	static vector v;
	if (platform < m_DeviceNames.size())
		return m_DeviceNames[platform];
	return v;
}
/// 
/// Get the total device index at the specified platform and device index.
/// 
/// The platform index of the device
/// The device index within the platform
/// The total device index if found, else 0
size_t OpenCLInfo::TotalDeviceIndex(size_t platform, size_t device) const
{
	size_t index = 0;
	pair p{ platform, device };
	for (size_t i = 0; i < m_DeviceIndices.size(); i++)
	{
		if (p == m_DeviceIndices[i])
		{
			index = i;
			break;
		}
	}
	return index;
}
/// 
/// Create a context that is optionally shared with OpenGL and plact it in the
/// passed in context ref parameter.
/// 
/// The platform object to create the context on
/// The context object to store the result in
/// True if shared with OpenGL, else not shared.
/// True if success, else false.
bool OpenCLInfo::CreateContext(const cl::Platform& platform, cl::Context& context, bool shared)
{
	cl_int err;
	if (shared)
	{
		//Define OS-specific context properties and create the OpenCL context.
#if defined (__APPLE__) || defined(MACOSX)
		CGLContextObj kCGLContext = CGLGetCurrentContext();
		CGLShareGroupObj kCGLShareGroup = CGLGetShareGroup(kCGLContext);
		cl_context_properties props[] =
		{
			CL_CONTEXT_PROPERTY_USE_CGL_SHAREGROUP_APPLE, (cl_context_properties)kCGLShareGroup,
			0
		};
		context = cl::Context(CL_DEVICE_TYPE_GPU, props, nullptr, nullptr, &err);//May need to tinker with this on Mac.
#else
#if defined WIN32
		cl_context_properties props[] =
		{
			CL_GL_CONTEXT_KHR, (cl_context_properties)wglGetCurrentContext(),
			CL_WGL_HDC_KHR, (cl_context_properties)wglGetCurrentDC(),
			CL_CONTEXT_PLATFORM, reinterpret_cast((platform)()),
			0
		};
		context = cl::Context(CL_DEVICE_TYPE_GPU, props, nullptr, nullptr, &err);
#else
		cl_context_properties props[] =
		{
			CL_GL_CONTEXT_KHR, cl_context_properties(glXGetCurrentContext()),
			CL_GLX_DISPLAY_KHR, cl_context_properties(glXGetCurrentDisplay()),
			CL_CONTEXT_PLATFORM, reinterpret_cast((platform)()),
			0
		};
		context = cl::Context(CL_DEVICE_TYPE_GPU, props, nullptr, nullptr, &err);
#endif
#endif
	}
	else
	{
		cl_context_properties props[3] =
		{
			CL_CONTEXT_PLATFORM,
			reinterpret_cast((platform)()),
			0
		};
		context = cl::Context(CL_DEVICE_TYPE_ALL, props, nullptr, nullptr, &err);
	}
	return CheckCL(err, "cl::Context()");
}
/// 
/// Return whether at least one device has been found and properly initialized.
/// 
/// True if success, else false.
bool OpenCLInfo::Ok() const
{
	return m_Init;
}
/// 
/// Get all information about all platforms and devices.
/// 
/// A string with all information about all platforms and devices
string OpenCLInfo::DumpInfo() const
{
	ostringstream os;
	vector sizes;
	os.imbue(locale(""));
	for (size_t platform = 0; platform < m_Platforms.size(); platform++)
	{
		os << "Platform " << platform << ": " << PlatformName(platform) << "\n";
		for (size_t device = 0; device < m_Devices[platform].size(); device++)
		{
			os << "Device " << device << ": " << DeviceName(platform, device);
			os << "\nCL_DEVICE_OPENCL_C_VERSION: " << GetInfo(platform, device, CL_DEVICE_OPENCL_C_VERSION);
			os << "\nCL_DEVICE_LOCAL_MEM_SIZE: " << GetInfo(platform, device, CL_DEVICE_LOCAL_MEM_SIZE);
			os << "\nCL_DEVICE_LOCAL_MEM_TYPE: " << GetInfo(platform, device, CL_DEVICE_LOCAL_MEM_TYPE);
			os << "\nCL_DEVICE_MAX_COMPUTE_UNITS: " << GetInfo(platform, device, CL_DEVICE_MAX_COMPUTE_UNITS);
			os << "\nCL_DEVICE_MAX_READ_IMAGE_ARGS: " << GetInfo(platform, device, CL_DEVICE_MAX_READ_IMAGE_ARGS);
			os << "\nCL_DEVICE_MAX_WRITE_IMAGE_ARGS: " << GetInfo(platform, device, CL_DEVICE_MAX_WRITE_IMAGE_ARGS);
			os << "\nCL_DEVICE_MAX_MEM_ALLOC_SIZE: " << GetInfo(platform, device, CL_DEVICE_MAX_MEM_ALLOC_SIZE);
			os << "\nCL_DEVICE_ADDRESS_BITS: " << GetInfo(platform, device, CL_DEVICE_ADDRESS_BITS);
			os << "\nCL_DEVICE_GLOBAL_MEM_CACHE_TYPE: " << GetInfo(platform, device, CL_DEVICE_GLOBAL_MEM_CACHE_TYPE);
			os << "\nCL_DEVICE_GLOBAL_MEM_CACHELINE_SIZE: " << GetInfo(platform, device, CL_DEVICE_GLOBAL_MEM_CACHELINE_SIZE);
			os << "\nCL_DEVICE_GLOBAL_MEM_CACHE_SIZE: " << GetInfo(platform, device, CL_DEVICE_GLOBAL_MEM_CACHE_SIZE);
			os << "\nCL_DEVICE_GLOBAL_MEM_SIZE: " << GetInfo(platform, device, CL_DEVICE_GLOBAL_MEM_SIZE);
			os << "\nCL_DEVICE_MAX_CONSTANT_BUFFER_SIZE: " << GetInfo(platform, device, CL_DEVICE_MAX_CONSTANT_BUFFER_SIZE);
			os << "\nCL_DEVICE_MAX_CONSTANT_ARGS: " << GetInfo(platform, device, CL_DEVICE_MAX_CONSTANT_ARGS);
			os << "\nCL_DEVICE_MAX_WORK_ITEM_DIMENSIONS: " << GetInfo(platform, device, CL_DEVICE_MAX_WORK_ITEM_DIMENSIONS);
			os << "\nCL_DEVICE_MAX_WORK_GROUP_SIZE: " << GetInfo(platform, device, CL_DEVICE_MAX_WORK_GROUP_SIZE);
			sizes = GetInfo>(platform, device, CL_DEVICE_MAX_WORK_ITEM_SIZES);
			os << "\nCL_DEVICE_MAX_WORK_ITEM_SIZES: " << sizes[0] << ", " << sizes[1] << ", " << sizes[2] << "\n" << "\n";
			if (device != m_Devices[platform].size() - 1 && platform != m_Platforms.size() - 1)
				os << "\n";
		}
		os << "\n";
	}
	return os.str();
}
/// 
/// Check an OpenCL return value for errors.
/// 
/// The error code to inspect
/// A description of where the value was gotten from
/// True if success, else false.
bool OpenCLInfo::CheckCL(cl_int err, const char* name)
{
	if (err != CL_SUCCESS)
	{
		ostringstream ss;
		ss << "ERROR: " << ErrorToStringCL(err) << " in " << name << ".\n";
		AddToReport(ss.str());
	}
	return err == CL_SUCCESS;
}
/// 
/// Translate an OpenCL error code into a human readable string.
/// 
/// The error code to translate
/// A human readable description of the error passed in
string OpenCLInfo::ErrorToStringCL(cl_int err)
{
	switch (err)
	{
		case CL_SUCCESS:								   return "Success";
		case CL_DEVICE_NOT_FOUND:						   return "Device not found";
		case CL_DEVICE_NOT_AVAILABLE:					   return "Device not available";
		case CL_COMPILER_NOT_AVAILABLE:					   return "Compiler not available";
		case CL_MEM_OBJECT_ALLOCATION_FAILURE:			   return "Memory object allocation failure";
		case CL_OUT_OF_RESOURCES:						   return "Out of resources";
		case CL_OUT_OF_HOST_MEMORY:						   return "Out of host memory";
		case CL_PROFILING_INFO_NOT_AVAILABLE:			   return "Profiling information not available";
		case CL_MEM_COPY_OVERLAP:						   return "Memory copy overlap";
		case CL_IMAGE_FORMAT_MISMATCH:					   return "Image format mismatch";
		case CL_IMAGE_FORMAT_NOT_SUPPORTED:				   return "Image format not supported";
		case CL_BUILD_PROGRAM_FAILURE:					   return "Program build failure";
		case CL_MAP_FAILURE:							   return "Map failure";
		case CL_MISALIGNED_SUB_BUFFER_OFFSET:			   return "Misaligned sub buffer offset";
		case CL_EXEC_STATUS_ERROR_FOR_EVENTS_IN_WAIT_LIST: return "Exec status error for events in wait list";
		case CL_INVALID_VALUE:							   return "Invalid value";
		case CL_INVALID_DEVICE_TYPE:					   return "Invalid device type";
		case CL_INVALID_PLATFORM:						   return "Invalid platform";
		case CL_INVALID_DEVICE:							   return "Invalid device";
		case CL_INVALID_CONTEXT:						   return "Invalid context";
		case CL_INVALID_QUEUE_PROPERTIES:				   return "Invalid queue properties";
		case CL_INVALID_COMMAND_QUEUE:					   return "Invalid command queue";
		case CL_INVALID_HOST_PTR:						   return "Invalid host pointer";
		case CL_INVALID_MEM_OBJECT:						   return "Invalid memory object";
		case CL_INVALID_IMAGE_FORMAT_DESCRIPTOR:		   return "Invalid image format descriptor";
		case CL_INVALID_IMAGE_SIZE:						   return "Invalid image size";
		case CL_INVALID_SAMPLER:						   return "Invalid sampler";
		case CL_INVALID_BINARY:							   return "Invalid binary";
		case CL_INVALID_BUILD_OPTIONS:					   return "Invalid build options";
		case CL_INVALID_PROGRAM:						   return "Invalid program";
		case CL_INVALID_PROGRAM_EXECUTABLE:				   return "Invalid program executable";
		case CL_INVALID_KERNEL_NAME:					   return "Invalid kernel name";
		case CL_INVALID_KERNEL_DEFINITION:				   return "Invalid kernel definition";
		case CL_INVALID_KERNEL:							   return "Invalid kernel";
		case CL_INVALID_ARG_INDEX:						   return "Invalid argument index";
		case CL_INVALID_ARG_VALUE:						   return "Invalid argument value";
		case CL_INVALID_ARG_SIZE:						   return "Invalid argument size";
		case CL_INVALID_KERNEL_ARGS:					   return "Invalid kernel arguments";
		case CL_INVALID_WORK_DIMENSION:					   return "Invalid work dimension";
		case CL_INVALID_WORK_GROUP_SIZE:				   return "Invalid work group size";
		case CL_INVALID_WORK_ITEM_SIZE:					   return "Invalid work item size";
		case CL_INVALID_GLOBAL_OFFSET:					   return "Invalid global offset";
		case CL_INVALID_EVENT_WAIT_LIST:				   return "Invalid event wait list";
		case CL_INVALID_EVENT:							   return "Invalid event";
		case CL_INVALID_OPERATION:						   return "Invalid operation";
		case CL_INVALID_GL_OBJECT:						   return "Invalid OpenGL object";
		case CL_INVALID_BUFFER_SIZE:					   return "Invalid buffer size";
		case CL_INVALID_MIP_LEVEL:						   return "Invalid mip-map level";
		case CL_INVALID_GLOBAL_WORK_SIZE:				   return "Invalid global work size";
		case CL_INVALID_PROPERTY:						   return "Invalid property";
		default:
		{
			ostringstream ss;
			ss << " " << err;
			return ss.str();
		}
	}
}
}