opencl_device_selection.h 13.2 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 22
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
23 24

#include <clew/clew.h>
25
#include <tools/stream.hxx>
26 27
#include <tools/XmlWriter.hxx>
#include <tools/XmlWalker.hxx>
28
#include <rtl/math.hxx>
29

30 31
#include <opencl/OpenCLZone.hxx>

32
#include <vector>
33

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

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

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

89 90
struct ds_profile
{
91
    std::vector<ds_device> devices;
92
    OString const version;
93

94
    ds_profile(OString const & inVersion)
95 96
        : version(inVersion)
    {}
97
};
98

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

106 107 108 109 110 111
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());
}
112

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

140
inline ds_status initDSProfile(std::unique_ptr<ds_profile>& rProfile, OString const & rVersion)
141
{
142 143
    OpenCLZone zone;

144 145
    int numDevices;
    cl_uint numPlatforms;
146 147 148
    std::vector<cl_platform_id> platforms;
    std::vector<cl_device_id> devices;

149 150 151
    unsigned int next;
    unsigned int i;

152
    rProfile.reset(new ds_profile(rVersion));
153

154
    clGetPlatformIDs(0, nullptr, &numPlatforms);
155 156
    if (numPlatforms != 0)
    {
157
        platforms.resize(numPlatforms);
158
        clGetPlatformIDs(numPlatforms, platforms.data(), nullptr);
159 160 161
    }

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

178 179
    if (numDevices != 0)
    {
180
        devices.resize(numDevices);
181 182
    }

183
    rProfile->devices.resize(numDevices + 1); // +1 to numDevices to include the native CPU
184 185

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

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

236 237
    return DS_SUCCESS;
}
238

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

246
    std::unique_ptr<SvStream> pStream;
247
    pStream.reset(new SvFileStream(rStreamName, StreamMode::STD_READWRITE | StreamMode::TRUNC));
248

249
    tools::XmlWriter aXmlWriter(pStream.get());
250

251
    if (!aXmlWriter.startDocument())
252
        return DS_FILE_ERROR;
253

254
    aXmlWriter.startElement("profile");
255

256
    aXmlWriter.startElement("version");
257
    aXmlWriter.content(pProfile->version);
258
    aXmlWriter.endElement();
259

260 261 262
    for (ds_device& rDevice : pProfile->devices)
    {
        aXmlWriter.startElement("device");
263

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

                aXmlWriter.startElement("name");
277
                aXmlWriter.content(rDevice.sDeviceName);
278 279 280
                aXmlWriter.endElement();

                aXmlWriter.startElement("driver");
281
                aXmlWriter.content(rDevice.sDriverVersion);
282 283 284 285
                aXmlWriter.endElement();
                break;
            default:
                break;
286 287
        }

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

295
        aXmlWriter.startElement("errors");
296
        aXmlWriter.content(rDevice.bErrors ? OString("true") : OString("false"));
297
        aXmlWriter.endElement();
298

299 300
        aXmlWriter.endElement();
    }
301

302 303
    aXmlWriter.endElement();
    aXmlWriter.endDocument();
304

305 306
    return DS_SUCCESS;
}
307

308
inline ds_status readProfile(const OUString& rStreamName, std::unique_ptr<ds_profile> const & pProfile)
309 310
{
    ds_status eStatus = DS_SUCCESS;
311

312 313 314 315 316
    if (rStreamName.isEmpty())
        return DS_INVALID_PROFILE;

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

319 320 321 322 323 324 325 326 327
    if (!aWalker.open(pStream.get()))
        return DS_FILE_ERROR;

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

336 337 338 339 340
                DeviceType eDeviceType = DeviceType::None;
                OString sName;
                OString sVersion;
                double fTime = -1.0;
                bool bErrors = true;
341

342
                while (aWalker.isValid())
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 370 371 372
                    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");
                    }
373

374
                    aWalker.next();
375 376
                }

377 378
                if (fTime < 0.0)
                    return DS_PROFILE_FILE_ERROR;
379

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

396
                aWalker.parent();
397
            }
398
            aWalker.next();
399
        }
400
        aWalker.parent();
401
    }
402

403
    return eStatus;
404 405 406 407 408
}

#endif

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