KhronosGroup / OpenCL-SDK

OpenCL SDK
Apache License 2.0
579 stars 120 forks source link

Utility library #2

Open MathiasMagnus opened 4 years ago

MathiasMagnus commented 4 years ago

In #1 I touched upon the issue of how to present utilities from the SDK to the users.

The way I imagine the OpenCL SDK hosted by Khronos, is that it provides all the necessary files to compile an OpenCL application (the ICD loader and headers) as well as a utility library which applications could rely on to take care of some tedium in the API.

Consume via CMake

The way I imagined this was to extend the current CMake namespace with the SDK library. Applications consuming both sub-projects may do so via:

find_package(OpenCL 3.0
  REQUIRED
  COMPONENTS
    OpenCL
    SDK
)

add_executable(my_app ${SOURCES})

target_link_libraries(my_app
  PRIVATE
    OpenCL::OpenCL
    OpenCL::SDK
)

Utilities

Here is a short list of things I've grown to use in a few applications of mine.

profiling a la std::chrono

std::cout <<
        "Device (kernel) execution took: " <<
        cl::util::get_duration<CL_PROFILING_COMMAND_START,
                               CL_PROFILING_COMMAND_END,
                               std::chrono::microseconds>(kernel_event).count() <<
        " us." << std::endl;

implementation

namespace cl::util
{
    template <cl_int From, cl_int To, typename Dur = std::chrono::nanoseconds>
    auto get_duration(cl::Event& ev)
    {
        return std::chrono::duration_cast<Dur>(std::chrono::nanoseconds{ ev.getProfilingInfo<To>() - ev.getProfilingInfo<From>() });
    }
}

Iterator-based entry point

This I would like to propose as well for opencl.hpp

std::vector<cl::Platform> platforms(cl::util::Platform::begin(),
                                    cl::util::Platform::end());

implementation

namespace cl::util
{
    namespace Platform
    {
        class PlatformIterator;
        PlatformIterator begin();
        PlatformIterator end();

        cl_uint count()
        {
            cl_int _err = CL_SUCCESS;
            cl_uint _numPlatforms = 0;

            _err = clGetPlatformIDs(0, nullptr, &_numPlatforms);
            if (_err != CL_SUCCESS) throw cl::Error{ _err, "clGetPlatformIDs(0, nullptr, &cl_uint)" };

            return _numPlatforms;
        }

        class PlatformIterator
        {
            friend PlatformIterator begin();
            friend PlatformIterator end();

        public:
            typedef std::input_iterator_tag iterator_category;
            typedef cl::Platform value_type;
            typedef std::ptrdiff_t difference_type;
            typedef cl::Platform* pointer;
            typedef cl::Platform reference;

            PlatformIterator& operator++() { ++_curr; return *this; }
            PlatformIterator operator++(int) { auto res = *this; ++_curr; return res; }
            reference operator*() const { return _plats.at(_curr); }
            pointer operator->() const;
            bool operator==(const PlatformIterator& b) const { return _curr == b._curr; }
            bool operator!=(const PlatformIterator& b) const { return _curr != b._curr; }

        private:
            cl_uint _curr;
            std::vector<cl::Platform> _plats;

            PlatformIterator() : PlatformIterator{ 0 } {}
            PlatformIterator(cl_uint curr)
                : _curr{ curr }
            {
                if (_curr != count()) cl::Platform::get(&_plats);
            }
        };

        PlatformIterator begin() { return PlatformIterator{ 0 }; }
        PlatformIterator end() { return PlatformIterator{ count() }; }
    }
}

Obtain executable path

auto kernel_path = cl::util::get_exe_path().parent_path().append("saxpy.cl");
        std::ifstream source_file{ kernel_path };
        if (!source_file.is_open())
            throw std::runtime_error{ std::string{ "Cannot open kernel source: " } + kernel_path.generic_string() };

implementation

// Header //
#if __has_include(<filesystem>)
#include <filesystem>
#elif __has_include(<experimental/filesystem>)
#include <experimental/filesystem>

namespace std {
    namespace filesystem = experimental::filesystem;
}
#else
#error "No STL filesystem support could be detected"
#endif

namespace cl::util
{
    std::filesystem::path get_exe_path();
}

// Source //
#ifdef _WIN32
#include <Windows.h>    //GetModuleFileNameW
#else
#include <limits.h>
#include <unistd.h>     //readlink
#endif

std::filesystem::path cl::util::get_exe_path()
{
#ifdef _WIN32
    wchar_t path[MAX_PATH] = { 0 };
    GetModuleFileNameW(NULL, path, MAX_PATH);
    return path;
#else
    char result[PATH_MAX];
    ssize_t count = readlink("/proc/self/exe", result, PATH_MAX);
    return std::string(result, (count > 0) ? count : 0);
#endif
}