opencl_device_selection.h 13.1 KB
Newer Older
1 2 3 4 5 6 7 8 9
/* -*- 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/.
 */

10 11
#ifndef INCLUDED_OPENCL_INC_OPENCL_DEVICE_SELECTION_H
#define INCLUDED_OPENCL_INC_OPENCL_DEVICE_SELECTION_H
12 13

#ifdef _MSC_VER
14
//#define _CRT_SECURE_NO_WARNINGS
15 16
#endif

17 18
#include <memory>

19
#include <float.h>
20 21

#include <clew/clew.h>
22
#include <tools/stream.hxx>
23 24
#include <tools/XmlWriter.hxx>
#include <tools/XmlWalker.hxx>
25
#include <rtl/math.hxx>
26

27 28
#include <opencl/OpenCLZone.hxx>

29
#include <vector>
30

31 32
enum ds_status
{
33 34 35 36 37 38 39 40 41 42 43
    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
44
};
45 46

// device type
47
enum class DeviceType
48
{
49
    None,
50 51
    // 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.)
52
    NativeCPU,
53 54 55
    // 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.
56
    OpenCLDevice
57
};
58

59 60
struct ds_device
{
61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83
    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
84
};
85

86 87
struct ds_profile
{
88
    std::vector<ds_device> devices;
89
    OString const version;
90

91
    ds_profile(OString const & inVersion)
92 93
        : version(inVersion)
    {}
94
};
95

96
inline OString getPlatformInfoString(cl_platform_id aPlatformId, cl_platform_info aPlatformInfo)
97
{
98 99 100
    std::vector<char> temporary(2048, 0);
    clGetPlatformInfo(aPlatformId, aPlatformInfo, temporary.size(), temporary.data(), nullptr);
    return OString(temporary.data());
101 102
}

103 104 105 106 107 108
inline OString getDeviceInfoString(cl_device_id aDeviceId, cl_device_info aDeviceInfo)
{
    std::vector<char> temporary(2048, 0);
    clGetDeviceInfo(aDeviceId, aDeviceInfo, temporary.size(), temporary.data(), nullptr);
    return OString(temporary.data());
}
109

110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129
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)
{
130 131 132
    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
133 134 135 136
    clGetDeviceInfo(aDeviceId, aDeviceInfo, sizeof(bCLBool), &bCLBool, nullptr);
    return bCLBool == CL_TRUE;
}

137
inline ds_status initDSProfile(std::unique_ptr<ds_profile>& rProfile, OString const & rVersion)
138
{
139 140
    OpenCLZone zone;

141 142
    int numDevices;
    cl_uint numPlatforms;
143 144 145
    std::vector<cl_platform_id> platforms;
    std::vector<cl_device_id> devices;

146 147 148
    unsigned int next;
    unsigned int i;

149
    rProfile.reset(new ds_profile(rVersion));
150

151
    clGetPlatformIDs(0, nullptr, &numPlatforms);
152 153
    if (numPlatforms != 0)
    {
154
        platforms.resize(numPlatforms);
155
        clGetPlatformIDs(numPlatforms, platforms.data(), nullptr);
156 157 158
    }

    numDevices = 0;
159
    for (i = 0; i < static_cast<unsigned int>(numPlatforms); i++)
160
    {
161
        cl_uint num = 0;
162
        cl_int err = clGetDeviceIDs(platforms[i], CL_DEVICE_TYPE_ALL, 0, nullptr, &num);
163 164 165 166 167 168 169 170 171
        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;
        }
172 173
        numDevices += num;
    }
174

175 176
    if (numDevices != 0)
    {
177
        devices.resize(numDevices);
178 179
    }

180
    rProfile->devices.resize(numDevices + 1); // +1 to numDevices to include the native CPU
181 182

    next = 0;
183
    for (i = 0; i < static_cast<unsigned int>(numPlatforms); i++)
184
    {
185
        cl_uint num = 0;
186
        unsigned j;
187 188 189 190 191 192 193 194

        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);
195 196 197 198 199 200 201 202 203
        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;
        }
204 205
        for (j = 0; j < num; j++, next++)
        {
206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231
            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;
232

233 234
    return DS_SUCCESS;
}
235

