/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ /* * This file is part of the LibreOffice project. * * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #ifndef INCLUDED_OPENCL_INC_OPENCL_DEVICE_SELECTION_H #define INCLUDED_OPENCL_INC_OPENCL_DEVICE_SELECTION_H #ifdef _MSC_VER //#define _CRT_SECURE_NO_WARNINGS #endif #include #include #include #include #include #include #include #include #include #include enum ds_status { DS_SUCCESS = 0 ,DS_INVALID_PROFILE = 1000 ,DS_MEMORY_ERROR , DS_INVALID_PERF_EVALUATOR_TYPE , DS_INVALID_PERF_EVALUATOR , DS_PERF_EVALUATOR_ERROR , DS_FILE_ERROR , DS_UNKNOWN_DEVICE_TYPE , DS_PROFILE_FILE_ERROR , DS_SCORE_SERIALIZER_ERROR , DS_SCORE_DESERIALIZER_ERROR }; // device type enum class DeviceType { None, // NativeCPU means the traditional Calc interpreter code path. (That also includes the so-called // "software interpreter", but note that it definitely does not mean *exclusively* that.) NativeCPU, // OpenCLDevice means an OpenCL device as supplied by an OpenCL platform, which might well be // implemented using code that runs on the CPU (and not a GPU). On Windows, OpenCL platforms // typically provide two devices, one for the GPU and one for the CPU. OpenCLDevice }; struct ds_device { DeviceType eType; cl_device_id aDeviceID; OString sPlatformName; OString sPlatformVendor; OString sPlatformVersion; OString sPlatformProfile; OString sPlatformExtensions; OString sDeviceName; OString sDeviceVendor; OString sDeviceVersion; OString sDriverVersion; OString sDeviceType; OString sDeviceExtensions; OString sDeviceOpenCLVersion; bool bDeviceAvailable; bool bDeviceCompilerAvailable; bool bDeviceLinkerAvailable; double fTime; // small time means faster device bool bErrors; // were there any opencl errors }; struct ds_profile { std::vector devices; OString version; ds_profile(OString inVersion) : version(std::move(inVersion)) {} }; inline OString getPlatformInfoString(cl_platform_id aPlatformId, cl_platform_info aPlatformInfo) { std::vector temporary(2048, 0); clGetPlatformInfo(aPlatformId, aPlatformInfo, temporary.size(), temporary.data(), nullptr); return temporary.data(); } inline OString getDeviceInfoString(cl_device_id aDeviceId, cl_device_info aDeviceInfo) { std::vector temporary(2048, 0); clGetDeviceInfo(aDeviceId, aDeviceInfo, temporary.size(), temporary.data(), nullptr); return temporary.data(); } inline OString getDeviceType(cl_device_id aDeviceId) { OString sType = ""; cl_device_type aDeviceType; clGetDeviceInfo(aDeviceId, CL_DEVICE_TYPE, sizeof(aDeviceType), &aDeviceType, nullptr); if (aDeviceType & CL_DEVICE_TYPE_CPU) sType += "cpu "; if (aDeviceType & CL_DEVICE_TYPE_GPU) sType += "gpu "; if (aDeviceType & CL_DEVICE_TYPE_ACCELERATOR) sType += "accelerator "; if (aDeviceType & CL_DEVICE_TYPE_CUSTOM) sType += "custom "; if (aDeviceType & CL_DEVICE_TYPE_DEFAULT) sType += "default "; return sType; } inline bool getDeviceInfoBool(cl_device_id aDeviceId, cl_device_info aDeviceInfo) { cl_bool bCLBool = 0; // init to false in case clGetDeviceInfo returns CL_INVALID_VALUE when // requesting unsupported (in version 1.0) CL_DEVICE_LINKER_AVAILABLE clGetDeviceInfo(aDeviceId, aDeviceInfo, sizeof(bCLBool), &bCLBool, nullptr); return bCLBool == CL_TRUE; } inline ds_status initDSProfile(std::unique_ptr& rProfile, OString const & rVersion) { OpenCLZone zone; int numDevices; cl_uint numPlatforms; std::vector platforms; std::vector devices; unsigned int next; unsigned int i; rProfile.reset(new ds_profile(rVersion)); clGetPlatformIDs(0, nullptr, &numPlatforms); if (numPlatforms != 0) { platforms.resize(numPlatforms); clGetPlatformIDs(numPlatforms, platforms.data(), nullptr); } numDevices = 0; for (i = 0; i < static_cast(numPlatforms); i++) { cl_uint num = 0; cl_int err = clGetDeviceIDs(platforms[i], CL_DEVICE_TYPE_ALL, 0, nullptr, &num); if (err != CL_SUCCESS) { /* we want to catch at least the case when the call returns * CL_DEVICE_NOT_FOUND (i.e. no devices), because some platforms * don't set num to 0 in this case; but in fact this is a good * thing to do for _any_ error returned by the call */ num = 0; } numDevices += num; } if (numDevices != 0) { devices.resize(numDevices); } rProfile->devices.resize(numDevices + 1); // +1 to numDevices to include the native CPU next = 0; for (i = 0; i < static_cast(numPlatforms); i++) { cl_uint num = 0; unsigned j; OString sPlatformProfile = getPlatformInfoString(platforms[i], CL_PLATFORM_PROFILE); OString sPlatformVersion = getPlatformInfoString(platforms[i], CL_PLATFORM_VERSION); OString sPlatformName = getPlatformInfoString(platforms[i], CL_PLATFORM_NAME); OString sPlatformVendor = getPlatformInfoString(platforms[i], CL_PLATFORM_VENDOR); OString sPlatformExts = getPlatformInfoString(platforms[i], CL_PLATFORM_EXTENSIONS); cl_int err = clGetDeviceIDs(platforms[i], CL_DEVICE_TYPE_ALL, numDevices, devices.data(), &num); if (err != CL_SUCCESS) { /* we want to catch at least the case when the call returns * CL_DEVICE_NOT_FOUND (i.e. no devices), because some platforms * don't set num to 0 in this case; but in fact this is a good * thing to do for _any_ error returned by the call */ num = 0; } for (j = 0; j < num; j++, next++) { cl_device_id aDeviceID = devices[j]; ds_device& rDevice = rProfile->devices[next]; rDevice.eType = DeviceType::OpenCLDevice; rDevice.aDeviceID = aDeviceID; rDevice.sPlatformName = sPlatformName; rDevice.sPlatformVendor = sPlatformVendor; rDevice.sPlatformVersion = sPlatformVersion; rDevice.sPlatformProfile = sPlatformProfile; rDevice.sPlatformExtensions = sPlatformExts; rDevice.sDeviceName = getDeviceInfoString(aDeviceID, CL_DEVICE_NAME); rDevice.sDeviceVendor = getDeviceInfoString(aDeviceID, CL_DEVICE_VENDOR); rDevice.sDeviceVersion = getDeviceInfoString(aDeviceID, CL_DEVICE_VERSION); rDevice.sDriverVersion = getDeviceInfoString(aDeviceID, CL_DRIVER_VERSION); rDevice.sDeviceType = getDeviceType(aDeviceID); rDevice.sDeviceExtensions = getDeviceInfoString(aDeviceID, CL_DEVICE_EXTENSIONS); rDevice.sDeviceOpenCLVersion = getDeviceInfoString(aDeviceID, CL_DEVICE_OPENCL_C_VERSION); rDevice.bDeviceAvailable = getDeviceInfoBool(aDeviceID, CL_DEVICE_AVAILABLE); rDevice.bDeviceCompilerAvailable = getDeviceInfoBool(aDeviceID, CL_DEVICE_COMPILER_AVAILABLE); rDevice.bDeviceLinkerAvailable = getDeviceInfoBool(aDeviceID, CL_DEVICE_LINKER_AVAILABLE); } } rProfile->devices[next].eType = DeviceType::NativeCPU; return DS_SUCCESS; } inline ds_status writeProfile(const OUString& rStreamName, std::unique_ptr const & pProfile) { if (pProfile == nullptr) return DS_INVALID_PROFILE; if (rStreamName.isEmpty()) return DS_INVALID_PROFILE; std::unique_ptr pStream; pStream.reset(new SvFileStream(rStreamName, StreamMode::STD_READWRITE | StreamMode::TRUNC)); tools::XmlWriter aXmlWriter(pStream.get()); if (!aXmlWriter.startDocument()) return DS_FILE_ERROR; aXmlWriter.startElement("profile"); aXmlWriter.startElement("version"); aXmlWriter.content(pProfile->version); aXmlWriter.endElement(); for (const ds_device& rDevice : pProfile->devices) { aXmlWriter.startElement("device"); switch(rDevice.eType) { case DeviceType::NativeCPU: aXmlWriter.startElement("type"); aXmlWriter.content(OString("native")); aXmlWriter.endElement(); break; case DeviceType::OpenCLDevice: aXmlWriter.startElement("type"); aXmlWriter.content(OString("opencl")); aXmlWriter.endElement(); aXmlWriter.startElement("name"); aXmlWriter.content(rDevice.sDeviceName); aXmlWriter.endElement(); aXmlWriter.startElement("driver"); aXmlWriter.content(rDevice.sDriverVersion); aXmlWriter.endElement(); break; default: break; } aXmlWriter.startElement("time"); if (rtl::math::approxEqual(rDevice.fTime, DBL_MAX)) aXmlWriter.content(OString("max")); else aXmlWriter.content(OString::number(rDevice.fTime)); aXmlWriter.endElement(); aXmlWriter.startElement("errors"); aXmlWriter.content(rDevice.bErrors ? OString("true") : OString("false")); aXmlWriter.endElement(); aXmlWriter.endElement(); } aXmlWriter.endElement(); aXmlWriter.endDocument(); return DS_SUCCESS; } inline ds_status readProfile(const OUString& rStreamName, std::unique_ptr const & pProfile) { ds_status eStatus = DS_SUCCESS; if (rStreamName.isEmpty()) return DS_INVALID_PROFILE; std::unique_ptr pStream; pStream.reset(new SvFileStream(rStreamName, StreamMode::READ)); tools::XmlWalker aWalker; if (!aWalker.open(pStream.get())) return DS_FILE_ERROR; if (aWalker.name() == "profile") { aWalker.children(); while (aWalker.isValid()) { if (aWalker.name() == "version") { if (aWalker.content() != pProfile->version) return DS_PROFILE_FILE_ERROR; } else if (aWalker.name() == "device") { aWalker.children(); DeviceType eDeviceType = DeviceType::None; OString sName; OString sVersion; double fTime = -1.0; bool bErrors = true; while (aWalker.isValid()) { if (aWalker.name() == "type") { OString sContent = aWalker.content(); if (sContent == "native") eDeviceType = DeviceType::NativeCPU; else if (sContent == "opencl") eDeviceType = DeviceType::OpenCLDevice; else return DS_PROFILE_FILE_ERROR; } else if (aWalker.name() == "name") { sName = aWalker.content(); } else if (aWalker.name() == "driver") { sVersion = aWalker.content(); } else if (aWalker.name() == "time") { if (aWalker.content() == "max") fTime = DBL_MAX; else fTime = aWalker.content().toDouble(); } else if (aWalker.name() == "errors") { bErrors = (aWalker.content() == "true"); } aWalker.next(); } if (fTime < 0.0) return DS_PROFILE_FILE_ERROR; for (ds_device& rDevice : pProfile->devices) { // type matches? either both are DS_DEVICE_OPENCL_DEVICE or DS_DEVICE_NATIVE_CPU if (rDevice.eType == eDeviceType) { // is DS_DEVICE_NATIVE_CPU or name + version matches? if (eDeviceType == DeviceType::NativeCPU || (sName == rDevice.sDeviceName && sVersion == rDevice.sDriverVersion)) { rDevice.fTime = fTime; rDevice.bErrors = bErrors; } } } aWalker.parent(); } aWalker.next(); } aWalker.parent(); } return eStatus; } #endif /* vim:set shiftwidth=4 softtabstop=4 expandtab: */