236
inline ds_status writeProfile(const OUString& rStreamName, std::unique_ptr<ds_profile> const & pProfile)
237
{
238 239 240 241
    if (pProfile == nullptr)
        return DS_INVALID_PROFILE;
    if (rStreamName.isEmpty())
        return DS_INVALID_PROFILE;
242

243
    std::unique_ptr<SvStream> pStream;
244
    pStream.reset(new SvFileStream(rStreamName, StreamMode::STD_READWRITE | StreamMode::TRUNC));
245

246
    tools::XmlWriter aXmlWriter(pStream.get());
247

248
    if (!aXmlWriter.startDocument())
249
        return DS_FILE_ERROR;
250

251
    aXmlWriter.startElement("profile");
252

253
    aXmlWriter.startElement("version");
254
    aXmlWriter.content(pProfile->version);
255
    aXmlWriter.endElement();
256

257 258 259
    for (ds_device& rDevice : pProfile->devices)
    {
        aXmlWriter.startElement("device");
260

261
        switch(rDevice.eType)
262
        {
263 264
            case DeviceType::NativeCPU:
                aXmlWriter.startElement("type");
265
                aXmlWriter.content(OString("native"));
266 267 268 269
                aXmlWriter.endElement();
                break;
            case DeviceType::OpenCLDevice:
                aXmlWriter.startElement("type");
270
                aXmlWriter.content(OString("opencl"));
271 272 273
                aXmlWriter.endElement();

                aXmlWriter.startElement("name");
274
                aXmlWriter.content(rDevice.sDeviceName);
275 276 277
                aXmlWriter.endElement();

                aXmlWriter.startElement("driver");
278
                aXmlWriter.content(rDevice.sDriverVersion);
279 280 281 282
                aXmlWriter.endElement();
                break;
            default:
                break;
283 284
        }

285
        aXmlWriter.startElement("time");
286
        if (rtl::math::approxEqual(rDevice.fTime, DBL_MAX))
287
            aXmlWriter.content(OString("max"));
288 289 290
        else
            aXmlWriter.content(OString::number(rDevice.fTime));
        aXmlWriter.endElement();
291

292
        aXmlWriter.startElement("errors");
293
        aXmlWriter.content(rDevice.bErrors ? OString("true") : OString("false"));
294
        aXmlWriter.endElement();
295

296 297
        aXmlWriter.endElement();
    }
298

299 300
    aXmlWriter.endElement();
    aXmlWriter.endDocument();
301

302 303
    return DS_SUCCESS;
}
304

305
inline ds_status readProfile(const OUString& rStreamName, std::unique_ptr<ds_profile> const & pProfile)
306 307
{
    ds_status eStatus = DS_SUCCESS;
308

309 310 311 312 313
    if (rStreamName.isEmpty())
        return DS_INVALID_PROFILE;

    std::unique_ptr<SvStream> pStream;
    pStream.reset(new SvFileStream(rStreamName, StreamMode::READ));
314
    tools::XmlWalker aWalker;
315

316 317 318 319 320 321 322 323 324
    if (!aWalker.open(pStream.get()))
        return DS_FILE_ERROR;

    if (aWalker.name() == "profile")
    {
        aWalker.children();
        while (aWalker.isValid())
        {
            if (aWalker.name() == "version")
325
            {
326 327
                if (aWalker.content() != pProfile->version)
                    return DS_PROFILE_FILE_ERROR;
328
            }
329
            else if (aWalker.name() == "device")
330
            {
331
                aWalker.children();
332

333 334 335 336 337
                DeviceType eDeviceType = DeviceType::None;
                OString sName;
                OString sVersion;
                double fTime = -1.0;
                bool bErrors = true;
338

339
                while (aWalker.isValid())
340
                {
341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369
                    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");
                    }
370

371
                    aWalker.next();
372 373
                }

374 375
                if (fTime < 0.0)
                    return DS_PROFILE_FILE_ERROR;
376

377
                for (ds_device& rDevice : pProfile->devices)
378
                {
379 380
                    // type matches? either both are DS_DEVICE_OPENCL_DEVICE or DS_DEVICE_NATIVE_CPU
                    if (rDevice.eType == eDeviceType)
381
                    {
382 383
                        // is DS_DEVICE_NATIVE_CPU or name + version matches?
                        if (eDeviceType == DeviceType::NativeCPU ||
384 385
                                (sName == rDevice.sDeviceName &&
                                 sVersion == rDevice.sDriverVersion))
386
                        {
387 388
                            rDevice.fTime = fTime;
                            rDevice.bErrors = bErrors;
389 390 391 392
                        }
                    }
                }

393
                aWalker.parent();
394
            }
395
            aWalker.next();
396
        }
397
        aWalker.parent();
398
    }
399

400
    return eStatus;
401 402 403 404 405
}

#endif

/* vim:set shiftwidth=4 softtabstop=4 expandtab: */